diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index df6fa97b9..3bb5242fe 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -3,6 +3,27 @@ on: [push, pull_request] jobs: + # Editor + editor-linux: + name: Editor (Linux, Development x64) + runs-on: "ubuntu-20.04" + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Checkout LFS + run: | + git lfs version + git lfs pull + - name: Install dependencies + run: | + sudo rm -f /etc/apt/sources.list.d/* + sudo cp -f .github/workflows/build_linux_sources.list /etc/apt/sources.list + sudo apt-get update + sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev + - name: Build + run: | + ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor + # Game game-linux: name: Game (Linux, Release x64) diff --git a/.github/workflows/build_linux_sources.list b/.github/workflows/build_linux_sources.list new file mode 100644 index 000000000..4d8f94f35 --- /dev/null +++ b/.github/workflows/build_linux_sources.list @@ -0,0 +1,4 @@ +deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse +deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse +deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse +deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 236fa6d67..ada5e5fa5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,3 +32,19 @@ Go check out our [Trello](https://trello.com/b/NQjLXRCP/flax-roadmap). Thank you for taking interest in contributing to Flax! + +## **Common issues** + +Below are some common issues that someone working with the FlaxEngine source code might run into. Hopefully some of those issues will get fixed in the future. If you know how, please contribute! + +* Missing MSVC toolset + * Install it through the Visual Studio Installer +* Building or attaching fails + * Run `GenerateProjectFiles.bat` + * Rebuild `Flax.Build` + * Make sure that there isn't a stray FlaxEngine process running in the background + * First start Flax and then attach the C# debugger + * Configure the C# FlaxEngine project by going into the project properties, then the debug tab and selecting "Start external program" `Flax\FlaxEngine\Binaries\Editor\Win64\Debug\FlaxEditor.exe` + * Then you can also set command line arguments such as `-project "C:\Users\PROFILE\Documents\Flax Projects\FlaxSamples\BasicTemplate"` +* Git LFS + * Push with `git push --no-verify` diff --git a/Content/Editor/IconsAtlas.flax b/Content/Editor/IconsAtlas.flax index 9dda6fec2..13af92065 100644 --- a/Content/Editor/IconsAtlas.flax +++ b/Content/Editor/IconsAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51daf141b7f0d7faf83bb42eceebf9c5442a2f101d02e352215b9684842b5fc7 -size 5636265 +oid sha256:de930eef22208b95882bc9f870063e339672b577d381bcfa6603f0be45c814c5 +size 5610110 diff --git a/Content/Editor/Particles/Constant Burst.flax b/Content/Editor/Particles/Constant Burst.flax index 6e8dc3057..7a160baae 100644 --- a/Content/Editor/Particles/Constant Burst.flax +++ b/Content/Editor/Particles/Constant Burst.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5c47c264658adc60ba1809e50039dbaa50c3157d678d31edb8328a143f6dd34 -size 2269 +oid sha256:116453e2aa2950832d6ca0300e65591f7ffbbe262dc5b3d41af4ad465eee130c +size 2671 diff --git a/Content/Editor/Particles/Periodic Burst.flax b/Content/Editor/Particles/Periodic Burst.flax index 6bbb5b7d7..b165f1ee9 100644 --- a/Content/Editor/Particles/Periodic Burst.flax +++ b/Content/Editor/Particles/Periodic Burst.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d7266e3179077f26b1a12617cdb9b1733fd753ee557f2d81ea58a48fa4265f4 -size 3039 +oid sha256:43ab024ecfd3b54aecce42f9bdc8d81fe1c35719146996e336c2105540de1594 +size 3621 diff --git a/Content/Editor/Particles/Ribbon Spiral.flax b/Content/Editor/Particles/Ribbon Spiral.flax index bff7f09a9..6cabcdce6 100644 --- a/Content/Editor/Particles/Ribbon Spiral.flax +++ b/Content/Editor/Particles/Ribbon Spiral.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1bbb361118d68c52d4fd8176c01c144f2f3105ac13b80b33ea23101f40589ea -size 1992 +oid sha256:43d743429f984745a3eeb22fa74fe4462e1f0d7b638f65ded06fc6f14dadb175 +size 2365 diff --git a/Content/Editor/Particles/Smoke.flax b/Content/Editor/Particles/Smoke.flax index cb474f3c3..e68821034 100644 --- a/Content/Editor/Particles/Smoke.flax +++ b/Content/Editor/Particles/Smoke.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9787b9b4cc2caf91c65b4d858cfcb11afe0fe4917c0f741f3e0099ba687e4da9 -size 15421 +oid sha256:84c4ea742b6b4775bc518731ecb56d7fe202302b3bdb61f1dcdf8727ade94353 +size 16019 diff --git a/Content/Editor/Particles/Sparks.flax b/Content/Editor/Particles/Sparks.flax index 9a94e1f98..8b076afdc 100644 --- a/Content/Editor/Particles/Sparks.flax +++ b/Content/Editor/Particles/Sparks.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1abf314458e98bd45638abab1092dea374d68c9cb5085f6d3a5de65947f62ea7 -size 14743 +oid sha256:3a6dc75b65f0eb1a66b12fbe4e20091b55aed9143a8574e7a9dfc000413eb2f6 +size 15081 diff --git a/Content/Editor/Scripting/CppAssetTemplate.h b/Content/Editor/Scripting/CppAssetTemplate.h new file mode 100644 index 000000000..ddd8109c7 --- /dev/null +++ b/Content/Editor/Scripting/CppAssetTemplate.h @@ -0,0 +1,23 @@ +%copyright% +#pragma once + +#include "Engine/Core/ISerializable.h" +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Content/Assets/Model.h" +#include "Engine/Scripting/ScriptingType.h" + +/// +/// %class% Json Asset. +/// +API_CLASS() class %module%%class% : public ISerializable +{ + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(%class%); +public: + // Custom float value. + API_FIELD(Attributes = "Range(0, 20), EditorOrder(0), EditorDisplay(\"Data\")") + float FloatValue = 20.0f; + // Custom vector data. + API_FIELD(Attributes = "EditorOrder(1), EditorDisplay(\"Data\")") + Vector3 Vector3Value = Vector3(0.1f); +}; diff --git a/Content/Editor/Scripting/CppStaticClassTemplate.cpp b/Content/Editor/Scripting/CppStaticClassTemplate.cpp new file mode 100644 index 000000000..08ffb9348 --- /dev/null +++ b/Content/Editor/Scripting/CppStaticClassTemplate.cpp @@ -0,0 +1,8 @@ +%copyright% +#include "%filename%.h" +#include "Engine/Core/Log.h" + +void %class%::RunNativeAction(Vector4 data) +{ + LOG(Warning, "Data in RunNativeAction: {0}", data); +} diff --git a/Content/Editor/Scripting/CppStaticClassTemplate.h b/Content/Editor/Scripting/CppStaticClassTemplate.h new file mode 100644 index 000000000..8b235695f --- /dev/null +++ b/Content/Editor/Scripting/CppStaticClassTemplate.h @@ -0,0 +1,20 @@ +%copyright% +#pragma once + +#include "Engine/Scripting/Script.h" +#include "Engine/Core/Math/Vector4.h" + +/// +/// %class% Function Library +/// +API_CLASS(Static) class %module%%class% +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(%class%); +public: + + /// + /// Logs the function parameter natively. + /// + /// Data to pass to native code + API_FUNCTION() static void RunNativeAction(Vector4 data); +}; diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs index c9ae5068e..2a150306d 100644 --- a/Content/Editor/Scripting/ScriptTemplate.cs +++ b/Content/Editor/Scripting/ScriptTemplate.cs @@ -4,23 +4,30 @@ using FlaxEngine; namespace %namespace% { + /// + /// %class% Script. + /// public class %class% : Script { + /// public override void OnStart() { // Here you can add code that needs to be called when script is created, just before the first game update } - + + /// public override void OnEnable() { // Here you can add code that needs to be called when script is enabled (eg. register for events) } + /// public override void OnDisable() { // Here you can add code that needs to be called when script is disabled (eg. unregister from events) } + /// public override void OnUpdate() { // Here you can add code that needs to be called every frame diff --git a/Content/Shaders/BitonicSort.flax b/Content/Shaders/BitonicSort.flax index 1d5b8a581..c9dd81dc9 100644 --- a/Content/Shaders/BitonicSort.flax +++ b/Content/Shaders/BitonicSort.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f46a61cf8d5183230176e661a51208bfeece16cc7238655f406288ff448af64e -size 6721 +oid sha256:0c780dc1881ef96dece237bde3e87fca3c9e4e542d89ebde7c9308f00f81c6a9 +size 6808 diff --git a/Content/Shaders/DepthOfField.flax b/Content/Shaders/DepthOfField.flax index a84047429..6c4d9daec 100644 --- a/Content/Shaders/DepthOfField.flax +++ b/Content/Shaders/DepthOfField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92355644db4cdfd96250b779a87385e6805d01ce084801b3a4be704f026e9bca -size 21025 +oid sha256:d8b69fce536cb27d497f6ff91f8adff3fbeaa9a704ced1b3271f4d621c195d53 +size 21054 diff --git a/Content/Shaders/EyeAdaptation.flax b/Content/Shaders/EyeAdaptation.flax index 9c538e798..93e17c1a2 100644 --- a/Content/Shaders/EyeAdaptation.flax +++ b/Content/Shaders/EyeAdaptation.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c12b7b43743e4e609a4155d7a4117421298d679d07548ffa48a386da496556b0 -size 4605 +oid sha256:f0d2b41560554e53692958ddea4cb53a082a7fbe88d7cb99eeccd095c45f631a +size 4600 diff --git a/Content/Shaders/GUI.flax b/Content/Shaders/GUI.flax index 73fd80725..0e9077247 100644 --- a/Content/Shaders/GUI.flax +++ b/Content/Shaders/GUI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea8cffddde04c7f3829ce0bd0cb61deecb50a9ac954c552cddb72d182538d605 -size 5108 +oid sha256:aae706e79bbc4eb9508da338a3ba2b4300f012771bac3c1a43835fd41ad7282a +size 5111 diff --git a/Content/Shaders/VolumetricFog.flax b/Content/Shaders/VolumetricFog.flax index 118e46c91..19420d9ad 100644 --- a/Content/Shaders/VolumetricFog.flax +++ b/Content/Shaders/VolumetricFog.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81de76f651892a4d410c4624de3cab685d29b6711938c04325dac776588e3ec9 -size 14293 +oid sha256:2b71f70eab5a3139707a624ec2ea796e83114d3cc5351c9648921e13bffa2386 +size 14288 diff --git a/Development/Documentation/mono.md b/Development/Documentation/mono.md index bbde0cd56..d292ebb3d 100644 --- a/Development/Documentation/mono.md +++ b/Development/Documentation/mono.md @@ -5,6 +5,7 @@ Custom fork: [https://github.com/FlaxEngine/mono](https://github.com/FlaxEngine/ ### Notes Some useful notes and tips for devs: +* Use `-monolog` to print Mono logs to Flax logs * When working with mono fork set `localRepoPath` to local repo location in `Source\Tools\Flax.Build\Deps\Dependencies\mono.cs` * To update mono deps when developing/updating use `.\Development\Scripts\Windows\CallBuildTool.bat -log -ReBuildDeps -verbose -depsToBuild=mono -platform=Windows`, then build engine and run it * `MONO_GC_DEBUG=check-remset-consistency` - it will do additional checks at each collection to see if there are any missing write barriers diff --git a/Flax.flaxproj b/Flax.flaxproj index 0c2683afb..681c1dc55 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -2,8 +2,8 @@ "Name": "Flax", "Version": { "Major": 1, - "Minor": 0, - "Build": 6216 + "Minor": 1, + "Build": 6218 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.", diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index b940e9c03..c91a48bfe 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -234,6 +234,7 @@ Deprecated (?<=\W|^)(?<TAG>\[Deprecated)(\W|$)(.*) Normal + True True True True diff --git a/PackageEditor.sh b/PackageEditor.sh new file mode 100755 index 000000000..865013285 --- /dev/null +++ b/PackageEditor.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. + +set -e + +echo Building and packaging Flax Editor... + +# Change the path to the script root +cd "`dirname "$0"`" + +# Run Flax.Build (also pass the arguments) +bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" diff --git a/PackagePlatforms.sh b/PackagePlatforms.sh index 83cf9f4a5..0e1ae76f8 100755 --- a/PackagePlatforms.sh +++ b/PackagePlatforms.sh @@ -8,5 +8,5 @@ echo Building and packaging platforms data... # Change the path to the script root cd "`dirname "$0"`" -# Run Flax.Build to generate project files (also pass the arguments) +# Run Flax.Build (also pass the arguments) bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" diff --git a/README.md b/README.md index f7f72e5c9..be785107d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de * Install Visual Studio Code * Install Mono ([https://www.mono-project.com/download/stable](https://www.mono-project.com/download/stable)) * Install Git with LFS -* Install requried packages: `sudo apt-get install nuget autoconf libtool libogg-dev automake build-essential gettext cmake python curl libtool-bin libx11-dev libpulse-dev libasound2-dev libjack-dev portaudio19-dev libcurl4-gnutls-dev` +* Install requried packages: `sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev nuget autoconf libogg-dev automake build-essential gettext cmake python libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev` * Install compiler `sudo apt-get install clang lldb lld` (Clang 6 or newer) * Clone repo (with LFS) * Run `./GenerateProjectFiles.sh` diff --git a/Source/Editor/Analytics/EditorAnalytics.cpp b/Source/Editor/Analytics/EditorAnalytics.cpp index 3f067719e..c2a293c15 100644 --- a/Source/Editor/Analytics/EditorAnalytics.cpp +++ b/Source/Editor/Analytics/EditorAnalytics.cpp @@ -10,6 +10,7 @@ #include "Editor/Editor.h" #include "Editor/ProjectInfo.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Engine/Globals.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Utilities/StringConverter.h" #include "FlaxEngine.Gen.h" diff --git a/Source/Editor/Content/Create/SettingsCreateEntry.cs b/Source/Editor/Content/Create/SettingsCreateEntry.cs index 369cc01e5..974de76cc 100644 --- a/Source/Editor/Content/Create/SettingsCreateEntry.cs +++ b/Source/Editor/Content/Create/SettingsCreateEntry.cs @@ -53,6 +53,11 @@ namespace FlaxEditor.Content.Create /// NavigationSettings, + /// + /// The localization settings. + /// + LocalizationSettings, + /// /// The build settings. /// @@ -92,6 +97,11 @@ namespace FlaxEditor.Content.Create /// The Android settings /// AndroidPlatformSettings, + + /// + /// The Switch settings + /// + SwitchPlatformSettings, } private static readonly Type[] _types = @@ -103,6 +113,7 @@ namespace FlaxEditor.Content.Create typeof(PhysicsSettings), typeof(GraphicsSettings), typeof(NavigationSettings), + typeof(LocalizationSettings), typeof(BuildSettings), typeof(InputSettings), typeof(WindowsPlatformSettings), @@ -111,6 +122,7 @@ namespace FlaxEditor.Content.Create TypeUtils.GetManagedType(GameSettings.PS4PlatformSettingsTypename), TypeUtils.GetManagedType(GameSettings.XboxScarlettPlatformSettingsTypename), typeof(AndroidPlatformSettings), + TypeUtils.GetManagedType(GameSettings.SwitchPlatformSettingsTypename), }; /// diff --git a/Source/Editor/Content/Import/ImportFileEntry.cs b/Source/Editor/Content/Import/ImportFileEntry.cs index 80cab94d3..5d21d3726 100644 --- a/Source/Editor/Content/Import/ImportFileEntry.cs +++ b/Source/Editor/Content/Import/ImportFileEntry.cs @@ -116,8 +116,7 @@ namespace FlaxEditor.Content.Import if (FileTypes.TryGetValue(extension, out ImportFileEntryHandler createDelegate)) return createDelegate(ref request); - // Use default type - return request.IsBinaryAsset ? new AssetImportEntry(ref request) : new ImportFileEntry(ref request); + return request.IsInBuilt ? new AssetImportEntry(ref request) : new ImportFileEntry(ref request); } internal static void RegisterDefaultTypes() diff --git a/Source/Editor/Content/Import/Request.cs b/Source/Editor/Content/Import/Request.cs index b9da30afb..d38b0bfd0 100644 --- a/Source/Editor/Content/Import/Request.cs +++ b/Source/Editor/Content/Import/Request.cs @@ -21,9 +21,9 @@ namespace FlaxEditor.Content.Import public string OutputPath; /// - /// Flag set to true for binary assets handled by the engine internally. + /// Flag set to true for the assets handled by the engine internally. /// - public bool IsBinaryAsset; + public bool IsInBuilt; /// /// Flag used to skip showing import settings dialog to used. Can be used for importing assets from code by plugins. diff --git a/Source/Editor/Content/Import/TextureImportEntry.cs b/Source/Editor/Content/Import/TextureImportEntry.cs index c276dce1b..db1e3ae8c 100644 --- a/Source/Editor/Content/Import/TextureImportEntry.cs +++ b/Source/Editor/Content/Import/TextureImportEntry.cs @@ -483,7 +483,9 @@ namespace FlaxEditor.Content.Import { if (settings is TextureImportSettings o) { + var sprites = o.Sprites ?? _settings.Sprites; // Preserve sprites if not specified to override _settings = o; + _settings.Sprites = sprites; return true; } return false; diff --git a/Source/Editor/Content/Items/CSharpScriptItem.cs b/Source/Editor/Content/Items/CSharpScriptItem.cs index 6160de2bb..332b48558 100644 --- a/Source/Editor/Content/Items/CSharpScriptItem.cs +++ b/Source/Editor/Content/Items/CSharpScriptItem.cs @@ -20,6 +20,6 @@ namespace FlaxEditor.Content } /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CSharpScript64; + public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CSharpScript128; } } diff --git a/Source/Editor/Content/Items/ContentFolder.cs b/Source/Editor/Content/Items/ContentFolder.cs index 8ae934b78..8c7e52b2c 100644 --- a/Source/Editor/Content/Items/ContentFolder.cs +++ b/Source/Editor/Content/Items/ContentFolder.cs @@ -121,7 +121,7 @@ namespace FlaxEditor.Content public override bool Exists => Directory.Exists(Path); /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Folder64; + public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Folder128; /// internal override void UpdatePath(string value) diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs index 2e75de940..9813cd44e 100644 --- a/Source/Editor/Content/Items/ContentItem.cs +++ b/Source/Editor/Content/Items/ContentItem.cs @@ -455,7 +455,7 @@ namespace FlaxEditor.Content const float thumbnailInShadowSize = 50.0f; var shadowRect = rectangle.MakeExpanded((DefaultThumbnailSize - thumbnailInShadowSize) * rectangle.Width / DefaultThumbnailSize * 1.3f); if (!_shadowIcon.IsValid) - _shadowIcon = Editor.Instance.Icons.AssetShadow; + _shadowIcon = Editor.Instance.Icons.AssetShadow128; Render2D.DrawSprite(_shadowIcon, shadowRect); } diff --git a/Source/Editor/Content/Items/CppScriptItem.cs b/Source/Editor/Content/Items/CppScriptItem.cs index 1f7bc9f19..1ffead58c 100644 --- a/Source/Editor/Content/Items/CppScriptItem.cs +++ b/Source/Editor/Content/Items/CppScriptItem.cs @@ -20,6 +20,6 @@ namespace FlaxEditor.Content } /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CppScript64; + public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CPPScript128; } } diff --git a/Source/Editor/Content/Items/FileItem.cs b/Source/Editor/Content/Items/FileItem.cs index 7a3d0fa65..a078178d8 100644 --- a/Source/Editor/Content/Items/FileItem.cs +++ b/Source/Editor/Content/Items/FileItem.cs @@ -26,6 +26,6 @@ namespace FlaxEditor.Content public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Other; /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document64; + public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128; } } diff --git a/Source/Editor/Content/Items/JsonAssetItem.cs b/Source/Editor/Content/Items/JsonAssetItem.cs index 6e32ad49a..1c9232c9c 100644 --- a/Source/Editor/Content/Items/JsonAssetItem.cs +++ b/Source/Editor/Content/Items/JsonAssetItem.cs @@ -11,6 +11,8 @@ namespace FlaxEditor.Content /// public class JsonAssetItem : AssetItem { + private readonly SpriteHandle _thumbnail; + /// /// Initializes a new instance of the class. /// @@ -20,13 +22,27 @@ namespace FlaxEditor.Content public JsonAssetItem(string path, Guid id, string typeName) : base(path, typeName, ref id) { + _thumbnail = Editor.Instance.Icons.Document128; + } + + /// + /// Initializes a new instance of the class. + /// + /// The path. + /// The identifier. + /// Name of the resource type. + /// Asset icon. + public JsonAssetItem(string path, Guid id, string typeName, SpriteHandle thumbnail) + : base(path, typeName, ref id) + { + _thumbnail = thumbnail; } /// public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Json; /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document64; + public override SpriteHandle DefaultThumbnail => _thumbnail; /// protected override bool DrawShadow => false; diff --git a/Source/Editor/Content/Items/NewItem.cs b/Source/Editor/Content/Items/NewItem.cs index da06c6bfd..366e07df8 100644 --- a/Source/Editor/Content/Items/NewItem.cs +++ b/Source/Editor/Content/Items/NewItem.cs @@ -40,7 +40,7 @@ namespace FlaxEditor.Content public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Other; /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document64; + public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128; /// protected override bool DrawShadow => true; diff --git a/Source/Editor/Content/Items/SceneItem.cs b/Source/Editor/Content/Items/SceneItem.cs index 190fe9807..15b79e96b 100644 --- a/Source/Editor/Content/Items/SceneItem.cs +++ b/Source/Editor/Content/Items/SceneItem.cs @@ -28,7 +28,7 @@ namespace FlaxEditor.Content public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Scene; /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Scene64; + public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Scene128; /// public override bool IsOfType(Type type) diff --git a/Source/Editor/Content/Items/ShaderSourceItem.cs b/Source/Editor/Content/Items/ShaderSourceItem.cs index 77cd6d6ee..7ae04257b 100644 --- a/Source/Editor/Content/Items/ShaderSourceItem.cs +++ b/Source/Editor/Content/Items/ShaderSourceItem.cs @@ -27,6 +27,6 @@ namespace FlaxEditor.Content public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Shader; /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document64; + public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128; } } diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index fd23887ec..04311644b 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -75,6 +75,9 @@ namespace FlaxEditor.Content } } + /// + public int MetadataToken => 0; + /// public bool HasAttribute(Type attributeType, bool inherit) { @@ -195,6 +198,8 @@ namespace FlaxEditor.Content /// public ScriptType ValueType => _returnType; + public int MetadataToken => 0; + /// public bool HasAttribute(Type attributeType, bool inherit) { @@ -534,7 +539,7 @@ namespace FlaxEditor.Content } /// - public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CodeScript64; + public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.VisualScript128; /// protected override bool DrawShadow => false; diff --git a/Source/Editor/Content/PreviewsCache.cpp b/Source/Editor/Content/PreviewsCache.cpp index c51e8cabb..c89b00004 100644 --- a/Source/Editor/Content/PreviewsCache.cpp +++ b/Source/Editor/Content/PreviewsCache.cpp @@ -76,7 +76,7 @@ void PreviewsCache::FlushTask::OnEnd() ThreadPoolTask::OnEnd(); } -REGISTER_BINARY_ASSET(PreviewsCache, "FlaxEditor.PreviewsCache", ::New(), false); +REGISTER_BINARY_ASSET_WITH_UPGRADER(PreviewsCache, "FlaxEditor.PreviewsCache", TextureAssetUpgrader, false); PreviewsCache::PreviewsCache(const SpawnParams& params, const AssetInfo* info) : SpriteAtlas(params, info) diff --git a/Source/Editor/Content/Proxy/ContentProxy.cs b/Source/Editor/Content/Proxy/ContentProxy.cs index 0948ef5e8..afb5c11fb 100644 --- a/Source/Editor/Content/Proxy/ContentProxy.cs +++ b/Source/Editor/Content/Proxy/ContentProxy.cs @@ -73,6 +73,16 @@ namespace FlaxEditor.Content throw new NotImplementedException(); } + /// + /// Determines whether the specified filename is valid for this proxy. + /// + /// The filename. + /// true if the filename is valid, otherwise false. + public virtual bool IsFileNameValid(string filename) + { + return true; + } + /// /// Determines whether this proxy can create items in the specified target location. /// diff --git a/Source/Editor/Content/Proxy/CppProxy.cs b/Source/Editor/Content/Proxy/CppProxy.cs new file mode 100644 index 000000000..9ced48653 --- /dev/null +++ b/Source/Editor/Content/Proxy/CppProxy.cs @@ -0,0 +1,134 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using System.Text; +using FlaxEditor.Content.Settings; +using FlaxEngine; + +namespace FlaxEditor.Content +{ + /// + /// Context proxy object for C++ files. + /// + /// + public abstract class CppProxy : ScriptProxy + { + /// + /// Gets the paths for header and source files to format. + /// + /// The header template path. + /// The source template path. + protected abstract void GetTemplatePaths(out string headerTemplate, out string sourceTemplate); + + /// + public override bool IsProxyFor(ContentItem item) + { + return false; + } + + /// + public override void Create(string outputPath, object arg) + { + // Find the module that this script is being added (based on the path) + var module = string.Empty; + var project = TryGetProjectAtFolder(outputPath, out var moduleName); + if (project != null) + { + module = moduleName.ToUpperInvariant() + "_API "; + } + + var gameSettings = GameSettings.Load(); + var scriptName = ScriptItem.CreateScriptName(outputPath); + var filename = Path.GetFileNameWithoutExtension(outputPath); + var copyrightComment = string.IsNullOrEmpty(gameSettings.CopyrightNotice) ? string.Empty : string.Format("// {0}{1}{1}", gameSettings.CopyrightNotice, Environment.NewLine); + + GetTemplatePaths(out var headerTemplatePath, out var sourceTemplatePath); + if (headerTemplatePath != null) + { + var headerTemplate = File.ReadAllText(headerTemplatePath); + headerTemplate = headerTemplate.Replace("%copyright%", copyrightComment); + headerTemplate = headerTemplate.Replace("%class%", scriptName); + headerTemplate = headerTemplate.Replace("%module%", module); + headerTemplate = headerTemplate.Replace("%filename%", filename); + File.WriteAllText(Path.ChangeExtension(outputPath, ".h"), headerTemplate, Encoding.UTF8); + } + if (sourceTemplatePath != null) + { + var sourceTemplate = File.ReadAllText(sourceTemplatePath); + sourceTemplate = sourceTemplate.Replace("%copyright%", copyrightComment); + sourceTemplate = sourceTemplate.Replace("%class%", scriptName); + sourceTemplate = sourceTemplate.Replace("%module%", module); + sourceTemplate = sourceTemplate.Replace("%filename%", filename); + File.WriteAllText(outputPath, sourceTemplate, Encoding.UTF8); + } + } + + /// + public override string FileExtension => "cpp"; + + /// + public override Color AccentColor => Color.FromRGB(0x9c1c9c); + } + + /// + /// Context proxy object for C++ script files. + /// + /// + public class CppScriptProxy : CppProxy + { + /// + public override string Name => "C++ Script"; + + /// + public override bool IsProxyFor(ContentItem item) + { + return item is CppScriptItem; + } + + /// + protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate) + { + headerTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.h"); + sourceTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.cpp"); + } + } + + /// + /// Context proxy object for C++ Json Asset files. + /// + /// + public class CppStaticClassProxy : CppProxy + { + /// + public override string Name => "C++ Function Library"; + + /// + protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate) + { + headerTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CppStaticClassTemplate.h"); + sourceTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CppStaticClassTemplate.cpp"); + } + } + + /// + /// Context proxy object for C++ Json Asset files. + /// + /// + public class CppAssetProxy : CppProxy + { + /// + public override string Name => "C++ Json Asset"; + + /// + protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate) + { + headerTemplate = null; + sourceTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CppAssetTemplate.h"); + //sourceTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CppAssetTemplate.cpp"); + } + + /// + public override string FileExtension => "h"; + } +} diff --git a/Source/Editor/Content/Proxy/CppScriptProxy.cs b/Source/Editor/Content/Proxy/CppScriptProxy.cs deleted file mode 100644 index 8bad93021..000000000 --- a/Source/Editor/Content/Proxy/CppScriptProxy.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System; -using System.IO; -using System.Text; -using FlaxEditor.Content.Settings; -using FlaxEngine; - -namespace FlaxEditor.Content -{ - /// - /// Context proxy object for C++ script files. - /// - /// - public class CppScriptProxy : ScriptProxy - { - /// - public override string Name => "C++ Script"; - - /// - public override bool IsProxyFor(ContentItem item) - { - return item is CppScriptItem; - } - - /// - public override void Create(string outputPath, object arg) - { - // Load templates - var headerTemplate = File.ReadAllText(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.h")); - var sourceTemplate = File.ReadAllText(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.cpp")); - - // Find the module that this script is being added (based on the path) - var module = string.Empty; - var project = TryGetProjectAtFolder(outputPath, out var moduleName); - if (project != null) - { - module = moduleName.ToUpperInvariant() + "_API "; - } - - // Format - var gameSettings = GameSettings.Load(); - var scriptName = ScriptItem.CreateScriptName(outputPath); - var filename = Path.GetFileNameWithoutExtension(outputPath); - var copyrightComment = string.IsNullOrEmpty(gameSettings.CopyrightNotice) ? string.Empty : string.Format("// {0}{1}{1}", gameSettings.CopyrightNotice, Environment.NewLine); - headerTemplate = headerTemplate.Replace("%copyright%", copyrightComment); - headerTemplate = headerTemplate.Replace("%class%", scriptName); - headerTemplate = headerTemplate.Replace("%module%", module); - sourceTemplate = sourceTemplate.Replace("%filename%", filename); - sourceTemplate = sourceTemplate.Replace("%copyright%", copyrightComment); - sourceTemplate = sourceTemplate.Replace("%class%", scriptName); - sourceTemplate = sourceTemplate.Replace("%filename%", filename); - - // Save - File.WriteAllText(Path.ChangeExtension(outputPath, ".h"), headerTemplate, Encoding.UTF8); - File.WriteAllText(outputPath, sourceTemplate, Encoding.UTF8); - } - - /// - public override string FileExtension => "cpp"; - - /// - public override Color AccentColor => Color.FromRGB(0x9c1c9c); - } -} diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs index 25155d39d..41cc06ee1 100644 --- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs +++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs @@ -2,7 +2,6 @@ using System; using FlaxEditor.Content.Create; -using FlaxEditor.Content.Settings; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.Windows; @@ -45,18 +44,12 @@ namespace FlaxEditor.Content /// public override bool IsProxyFor(ContentItem item) { - return item is JsonAssetItem; + return item is JsonAssetItem json && json.TypeName == TypeName; } /// public override Color AccentColor => Color.FromRGB(0xd14f67); - /// - public override bool AcceptsAsset(string typeName, string path) - { - return typeName == TypeName && base.AcceptsAsset(typeName, path); - } - /// public override AssetItem ConstructItem(string path, string typeName, ref Guid id) { @@ -143,6 +136,12 @@ namespace FlaxEditor.Content return path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase); } + /// + public override bool IsProxyFor(ContentItem item) + { + return item is JsonAssetItem; + } + /// public override bool CanCreate(ContentFolder targetLocation) { diff --git a/Source/Editor/Content/Proxy/LocalizedStringTableProxy.cs b/Source/Editor/Content/Proxy/LocalizedStringTableProxy.cs new file mode 100644 index 000000000..2e15f9543 --- /dev/null +++ b/Source/Editor/Content/Proxy/LocalizedStringTableProxy.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEditor.Windows; +using FlaxEditor.Windows.Assets; +using FlaxEngine; + +namespace FlaxEditor.Content +{ + /// + /// proxy. + /// + /// + public class LocalizedStringTableProxy : JsonAssetProxy + { + /// + public override EditorWindow Open(Editor editor, ContentItem item) + { + return new LocalizedStringTableWindow(editor, (JsonAssetItem)item); + } + + /// + public override string TypeName => "FlaxEngine.LocalizedStringTable"; + } +} diff --git a/Source/Editor/Content/Proxy/ScriptProxy.cs b/Source/Editor/Content/Proxy/ScriptProxy.cs index 89a08bb4f..0c8140d1a 100644 --- a/Source/Editor/Content/Proxy/ScriptProxy.cs +++ b/Source/Editor/Content/Proxy/ScriptProxy.cs @@ -66,6 +66,15 @@ namespace FlaxEditor.Content return false; } + /// + public override bool IsFileNameValid(string filename) + { + // Scripts cannot start with digit. + if (Char.IsDigit(filename[0])) + return false; + return true; + } + /// public override void Create(string outputPath, object arg) { diff --git a/Source/Editor/Content/Proxy/SettingsProxy.cs b/Source/Editor/Content/Proxy/SettingsProxy.cs index a6b74b0ea..2bebc3254 100644 --- a/Source/Editor/Content/Proxy/SettingsProxy.cs +++ b/Source/Editor/Content/Proxy/SettingsProxy.cs @@ -3,6 +3,7 @@ using System; using FlaxEditor.Content.Create; using FlaxEditor.Content.Settings; +using FlaxEngine; namespace FlaxEditor.Content { @@ -13,15 +14,18 @@ namespace FlaxEditor.Content public sealed class SettingsProxy : JsonAssetProxy { private readonly Type _type; + private readonly SpriteHandle _thumbnail; /// /// Initializes a new instance of the class. /// /// The settings asset type (must be subclass of SettingsBase type). - public SettingsProxy(Type type) + /// Asset icon. + public SettingsProxy(Type type, SpriteHandle thumbnail) { _type = type; TypeName = type.FullName; + _thumbnail = thumbnail; } /// @@ -44,6 +48,12 @@ namespace FlaxEditor.Content Editor.Instance.ContentImporting.Create(new SettingsCreateEntry(outputPath)); } + /// + public override AssetItem ConstructItem(string path, string typeName, ref Guid id) + { + return new JsonAssetItem(path, id, typeName, _thumbnail); + } + /// public override bool IsProxyFor() { diff --git a/Source/Editor/Content/Proxy/ShaderProxy.cs b/Source/Editor/Content/Proxy/ShaderProxy.cs index e6e288318..71350e277 100644 --- a/Source/Editor/Content/Proxy/ShaderProxy.cs +++ b/Source/Editor/Content/Proxy/ShaderProxy.cs @@ -29,7 +29,7 @@ namespace FlaxEditor.Content if (asset) { var source = Editor.GetShaderSourceCode(asset); - Utilities.Utils.ShowSourceCodeWindow(source, "Shader Source"); + Utilities.Utils.ShowSourceCodeWindow(source, "Shader Source", item.RootWindow.Window); } return null; } diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index 78cc7f2b5..9179c3c07 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -77,7 +77,7 @@ namespace FlaxEditor.Content /// The folder type. /// The folder path. protected ContentTreeNode(ContentTreeNode parent, ContentFolderType type, string path) - : base(false, Editor.Instance.Icons.FolderClosed12, Editor.Instance.Icons.FolderOpened12) + : base(false, Editor.Instance.Icons.FolderClosed32, Editor.Instance.Icons.FolderOpen32) { _folder = new ContentFolder(type, path, this); Text = _folder.ShortName; diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h index 56b15af23..77774a9df 100644 --- a/Source/Editor/Cooker/CookingData.h +++ b/Source/Editor/Cooker/CookingData.h @@ -7,6 +7,7 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/HashSet.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Types/Guid.h" class GameCooker; class PlatformTools; @@ -87,34 +88,14 @@ API_ENUM() enum class BuildPlatform /// API_ENUM(Attributes="EditorDisplay(null, \"Android ARM64 (arm64-v8a)\")") AndroidARM64 = 9, + + /// + /// Switch. + /// + Switch = 10, }; -inline const Char* ToString(const BuildPlatform platform) -{ - switch (platform) - { - case BuildPlatform::Windows32: - return TEXT("Windows x86"); - case BuildPlatform::Windows64: - return TEXT("Windows x64"); - case BuildPlatform::UWPx86: - return TEXT("Windows Store x86"); - case BuildPlatform::UWPx64: - return TEXT("Windows Store x64"); - case BuildPlatform::XboxOne: - return TEXT("Xbox One"); - case BuildPlatform::LinuxX64: - return TEXT("Linux x64"); - case BuildPlatform::PS4: - return TEXT("PlayStation 4"); - case BuildPlatform::XboxScarlett: - return TEXT("Xbox Scarlett"); - case BuildPlatform::AndroidARM64: - return TEXT("Android ARM64"); - default: - return TEXT("?"); - } -} +extern FLAXENGINE_API const Char* ToString(const BuildPlatform platform); /// /// Game build configuration modes. @@ -137,20 +118,7 @@ API_ENUM() enum class BuildConfiguration Release = 2, }; -inline const Char* ToString(const BuildConfiguration configuration) -{ - switch (configuration) - { - case BuildConfiguration::Debug: - return TEXT("Debug"); - case BuildConfiguration::Development: - return TEXT("Development"); - case BuildConfiguration::Release: - return TEXT("Release"); - default: - return TEXT("?"); - } -} +extern FLAXENGINE_API const Char* ToString(const BuildConfiguration configuration); #define BUILD_STEP_CANCEL_CHECK if (GameCooker::IsCancelRequested()) return true @@ -185,9 +153,14 @@ struct FLAXENGINE_API CookingData String OriginalOutputPath; /// - /// The output path. + /// The output path for data files (Content, Mono, etc.). /// - String OutputPath; + String DataOutputPath; + + /// + /// The output path for binaries (executable and code libraries). + /// + String CodeOutputPath; /// /// The platform tools. diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 9eca81eba..f64045ff5 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -10,6 +10,7 @@ #include "Engine/Serialization/JsonTools.h" #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Engine/Globals.h" #include "Engine/Threading/ThreadSpawner.h" #include "Engine/Platform/FileSystem.h" #include "Steps/ValidateStep.h" @@ -39,7 +40,6 @@ #endif #if PLATFORM_TOOLS_PS4 #include "Platforms/PS4/Editor/PlatformTools/PS4PlatformTools.h" -#include "Platforms/PS4/Engine/Platform/PS4PlatformSettings.h" #endif #if PLATFORM_TOOLS_XBOX_SCARLETT #include "Platforms/XboxScarlett/Editor/PlatformTools/XboxScarlettPlatformTools.h" @@ -47,6 +47,9 @@ #if PLATFORM_TOOLS_ANDROID #include "Platform/Android/AndroidPlatformTools.h" #endif +#if PLATFORM_TOOLS_SWITCH +#include "Platforms/Switch/Editor/PlatformTools/SwitchPlatformTools.h" +#endif namespace GameCookerImpl { @@ -89,6 +92,50 @@ using namespace GameCookerImpl; Delegate GameCooker::OnEvent; Delegate GameCooker::OnProgress; +const Char* ToString(const BuildPlatform platform) +{ + switch (platform) + { + case BuildPlatform::Windows32: + return TEXT("Windows x86"); + case BuildPlatform::Windows64: + return TEXT("Windows x64"); + case BuildPlatform::UWPx86: + return TEXT("Windows Store x86"); + case BuildPlatform::UWPx64: + return TEXT("Windows Store x64"); + case BuildPlatform::XboxOne: + return TEXT("Xbox One"); + case BuildPlatform::LinuxX64: + return TEXT("Linux x64"); + case BuildPlatform::PS4: + return TEXT("PlayStation 4"); + case BuildPlatform::XboxScarlett: + return TEXT("Xbox Scarlett"); + case BuildPlatform::AndroidARM64: + return TEXT("Android ARM64"); + case BuildPlatform::Switch: + return TEXT("Switch"); + default: + return TEXT("?"); + } +} + +const Char* ToString(const BuildConfiguration configuration) +{ + switch (configuration) + { + case BuildConfiguration::Debug: + return TEXT("Debug"); + case BuildConfiguration::Development: + return TEXT("Development"); + case BuildConfiguration::Release: + return TEXT("Release"); + default: + return TEXT("?"); + } +} + bool CookingData::AssetTypeStatistics::operator<(const AssetTypeStatistics& other) const { if (ContentSize != other.ContentSize) @@ -250,6 +297,11 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform) case BuildPlatform::AndroidARM64: result = New(ArchitectureType::ARM64); break; +#endif +#if PLATFORM_TOOLS_SWITCH + case BuildPlatform::Switch: + result = New(); + break; #endif } Tools.Add(platform, result); @@ -282,9 +334,10 @@ void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, data.Configuration = configuration; data.Options = options; data.CustomDefines = customDefines; - data.OutputPath = outputPath; - FileSystem::NormalizePath(data.OutputPath); - data.OutputPath = data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OutputPath); + data.OriginalOutputPath = outputPath; + FileSystem::NormalizePath(data.OriginalOutputPath); + data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OriginalOutputPath); + data.CodeOutputPath = data.DataOutputPath = data.OriginalOutputPath; data.CacheDirectory = Globals::ProjectCacheFolder / TEXT("Cooker") / tools->GetName(); if (!FileSystem::DirectoryExists(data.CacheDirectory)) { @@ -367,7 +420,7 @@ bool GameCookerImpl::Build() CookingData& data = Data; LOG(Info, "Starting Game Cooker..."); LOG(Info, "Platform: {0}, Configuration: {2}, Options: {1}", ::ToString(data.Platform), (int32)data.Options, ::ToString(data.Configuration)); - LOG(Info, "Output Path: {0}", data.OutputPath); + LOG(Info, "Output Path: {0}", data.OriginalOutputPath); // Late init feature if (Steps.IsEmpty()) diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp index fcc53f26a..3987fcb4a 100644 --- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp @@ -104,7 +104,8 @@ PixelFormat AndroidPlatformTools::GetTextureFormat(CookingData& data, TextureBas void AndroidPlatformTools::OnBuildStarted(CookingData& data) { // Adjust the cooking output folder to be located inside the Gradle assets directory - data.OutputPath /= TEXT("app/assets"); + data.DataOutputPath /= TEXT("app/assets"); + data.CodeOutputPath /= TEXT("app/assets"); PlatformTools::OnBuildStarted(data); } @@ -114,7 +115,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) const auto gameSettings = GameSettings::Get(); const auto platformSettings = AndroidPlatformSettings::Get(); const auto platformDataPath = data.GetPlatformBinariesRoot(); - const auto assetsPath = data.OutputPath; + const auto assetsPath = data.DataOutputPath; const auto jniLibsPath = data.OriginalOutputPath / TEXT("app/jniLibs"); const auto projectVersion = Editor::Project->Version.ToString(); const Char* abi; diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp index fa47c5ee5..57562f537 100644 --- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp @@ -38,7 +38,7 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) { const auto gameSettings = GameSettings::Get(); const auto platformSettings = LinuxPlatformSettings::Get(); - const auto outputPath = data.OutputPath; + const auto outputPath = data.DataOutputPath; // Copy binaries { @@ -79,6 +79,11 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) const String gameExePath = outputPath / TEXT("FlaxGame"); #endif + // Ensure the output binary can be executed +#if PLATFORM_LINUX + system(*StringAnsi(String::Format(TEXT("chmod +x \"{0}\""), gameExePath))); +#endif + // Apply game icon TextureData iconData; if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData)) diff --git a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp index c8f9e45a7..e6516bf67 100644 --- a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp @@ -25,7 +25,7 @@ bool UWPPlatformTools::OnScriptsStepDone(CookingData& data) { // Override Newtonsoft.Json.dll for some platforms (that don't support runtime code generation) const String customBinPath = data.GetPlatformBinariesRoot() / TEXT("Newtonsoft.Json.dll"); - const String assembliesPath = data.OutputPath; + const String assembliesPath = data.CodeOutputPath; if (FileSystem::CopyFile(assembliesPath / TEXT("Newtonsoft.Json.dll"), customBinPath)) { data.Error(TEXT("Failed to copy deploy custom assembly.")); @@ -43,7 +43,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) const auto uwpDataPath = platformDataPath / (isXboxOne ? TEXT("XboxOne") : TEXT("UWP")) / TEXT("Binaries"); const auto gameSettings = GameSettings::Get(); const auto platformSettings = UWPPlatformSettings::Get(); - Array fileTemplate; + StringAnsi fileTemplate; // Copy binaries const auto binPath = data.GetGameBinariesPath(); @@ -64,7 +64,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) return true; } - if (FileSystem::CopyFile(data.OutputPath / StringUtils::GetFileName(files[i]), files[i])) + if (FileSystem::CopyFile(data.DataOutputPath / StringUtils::GetFileName(files[i]), files[i])) { data.Error(TEXT("Failed to setup output directory.")); return true; @@ -92,7 +92,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) // Prepare certificate const auto srcCertificatePath = Globals::ProjectFolder / platformSettings->CertificateLocation; - const auto dstCertificatePath = data.OutputPath / TEXT("WSACertificate.pfx"); + const auto dstCertificatePath = data.DataOutputPath / TEXT("WSACertificate.pfx"); if (platformSettings->CertificateLocation.HasChars() && FileSystem::FileExists(srcCertificatePath)) { // Use cert from settings @@ -115,7 +115,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) } // Copy assets - const auto dstAssetsPath = data.OutputPath / TEXT("Assets"); + const auto dstAssetsPath = data.DataOutputPath / TEXT("Assets"); const auto srcAssetsPath = uwpDataPath / TEXT("Assets"); if (!FileSystem::DirectoryExists(dstAssetsPath)) { @@ -125,7 +125,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) return true; } } - const auto dstPropertiesPath = data.OutputPath / TEXT("Properties"); + const auto dstPropertiesPath = data.DataOutputPath / TEXT("Properties"); if (!FileSystem::DirectoryExists(dstPropertiesPath)) { if (FileSystem::CreateDirectory(dstPropertiesPath)) @@ -149,12 +149,11 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) if (!FileSystem::FileExists(dstAssemblyInfoPath)) { // Get template - if (File::ReadAllBytes(srcAssemblyInfoPath, fileTemplate)) + if (File::ReadAllText(srcAssemblyInfoPath, fileTemplate)) { data.Error(TEXT("Failed to load AssemblyInfo.cs template.")); return true; } - fileTemplate[fileTemplate.Count() - 1] = 0; // Write data to file auto file = FileWriteStream::Open(dstAssemblyInfoPath); @@ -163,7 +162,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) { auto now = DateTime::Now(); file->WriteTextFormatted( - (char*)fileTemplate.Get() + fileTemplate.Get() , gameSettings->ProductName.ToStringAnsi() , gameSettings->CompanyName.ToStringAnsi() , now.GetYear() @@ -177,17 +176,16 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) return true; } } - const auto dstAppPath = data.OutputPath / TEXT("App.cs"); + const auto dstAppPath = data.DataOutputPath / TEXT("App.cs"); const auto srcAppPath = uwpDataPath / TEXT("App.cs"); if (!FileSystem::FileExists(dstAppPath)) { // Get template - if (File::ReadAllBytes(srcAppPath, fileTemplate)) + if (File::ReadAllText(srcAppPath, fileTemplate)) { data.Error(TEXT("Failed to load App.cs template.")); return true; } - fileTemplate[fileTemplate.Count() - 1] = 0; // Write data to file auto file = FileWriteStream::Open(dstAppPath); @@ -195,7 +193,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) if (file) { file->WriteTextFormatted( - (char*)fileTemplate.Get() + fileTemplate.Get() , defaultNamespace.ToStringAnsi() // {0} Default Namespace ); hasError = file->HasError(); @@ -207,16 +205,15 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) return true; } } - const auto dstFlaxGeneratedPath = data.OutputPath / TEXT("FlaxGenerated.cs"); + const auto dstFlaxGeneratedPath = data.DataOutputPath / TEXT("FlaxGenerated.cs"); const auto srcFlaxGeneratedPath = uwpDataPath / TEXT("FlaxGenerated.cs"); { // Get template - if (File::ReadAllBytes(srcFlaxGeneratedPath, fileTemplate)) + if (File::ReadAllText(srcFlaxGeneratedPath, fileTemplate)) { data.Error(TEXT("Failed to load FlaxGenerated.cs template.")); return true; } - fileTemplate[fileTemplate.Count() - 1] = 0; // Prepare StringAnsi autoRotationPreferences; @@ -252,7 +249,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) if (file) { file->WriteTextFormatted( - (char*)fileTemplate.Get() + fileTemplate.Get() , autoRotationPreferences.Get() , preferredLaunchWindowingMode.Get() ); @@ -267,17 +264,16 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) } // Create solution - const auto dstSolutionPath = data.OutputPath / projectName + TEXT(".sln"); + const auto dstSolutionPath = data.DataOutputPath / projectName + TEXT(".sln"); const auto srcSolutionPath = uwpDataPath / TEXT("Solution.sln"); if (!FileSystem::FileExists(dstSolutionPath)) { // Get template - if (File::ReadAllBytes(srcSolutionPath, fileTemplate)) + if (File::ReadAllText(srcSolutionPath, fileTemplate)) { data.Error(TEXT("Failed to load Solution.sln template.")); return true; } - fileTemplate[fileTemplate.Count() - 1] = 0; // Write data to file auto file = FileWriteStream::Open(dstSolutionPath); @@ -285,7 +281,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) if (file) { file->WriteTextFormatted( - (char*)fileTemplate.Get() + fileTemplate.Get() , projectName.ToStringAnsi() // {0} Project Name , mode // {1} Platform Mode , projectGuid.ToStringAnsi() // {2} Project ID @@ -301,16 +297,15 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) } // Create project - const auto dstProjectPath = data.OutputPath / projectName + TEXT(".csproj"); + const auto dstProjectPath = data.DataOutputPath / projectName + TEXT(".csproj"); const auto srcProjectPath = uwpDataPath / TEXT("Project.csproj"); { // Get template - if (File::ReadAllBytes(srcProjectPath, fileTemplate)) + if (File::ReadAllText(srcProjectPath, fileTemplate)) { data.Error(TEXT("Failed to load Project.csproj template.")); return true; } - fileTemplate[fileTemplate.Count() - 1] = 0; // Build included files data StringBuilder filesInclude(2048); @@ -334,7 +329,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) if (file) { file->WriteTextFormatted( - (char*)fileTemplate.Get() + fileTemplate.Get() , projectName.ToStringAnsi() // {0} Project Name , mode // {1} Platform Mode , projectGuid.Get() // {2} Project ID @@ -352,17 +347,16 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) } // Create manifest - const auto dstManifestPath = data.OutputPath / TEXT("Package.appxmanifest"); + const auto dstManifestPath = data.DataOutputPath / TEXT("Package.appxmanifest"); const auto srcManifestPath = uwpDataPath / TEXT("Package.appxmanifest"); if (!FileSystem::FileExists(dstManifestPath)) { // Get template - if (File::ReadAllBytes(srcManifestPath, fileTemplate)) + if (File::ReadAllText(srcManifestPath, fileTemplate)) { data.Error(TEXT("Failed to load Package.appxmanifest template.")); return true; } - fileTemplate[fileTemplate.Count() - 1] = 0; // Build included files data StringBuilder filesInclude(2048); @@ -385,7 +379,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) if (file) { file->WriteTextFormatted( - (char*)fileTemplate.Get() + fileTemplate.Get() , projectName.ToStringAnsi() // {0} Display Name , gameSettings->CompanyName.ToStringAnsi() // {1} Company Name , productId.ToStringAnsi() // {2} Product ID @@ -490,8 +484,8 @@ bool UWPPlatformTools::OnPostProcess(CookingData& data) // Special case for UWP // FlaxEngine.dll cannot be added to the solution as `Content` item (due to conflicts with C++ /CX FlaxEngine.dll) // Use special directory for it (generated UWP project handles this case and copies lib to the output) - const String assembliesPath = data.OutputPath; - const auto dstPath1 = data.OutputPath / TEXT("DataSecondary"); + const String assembliesPath = data.DataOutputPath; + const auto dstPath1 = data.DataOutputPath / TEXT("DataSecondary"); if (!FileSystem::DirectoryExists(dstPath1)) { if (FileSystem::CreateDirectory(dstPath1)) diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index 348930dd5..d5dc1c933 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -36,7 +36,7 @@ ArchitectureType WindowsPlatformTools::GetArchitecture() const bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) { const auto platformSettings = WindowsPlatformSettings::Get(); - const auto& outputPath = data.OutputPath; + const auto& outputPath = data.CodeOutputPath; // Apply executable icon Array files; diff --git a/Source/Editor/Cooker/PlatformTools.h b/Source/Editor/Cooker/PlatformTools.h index c98c008bc..20d578d2e 100644 --- a/Source/Editor/Cooker/PlatformTools.h +++ b/Source/Editor/Cooker/PlatformTools.h @@ -144,8 +144,8 @@ public: AotConfig(CookingData& data) { Platform::GetEnvironmentVariables(EnvVars); - EnvVars[TEXT("MONO_PATH")] = data.OutputPath / TEXT("Mono/lib/mono/4.5"); - AssembliesSearchDirs.Add(data.OutputPath / TEXT("Mono/lib/mono/4.5")); + EnvVars[TEXT("MONO_PATH")] = data.DataOutputPath / TEXT("Mono/lib/mono/4.5"); + AssembliesSearchDirs.Add(data.DataOutputPath / TEXT("Mono/lib/mono/4.5")); } }; diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp index 91f22abae..5d3125960 100644 --- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp +++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp @@ -101,7 +101,7 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c // Deploy files Array files(16); const String outputPath = StringUtils::GetDirectoryName(path); - FileSystem::DirectoryGetFiles(files, outputPath, TEXT("*.*"), DirectorySearchOption::TopDirectoryOnly); + FileSystem::DirectoryGetFiles(files, outputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly); for (int32 i = files.Count() - 1; i >= 0; i--) { bool skip = false; @@ -119,7 +119,7 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c } for (auto& file : files) { - const String dst = data.OutputPath / StringUtils::GetFileName(file); + const String dst = data.CodeOutputPath / StringUtils::GetFileName(file); if (dst != file && FileSystem::CopyFile(dst, file)) { data.Error(TEXT("Failed to copy file from {0} to {1}."), file, dst); @@ -180,7 +180,12 @@ bool CompileScriptsStep::Perform(CookingData& data) platform = TEXT("Android"); architecture = TEXT("ARM64"); break; + case BuildPlatform::Switch: + platform = TEXT("Switch"); + architecture = TEXT("ARM64"); + break; default: + LOG(Error, "Unknown or unsupported build platform."); return true; } _extensionsToSkip.Clear(); @@ -289,7 +294,7 @@ bool CompileScriptsStep::Perform(CookingData& data) } writer.EndObject(); - const String outputBuildInfo = data.OutputPath / TEXT("Game.Build.json"); + const String outputBuildInfo = data.CodeOutputPath / TEXT("Game.Build.json"); if (File::WriteAllBytes(outputBuildInfo, (byte*)buffer.GetString(), (int32)buffer.GetSize())) { LOG(Error, "Failed to save binary modules info file {0}.", outputBuildInfo); diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index be4dca78d..5f097f638 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -29,6 +29,7 @@ #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Engine/Base/GameBase.h" +#include "Engine/Engine/Globals.h" #include "Engine/Tools/TextureTool/TextureTool.h" #if PLATFORM_TOOLS_WINDOWS #include "Engine/Platform/Windows/WindowsPlatformSettings.h" @@ -368,6 +369,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass // Compile for a target platform switch (data.Data.Platform) { +#if PLATFORM_TOOLS_WINDOWS case BuildPlatform::Windows32: case BuildPlatform::Windows64: { @@ -391,6 +393,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass } break; } +#endif #if PLATFORM_TOOLS_UWP case BuildPlatform::UWPx86: case BuildPlatform::UWPx64: @@ -408,12 +411,14 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass break; } #endif +#if PLATFORM_TOOLS_UWP case BuildPlatform::XboxOne: { const char* platformDefineName = "PLATFORM_XBOX_ONE"; COMPILE_PROFILE(DirectX_SM4, SHADER_FILE_CHUNK_INTERNAL_D3D_SM4_CACHE); break; } +#endif #if PLATFORM_TOOLS_LINUX case BuildPlatform::LinuxX64: { @@ -426,24 +431,38 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass break; } #endif +#if PLATFORM_TOOLS_PS4 case BuildPlatform::PS4: { const char* platformDefineName = "PLATFORM_PS4"; COMPILE_PROFILE(PS4, SHADER_FILE_CHUNK_INTERNAL_GENERIC_CACHE); break; } +#endif +#if PLATFORM_TOOLS_XBOX_SCARLETT case BuildPlatform::XboxScarlett: { const char* platformDefineName = "PLATFORM_XBOX_SCARLETT"; COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE); break; } +#endif +#if PLATFORM_TOOLS_ANDROID case BuildPlatform::AndroidARM64: { const char* platformDefineName = "PLATFORM_ANDROID"; COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE); break; } +#endif +#if PLATFORM_TOOLS_SWITCH + case BuildPlatform::Switch: + { + const char* platformDefineName = "PLATFORM_SWITCH"; + COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE); + break; + } +#endif default: { LOG(Warning, "Not implemented platform or shaders not supported."); @@ -851,7 +870,7 @@ public: // Create package // Note: FlaxStorage::Create overrides chunks locations in file so don't use files anymore (only readonly) const String localPath = String::Format(TEXT("Content/Data_{0}.{1}"), _packageIndex, PACKAGE_FILES_EXTENSION); - const String path = data.OutputPath / localPath; + const String path = data.DataOutputPath / localPath; if (FlaxStorage::Create(path, assetsData, false, &CustomData)) { data.Error(TEXT("Failed to create assets package.")); @@ -895,21 +914,27 @@ bool CookAssetsStep::Perform(CookingData& data) cache.Load(data); // Update build settings +#if PLATFORM_TOOLS_WINDOWS { const auto settings = WindowsPlatformSettings::Get(); cache.Settings.Windows.SupportDX11 = settings->SupportDX11; cache.Settings.Windows.SupportDX10 = settings->SupportDX10; cache.Settings.Windows.SupportVulkan = settings->SupportVulkan; } +#endif +#if PLATFORM_TOOLS_UWP { const auto settings = UWPPlatformSettings::Get(); cache.Settings.UWP.SupportDX11 = settings->SupportDX11; cache.Settings.UWP.SupportDX10 = settings->SupportDX10; } +#endif +#if PLATFORM_TOOLS_LINUX { const auto settings = LinuxPlatformSettings::Get(); cache.Settings.Linux.SupportVulkan = settings->SupportVulkan; } +#endif { cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize; cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; @@ -1011,7 +1036,7 @@ bool CookAssetsStep::Perform(CookingData& data) gameFlags |= GameHeaderFlags::ShowSplashScreen; // Open file - auto stream = FileWriteStream::Open(data.OutputPath / TEXT("Content/head")); + auto stream = FileWriteStream::Open(data.DataOutputPath / TEXT("Content/head")); if (stream == nullptr) { data.Error(TEXT("Failed to create game data file.")); @@ -1105,7 +1130,7 @@ bool CookAssetsStep::Perform(CookingData& data) BUILD_STEP_CANCEL_CHECK; // Save assets cache - if (AssetsCache::Save(data.OutputPath / TEXT("Content/AssetsCache.dat"), AssetsRegistry, AssetPathsMapping, AssetsCacheFlags::RelativePaths)) + if (AssetsCache::Save(data.DataOutputPath / TEXT("Content/AssetsCache.dat"), AssetsRegistry, AssetPathsMapping, AssetsCacheFlags::RelativePaths)) { data.Error(TEXT("Failed to create assets registry.")); return true; diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index f90658cab..17ddbf73c 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Config/GameSettings.h" #include "Engine/Renderer/ReflectionsPass.h" #include "Engine/Renderer/AntiAliasing/SMAA.h" +#include "Engine/Engine/Globals.h" bool DeployDataStep::Perform(CookingData& data) { @@ -15,7 +16,7 @@ bool DeployDataStep::Perform(CookingData& data) const auto gameSettings = GameSettings::Get(); // Setup output folders and copy required data - const auto contentDir = data.OutputPath / TEXT("Content"); + const auto contentDir = data.DataOutputPath / TEXT("Content"); if (FileSystem::DirectoryExists(contentDir)) { // Remove old content files @@ -26,7 +27,7 @@ bool DeployDataStep::Perform(CookingData& data) } FileSystem::CreateDirectory(contentDir); const auto srcMono = depsRoot / TEXT("Mono"); - const auto dstMono = data.OutputPath / TEXT("Mono"); + const auto dstMono = data.DataOutputPath / TEXT("Mono"); if (!FileSystem::DirectoryExists(dstMono)) { if (!FileSystem::DirectoryExists(srcMono)) diff --git a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp index 4c482c529..6e5fe50a7 100644 --- a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp +++ b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp @@ -24,7 +24,7 @@ bool PrecompileAssembliesStep::Perform(CookingData& data) data.Tools->OnConfigureAOT(data, config); // Prepare output directory - config.AotCachePath = data.OutputPath / TEXT("Mono/lib/mono/aot-cache"); + config.AotCachePath = data.DataOutputPath / TEXT("Mono/lib/mono/aot-cache"); switch (data.Tools->GetArchitecture()) { case ArchitectureType::x86: @@ -52,9 +52,9 @@ bool PrecompileAssembliesStep::Perform(CookingData& data) FileSystem::DirectoryGetFiles(config.Assemblies, dir, TEXT("*.dll"), DirectorySearchOption::TopDirectoryOnly); for (auto& binaryModule : data.BinaryModules) if (binaryModule.ManagedPath.HasChars()) - config.Assemblies.Add(data.OutputPath / binaryModule.ManagedPath); + config.Assemblies.Add(data.CodeOutputPath / binaryModule.ManagedPath); // TODO: move AOT to Flax.Build and perform it on all C# assemblies used in target build - config.Assemblies.Add(data.OutputPath / TEXT("Newtonsoft.Json.dll")); + config.Assemblies.Add(data.CodeOutputPath / TEXT("Newtonsoft.Json.dll")); // Perform AOT for the assemblies for (int32 i = 0; i < config.Assemblies.Count(); i++) diff --git a/Source/Editor/Cooker/Steps/ValidateStep.cpp b/Source/Editor/Cooker/Steps/ValidateStep.cpp index 915f7839d..a64317496 100644 --- a/Source/Editor/Cooker/Steps/ValidateStep.cpp +++ b/Source/Editor/Cooker/Steps/ValidateStep.cpp @@ -11,9 +11,17 @@ bool ValidateStep::Perform(CookingData& data) data.StepProgress(TEXT("Performing validation"), 0); // Ensure output and cache directories exist - if (!FileSystem::DirectoryExists(data.OutputPath)) + if (!FileSystem::DirectoryExists(data.CodeOutputPath)) { - if (FileSystem::CreateDirectory(data.OutputPath)) + if (FileSystem::CreateDirectory(data.CodeOutputPath)) + { + data.Error(TEXT("Failed to create build output directory.")); + return true; + } + } + if (!FileSystem::DirectoryExists(data.DataOutputPath)) + { + if (FileSystem::CreateDirectory(data.DataOutputPath)) { data.Error(TEXT("Failed to create build output directory.")); return true; diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index c5c628755..ca45652b9 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -250,6 +250,15 @@ namespace FlaxEditor.CustomEditors _children[i].RefreshInternal(); } + /// + /// Synchronizes the value of the container. Called during Refresh to flush property after editing it in UI. + /// + /// The value to set. + protected virtual void SynchronizeValue(object value) + { + _values.Set(_parent.Values, value); + } + internal virtual void RefreshInternal() { if (_values == null) @@ -264,7 +273,7 @@ namespace FlaxEditor.CustomEditors _valueToSet = null; // Assign value - _values.Set(_parent.Values, val); + SynchronizeValue(val); // 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; diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index 2cea89f08..be7b4de5c 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -139,7 +139,7 @@ namespace FlaxEditor.CustomEditors /// protected override void OnModified() { - Presenter.Modified?.Invoke(); + Presenter.OnModified(); base.OnModified(); } @@ -250,6 +250,7 @@ namespace FlaxEditor.CustomEditors Selection.Clear(); Selection.Add(obj); + Selection.SetType(new ScriptType(obj.GetType())); OnSelectionChanged(); } @@ -271,6 +272,7 @@ namespace FlaxEditor.CustomEditors Selection.Clear(); Selection.AddRange(objectsArray); + Selection.SetType(new ScriptType(objectsArray.GetType())); OnSelectionChanged(); } @@ -284,6 +286,7 @@ namespace FlaxEditor.CustomEditors return; Selection.Clear(); + Selection.SetType(ScriptType.Null); OnSelectionChanged(); } @@ -351,6 +354,14 @@ namespace FlaxEditor.CustomEditors ExpandGroups(this, false); } + /// + /// Invokes event. + /// + public void OnModified() + { + Modified?.Invoke(); + } + /// /// Called when selection gets changed. /// diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp index ffd42d750..519acdf60 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp @@ -3,6 +3,7 @@ #include "CustomEditorsUtil.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Engine/EngineService.h" #include "Engine/Scripting/Scripting.h" diff --git a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs new file mode 100644 index 000000000..521b844ec --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs @@ -0,0 +1,429 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using FlaxEditor.Content.Settings; +using FlaxEditor.CustomEditors.Editors; +using FlaxEditor.Scripting; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Object = FlaxEngine.Object; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + [CustomEditor(typeof(LocalizationSettings))] + sealed class LocalizationSettingsEditor : GenericEditor + { + private CultureInfo _theMostTranslatedCulture; + private int _theMostTranslatedCultureCount; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + Profiler.BeginEvent("LocalizationSettingsEditor.Initialize"); + var settings = (LocalizationSettings)Values[0]; + var tablesLength = settings.LocalizedStringTables?.Length ?? 0; + var tables = new List(tablesLength); + for (int i = 0; i < tablesLength; i++) + { + var table = settings.LocalizedStringTables[i]; + if (table && !table.WaitForLoaded()) + tables.Add(table); + } + var locales = tables.GroupBy(x => x.Locale); + var tableEntries = new Dictionary>(); + var allKeys = new HashSet(); + foreach (var e in locales) + { + foreach (var table in e) + { + var entries = table.Entries; + tableEntries[table] = entries; + allKeys.AddRange(entries.Keys); + } + } + + { + var group = layout.Group("Preview"); + + // Current language and culture preview management + group.Object("Current Language", new CustomValueContainer(new ScriptType(typeof(CultureInfo)), Localization.CurrentLanguage, (instance, index) => Localization.CurrentLanguage, (instance, index, value) => Localization.CurrentLanguage = value as CultureInfo), null, "Current UI display language for the game preview."); + group.Object("Current Culture", new CustomValueContainer(new ScriptType(typeof(CultureInfo)), Localization.CurrentCulture, (instance, index) => Localization.CurrentCulture, (instance, index, value) => Localization.CurrentCulture = value as CultureInfo), null, "Current values formatting culture for the game preview."); + } + + { + var group = layout.Group("Locales"); + + // Show all existing locales + _theMostTranslatedCulture = null; + _theMostTranslatedCultureCount = -1; + foreach (var e in locales) + { + var culture = new CultureInfo(e.Key); + var prop = group.AddPropertyItem(CultureInfoEditor.GetName(culture), culture.NativeName); + int count = e.Sum(x => tableEntries[x].Count); + int validCount = e.Sum(x => tableEntries[x].Values.Count(y => y != null && y.Length != 0 && !string.IsNullOrEmpty(y[0]))); + if (count > _theMostTranslatedCultureCount) + { + _theMostTranslatedCulture = culture; + _theMostTranslatedCultureCount = count; + } + prop.Label(string.Format("Progress: {0}% ({1}/{2})", (int)(((float)validCount / allKeys.Count * 100.0f)), validCount, allKeys.Count)); + prop.Label("Tables:"); + foreach (var table in e) + { + var namePath = table.Path; + if (namePath.StartsWith(Globals.ProjectFolder)) + namePath = namePath.Substring(Globals.ProjectFolder.Length + 1); + var tableLabel = prop.ClickableLabel(namePath).CustomControl; + tableLabel.TextColorHighlighted = Color.Wheat; + tableLabel.DoubleClick += delegate { Editor.Instance.Windows.ContentWin.Select(table); }; + } + group.Space(10); + } + + // Update add button + var update = group.Button("Update").Button; + update.TooltipText = "Refreshes the dashboard statistics"; + update.Height = 16.0f; + update.Clicked += RebuildLayout; + + // New locale add button + var addLocale = group.Button("Add Locale...").Button; + addLocale.TooltipText = "Shows a locale picker and creates new localization for it with not translated string tables"; + addLocale.Height = 16.0f; + addLocale.ButtonClicked += delegate(Button button) + { + var menu = CultureInfoEditor.CreatePicker(null, culture => + { + var displayName = CultureInfoEditor.GetName(culture); + if (locales.Any(x => x.Key == culture.Name)) + { + MessageBox.Show($"Culture '{displayName}' is already added."); + return; + } + Profiler.BeginEvent("LocalizationSettingsEditor.AddLocale"); + Editor.Log($"Adding culture '{displayName}' to localization settings"); + var newTables = settings.LocalizedStringTables.ToList(); + if (_theMostTranslatedCulture != null) + { + // Duplicate localization for culture with the highest amount of keys + var g = locales.First(x => x.Key == _theMostTranslatedCulture.Name); + foreach (var e in g) + { + var path = e.Path; + var filename = Path.GetFileNameWithoutExtension(path); + if (filename.EndsWith(_theMostTranslatedCulture.Name)) + filename = filename.Substring(0, filename.Length - _theMostTranslatedCulture.Name.Length); + path = Path.Combine(Path.GetDirectoryName(path), filename + culture.Name + ".json"); + var table = FlaxEngine.Content.CreateVirtualAsset(); + table.Locale = culture.Name; + var entries = new Dictionary(); + foreach (var ee in tableEntries[e]) + { + var vv = (string[])ee.Value.Clone(); + for (var i = 0; i < vv.Length; i++) + vv[i] = string.Empty; + entries.Add(ee.Key, vv); + } + table.Entries = entries; + if (!table.Save(path)) + { + Object.Destroy(table); + newTables.Add(FlaxEngine.Content.LoadAsync(path)); + } + } + } + else + { + // No localization so initialize with empty table + var path = Path.Combine(Path.Combine(Path.GetDirectoryName(GameSettings.Load().Localization.Path), "Localization", culture.Name + ".json")); + var table = FlaxEngine.Content.CreateVirtualAsset(); + table.Locale = culture.Name; + if (!table.Save(path)) + { + Object.Destroy(table); + newTables.Add(FlaxEngine.Content.LoadAsync(path)); + } + } + settings.LocalizedStringTables = newTables.ToArray(); + Presenter.OnModified(); + RebuildLayout(); + Profiler.EndEvent(); + }); + menu.Show(button, new Vector2(0, button.Height)); + }; + + // Export button + var exportLocalization = group.Button("Export...").Button; + exportLocalization.TooltipText = "Exports the localization strings into .pot file for translation"; + exportLocalization.Height = 16.0f; + exportLocalization.Clicked += delegate + { + if (FileSystem.ShowSaveFileDialog(null, null, "*.pot", false, "Export localization for translation to .pot file", out var filenames)) + return; + Profiler.BeginEvent("LocalizationSettingsEditor.Export"); + if (!filenames[0].EndsWith(".pot")) + filenames[0] += ".pot"; + var nplurals = 1; + foreach (var e in tableEntries) + { + foreach (var value in e.Value.Values) + { + if (value != null && value.Length > nplurals) + nplurals = value.Length; + } + } + using (var writer = new StreamWriter(filenames[0], false, Encoding.UTF8)) + { + writer.WriteLine("msgid \"\""); + writer.WriteLine("msgstr \"\""); + writer.WriteLine("\"Language: English\\n\""); + writer.WriteLine("\"MIME-Version: 1.0\\n\""); + writer.WriteLine("\"Content-Type: text/plain; charset=UTF-8\\n\""); + writer.WriteLine("\"Content-Transfer-Encoding: 8bit\\n\""); + writer.WriteLine($"\"Plural-Forms: nplurals={nplurals}; plural=(n != 1);\\n\""); + writer.WriteLine("\"X-Generator: FlaxEngine\\n\""); + var written = new HashSet(); + foreach (var e in tableEntries) + { + foreach (var pair in e.Value) + { + if (written.Contains(pair.Key)) + continue; + written.Add(pair.Key); + + writer.WriteLine(""); + writer.WriteLine($"msgid \"{pair.Key}\""); + if (pair.Value == null || pair.Value.Length < 2) + { + writer.WriteLine("msgstr \"\""); + } + else + { + writer.WriteLine("msgid_plural \"\""); + for (int i = 0; i < pair.Value.Length; i++) + writer.WriteLine($"msgstr[{i}] \"\""); + } + } + if (written.Count == allKeys.Count) + break; + } + } + Profiler.EndEvent(); + }; + + // Find localized strings in code button + var findStringsCode = group.Button("Find localized strings in code").Button; + findStringsCode.TooltipText = "Searches for localized string usage in inside a project source files"; + findStringsCode.Height = 16.0f; + findStringsCode.Clicked += delegate + { + var newKeys = new Dictionary(); + Profiler.BeginEvent("LocalizationSettingsEditor.FindLocalizedStringsInSource"); + + // C# + var files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cs", SearchOption.AllDirectories); + var filesCount = files.Length; + foreach (var file in files) + FindNewKeysCSharp(file, newKeys, allKeys); + + // C++ + files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cpp", SearchOption.AllDirectories); + filesCount += files.Length; + foreach (var file in files) + FindNewKeysCpp(file, newKeys, allKeys); + files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.h", SearchOption.AllDirectories); + filesCount += files.Length; + foreach (var file in files) + FindNewKeysCpp(file, newKeys, allKeys); + + AddNewKeys(newKeys, filesCount, locales, tableEntries); + Profiler.EndEvent(); + }; + + // Find localized strings in content button + var findStringsContent = group.Button("Find localized strings in content").Button; + findStringsContent.TooltipText = "Searches for localized string usage in inside a project content files (scenes, prefabs)"; + findStringsContent.Height = 16.0f; + findStringsContent.Clicked += delegate + { + var newKeys = new Dictionary(); + Profiler.BeginEvent("LocalizationSettingsEditor.FindLocalizedStringsInContent"); + + // Scenes + var files = Directory.GetFiles(Globals.ProjectContentFolder, "*.scene", SearchOption.AllDirectories); + var filesCount = files.Length; + foreach (var file in files) + FindNewKeysJson(file, newKeys, allKeys); + + // Prefabs + files = Directory.GetFiles(Globals.ProjectContentFolder, "*.prefab", SearchOption.AllDirectories); + filesCount += files.Length; + foreach (var file in files) + FindNewKeysJson(file, newKeys, allKeys); + + AddNewKeys(newKeys, filesCount, locales, tableEntries); + Profiler.EndEvent(); + }; + } + + { + // Raw asset data editing + var group = layout.Group("Data"); + base.Initialize(group); + } + + Profiler.EndEvent(); + } + + private static void FindNewKeysCSharp(string file, Dictionary newKeys, HashSet allKeys) + { + var startToken = "Localization.GetString"; + var textToken = "\""; + FindNewKeys(file, newKeys, allKeys, startToken, textToken); + } + + private static void FindNewKeysCpp(string file, Dictionary newKeys, HashSet allKeys) + { + var startToken = "Localization::GetString"; + var textToken = "TEXT(\""; + FindNewKeys(file, newKeys, allKeys, startToken, textToken); + } + + private static void FindNewKeys(string file, Dictionary newKeys, HashSet allKeys, string startToken, string textToken) + { + var contents = File.ReadAllText(file); + var idx = contents.IndexOf(startToken); + while (idx != -1) + { + idx += startToken.Length + 1; + int braces = 1; + int start = idx; + while (idx < contents.Length && braces != 0) + { + if (contents[idx] == '(') + braces++; + if (contents[idx] == ')') + braces--; + idx++; + } + if (idx == contents.Length) + break; + var inside = contents.Substring(start, idx - start - 1); + var textStart = inside.IndexOf(textToken); + if (textStart != -1) + { + textStart += textToken.Length; + var textEnd = textStart; + while (textEnd < inside.Length && inside[textEnd] != '\"') + { + if (inside[textEnd] == '\\') + textEnd++; + textEnd++; + } + var id = inside.Substring(textStart, textEnd - textStart); + textStart = inside.Length > textEnd + 2 ? inside.IndexOf(textToken, textEnd + 2) : -1; + string value = null; + if (textStart != -1) + { + textStart += textToken.Length; + textEnd = textStart; + while (textEnd < inside.Length && inside[textEnd] != '\"') + { + if (inside[textEnd] == '\\') + textEnd++; + textEnd++; + } + value = inside.Substring(textStart, textEnd - textStart); + } + + if (!allKeys.Contains(id)) + newKeys[id] = value; + } + + idx = contents.IndexOf(startToken, idx); + } + } + + private static void FindNewKeysJson(Dictionary newKeys, HashSet allKeys, JToken token) + { + if (token is JObject o) + { + foreach (var p in o) + { + if (string.Equals(p.Key, "Id", StringComparison.Ordinal) && p.Value is JValue i && i.Value is string id && !allKeys.Contains(id)) + { + var count = o.Properties().Count(); + if (count == 1) + { + newKeys[id] = null; + return; + } + if (count == 2) + { + var v = o.Property("Value")?.Value as JValue; + if (v?.Value is string value) + { + newKeys[id] = value; + return; + } + } + } + FindNewKeysJson(newKeys, allKeys, p.Value); + } + } + else if (token is JArray a) + { + foreach (var p in a) + { + FindNewKeysJson(newKeys, allKeys, p); + } + } + } + + private static void FindNewKeysJson(string file, Dictionary newKeys, HashSet allKeys) + { + using (var reader = new StreamReader(file)) + using (var jsonReader = new JsonTextReader(reader)) + { + var token = JToken.ReadFrom(jsonReader); + FindNewKeysJson(newKeys, allKeys, token); + } + } + + private void AddNewKeys(Dictionary newKeys, int filesCount, IEnumerable> locales, Dictionary> tableEntries) + { + Editor.Log($"Found {newKeys.Count} new localized strings in {filesCount} files"); + if (newKeys.Count == 0) + return; + foreach (var e in newKeys) + Editor.Log(e.Key + (e.Value != null ? " = " + e.Value : string.Empty)); + foreach (var locale in locales) + { + var table = locale.First(); + var entries = tableEntries[table]; + if (table.Locale == "en") + { + foreach (var e in newKeys) + entries[e.Key] = new[] { e.Value }; + } + else + { + foreach (var e in newKeys) + entries[e.Key] = new[] { string.Empty }; + } + table.Entries = entries; + table.Save(); + } + RebuildLayout(); + } + } +} diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 0fcf9d78a..dcd70a340 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -2,7 +2,9 @@ using System; using System.Linq; +using System.Reflection; using FlaxEditor.CustomEditors.Editors; +using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Scripting; @@ -30,17 +32,13 @@ namespace FlaxEditor.CustomEditors.Dedicated if (_presets != value) { _presets = value; - OnPresetsChanged(); + TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString()); } } } public bool IsSelected; - - private void OnPresetsChanged() - { - TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString()); - } + public bool SupportsShiftModulation; /// public override void Draw() @@ -77,6 +75,11 @@ namespace FlaxEditor.CustomEditors.Dedicated borderColor = BorderColorHighlighted; } + if (SupportsShiftModulation && Input.GetKey(KeyboardKeys.Shift)) + { + backgroundColor = BackgroundColorSelected; + } + // Calculate fill area float fillSize = rect.Width / 3; Rectangle fillArea; @@ -152,24 +155,83 @@ namespace FlaxEditor.CustomEditors.Dedicated { Render2D.DrawRectangle(rect, style.BackgroundSelected.AlphaMultiplied(0.8f), 1.1f); } + + // Draw pivot point + if (SupportsShiftModulation && Input.GetKey(KeyboardKeys.Control)) + { + Vector2 pivotPoint; + switch (_presets) + { + case AnchorPresets.Custom: + pivotPoint = Vector2.Minimum; + break; + case AnchorPresets.TopLeft: + pivotPoint = new Vector2(0, 0); + break; + case AnchorPresets.TopCenter: + case AnchorPresets.HorizontalStretchTop: + pivotPoint = new Vector2(rect.Width / 2, 0); + break; + case AnchorPresets.TopRight: + pivotPoint = new Vector2(rect.Width, 0); + break; + case AnchorPresets.MiddleLeft: + case AnchorPresets.VerticalStretchLeft: + pivotPoint = new Vector2(0, rect.Height / 2); + break; + case AnchorPresets.MiddleCenter: + case AnchorPresets.VerticalStretchCenter: + case AnchorPresets.HorizontalStretchMiddle: + case AnchorPresets.StretchAll: + pivotPoint = new Vector2(rect.Width / 2, rect.Height / 2); + break; + case AnchorPresets.MiddleRight: + case AnchorPresets.VerticalStretchRight: + pivotPoint = new Vector2(rect.Width, rect.Height / 2); + break; + case AnchorPresets.BottomLeft: + pivotPoint = new Vector2(0, rect.Height); + break; + case AnchorPresets.BottomCenter: + case AnchorPresets.HorizontalStretchBottom: + pivotPoint = new Vector2(rect.Width / 2, rect.Height); + break; + case AnchorPresets.BottomRight: + pivotPoint = new Vector2(rect.Width, rect.Height); + break; + default: throw new ArgumentOutOfRangeException(); + } + var pivotPointSize = new Vector2(3.0f); + Render2D.DrawRectangle(new Rectangle(pivotPoint - pivotPointSize * 0.5f, pivotPointSize), style.ProgressNormal, 1.1f); + } } } - class AnchorPresetsEditorPopup : ContextMenuBase + /// + /// Context menu for anchors presets editing. + /// + /// + public sealed class AnchorPresetsEditorPopup : ContextMenuBase { const float ButtonsMargin = 10.0f; const float ButtonsMarginStretch = 8.0f; const float ButtonsSize = 32.0f; const float TitleHeight = 23.0f; + const float InfoHeight = 23.0f; const float DialogWidth = ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch; - const float DialogHeight = TitleHeight + ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch; + const float DialogHeight = TitleHeight + InfoHeight + ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch; + + private readonly bool _supportsShiftModulation; /// /// Initializes a new instance of the class. /// /// The initial value. - public AnchorPresetsEditorPopup(AnchorPresets presets) + /// If the popup should react to shift + public AnchorPresetsEditorPopup(AnchorPresets presets, bool supportsShiftModulation = true) { + _supportsShiftModulation = supportsShiftModulation; + var style = FlaxEngine.GUI.Style.Current; Tag = presets; Size = new Vector2(DialogWidth, DialogHeight); @@ -182,9 +244,17 @@ namespace FlaxEditor.CustomEditors.Dedicated Parent = this }; + // Info + var info = new Label(0, title.Bottom, DialogWidth, InfoHeight) + { + Font = new FontReference(style.FontSmall), + Text = "Shift: also set bounds\nControl: also set pivot", + Parent = this + }; + // Buttons var buttonsX = ButtonsMargin; - var buttonsY = title.Bottom + ButtonsMargin; + var buttonsY = info.Bottom + ButtonsMargin; var buttonsSpacingX = ButtonsSize + ButtonsMargin; var buttonsSpacingY = ButtonsSize + ButtonsMargin; // @@ -217,6 +287,7 @@ namespace FlaxEditor.CustomEditors.Dedicated Parent = this, Presets = presets, IsSelected = presets == (AnchorPresets)Tag, + SupportsShiftModulation = _supportsShiftModulation, Tag = presets, }; button.ButtonClicked += OnButtonClicked; @@ -276,7 +347,7 @@ namespace FlaxEditor.CustomEditors.Dedicated private void OnButtonClicked() { var location = _button.Center + new Vector2(3.0f); - var editor = new AnchorPresetsEditorPopup(_button.Presets); + var editor = new AnchorPresetsEditorPopup(_button.Presets, true); editor.VisibleChanged += OnEditorVisibleChanged; editor.Show(_button.Parent, location); } @@ -288,6 +359,30 @@ namespace FlaxEditor.CustomEditors.Dedicated SetValue(control.Tag); } + /// + protected override void SynchronizeValue(object value) + { + // Custom anchors editing for Control to handle bounds preservation via key modifiers + if (ParentEditor != null) + { + var centerToPosition = Input.GetKey(KeyboardKeys.Shift); + var setPivot = Input.GetKey(KeyboardKeys.Control); + var editedAny = false; + foreach (var parentValue in ParentEditor.Values) + { + if (parentValue is Control parentControl) + { + parentControl.SetAnchorPreset((AnchorPresets)value, !centerToPosition, setPivot); + editedAny = true; + } + } + if (editedAny) + return; + } + + base.SynchronizeValue(value); + } + /// public override void Refresh() { @@ -309,7 +404,7 @@ namespace FlaxEditor.CustomEditors.Dedicated /// Dedicated custom editor for object. /// /// - public sealed class UIControlControlEditor : GenericEditor + public class UIControlControlEditor : GenericEditor { private Type _cachedType; @@ -349,6 +444,176 @@ namespace FlaxEditor.CustomEditors.Dedicated // Show control properties base.Initialize(layout); + + for (int i = 0; i < layout.Children.Count; i++) + { + if (layout.Children[i] is GroupElement group && group.Panel.HeaderText == "Transform") + { + VerticalPanelElement mainHor = VerticalPanelWithoutMargin(group); + CreateTransformElements(mainHor, ValuesTypes); + group.ContainerControl.ChangeChildIndex(mainHor.Control, 0); + break; + } + } + } + + private void CreateTransformElements(LayoutElementsContainer main, ScriptType[] valueTypes) + { + main.Space(10); + HorizontalPanelElement sidePanel = main.HorizontalPanel(); + sidePanel.Panel.ClipChildren = false; + + ScriptMemberInfo anchorInfo = valueTypes[0].GetProperty("AnchorPreset"); + ItemInfo anchorItem = new ItemInfo(anchorInfo); + sidePanel.Object(anchorItem.GetValues(Values)); + + VerticalPanelElement group = VerticalPanelWithoutMargin(sidePanel); + + group.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop; + group.Panel.Offsets = new Margin(100, 10, 0, 0); + + var horUp = UniformGridTwoByOne(group); + horUp.CustomControl.Height = TextBoxBase.DefaultHeight; + var horDown = UniformGridTwoByOne(group); + horDown.CustomControl.Height = TextBoxBase.DefaultHeight; + + GetAnchorEquality(out _cachedXEq, out _cachedYEq, valueTypes); + + BuildLocationSizeOffsets(horUp, horDown, _cachedXEq, _cachedYEq, valueTypes); + + main.Space(10); + BuildAnchorsDropper(main, valueTypes); + } + + private void BuildAnchorsDropper(LayoutElementsContainer main, ScriptType[] valueTypes) + { + ScriptMemberInfo minInfo = valueTypes[0].GetProperty("AnchorMin"); + ScriptMemberInfo maxInfo = valueTypes[0].GetProperty("AnchorMax"); + ItemInfo minItem = new ItemInfo(minInfo); + ItemInfo maxItem = new ItemInfo(maxInfo); + + GroupElement ng = main.Group("Anchors", true); + ng.Panel.Close(false); + ng.Property("Min", minItem.GetValues(Values)); + ng.Property("Max", maxItem.GetValues(Values)); + } + + private void GetAnchorEquality(out bool xEq, out bool yEq, ScriptType[] valueTypes) + { + ScriptMemberInfo minInfo = valueTypes[0].GetProperty("AnchorMin"); + ScriptMemberInfo maxInfo = valueTypes[0].GetProperty("AnchorMax"); + ItemInfo minItem = new ItemInfo(minInfo); + ItemInfo maxItem = new ItemInfo(maxInfo); + ValueContainer minVal = minItem.GetValues(Values); + ValueContainer maxVal = maxItem.GetValues(Values); + + ItemInfo xItem = new ItemInfo(minInfo.ValueType.GetField("X")); + ItemInfo yItem = new ItemInfo(minInfo.ValueType.GetField("Y")); + + xEq = xItem.GetValues(minVal).ToList().Any(xItem.GetValues(maxVal).ToList().Contains); + yEq = yItem.GetValues(minVal).ToList().Any(yItem.GetValues(maxVal).ToList().Contains); + } + + private void BuildLocationSizeOffsets(LayoutElementsContainer horUp, LayoutElementsContainer horDown, bool xEq, bool yEq, ScriptType[] valueTypes) + { + ScriptMemberInfo xInfo = valueTypes[0].GetProperty("LocalX"); + ItemInfo xItem = new ItemInfo(xInfo); + ScriptMemberInfo yInfo = valueTypes[0].GetProperty("LocalY"); + ItemInfo yItem = new ItemInfo(yInfo); + ScriptMemberInfo widthInfo = valueTypes[0].GetProperty("Width"); + ItemInfo widthItem = new ItemInfo(widthInfo); + ScriptMemberInfo heightInfo = valueTypes[0].GetProperty("Height"); + ItemInfo heightItem = new ItemInfo(heightInfo); + + ScriptMemberInfo leftInfo = valueTypes[0].GetProperty("Proxy_Offset_Left", BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + ItemInfo leftItem = new ItemInfo(leftInfo); + ScriptMemberInfo rightInfo = valueTypes[0].GetProperty("Proxy_Offset_Right", BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + ItemInfo rightItem = new ItemInfo(rightInfo); + ScriptMemberInfo topInfo = valueTypes[0].GetProperty("Proxy_Offset_Top", BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + ItemInfo topItem = new ItemInfo(topInfo); + ScriptMemberInfo bottomInfo = valueTypes[0].GetProperty("Proxy_Offset_Bottom", BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + ItemInfo bottomItem = new ItemInfo(bottomInfo); + + LayoutElementsContainer xEl; + LayoutElementsContainer yEl; + LayoutElementsContainer hEl; + LayoutElementsContainer vEl; + if (xEq) + { + xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values)); + vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values)); + } + else + { + xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values)); + vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values)); + } + if (yEq) + { + yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values)); + hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values)); + } + else + { + yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values)); + hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values)); + } + xEl.Control.AnchorMin = new Vector2(0, xEl.Control.AnchorMin.Y); + xEl.Control.AnchorMax = new Vector2(0.5f, xEl.Control.AnchorMax.Y); + + vEl.Control.AnchorMin = new Vector2(0, xEl.Control.AnchorMin.Y); + vEl.Control.AnchorMax = new Vector2(0.5f, xEl.Control.AnchorMax.Y); + + yEl.Control.AnchorMin = new Vector2(0.5f, xEl.Control.AnchorMin.Y); + yEl.Control.AnchorMax = new Vector2(1, xEl.Control.AnchorMax.Y); + + hEl.Control.AnchorMin = new Vector2(0.5f, xEl.Control.AnchorMin.Y); + hEl.Control.AnchorMax = new Vector2(1, xEl.Control.AnchorMax.Y); + } + + private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont) + { + var horUp = cont.VerticalPanel(); + horUp.Panel.Margin = Margin.Zero; + return horUp; + } + + private CustomElementsContainer UniformGridTwoByOne(LayoutElementsContainer cont) + { + var horUp = cont.CustomContainer(); + horUp.CustomControl.SlotsHorizontally = 2; + horUp.CustomControl.SlotsVertically = 1; + horUp.CustomControl.SlotPadding = Margin.Zero; + horUp.CustomControl.ClipChildren = false; + return horUp; + } + + private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values) + { + CustomElementsContainer hor = UniformGridTwoByOne(el); + hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0); + LabelElement lab = hor.Label(text); + hor.Object(values); + return hor; + } + + private bool _cachedXEq; + private bool _cachedYEq; + + /// + /// Refreshes if equality of anchors does not correspond to the cached equality + /// + public void RefreshBaseOnAnchorsEquality() + { + if (Values.HasNull) + return; + + GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes); + if (xEq != _cachedXEq || yEq != _cachedYEq) + { + RebuildLayout(); + return; + } } /// @@ -361,6 +626,8 @@ namespace FlaxEditor.CustomEditors.Dedicated RebuildLayout(); return; } + RefreshBaseOnAnchorsEquality(); + //RefreshValues(); base.Refresh(); } @@ -405,7 +672,13 @@ namespace FlaxEditor.CustomEditors.Dedicated for (int i = 0; i < uiControls.Count; i++) { var uiControl = (UIControl)uiControls[i]; + string previousName = uiControl.Control?.GetType()?.Name ?? typeof(UIControl).Name; uiControl.Control = (Control)controlType.CreateInstance(); + if (uiControl.Name.StartsWith(previousName)) + { + string newName = controlType.Name + uiControl.Name.Substring(previousName.Length); + uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null); + } } } } @@ -414,7 +687,13 @@ namespace FlaxEditor.CustomEditors.Dedicated for (int i = 0; i < uiControls.Count; i++) { var uiControl = (UIControl)uiControls[i]; + string previousName = uiControl.Control?.GetType()?.Name ?? typeof(UIControl).Name; uiControl.Control = (Control)controlType.CreateInstance(); + if (uiControl.Name.StartsWith(previousName)) + { + string newName = controlType.Name + uiControl.Name.Substring(previousName.Length); + uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null); + } } } diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs index a6aa34880..555ebc870 100644 --- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs @@ -22,10 +22,9 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { element = layout.ComboBox(); - element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged; - - // Set layer names element.ComboBox.SetItems(LayersAndTagsSettings.GetCurrentLayers()); + element.ComboBox.SelectedIndex = (int)Values[0]; + element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged; } private void GetActorsTree(List list, Actor a) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 2e5aeade7..141330aea 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -154,9 +154,20 @@ namespace FlaxEditor.CustomEditors.Editors if (i != 0 && spacing > 0f) { if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement) + { + if (propertiesListElement.Labels.Count > 0) + { + var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1]; + var margin = label.Margin; + margin.Bottom += spacing; + label.Margin = margin; + } propertiesListElement.Space(spacing); + } else + { layout.Space(spacing); + } } var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; diff --git a/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs b/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs new file mode 100644 index 000000000..67533ed56 --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs @@ -0,0 +1,164 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Globalization; +using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Tree; +using FlaxEditor.Utilities; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Editors +{ + /// + /// Default implementation of the inspector used to edit value type properties. Supports editing property of type (as culture name). + /// + [CustomEditor(typeof(CultureInfo)), DefaultEditor] + internal class CultureInfoEditor : CustomEditor + { + private ClickableLabel _label; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + _label = layout.ClickableLabel(GetName(Culture)).CustomControl; + _label.RightClick += ShowPicker; + var button = new Button + { + Width = 16.0f, + Text = "...", + Parent = _label, + }; + button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); + button.Clicked += ShowPicker; + } + + /// + public override void Refresh() + { + base.Refresh(); + + _label.Text = GetName(Culture); + } + + /// + protected override void Deinitialize() + { + _label = null; + + base.Deinitialize(); + } + + private CultureInfo Culture + { + get + { + if (Values[0] is CultureInfo asCultureInfo) + return asCultureInfo; + if (Values[0] is string asString) + return new CultureInfo(asString); + return null; + } + set + { + if (Values[0] is CultureInfo) + SetValue(value); + else if (Values[0] is string) + SetValue(value.Name); + } + } + + private class CultureInfoComparer : IComparer + { + public int Compare(CultureInfo a, CultureInfo b) + { + return string.Compare(a.Name, b.Name, StringComparison.Ordinal); + } + } + + private static void UpdateFilter(TreeNode node, string filterText) + { + // Update children + bool isAnyChildVisible = false; + for (int i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is TreeNode child) + { + UpdateFilter(child, filterText); + isAnyChildVisible |= child.Visible; + } + } + + // Update itself + bool noFilter = string.IsNullOrWhiteSpace(filterText); + bool isThisVisible = noFilter || QueryFilterHelper.Match(filterText, node.Text); + bool isExpanded = isAnyChildVisible; + if (isExpanded) + node.Expand(true); + else + node.Collapse(true); + node.Visible = isThisVisible | isAnyChildVisible; + } + + private void ShowPicker() + { + var menu = CreatePicker(Culture, value => { Culture = value; }); + menu.Show(_label, new Vector2(0, _label.Height)); + } + + internal static ContextMenuBase CreatePicker(CultureInfo value, Action changed) + { + var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree); + tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node + var root = tree.AddChild(); + var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); + Array.Sort(cultures, 1, cultures.Length - 2, new CultureInfoComparer()); // at 0 there is Invariant Culture + var lcidToNode = new Dictionary(); + for (var i = 0; i < cultures.Length; i++) + { + var culture = cultures[i]; + var node = new TreeNode + { + Tag = culture, + Text = GetName(culture), + }; + if (!lcidToNode.TryGetValue(culture.Parent.LCID, out ContainerControl parent)) + parent = root; + node.Parent = parent; + lcidToNode[culture.LCID] = node; + } + if (value != null) + tree.Select((TreeNode)lcidToNode[value.LCID]); + tree.SelectedChanged += delegate(List before, List after) + { + if (after.Count == 1) + { + menu.Hide(); + changed((CultureInfo)after[0].Tag); + } + }; + searchBox.TextChanged += delegate + { + if (tree.IsLayoutLocked) + return; + root.LockChildrenRecursive(); + var query = searchBox.Text; + UpdateFilter(root, query); + root.UnlockChildrenRecursive(); + menu.PerformLayout(); + }; + root.ExpandAll(true); + return menu; + } + + internal static string GetName(CultureInfo value) + { + return value != null ? string.Format("{0} - {1}", value.Name, value.EnglishName) : null; + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 4e4048dba..4a94136db 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -61,7 +61,7 @@ namespace FlaxEditor.CustomEditors.Editors var keyType = _editor.Values.Type.GetGenericArguments()[0]; if (keyType == typeof(string) || keyType.IsPrimitive) { - var popup = RenamePopup.Show(Parent, Bounds, Text, false); + var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false); popup.Validate += (renamePopup, value) => { object newKey; @@ -86,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Editors } else if (keyType.IsEnum) { - var popup = RenamePopup.Show(Parent, Bounds, Text, false); + var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false); var picker = new EnumComboBox(keyType) { AnchorPreset = AnchorPresets.StretchAll, @@ -220,9 +220,20 @@ namespace FlaxEditor.CustomEditors.Editors if (i != 0 && spacing > 0f) { if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement) + { + if (propertiesListElement.Labels.Count > 0) + { + var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1]; + var margin = label.Margin; + margin.Bottom += spacing; + label.Margin = margin; + } propertiesListElement.Space(spacing); + } else + { layout.Space(spacing); + } } var key = keys.ElementAt(i); diff --git a/Source/Editor/CustomEditors/Editors/EnumEditor.cs b/Source/Editor/CustomEditors/Editors/EnumEditor.cs index 7954e18bf..b91c0cf95 100644 --- a/Source/Editor/CustomEditors/Editors/EnumEditor.cs +++ b/Source/Editor/CustomEditors/Editors/EnumEditor.cs @@ -9,7 +9,7 @@ using FlaxEngine; namespace FlaxEditor.CustomEditors.Editors { /// - /// Default implementation of the inspector used to edit float value type properties. + /// Default implementation of the inspector used to edit enum value type properties. /// [CustomEditor(typeof(Enum)), DefaultEditor] public class EnumEditor : CustomEditor diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index ed6d88e9e..d4079ad79 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -173,6 +173,15 @@ namespace FlaxEditor.CustomEditors.Editors return string.Compare(Display.Group, other.Display.Group, StringComparison.InvariantCulture); } + if (Editor.Instance.Options.Options.General.ScriptMembersOrder == Options.GeneralOptions.MembersOrder.Declaration) + { + // By declaration order + if (Info.MetadataToken > other.Info.MetadataToken) + return 1; + if (Info.MetadataToken < other.Info.MetadataToken) + return -1; + } + // By name return string.Compare(Info.Name, other.Info.Name, StringComparison.InvariantCulture); } @@ -205,6 +214,7 @@ namespace FlaxEditor.CustomEditors.Editors public ScriptMemberInfo Target; public ScriptMemberInfo Source; public PropertiesListElement PropertiesList; + public GroupElement Group; public bool Invert; public int LabelIndex; @@ -248,15 +258,15 @@ namespace FlaxEditor.CustomEditors.Editors for (int i = 0; i < properties.Length; i++) { var p = properties[i]; + var attributes = p.GetAttributes(true); + var showInEditor = attributes.Any(x => x is ShowInEditorAttribute); // Skip properties without getter or setter - if (!p.HasGet || !p.HasSet) + if (!p.HasGet || (!p.HasSet && !showInEditor)) continue; - - var attributes = p.GetAttributes(true); - + // Skip hidden fields, handle special attributes - if ((!p.IsPublic && !attributes.Any(x => x is ShowInEditorAttribute)) || attributes.Any(x => x is HideInEditorAttribute)) + if ((!p.IsPublic && !showInEditor) || attributes.Any(x => x is HideInEditorAttribute)) continue; items.Add(new ItemInfo(p, attributes)); @@ -370,26 +380,22 @@ namespace FlaxEditor.CustomEditors.Editors } } } - if (item.VisibleIf != null) + if (item.VisibleIf != null && itemLayout.Children.Count > 0) { - PropertiesListElement list; - if (itemLayout.Children.Count > 0 && itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement list1) - { + PropertiesListElement list = null; + GroupElement group = null; + if (itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement list1) list = list1; - } + else if (itemLayout.Children[itemLayout.Children.Count - 1] is GroupElement group1) + group = group1; else - { - // TODO: support inlined objects hiding? return; - } // Get source member used to check rule var sourceMember = GetVisibleIfSource(item.Info.DeclaringType, item.VisibleIf); if (sourceMember == ScriptType.Null) return; - // Find the target control to show/hide - // Resize cache if (_visibleIfCaches == null) _visibleIfCaches = new VisibleIfCache[8]; @@ -405,6 +411,7 @@ namespace FlaxEditor.CustomEditors.Editors Target = item.Info, Source = sourceMember, PropertiesList = list, + Group = group, LabelIndex = labelIndex, Invert = item.VisibleIf.Invert, }; @@ -560,8 +567,7 @@ namespace FlaxEditor.CustomEditors.Editors { for (int i = 0; i < _visibleIfCaches.Length; i++) { - var c = _visibleIfCaches[i]; - + ref var c = ref _visibleIfCaches[i]; if (c.Target == ScriptMemberInfo.Null) break; @@ -577,7 +583,7 @@ namespace FlaxEditor.CustomEditors.Editors } // Apply the visibility (note: there may be no label) - if (c.LabelIndex != -1 && c.PropertiesList.Labels.Count > c.LabelIndex) + if (c.LabelIndex != -1 && c.PropertiesList != null && c.PropertiesList.Labels.Count > c.LabelIndex) { var label = c.PropertiesList.Labels[c.LabelIndex]; label.Visible = visible; @@ -590,6 +596,10 @@ namespace FlaxEditor.CustomEditors.Editors child.Visible = visible; } } + if (c.Group != null) + { + c.Group.Panel.Visible = visible; + } } } catch (Exception ex) diff --git a/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs b/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs new file mode 100644 index 000000000..82503d9bc --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs @@ -0,0 +1,234 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using FlaxEditor.Content.Settings; +using FlaxEditor.CustomEditors.Elements; +using FlaxEditor.GUI.Tree; +using FlaxEditor.Scripting; +using FlaxEditor.Utilities; +using FlaxEngine; +using FlaxEngine.GUI; +using Utils = FlaxEditor.Utilities.Utils; + +namespace FlaxEditor.CustomEditors.Editors +{ + /// + /// Default implementation of the inspector used to edit localized string properties. + /// + [CustomEditor(typeof(LocalizedString)), DefaultEditor] + public sealed class LocalizedStringEditor : GenericEditor + { + private TextBoxElement _idElement, _valueElement; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + + if (layout.Children.Count == 0) + return; + var propList = layout.Children[layout.Children.Count - 1] as PropertiesListElement; + if (propList == null || propList.Children.Count != 2) + return; + var idElement = propList.Children[0] as TextBoxElement; + var valueElement = propList.Children[1] as TextBoxElement; + if (idElement == null || valueElement == null) + return; + _idElement = idElement; + _valueElement = valueElement; + + var attributes = Values.GetAttributes(); + var multiLine = attributes?.FirstOrDefault(x => x is MultilineTextAttribute); + if (multiLine != null) + { + valueElement.TextBox.IsMultiline = true; + valueElement.TextBox.Height *= 3; + } + + var selectString = new Button + { + Width = 16.0f, + Text = "...", + TooltipText = "Select localized text from Localization Settings...", + Parent = idElement.TextBox, + }; + selectString.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); + selectString.ButtonClicked += OnSelectStringClicked; + + var addString = new Button + { + Width = 16.0f, + Text = "+", + TooltipText = "Add new localized text to Localization Settings (all used locales)", + Parent = _valueElement.TextBox, + Enabled = IsSingleObject, + }; + addString.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); + addString.ButtonClicked += OnAddStringClicked; + } + + /// + internal override void RefreshInternal() + { + base.RefreshInternal(); + + if (_valueElement != null) + { + _valueElement.TextBox.WatermarkText = Localization.GetString(_idElement.Text); + } + } + + /// + protected override void Deinitialize() + { + base.Deinitialize(); + + _idElement = null; + _valueElement = null; + } + + private void OnSelectStringClicked(Button button) + { + var settings = GameSettings.Load(); + if (settings?.LocalizedStringTables == null || settings.LocalizedStringTables.Length == 0) + { + MessageBox.Show("No valid localization settings setup."); + return; + } + Profiler.BeginEvent("LocalizedStringEditor.OnSelectStringClicked"); + var allKeys = new HashSet(); + for (int i = 0; i < settings.LocalizedStringTables.Length; i++) + { + var table = settings.LocalizedStringTables[i]; + if (table && !table.WaitForLoaded()) + { + var entries = table.Entries; + foreach (var e in entries) + allKeys.Add(e.Key); + } + } + var allKeysSorted = allKeys.ToList(); + allKeysSorted.Sort(); + var value = _idElement?.TextBox.Text; + var menu = Utils.CreateSearchPopup(out var searchBox, out var tree); + var idToNode = new TreeNode[allKeysSorted.Count]; + for (var i = 0; i < allKeysSorted.Count; i++) + { + var key = allKeysSorted[i]; + var node = new TreeNode + { + Text = key, + TooltipText = Localization.GetString(key), + Parent = tree, + }; + if (key == value) + tree.Select(node); + idToNode[i] = node; + } + tree.SelectedChanged += delegate(List before, List after) + { + if (after.Count == 1) + { + menu.Hide(); + _idElement.TextBox.SetTextAsUser(after[0].Text); + } + }; + searchBox.TextChanged += delegate + { + if (tree.IsLayoutLocked) + return; + tree.LockChildrenRecursive(); + var query = searchBox.Text; + for (int i = 0; i < idToNode.Length; i++) + { + var node = idToNode[i]; + node.Visible = string.IsNullOrWhiteSpace(query) || QueryFilterHelper.Match(query, node.Text); + } + tree.UnlockChildrenRecursive(); + menu.PerformLayout(); + }; + menu.Show(button, new Vector2(0, button.Height)); + Profiler.EndEvent(); + } + + private void OnAddStringClicked(Button button) + { + var settings = GameSettings.Load(); + if (settings?.LocalizedStringTables == null || settings.LocalizedStringTables.Length == 0) + { + MessageBox.Show("No valid localization settings setup."); + return; + } + Profiler.BeginEvent("LocalizedStringEditor.OnAddStringClicked"); + var allKeys = new HashSet(); + for (int i = 0; i < settings.LocalizedStringTables.Length; i++) + { + var table = settings.LocalizedStringTables[i]; + if (table && !table.WaitForLoaded()) + { + var entries = table.Entries; + foreach (var e in entries) + allKeys.Add(e.Key); + } + } + _valueElement.TextBox.SetTextAsUser(null); + string newKey = null; + if (string.IsNullOrEmpty(_idElement.Text)) + { + CustomEditor customEditor = this; + while (customEditor?.Values != null) + { + if (customEditor.Values.Info != ScriptMemberInfo.Null) + if (newKey == null) + newKey = customEditor.Values.Info.Name; + else + newKey = customEditor.Values.Info.Name + '.' + newKey; + else if (customEditor.Values[0] is SceneObject sceneObject) + if (newKey == null) + newKey = sceneObject.GetNamePath('.'); + else + newKey = sceneObject.GetNamePath('.') + '.' + newKey; + else + break; + customEditor = customEditor.ParentEditor; + } + if (string.IsNullOrWhiteSpace(newKey)) + newKey = Guid.NewGuid().ToString("N"); + } + else + { + newKey = _idElement.Text; + } + if (allKeys.Contains(newKey)) + { + Profiler.EndEvent(); + if (_idElement.Text != newKey) + _idElement.TextBox.SetTextAsUser(newKey); + else + MessageBox.Show("Already added."); + return; + } + var newValue = _valueElement.Text; + Editor.Log(newKey + (newValue != null ? " = " + newValue : string.Empty)); + var locales = settings.LocalizedStringTables.GroupBy(x => x.Locale); + foreach (var locale in locales) + { + var table = locale.First(); + var entries = table.Entries; + if (table.Locale == "en") + entries[newKey] = new[] { newValue }; + else + entries[newKey] = new[] { string.Empty }; + table.Entries = entries; + table.Save(); + } + _idElement.TextBox.SetTextAsUser(newKey); + Profiler.EndEvent(); + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs index 7724acf18..dcdf3114b 100644 --- a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs @@ -3,7 +3,6 @@ using System; using FlaxEditor.GUI; using FlaxEditor.Scripting; -using FlaxEngine; namespace FlaxEditor.CustomEditors.Editors { diff --git a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs index 338c8eca6..f80c81e27 100644 --- a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs @@ -12,6 +12,9 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(Quaternion)), DefaultEditor] public class QuaternionEditor : CustomEditor { + private Vector3 _cachedAngles = Vector3.Zero; + private object _cachedToken; + /// /// The X component element /// @@ -58,15 +61,36 @@ namespace FlaxEditor.CustomEditors.Editors if (IsSetBlocked) return; - float x = XElement.FloatValue.Value; - float y = YElement.FloatValue.Value; - float z = ZElement.FloatValue.Value; var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding; var token = isSliding ? this : null; + var useCachedAngles = isSliding && token == _cachedToken; + + float x = (useCachedAngles && !XElement.IsSliding) ? _cachedAngles.X : XElement.FloatValue.Value; + float y = (useCachedAngles && !YElement.IsSliding) ? _cachedAngles.Y : YElement.FloatValue.Value; + float z = (useCachedAngles && !ZElement.IsSliding) ? _cachedAngles.Z : ZElement.FloatValue.Value; + + x = Mathf.UnwindDegrees(x); + y = Mathf.UnwindDegrees(y); + z = Mathf.UnwindDegrees(z); + + if (!useCachedAngles) + { + _cachedAngles = new Vector3(x, y, z); + } + + _cachedToken = token; + Quaternion.Euler(x, y, z, out Quaternion value); SetValue(value, token); } + /// + protected override void ClearToken() + { + _cachedToken = null; + base.ClearToken(); + } + /// public override void Refresh() { diff --git a/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs b/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs index 2958cd337..de015a371 100644 --- a/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs +++ b/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs @@ -5,15 +5,15 @@ using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Elements { /// - /// The vertical panel element. + /// The horizontal panel element. /// /// - public class VerticalPanelElement : LayoutElementsContainer + public class HorizontalPanelElement : LayoutElementsContainer { /// /// The panel. /// - public readonly VerticalPanel Panel = new VerticalPanel(); + public readonly HorizontalPanel Panel = new HorizontalPanel(); /// public override ContainerControl ContainerControl => Panel; diff --git a/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs b/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs index de015a371..2958cd337 100644 --- a/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs +++ b/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs @@ -5,15 +5,15 @@ using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Elements { /// - /// The horizontal panel element. + /// The vertical panel element. /// /// - public class HorizontalPanelElement : LayoutElementsContainer + public class VerticalPanelElement : LayoutElementsContainer { /// /// The panel. /// - public readonly HorizontalPanel Panel = new HorizontalPanel(); + public readonly VerticalPanel Panel = new VerticalPanel(); /// public override ContainerControl ContainerControl => Panel; diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index ae33599c1..6f645789a 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -112,7 +112,7 @@ namespace FlaxEditor.CustomEditors OnAddElement(element); return element; } - + /// /// Adds new horizontal panel element. /// @@ -690,6 +690,17 @@ namespace FlaxEditor.CustomEditors return element; } + /// + /// Adds custom element to the layout. + /// + /// The element. + public void AddElement(LayoutElement element) + { + if (element == null) + throw new ArgumentNullException(); + OnAddElement(element); + } + /// /// Called when element is added to the layout. /// diff --git a/Source/Editor/CustomEditors/Values/ListValueContainer.cs b/Source/Editor/CustomEditors/Values/ListValueContainer.cs index f8e5dd26c..416b866cc 100644 --- a/Source/Editor/CustomEditors/Values/ListValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ListValueContainer.cs @@ -46,10 +46,7 @@ namespace FlaxEditor.CustomEditors if (values.HasReferenceValue) { - var v = (IList)values.ReferenceValue; - - // Get the reference value if collections are the same size - if (v != null && values.Count == v.Count) + if (values.ReferenceValue is IList v && values.Count == v.Count && v.Count > index) { _referenceValue = v[index]; _hasReferenceValue = true; diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index f8cecb801..49d3b5591 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors /// /// Gets the values type. /// - public ScriptType Type { get; } + public ScriptType Type { get; private set; } /// /// Gets a value indicating whether single object is selected. @@ -167,14 +167,14 @@ namespace FlaxEditor.CustomEditors { if (_hasReferenceValue) { - if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) + if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject && referenceSceneObject.HasPrefabLink) { for (int i = 0; i < Count; i++) { if (this[i] == referenceSceneObject) continue; - if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) + if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) return true; } } @@ -251,6 +251,10 @@ namespace FlaxEditor.CustomEditors } if (instanceValues._hasReferenceValue) { + // If the reference value is set for the parent values but it's null object then skip it + if (instanceValues._referenceValue == null && !instanceValues.Type.IsValueType) + return; + _referenceValue = Info.GetValue(instanceValues._referenceValue); _hasReferenceValue = true; } @@ -280,6 +284,15 @@ namespace FlaxEditor.CustomEditors Type = type; } + /// + /// Sets the type. Use with caution. + /// + /// The type. + public void SetType(ScriptType type) + { + Type = type; + } + /// /// Gets the custom attributes defined for the values source member. /// diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index 3ca463a2c..69c842820 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -51,14 +51,17 @@ public class Editor : EditorModule var platformToolsRoot = Path.Combine(FolderPath, "Cooker", "Platform"); var platformToolsRootExternal = Path.Combine(Globals.EngineRoot, "Source", "Platforms"); - AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Windows", "PLATFORM_TOOLS_WINDOWS"); - AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "UWP", "PLATFORM_TOOLS_UWP"); - AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "UWP", "PLATFORM_TOOLS_XBOX_ONE"); + if (options.Platform.Target == TargetPlatform.Windows) + { + AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Windows", "PLATFORM_TOOLS_WINDOWS"); + AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "UWP", "PLATFORM_TOOLS_UWP"); + AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "UWP", "PLATFORM_TOOLS_XBOX_ONE"); + AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "PS4", "PLATFORM_TOOLS_PS4"); + AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "XboxScarlett", "PLATFORM_TOOLS_XBOX_SCARLETT"); + AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID"); + AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Switch", "PLATFORM_TOOLS_SWITCH"); + } AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Linux", "PLATFORM_TOOLS_LINUX"); - AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "PS4", "PLATFORM_TOOLS_PS4"); - AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "XboxScarlett", "PLATFORM_TOOLS_XBOX_SCARLETT"); - AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID"); - AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Switch", "PLATFORM_TOOLS_SWITCH"); // Visual Studio integration if (options.Platform.Target == TargetPlatform.Windows) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 3aa8346c8..adf4d566b 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -220,7 +220,7 @@ namespace FlaxEditor GameProject = ProjectInfo.Load(Internal_GetProjectPath()); Icons = new EditorIcons(); - Icons.GetIcons(); + Icons.LoadIcons(); // Create common editor modules RegisterModule(Options = new OptionsModule(this)); @@ -752,10 +752,11 @@ namespace FlaxEditor /// The collision data type. /// The source model. /// The source model LOD index. + /// The source model material slots mask. One bit per-slot. Can be sued to exclude particular material slots from collision cooking. /// The convex mesh generation flags. /// The convex mesh vertex limit. Use values in range [8;255] /// True if failed, otherwise false. - public static bool CookMeshCollision(string path, CollisionDataType type, Model model, int modelLodIndex = 0, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255) + public static bool CookMeshCollision(string path, CollisionDataType type, ModelBase model, int modelLodIndex = 0, uint materialSlotsMask = uint.MaxValue, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255) { if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -764,7 +765,7 @@ namespace FlaxEditor if (type == CollisionDataType.None) throw new ArgumentException(nameof(type)); - return Internal_CookMeshCollision(path, type, FlaxEngine.Object.GetUnmanagedPtr(model), modelLodIndex, convexFlags, convexVertexLimit); + return Internal_CookMeshCollision(path, type, FlaxEngine.Object.GetUnmanagedPtr(model), modelLodIndex, materialSlotsMask, convexFlags, convexVertexLimit); } /// @@ -816,9 +817,16 @@ namespace FlaxEditor /// The bounding sphere. public static void GetActorEditorSphere(Actor actor, out BoundingSphere sphere) { - Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out var box); - BoundingSphere.FromBox(ref box, out sphere); - sphere.Radius = Math.Max(sphere.Radius, 15.0f); + if (actor) + { + Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out var box); + BoundingSphere.FromBox(ref box, out sphere); + sphere.Radius = Math.Max(sphere.Radius, 15.0f); + } + else + { + sphere = BoundingSphere.Empty; + } } /// @@ -828,7 +836,14 @@ namespace FlaxEditor /// The bounding box. public static void GetActorEditorBox(Actor actor, out BoundingBox box) { - Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out box); + if (actor) + { + Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out box); + } + else + { + box = BoundingBox.Zero; + } } /// @@ -863,10 +878,12 @@ namespace FlaxEditor /// Checks if can import asset with the given extension. /// /// The file extension. + /// The output file extension (flax, json, etc.). /// True if can import files with given extension, otherwise false. - public static bool CanImport(string extension) + public static bool CanImport(string extension, out string outputExtension) { - return Internal_CanImport(extension); + outputExtension = Internal_CanImport(extension); + return outputExtension != null; } /// @@ -1166,8 +1183,10 @@ namespace FlaxEditor if (Windows.GameWin != null && Windows.GameWin.ContainsFocus) { var win = Windows.GameWin.Root; - if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused) + if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused) { + if (StateMachine.IsPlayMode && StateMachine.PlayingState.IsPaused) + return false; return true; } } @@ -1179,9 +1198,9 @@ namespace FlaxEditor if (Windows.GameWin != null && Windows.GameWin.ContainsFocus) { var win = Windows.GameWin.Root; - if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused) + if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused) { - pos = Vector2.Round(Windows.GameWin.Viewport.PointFromWindow(root.Window.ScreenToClient(pos))); + pos = Vector2.Round(Windows.GameWin.Viewport.PointFromScreen(pos) * root.DpiScale); } else { @@ -1199,9 +1218,9 @@ namespace FlaxEditor if (Windows.GameWin != null && Windows.GameWin.ContainsFocus) { var win = Windows.GameWin.Root; - if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused) + if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused) { - pos = Vector2.Round(root.Window.ClientToScreen(Windows.GameWin.Viewport.PointToWindow(pos))); + pos = Vector2.Round(Windows.GameWin.Viewport.PointToScreen(pos / root.DpiScale)); } else { @@ -1231,13 +1250,18 @@ namespace FlaxEditor var gameWin = Windows.GameWin; if (gameWin != null) { - // Handle case when Game window is not selected in tab view - var dockedTo = gameWin.ParentDockPanel; - if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null) - resultAsRef = dockedTo.SelectedTab.Size; - else - resultAsRef = gameWin.Size; - resultAsRef = Vector2.Round(resultAsRef); + var win = gameWin.Root; + if (win != null && win.RootWindow is WindowRootControl root) + { + // Handle case when Game window is not selected in tab view + var dockedTo = gameWin.ParentDockPanel; + if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null) + resultAsRef = dockedTo.SelectedTab.Size * root.DpiScale; + else + resultAsRef = gameWin.Size * root.DpiScale; + + resultAsRef = Vector2.Round(resultAsRef); + } } } @@ -1325,7 +1349,7 @@ namespace FlaxEditor internal static extern string Internal_GetShaderAssetSourceCode(IntPtr obj); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit); + internal static extern bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, uint materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void Internal_GetCollisionWires(IntPtr collisionData, out Vector3[] triangles, out int[] indices); @@ -1349,7 +1373,7 @@ namespace FlaxEditor internal static extern bool Internal_CreateVisualScript(string outputPath, string baseTypename); [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_CanImport(string extension); + internal static extern string Internal_CanImport(string extension); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool Internal_CanExport(string path); diff --git a/Source/Editor/EditorIcons.cs b/Source/Editor/EditorIcons.cs index 700b5e25e..d4b89b8d3 100644 --- a/Source/Editor/EditorIcons.cs +++ b/Source/Editor/EditorIcons.cs @@ -16,102 +16,124 @@ namespace FlaxEditor [HideInEditor] public sealed class EditorIcons { - public SpriteHandle FolderClosed12; - public SpriteHandle FolderOpened12; + // 12px public SpriteHandle DragBar12; - public SpriteHandle ArrowDown12; - public SpriteHandle ArrowRight12; public SpriteHandle Search12; + public SpriteHandle WindowDrag12; + public SpriteHandle CheckBoxIntermediate12; + public SpriteHandle ArrowRight12; public SpriteHandle Settings12; public SpriteHandle Cross12; - public SpriteHandle CheckBoxIntermediate12; public SpriteHandle CheckBoxTick12; - public SpriteHandle StatusBarSizeGrip12; + public SpriteHandle ArrowDown12; - public SpriteHandle ArrowRightBorder16; - public SpriteHandle World16; - public SpriteHandle ScaleStep16; - public SpriteHandle RotateStep16; - public SpriteHandle Grid16; - public SpriteHandle Translate16; - public SpriteHandle Rotate16; - public SpriteHandle Scale16; - public SpriteHandle Link16; - public SpriteHandle Docs16; - - public SpriteHandle Save32; - public SpriteHandle Undo32; - public SpriteHandle Redo32; + // 32px + public SpriteHandle Scalar32; public SpriteHandle Translate32; public SpriteHandle Rotate32; public SpriteHandle Scale32; - public SpriteHandle Play32; - public SpriteHandle Pause32; - public SpriteHandle Step32; - public SpriteHandle Stop32; - public SpriteHandle PageScale32; - public SpriteHandle Bone32; - public SpriteHandle Docs32; - public SpriteHandle Import32; - public SpriteHandle AddDoc32; - public SpriteHandle RemoveDoc32; - public SpriteHandle BracketsSlash32; - public SpriteHandle Find32; - public SpriteHandle Reload32; - public SpriteHandle ArrowLeft32; - public SpriteHandle ArrowRight32; - public SpriteHandle ArrowDown32; - public SpriteHandle ArrowUp32; - public SpriteHandle Error32; - public SpriteHandle Warning32; - public SpriteHandle Info32; - public SpriteHandle UV32; - public SpriteHandle Image32; + public SpriteHandle Grid32; + public SpriteHandle Flax32; + public SpriteHandle RotateSnap32; + public SpriteHandle ScaleSnap32; + public SpriteHandle Globe32; + public SpriteHandle CamSpeed32; public SpriteHandle Link32; - public SpriteHandle Next32; - public SpriteHandle Camera32; - public SpriteHandle Build32; + public SpriteHandle Add32; + public SpriteHandle Left32; + public SpriteHandle Right32; + public SpriteHandle Up32; + public SpriteHandle Down32; + public SpriteHandle FolderClosed32; + public SpriteHandle FolderOpen32; - public SpriteHandle Add48; - public SpriteHandle Paint48; - public SpriteHandle Foliage48; - public SpriteHandle Mountain48; + // Visject + public SpriteHandle VisjectBoxOpen32; + public SpriteHandle VisjectBoxClosed32; + public SpriteHandle VisjectArrowOpen32; + public SpriteHandle VisjectArrowClosed32; - public SpriteHandle Plugin64; - public SpriteHandle Document64; - public SpriteHandle CSharpScript64; - public SpriteHandle CppScript64; + // 64px + public SpriteHandle Flax64; + public SpriteHandle Save64; + public SpriteHandle Play64; + public SpriteHandle Stop64; + public SpriteHandle Pause64; + public SpriteHandle Skip64; + public SpriteHandle Info64; + public SpriteHandle Error64; + public SpriteHandle Warning64; + public SpriteHandle AddFile64; + public SpriteHandle DeleteFile64; + public SpriteHandle Import64; + public SpriteHandle Left64; + public SpriteHandle Right64; + public SpriteHandle Up64; + public SpriteHandle Down64; + public SpriteHandle Undo64; + public SpriteHandle Redo64; + public SpriteHandle Translate64; + public SpriteHandle Rotate64; + public SpriteHandle Scale64; + public SpriteHandle Refresh64; + public SpriteHandle Shift64; + public SpriteHandle Code64; public SpriteHandle Folder64; - public SpriteHandle Scene64; - public SpriteHandle CodeScript64; + public SpriteHandle CenterView64; + public SpriteHandle Image64; + public SpriteHandle Camera64; + public SpriteHandle Docs64; + public SpriteHandle Search64; + public SpriteHandle Bone64; + public SpriteHandle Link64; + public SpriteHandle Build64; + public SpriteHandle Add64; - public SpriteHandle Logo128; + // 96px + public SpriteHandle Toolbox96; + public SpriteHandle Paint96; + public SpriteHandle Foliage96; + public SpriteHandle Terrain96; - public SpriteHandle VisjectBoxOpen; - public SpriteHandle VisjectBoxClose; - public SpriteHandle VisjectArrowOpen; - public SpriteHandle VisjectArrowClose; + // 128px + public SpriteHandle AndroidSettings128; + public SpriteHandle PlaystationSettings128; + public SpriteHandle InputSettings128; + public SpriteHandle PhysicsSettings128; + public SpriteHandle CSharpScript128; + public SpriteHandle Folder128; + public SpriteHandle WindowsIcon128; + public SpriteHandle LinuxIcon128; + public SpriteHandle UWPSettings128; + public SpriteHandle XBOXSettings128; + public SpriteHandle LayersTagsSettings128; + public SpriteHandle GraphicsSettings128; + public SpriteHandle CPPScript128; + public SpriteHandle Plugin128; + public SpriteHandle XBoxScarletIcon128; + public SpriteHandle AssetShadow128; + public SpriteHandle WindowsSettings128; + public SpriteHandle TimeSettings128; + public SpriteHandle GameSettings128; + public SpriteHandle VisualScript128; + public SpriteHandle Document128; + public SpriteHandle XBoxOne128; + public SpriteHandle UWPStore128; + public SpriteHandle ColorWheel128; + public SpriteHandle LinuxSettings128; + public SpriteHandle NavigationSettings128; + public SpriteHandle AudioSettings128; + public SpriteHandle BuildSettings128; + public SpriteHandle Scene128; + public SpriteHandle AndroidIcon128; + public SpriteHandle PS4Icon128; + public SpriteHandle FlaxLogo128; - public SpriteHandle AssetShadow; - public SpriteHandle ColorWheel; - public SpriteHandle Windows; - public SpriteHandle XboxOne; - public SpriteHandle WindowsStore; - public SpriteHandle Linux; - public SpriteHandle PS4; - public SpriteHandle XboxSeriesX; - public SpriteHandle Android; - - internal void GetIcons() + internal void LoadIcons() { - // Load asset + // Load & validate var iconsAtlas = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.IconsAtlas); - if (iconsAtlas == null) - { - Editor.LogError("Cannot load editor icons atlas."); - return; - } - if (iconsAtlas.WaitForLoaded()) + if (iconsAtlas is null || iconsAtlas.WaitForLoaded()) { Editor.LogError("Failed to load editor icons atlas."); return; @@ -122,11 +144,11 @@ namespace FlaxEditor for (int i = 0; i < fields.Length; i++) { var field = fields[i]; + var sprite = iconsAtlas.FindSprite(field.Name); if (!sprite.IsValid) - { - Editor.LogWarning(string.Format("Failed to load sprite icon \'{0}\'.", field.Name)); - } + Editor.LogWarning($"Failed to load sprite icon \'{field.Name}\'."); + field.SetValue(this, sprite); } } diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index aa1c13c8a..d06c1eef9 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -349,6 +349,8 @@ namespace FlaxEditor.GUI /// protected virtual void OnSelectedIndexChanged() { + if (_tooltips != null && _tooltips.Length == _items.Count) + TooltipText = _selectedIndices.Count == 1 ? _tooltips[_selectedIndices[0]] : null; SelectedIndexChanged?.Invoke(this); } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index 04dded53c..966f3502b 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -439,7 +439,7 @@ namespace FlaxEditor.GUI.ContextMenu } } } - if (startIndex != -1) + if (startIndex > 0 && startIndex <= _panel.Children.Count) { // No more items found so start from the top if there are matching items _panel.Children[startIndex - 1].Defocus(); diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index 6caf7f651..402374a8e 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -47,6 +47,11 @@ namespace FlaxEditor.GUI.ContextMenu private Window _window; private Control _previouslyFocused; + /// + /// Gets a value indicating whether use automatic popup direction fix based on the screen dimensions. + /// + protected virtual bool UseAutomaticDirectionFix => true; + /// /// Returns true if context menu is opened /// @@ -124,7 +129,7 @@ namespace FlaxEditor.GUI.ContextMenu PerformLayout(); // Calculate popup direction and initial location (fit on a single monitor) - var dpiScale = Platform.DpiScale; + var dpiScale = parentWin.DpiScale; Vector2 dpiSize = Size * dpiScale; Vector2 locationWS = parent.PointToWindow(location); Vector2 locationSS = parentWin.PointToScreen(locationWS); @@ -132,21 +137,24 @@ namespace FlaxEditor.GUI.ContextMenu Rectangle monitorBounds = Platform.GetMonitorBounds(locationSS); Vector2 rightBottomLocationSS = locationSS + dpiSize; bool isUp = false, isLeft = false; - if (monitorBounds.Bottom < rightBottomLocationSS.Y) + if (UseAutomaticDirectionFix) { - // Direction: up - isUp = true; - locationSS.Y -= dpiSize.Y; + if (monitorBounds.Bottom < rightBottomLocationSS.Y) + { + // Direction: up + isUp = true; + locationSS.Y -= dpiSize.Y; - // Offset to fix sub-menu location - if (parent is ContextMenu menu && menu._childCM != null) - locationSS.Y += 30.0f * dpiScale; - } - if (monitorBounds.Right < rightBottomLocationSS.X) - { - // Direction: left - isLeft = true; - locationSS.X -= dpiSize.X; + // Offset to fix sub-menu location + if (parent is ContextMenu menu && menu._childCM != null) + locationSS.Y += 30.0f * dpiScale; + } + if (monitorBounds.Right < rightBottomLocationSS.X) + { + // Direction: left + isLeft = true; + locationSS.X -= dpiSize.X; + } } // Update direction flag @@ -275,7 +283,7 @@ namespace FlaxEditor.GUI.ContextMenu { if (_window != null) { - _window.ClientSize = Size * Platform.DpiScale; + _window.ClientSize = Size * _window.DpiScale; } } diff --git a/Source/Editor/GUI/Dialogs/ColorSelector.cs b/Source/Editor/GUI/Dialogs/ColorSelector.cs index da1b913a5..0cfc452fe 100644 --- a/Source/Editor/GUI/Dialogs/ColorSelector.cs +++ b/Source/Editor/GUI/Dialogs/ColorSelector.cs @@ -61,7 +61,7 @@ namespace FlaxEditor.GUI.Dialogs public ColorSelector(float wheelSize = 64) : base(0, 0, wheelSize, wheelSize) { - _colorWheelSprite = Editor.Instance.Icons.ColorWheel; + _colorWheelSprite = Editor.Instance.Icons.ColorWheel128; _wheelRect = new Rectangle(0, 0, wheelSize, wheelSize); } diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs index 173dd9128..c33429609 100644 --- a/Source/Editor/GUI/Dialogs/Dialog.cs +++ b/Source/Editor/GUI/Dialogs/Dialog.cs @@ -143,7 +143,7 @@ namespace FlaxEditor.GUI.Dialogs // Setup initial window settings CreateWindowSettings settings = CreateWindowSettings.Default; settings.Title = _title; - settings.Size = _dialogSize * Platform.DpiScale; + settings.Size = _dialogSize * parentWindow.DpiScale; settings.AllowMaximize = false; settings.AllowMinimize = false; settings.HasSizingFrame = false; diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs index 2865515da..f968c2501 100644 --- a/Source/Editor/GUI/Docking/DockPanel.cs +++ b/Source/Editor/GUI/Docking/DockPanel.cs @@ -123,9 +123,8 @@ namespace FlaxEditor.GUI.Docking if (parentWin == null) throw new InvalidOperationException("Missing parent window."); var control = _tabsProxy != null ? (Control)_tabsProxy : this; - var dpiScale = Platform.DpiScale; var clientPos = control.PointToWindow(Vector2.Zero); - return new Rectangle(parentWin.PointToScreen(clientPos), control.Size * dpiScale); + return new Rectangle(parentWin.PointToScreen(clientPos), control.Size * DpiScale); } } @@ -290,6 +289,10 @@ namespace FlaxEditor.GUI.Docking } OnSelectedTabChanged(); } + else if (autoFocus && _selectedTab != null && !_selectedTab.ContainsFocus) + { + _selectedTab.Focus(); + } } /// diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index 1f6b3924e..fbc9d7bee 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -348,7 +348,7 @@ namespace FlaxEditor.GUI.Docking // Cache data IsMouseRightButtonDown = true; if (MouseDownWindow != null) - _panel.SelectTab(MouseDownWindow); + _panel.SelectTab(MouseDownWindow, false); } else if (button == MouseButton.Middle) { diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 5e94ef547..1a28b1307 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -59,7 +59,10 @@ namespace FlaxEditor.GUI.Docking /// /// Gets the default window size. /// - public virtual Vector2 DefaultSize => new Vector2(900, 580); + /// + /// Scaled by the DPI, because the window should be large enough for its content on every monitor + /// + public virtual Vector2 DefaultSize => new Vector2(900, 580) * DpiScale; /// /// Gets the serialization typename. diff --git a/Source/Editor/GUI/EnumComboBox.cs b/Source/Editor/GUI/EnumComboBox.cs index 8ceae0bb0..ed1c80cd6 100644 --- a/Source/Editor/GUI/EnumComboBox.cs +++ b/Source/Editor/GUI/EnumComboBox.cs @@ -29,7 +29,7 @@ namespace FlaxEditor.GUI /// /// The cached value from the UI. /// - protected int _cachedValue; + protected long _cachedValue; /// /// True if has value cached, otherwise false. @@ -54,7 +54,7 @@ namespace FlaxEditor.GUI /// /// The value. /// - public int Value; + public long Value; /// /// Initializes a new instance of the struct. @@ -62,7 +62,7 @@ namespace FlaxEditor.GUI /// The name. /// The tooltip. /// The value. - public Entry(string name, int value, string tooltip = null) + public Entry(string name, long value, string tooltip = null) { Name = name; Tooltip = tooltip; @@ -88,13 +88,13 @@ namespace FlaxEditor.GUI public object EnumTypeValue { get => Enum.ToObject(_enumType, Value); - set => Value = Convert.ToInt32(value); + set => Value = Convert.ToInt64(value); } /// /// Gets or sets the value. /// - public int Value + public long Value { get => _cachedValue; set @@ -209,13 +209,13 @@ namespace FlaxEditor.GUI /// protected void CacheValue() { - int value = 0; + long value = 0; if (IsFlags) { var selection = Selection; for (int i = 0; i < selection.Count; i++) { - int index = selection[i]; + var index = selection[i]; value |= _entries[index].Value; } } @@ -276,7 +276,7 @@ namespace FlaxEditor.GUI tooltip = tooltipAttr.Text; } - entries.Add(new Entry(name, Convert.ToInt32(field.GetRawConstantValue()), tooltip)); + entries.Add(new Entry(name, Convert.ToInt64(field.GetRawConstantValue()), tooltip)); } } @@ -295,9 +295,9 @@ namespace FlaxEditor.GUI } // Calculate value that will be set after change - int valueAfter = 0; + long valueAfter = 0; bool isSelected = _selectedIndices.Contains(index); - int selectedValue = entries[index].Value; + long selectedValue = entries[index].Value; for (int i = 0; i < _selectedIndices.Count; i++) { int selectedIndex = _selectedIndices[i]; diff --git a/Source/Editor/GUI/Input/FloatValueBox.cs b/Source/Editor/GUI/Input/FloatValueBox.cs index 1bbd56c9a..e66fb721b 100644 --- a/Source/Editor/GUI/Input/FloatValueBox.cs +++ b/Source/Editor/GUI/Input/FloatValueBox.cs @@ -23,10 +23,7 @@ namespace FlaxEditor.GUI.Input value = Mathf.Clamp(value, _min, _max); if (Math.Abs(_value - value) > Mathf.Epsilon) { - // Set value _value = value; - - // Update UpdateText(); OnValueChanged(); } @@ -43,7 +40,6 @@ namespace FlaxEditor.GUI.Input { if (value > _max) throw new ArgumentException(); - _min = value; Value = Value; } @@ -60,7 +56,6 @@ namespace FlaxEditor.GUI.Input { if (value < _min) throw new ArgumentException(); - _max = value; Value = Value; } @@ -80,9 +75,19 @@ namespace FlaxEditor.GUI.Input public FloatValueBox(float value, float x = 0, float y = 0, float width = 120, float min = float.MinValue, float max = float.MaxValue, float slideSpeed = 1) : base(Mathf.Clamp(value, min, max), x, y, width, min, max, slideSpeed) { + TryUseAutoSliderSpeed(); UpdateText(); } + private void TryUseAutoSliderSpeed() + { + var range = _max - _min; + if (Mathf.IsOne(_slideSpeed) && range > Mathf.Epsilon * 200.0f && range < 1000000.0f) + { + _slideSpeed = range * 0.01f; + } + } + /// /// Sets the value limits. /// @@ -103,6 +108,7 @@ namespace FlaxEditor.GUI.Input { _min = limits.Min; _max = Mathf.Max(_min, limits.Max); + TryUseAutoSliderSpeed(); Value = Value; } @@ -115,6 +121,7 @@ namespace FlaxEditor.GUI.Input _min = limits.Min; _max = Mathf.Max(_min, limits.Max); _slideSpeed = limits.SliderSpeed; + TryUseAutoSliderSpeed(); Value = Value; } diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs index d34acd17b..b1b0408bd 100644 --- a/Source/Editor/GUI/Input/ValueBox.cs +++ b/Source/Editor/GUI/Input/ValueBox.cs @@ -184,7 +184,7 @@ namespace FlaxEditor.GUI.Input var style = Style.Current; // Draw sliding UI - Render2D.DrawSprite(style.Scale, SlideRect, style.Foreground); + Render2D.DrawSprite(style.Scalar, SlideRect, style.Foreground); // Check if is sliding if (_isSliding) diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index ff4e02c4b..bf375a358 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -190,15 +190,15 @@ namespace FlaxEditor.GUI private WindowHitCodes OnHitTest(ref Vector2 mouse) { - var pos = _window.ScreenToClient(mouse * Platform.DpiScale); + var dpiScale = _window.DpiScale; if (_window.IsMinimized) return WindowHitCodes.NoWhere; if (!_window.IsMaximized) { - var dpiScale = Platform.DpiScale; - var winSize = RootWindow.Size * dpiScale; + var pos = _window.ScreenToClient(mouse * dpiScale); // pos is not DPI adjusted + var winSize = _window.Size; // Distance from which the mouse is considered to be on the border/corner float distance = 5.0f * dpiScale; @@ -228,11 +228,11 @@ namespace FlaxEditor.GUI return WindowHitCodes.Bottom; } - var menuPos = PointFromWindow(pos); - var controlUnderMouse = GetChildAt(menuPos); + var mousePos = PointFromScreen(mouse * dpiScale); + var controlUnderMouse = GetChildAt(mousePos); var isMouseOverSth = controlUnderMouse != null && controlUnderMouse != _title; var rb = GetRightButton(); - if (rb != null && _minimizeButton != null && new Rectangle(rb.UpperRight * Platform.DpiScale, (_minimizeButton.BottomLeft - rb.UpperRight) * Platform.DpiScale).Contains(ref menuPos) && !isMouseOverSth) + if (rb != null && _minimizeButton != null && new Rectangle(rb.UpperRight, _minimizeButton.BottomLeft - rb.UpperRight).Contains(ref mousePos) && !isMouseOverSth) return WindowHitCodes.Caption; return WindowHitCodes.Client; diff --git a/Source/Editor/GUI/PlatformSelector.cs b/Source/Editor/GUI/PlatformSelector.cs index d2e8bf682..cc7bc003a 100644 --- a/Source/Editor/GUI/PlatformSelector.cs +++ b/Source/Editor/GUI/PlatformSelector.cs @@ -82,13 +82,15 @@ namespace FlaxEditor.GUI var icons = Editor.Instance.Icons; var platforms = new[] { - new PlatformData(PlatformType.Windows, icons.Windows, "Windows"), - new PlatformData(PlatformType.XboxOne, icons.XboxOne, "Xbox One"), - new PlatformData(PlatformType.UWP, icons.WindowsStore, "Windows Store"), - new PlatformData(PlatformType.Linux, icons.Linux, "Linux"), - new PlatformData(PlatformType.PS4, icons.PS4, "PlayStation 4"), - new PlatformData(PlatformType.XboxScarlett, icons.XboxSeriesX, "Xbox Scarlett"), - new PlatformData(PlatformType.Android, icons.Android, "Android"), + new PlatformData(PlatformType.Windows, icons.WindowsIcon128, "Windows"), + new PlatformData(PlatformType.XboxOne, icons.XBoxOne128, "Xbox One"), + new PlatformData(PlatformType.UWP, icons.UWPStore128, "Windows Store"), + new PlatformData(PlatformType.Linux, icons.LinuxIcon128, "Linux"), + new PlatformData(PlatformType.PS4, icons.PS4Icon128, "PlayStation 4"), + new PlatformData(PlatformType.XboxScarlett, icons.XBoxScarletIcon128, "Xbox Scarlett"), + new PlatformData(PlatformType.Android, icons.AndroidIcon128, "Android"), + new PlatformData(PlatformType.Switch, icons.ColorWheel128, "Switch"), + }; const float IconSize = 48.0f; diff --git a/Source/Editor/GUI/Popups/RenamePopup.cs b/Source/Editor/GUI/Popups/RenamePopup.cs index 834e620d9..c1da5dc3e 100644 --- a/Source/Editor/GUI/Popups/RenamePopup.cs +++ b/Source/Editor/GUI/Popups/RenamePopup.cs @@ -42,9 +42,6 @@ namespace FlaxEditor.GUI /// /// Gets or sets the initial value. /// - /// - /// The initial value. - /// public string InitialValue { get => _startValue; @@ -54,9 +51,6 @@ namespace FlaxEditor.GUI /// /// Gets or sets the input field text. /// - /// - /// The text. - /// public string Text { get => _inputField.Text; @@ -138,6 +132,9 @@ namespace FlaxEditor.GUI Hide(); } + /// + protected override bool UseAutomaticDirectionFix => false; + /// public override bool OnKeyDown(KeyboardKeys key) { diff --git a/Source/Editor/GUI/Popups/TypeSearchPopup.cs b/Source/Editor/GUI/Popups/TypeSearchPopup.cs index f1300d299..a35b2a1c5 100644 --- a/Source/Editor/GUI/Popups/TypeSearchPopup.cs +++ b/Source/Editor/GUI/Popups/TypeSearchPopup.cs @@ -79,7 +79,7 @@ namespace FlaxEditor.GUI var type = allTypes[i]; if (_isValid(type)) { - var attributes = type.GetAttributes(false); + var attributes = type.GetAttributes(true); if (attributes.FirstOrDefault(x => x is HideInEditorAttribute) == null) { AddItem(new TypeItemView(type, attributes)); diff --git a/Source/Editor/GUI/Timeline/GUI/GradientEditor.cs b/Source/Editor/GUI/Timeline/GUI/GradientEditor.cs index 1fc3578cb..916568571 100644 --- a/Source/Editor/GUI/Timeline/GUI/GradientEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/GradientEditor.cs @@ -63,7 +63,7 @@ namespace FlaxEditor.GUI.Timeline.GUI var isMouseOver = IsMouseOver; var color = Gradient._data[Index].Value; var icons = Editor.Instance.Icons; - var icon = icons.VisjectBoxClose; + var icon = icons.VisjectBoxClosed32; Render2D.DrawSprite(icon, new Rectangle(0.0f, 0.0f, 10.0f, 10.0f), isMouseOver ? Color.Gray : Color.Black); Render2D.DrawSprite(icon, new Rectangle(1.0f, 1.0f, 8.0f, 8.0f), color); diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index 079ef9957..93d9261e5 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -26,7 +26,7 @@ namespace FlaxEditor.GUI.Timeline.GUI public override void Draw() { var style = Style.Current; - var icon = Editor.Instance.Icons.VisjectArrowClose; + var icon = Editor.Instance.Icons.VisjectArrowClosed32; var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y; Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1); diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index e0e4083c1..58e6a0f32 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -738,7 +738,7 @@ namespace FlaxEditor.GUI.Timeline _playbackNavigation[0] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize) { TooltipText = "Rewind to timeline start (Home)", - Brush = new SpriteBrush(icons.Step32), + Brush = new SpriteBrush(icons.Skip64), Enabled = false, Visible = false, Rotation = 180.0f, @@ -750,7 +750,7 @@ namespace FlaxEditor.GUI.Timeline _playbackNavigation[1] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize) { TooltipText = "Seek back to the previous keyframe (Page Down)", - Brush = new SpriteBrush(icons.Next32), + Brush = new SpriteBrush(icons.Shift64), Enabled = false, Visible = false, Rotation = 180.0f, @@ -766,7 +766,7 @@ namespace FlaxEditor.GUI.Timeline _playbackNavigation[2] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize) { TooltipText = "Move one frame back (Left Arrow)", - Brush = new SpriteBrush(icons.ArrowLeft32), + Brush = new SpriteBrush(icons.Left32), Enabled = false, Visible = false, Parent = playbackButtonsPanel @@ -779,7 +779,7 @@ namespace FlaxEditor.GUI.Timeline _playbackStop = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize) { TooltipText = "Stop playback", - Brush = new SpriteBrush(icons.Stop32), + Brush = new SpriteBrush(icons.Stop64), Visible = false, Enabled = false, Parent = playbackButtonsPanel @@ -792,7 +792,7 @@ namespace FlaxEditor.GUI.Timeline _playbackPlay = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize) { TooltipText = "Play/pause playback (Space)", - Brush = new SpriteBrush(icons.Play32), + Brush = new SpriteBrush(icons.Play64), Visible = false, Tag = false, // Set to true if image is set to Pause, false if Play Parent = playbackButtonsPanel @@ -805,7 +805,7 @@ namespace FlaxEditor.GUI.Timeline _playbackNavigation[3] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize) { TooltipText = "Move one frame forward (Right Arrow)", - Brush = new SpriteBrush(icons.ArrowRight32), + Brush = new SpriteBrush(icons.Right32), Enabled = false, Visible = false, Parent = playbackButtonsPanel @@ -816,7 +816,7 @@ namespace FlaxEditor.GUI.Timeline _playbackNavigation[4] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize) { TooltipText = "Seek to the next keyframe (Page Up)", - Brush = new SpriteBrush(icons.Next32), + Brush = new SpriteBrush(icons.Shift64), Enabled = false, Visible = false, Parent = playbackButtonsPanel @@ -831,7 +831,7 @@ namespace FlaxEditor.GUI.Timeline _playbackNavigation[5] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize) { TooltipText = "Rewind to timeline end (End)", - Brush = new SpriteBrush(icons.Step32), + Brush = new SpriteBrush(icons.Skip64), Enabled = false, Visible = false, Parent = playbackButtonsPanel @@ -1189,7 +1189,7 @@ namespace FlaxEditor.GUI.Timeline { _playbackPlay.Visible = true; _playbackPlay.Enabled = _canPlayPauseStop; - _playbackPlay.Brush = new SpriteBrush(icons.Play32); + _playbackPlay.Brush = new SpriteBrush(icons.Play64); _playbackPlay.Tag = false; } if (_positionHandle != null) @@ -1215,7 +1215,7 @@ namespace FlaxEditor.GUI.Timeline { _playbackPlay.Visible = true; _playbackPlay.Enabled = _canPlayPauseStop; - _playbackPlay.Brush = new SpriteBrush(icons.Pause32); + _playbackPlay.Brush = new SpriteBrush(icons.Pause64); _playbackPlay.Tag = true; } if (_positionHandle != null) @@ -1241,7 +1241,7 @@ namespace FlaxEditor.GUI.Timeline { _playbackPlay.Visible = true; _playbackPlay.Enabled = _canPlayPauseStop; - _playbackPlay.Brush = new SpriteBrush(icons.Play32); + _playbackPlay.Brush = new SpriteBrush(icons.Play64); _playbackPlay.Tag = false; } if (_positionHandle != null) @@ -1438,6 +1438,7 @@ namespace FlaxEditor.GUI.Timeline ArrangeTracks(); PerformLayout(true); UnlockChildrenRecursive(); + PerformLayout(true); Profiler.EndEvent(); ClearEditedFlag(); diff --git a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs index 031226a02..98cb3979f 100644 --- a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs @@ -322,7 +322,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks IsScrollable = false, Color = Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(icons.ArrowRight32), + Brush = new SpriteBrush(icons.Right32), Offsets = new Margin(-buttonSize - 2 + _muteCheckbox.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; @@ -335,7 +335,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks IsScrollable = false, Color = Style.Current.ForegroundGrey, Margin = new Margin(3), - Brush = new SpriteBrush(icons.Add48), + Brush = new SpriteBrush(icons.Add32), Offsets = new Margin(-buttonSize - 2 + rightKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; @@ -348,7 +348,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks IsScrollable = false, Color = Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(icons.ArrowLeft32), + Brush = new SpriteBrush(icons.Left32), Offsets = new Margin(-buttonSize - 2 + addKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; diff --git a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs index 0c3ab7851..fc9b17601 100644 --- a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs @@ -695,7 +695,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks IsScrollable = false, Color = Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(icons.Camera32), + Brush = new SpriteBrush(icons.Camera64), Offsets = new Margin(-buttonSize - 2 + _selectActor.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index 250fb73c3..302570fae 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -128,7 +128,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks IsScrollable = false, Color = Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(icons.ArrowRight32), + Brush = new SpriteBrush(icons.Right64), Offsets = new Margin(-buttonSize - 2 + uiLeft, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; @@ -138,9 +138,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks AutoFocus = true, AnchorPreset = AnchorPresets.MiddleRight, IsScrollable = false, - Color = Style.Current.ForegroundGrey, + Color = Style.Current.Foreground, Margin = new Margin(3), - Brush = new SpriteBrush(icons.Add48), + Brush = new SpriteBrush(icons.Add64), Offsets = new Margin(-buttonSize - 2 + _rightKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; @@ -150,9 +150,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks AutoFocus = true, AnchorPreset = AnchorPresets.MiddleRight, IsScrollable = false, - Color = Style.Current.ForegroundGrey, + Color = Style.Current.Foreground, Margin = new Margin(1), - Brush = new SpriteBrush(icons.ArrowLeft32), + Brush = new SpriteBrush(icons.Left64), Offsets = new Margin(-buttonSize - 2 + _addKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index afda8484e..f490de430 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -50,6 +50,41 @@ namespace FlaxEditor.Gizmo { } + /// + /// Helper function, recursively finds the Prefab Root of node or null. + /// + /// The node from which to start. + /// The prefab root or null. + public ActorNode GetPrefabRootInParent(ActorNode node) + { + if (!node.HasPrefabLink) + return null; + if (node.Actor.IsPrefabRoot) + return node; + if (node.ParentNode is ActorNode parAct) + return GetPrefabRootInParent(parAct); + return null; + } + + /// + /// Recursively walks up from the node up to ceiling node(inclusive) or selection(exclusive). + /// + /// The node from which to start + /// The ceiling(inclusive) + /// The node to select. + public ActorNode WalkUpAndFindActorNodeBeforeSelection(ActorNode node, ActorNode ceiling) + { + if (node == ceiling || _selection.Contains(node)) + return node; + if (node.ParentNode is ActorNode parentNode) + { + if (_selection.Contains(node.ParentNode)) + return node; + return WalkUpAndFindActorNodeBeforeSelection(parentNode, ceiling); + } + return node; + } + /// public override void Pick() { @@ -100,6 +135,16 @@ namespace FlaxEditor.Gizmo } } + // Select prefab root and then go down until you find the actual item in which case select the prefab root again + if (hit is ActorNode actorNode) + { + ActorNode prefabRoot = GetPrefabRootInParent(actorNode); + if (prefabRoot != null && actorNode != prefabRoot) + { + hit = WalkUpAndFindActorNodeBeforeSelection(actorNode, prefabRoot); + } + } + bool addRemove = Owner.IsControlDown; bool isSelected = sceneEditing.Selection.Contains(hit); diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs b/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs index c1f7d1d0d..f8277999e 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs @@ -26,7 +26,7 @@ namespace FlaxEditor.Gizmo // Return arithmetic average or whatever it means return center / count; } - + private bool IntersectsRotateCircle(Vector3 normal, ref Ray ray, out float distance) { var plane = new Plane(Vector3.Zero, normal); @@ -51,7 +51,7 @@ namespace FlaxEditor.Gizmo Vector3.Transform(ref ray.Position, ref invGizmoWorld, out localRay.Position); // Find gizmo collisions with mouse - float closestintersection = float.MaxValue; + float closestIntersection = float.MaxValue; float intersection; _activeAxis = Axis.None; switch (_activeMode) @@ -59,42 +59,42 @@ namespace FlaxEditor.Gizmo case Mode.Translate: { // Axis boxes collision - if (XAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection) + if (XAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.X; - closestintersection = intersection; + closestIntersection = intersection; } - if (YAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection) + if (YAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Y; - closestintersection = intersection; + closestIntersection = intersection; } - if (ZAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection) + if (ZAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Z; - closestintersection = intersection; + closestIntersection = intersection; } // Quad planes collision - if (closestintersection >= float.MaxValue) - closestintersection = float.MinValue; - if (XYBox.Intersects(ref localRay, out intersection) && intersection > closestintersection) + if (closestIntersection >= float.MaxValue) + closestIntersection = float.MinValue; + if (XYBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection) { _activeAxis = Axis.XY; - closestintersection = intersection; + closestIntersection = intersection; } - if (XZBox.Intersects(ref localRay, out intersection) && intersection > closestintersection) + if (XZBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection) { _activeAxis = Axis.ZX; - closestintersection = intersection; + closestIntersection = intersection; } - if (YZBox.Intersects(ref localRay, out intersection) && intersection > closestintersection) + if (YZBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection) { _activeAxis = Axis.YZ; - closestintersection = intersection; + closestIntersection = intersection; } break; @@ -103,20 +103,20 @@ namespace FlaxEditor.Gizmo case Mode.Rotate: { // Circles - if (IntersectsRotateCircle(Vector3.UnitX, ref localRay, out intersection) && intersection < closestintersection) + if (IntersectsRotateCircle(Vector3.UnitX, ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.X; - closestintersection = intersection; + closestIntersection = intersection; } - if (IntersectsRotateCircle(Vector3.UnitY, ref localRay, out intersection) && intersection < closestintersection) + if (IntersectsRotateCircle(Vector3.UnitY, ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Y; - closestintersection = intersection; + closestIntersection = intersection; } - if (IntersectsRotateCircle(Vector3.UnitZ, ref localRay, out intersection) && intersection < closestintersection) + if (IntersectsRotateCircle(Vector3.UnitZ, ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Z; - closestintersection = intersection; + closestIntersection = intersection; } // Center @@ -132,27 +132,27 @@ namespace FlaxEditor.Gizmo case Mode.Scale: { // Spheres collision - if (ScaleXSphere.Intersects(ref ray, out intersection) && intersection < closestintersection) + if (ScaleXSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.X; - closestintersection = intersection; + closestIntersection = intersection; } - if (ScaleYSphere.Intersects(ref ray, out intersection) && intersection < closestintersection) + if (ScaleYSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Y; - closestintersection = intersection; + closestIntersection = intersection; } - if (ScaleZSphere.Intersects(ref ray, out intersection) && intersection < closestintersection) + if (ScaleZSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Z; - closestintersection = intersection; + closestIntersection = intersection; } // Center - if (CenterBox.Intersects(ref ray, out intersection) && intersection < closestintersection) + if (CenterBox.Intersects(ref ray, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Center; - closestintersection = intersection; + closestIntersection = intersection; } break; diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 8c997bc2c..e123bd3f6 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -174,7 +174,7 @@ namespace FlaxEditor.Gizmo _axisAlignedWorld = _screenScaleMatrix * Matrix.CreateWorld(Position, Vector3.Backward, Vector3.Up); // Assign world - if (_activeTransformSpace == TransformSpace.World) + if (_activeTransformSpace == TransformSpace.World && _activeMode != Mode.Scale) { _gizmoWorld = _axisAlignedWorld; @@ -297,29 +297,6 @@ namespace FlaxEditor.Gizmo else if (_activeMode == Mode.Scale) { // Scale - if (_activeTransformSpace == TransformSpace.World && _activeAxis != Axis.Center) - { - var deltaLocal = delta; - Quaternion orientation = GetSelectedObject(0).Orientation; - delta = Vector3.Transform(delta, orientation); - - // Fix axis sign of delta movement for rotated object in some cases (eg. rotated object by 90 deg on Y axis and scale in world space with Red/X axis) - switch (_activeAxis) - { - case Axis.X: - if (deltaLocal.X < 0) - delta *= -1; - break; - case Axis.Y: - if (deltaLocal.Y < 0) - delta *= -1; - break; - case Axis.Z: - if (deltaLocal.Z < 0) - delta *= -1; - break; - } - } _scaleDelta = delta; } } diff --git a/Source/Editor/History/UndoActionObject.cs b/Source/Editor/History/UndoActionObject.cs index cac710bff..a87637f4f 100644 --- a/Source/Editor/History/UndoActionObject.cs +++ b/Source/Editor/History/UndoActionObject.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using FlaxEditor.Utilities; using FlaxEngine; +using Newtonsoft.Json; +using JsonSerializer = FlaxEngine.Json.JsonSerializer; namespace FlaxEditor.History { @@ -114,6 +116,8 @@ namespace FlaxEditor.History public object TargetInstance; } + internal static JsonSerializerSettings JsonSettings; + // For objects that cannot be referenced in undo action like: FlaxEngine.Object or SceneGraphNode we store them in DataStorage, // otherwise here: private readonly object TargetInstance; @@ -177,6 +181,24 @@ namespace FlaxEditor.History }; } + /// + public override DataStorage Data + { + protected set + { + // Inject objects typename serialization to prevent data type mismatch when loading from saved state + var settings = JsonSettings; + if (settings == null) + { + settings = JsonSerializer.CreateDefaultSettings(false); + settings.TypeNameHandling = TypeNameHandling.All; + JsonSettings = settings; + } + _data = JsonConvert.SerializeObject(value, Formatting.Indented, settings); + //Editor.Log(_data); + } + } + /// public override string ActionString { get; } diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 7b178b956..a312b38c4 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -96,10 +96,9 @@ struct InternalTextureOptions to->Sprites.EnsureCapacity(count); for (int32 i = 0; i < count; i++) { - Sprite sprite; + Sprite& sprite = to->Sprites.AddOne(); sprite.Area = mono_array_get(from->SpriteAreas, Rectangle, i); sprite.Name = MUtils::ToString(mono_array_get(from->SpriteNames, MonoString*, i)); - to->Sprites.Add(sprite); } } } @@ -125,7 +124,7 @@ struct InternalTextureOptions { const auto domain = mono_domain_get(); int32 count = from->Sprites.Count(); - auto rectClass = Scripting::FindClass("FlaxEngine.Rectangle"); + auto rectClass = Rectangle::TypeInitializer.GetType().ManagedClass; ASSERT(rectClass != nullptr); to->SpriteAreas = mono_array_new(domain, rectClass->GetNative(), count); to->SpriteNames = mono_array_new(domain, mono_get_string_class(), count); @@ -521,13 +520,14 @@ public: return AssetsImportingManager::Create(AssetsImportingManager::CreateVisualScriptTag, outputPath, &baseTypename); } - static bool CanImport(MonoString* extensionObj) + static MonoString* CanImport(MonoString* extensionObj) { String extension; MUtils::ToString(extensionObj, extension); if (extension.Length() > 0 && extension[0] == '.') extension.Remove(0, 1); - return AssetsImportingManager::GetImporter(extension) != nullptr; + const AssetImporter* importer = AssetsImportingManager::GetImporter(extension); + return importer ? MUtils::ToString(importer->ResultExtension) : nullptr; } static bool Import(MonoString* inputPathObj, MonoString* outputPathObj, void* arg) @@ -715,7 +715,7 @@ public: return str; } - static bool CookMeshCollision(MonoString* pathObj, CollisionDataType type, Model* modelObj, int32 modelLodIndex, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) + static bool CookMeshCollision(MonoString* pathObj, CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { #if COMPILE_WITH_PHYSICS_COOKING CollisionCooking::Argument arg; @@ -725,9 +725,9 @@ public: arg.Type = type; arg.Model = modelObj; arg.ModelLodIndex = modelLodIndex; + arg.MaterialSlotsMask = materialSlotsMask; arg.ConvexFlags = convexFlags; arg.ConvexVertexLimit = convexVertexLimit; - return CreateCollisionData::CookMeshCollision(path, arg); #else LOG(Warning, "Collision cooking is disabled."); @@ -743,8 +743,8 @@ public: const auto& debugLines = collisionData->GetDebugLines(); const int32 linesCount = debugLines.Count() / 2; - *triangles = mono_array_new(mono_domain_get(), StdTypesContainer::Instance()->Vector3Class->GetNative(), debugLines.Count()); - *indices = mono_array_new(mono_domain_get(), mono_get_int32_class(), linesCount * 3); + mono_gc_wbarrier_generic_store(triangles, (MonoObject*)mono_array_new(mono_domain_get(), StdTypesContainer::Instance()->Vector3Class->GetNative(), debugLines.Count())); + mono_gc_wbarrier_generic_store(indices, (MonoObject*)mono_array_new(mono_domain_get(), mono_get_int32_class(), linesCount * 3)); // Use one triangle per debug line for (int32 i = 0; i < debugLines.Count(); i++) diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index c90a386c1..ec71b8564 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -911,6 +911,8 @@ namespace FlaxEditor.Modules Proxy.Add(new ParticleSystemProxy()); Proxy.Add(new SceneAnimationProxy()); Proxy.Add(new CSharpScriptProxy()); + Proxy.Add(new CppAssetProxy()); + Proxy.Add(new CppStaticClassProxy()); Proxy.Add(new CppScriptProxy()); Proxy.Add(new SceneProxy()); Proxy.Add(new PrefabProxy()); @@ -923,29 +925,34 @@ namespace FlaxEditor.Modules Proxy.Add(new SkeletonMaskProxy()); Proxy.Add(new GameplayGlobalsProxy()); Proxy.Add(new VisualScriptProxy()); + Proxy.Add(new LocalizedStringTableProxy()); Proxy.Add(new FileProxy()); Proxy.Add(new SpawnableJsonAssetProxy()); // Settings - Proxy.Add(new SettingsProxy(typeof(GameSettings))); - Proxy.Add(new SettingsProxy(typeof(TimeSettings))); - Proxy.Add(new SettingsProxy(typeof(LayersAndTagsSettings))); - Proxy.Add(new SettingsProxy(typeof(PhysicsSettings))); - Proxy.Add(new SettingsProxy(typeof(GraphicsSettings))); - Proxy.Add(new SettingsProxy(typeof(NavigationSettings))); - Proxy.Add(new SettingsProxy(typeof(BuildSettings))); - Proxy.Add(new SettingsProxy(typeof(InputSettings))); - Proxy.Add(new SettingsProxy(typeof(WindowsPlatformSettings))); - Proxy.Add(new SettingsProxy(typeof(UWPPlatformSettings))); - Proxy.Add(new SettingsProxy(typeof(LinuxPlatformSettings))); + Proxy.Add(new SettingsProxy(typeof(GameSettings), Editor.Instance.Icons.GameSettings128)); + Proxy.Add(new SettingsProxy(typeof(TimeSettings), Editor.Instance.Icons.TimeSettings128)); + Proxy.Add(new SettingsProxy(typeof(LayersAndTagsSettings), Editor.Instance.Icons.LayersTagsSettings128)); + Proxy.Add(new SettingsProxy(typeof(PhysicsSettings), Editor.Instance.Icons.PhysicsSettings128)); + Proxy.Add(new SettingsProxy(typeof(GraphicsSettings), Editor.Instance.Icons.GraphicsSettings128)); + Proxy.Add(new SettingsProxy(typeof(NavigationSettings), Editor.Instance.Icons.NavigationSettings128)); + Proxy.Add(new SettingsProxy(typeof(LocalizationSettings), Editor.Instance.Icons.Document128)); + Proxy.Add(new SettingsProxy(typeof(BuildSettings), Editor.Instance.Icons.BuildSettings128)); + Proxy.Add(new SettingsProxy(typeof(InputSettings), Editor.Instance.Icons.InputSettings128)); + Proxy.Add(new SettingsProxy(typeof(WindowsPlatformSettings), Editor.Instance.Icons.WindowsSettings128)); + Proxy.Add(new SettingsProxy(typeof(UWPPlatformSettings), Editor.Instance.Icons.UWPSettings128)); + Proxy.Add(new SettingsProxy(typeof(LinuxPlatformSettings), Editor.Instance.Icons.LinuxSettings128)); var typePS4PlatformSettings = TypeUtils.GetManagedType(GameSettings.PS4PlatformSettingsTypename); if (typePS4PlatformSettings != null) - Proxy.Add(new SettingsProxy(typePS4PlatformSettings)); + Proxy.Add(new SettingsProxy(typePS4PlatformSettings, Editor.Instance.Icons.PlaystationSettings128)); var typeXboxScarlettPlatformSettings = TypeUtils.GetManagedType(GameSettings.XboxScarlettPlatformSettingsTypename); if (typeXboxScarlettPlatformSettings != null) - Proxy.Add(new SettingsProxy(typeXboxScarlettPlatformSettings)); - Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings))); - Proxy.Add(new SettingsProxy(typeof(AudioSettings))); + Proxy.Add(new SettingsProxy(typeXboxScarlettPlatformSettings, Editor.Instance.Icons.XBoxScarletIcon128)); + Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings), Editor.Instance.Icons.AndroidSettings128)); + var typeSwitchPlatformSettings = TypeUtils.GetManagedType(GameSettings.SwitchPlatformSettingsTypename); + if (typeSwitchPlatformSettings != null) + Proxy.Add(new SettingsProxy(typeSwitchPlatformSettings, Editor.Instance.Icons.Document128)); + Proxy.Add(new SettingsProxy(typeof(AudioSettings), Editor.Instance.Icons.AudioSettings128)); // Last add generic json (won't override other json proxies) Proxy.Add(new GenericJsonAssetProxy()); diff --git a/Source/Editor/Modules/ContentEditingModule.cs b/Source/Editor/Modules/ContentEditingModule.cs index 0d0e65ca9..6d0299a43 100644 --- a/Source/Editor/Modules/ContentEditingModule.cs +++ b/Source/Editor/Modules/ContentEditingModule.cs @@ -118,6 +118,25 @@ namespace FlaxEditor.Modules return false; } + // Check proxy name restrictions + if (item is NewItem ni) + { + if (!ni.Proxy.IsFileNameValid(shortName)) + { + hint = "Name does not follow " + ni.Proxy.Name + " name restrictions !"; + return false; + } + } + else + { + var proxy = Editor.ContentDatabase.GetProxy(item); + if (proxy != null && !proxy.IsFileNameValid(shortName)) + { + hint = "Name does not follow " + proxy.Name + " name restrictions !"; + return false; + } + } + // Cache data string sourcePath = item.Path; string sourceFolder = System.IO.Path.GetDirectoryName(sourcePath); diff --git a/Source/Editor/Modules/ContentFindingModule.cs b/Source/Editor/Modules/ContentFindingModule.cs index 9be15e0d1..45748f7f4 100644 --- a/Source/Editor/Modules/ContentFindingModule.cs +++ b/Source/Editor/Modules/ContentFindingModule.cs @@ -309,6 +309,7 @@ namespace FlaxEditor.Modules { "FlaxEditor.Content.Settings.InputSettings", "Settings" }, { "FlaxEditor.Content.Settings.LayersAndTagsSettings", "Settings" }, { "FlaxEditor.Content.Settings.NavigationSettings", "Settings" }, + { "FlaxEditor.Content.Settings.LocalizationSettings", "Settings" }, { "FlaxEditor.Content.Settings.PhysicsSettings", "Settings" }, { "FlaxEditor.Content.Settings.TimeSettings", "Settings" }, { "FlaxEditor.Content.Settings.UWPPlatformSettings", "Settings" }, diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs index d5ec4b69f..424868fd8 100644 --- a/Source/Editor/Modules/ContentImportingModule.cs +++ b/Source/Editor/Modules/ContentImportingModule.cs @@ -193,12 +193,10 @@ namespace FlaxEditor.Modules var extension = System.IO.Path.GetExtension(inputPath) ?? string.Empty; // Check if given file extension is a binary asset (.flax files) and can be imported by the engine - bool isBinaryAsset = Editor.CanImport(extension); - string outputExtension; - if (isBinaryAsset) + bool isBuilt = Editor.CanImport(extension, out var outputExtension); + if (isBuilt) { - // Flax it up! - outputExtension = ".flax"; + outputExtension = '.' + outputExtension; if (!targetLocation.CanHaveAssets) { @@ -234,7 +232,7 @@ namespace FlaxEditor.Modules var shortName = System.IO.Path.GetFileNameWithoutExtension(inputPath); var outputPath = System.IO.Path.Combine(targetLocation.Path, shortName + outputExtension); - Import(inputPath, outputPath, isBinaryAsset, skipSettingsDialog, settings); + Import(inputPath, outputPath, isBuilt, skipSettingsDialog, settings); } /// @@ -243,10 +241,10 @@ namespace FlaxEditor.Modules /// /// The input path. /// The output path. - /// True if output file is a binary asset. + /// True if use in-built importer (engine backend). /// True if skip any popup dialogs showing for import options adjusting. Can be used when importing files from code. /// Import settings to override. Use null to skip this value. - private void Import(string inputPath, string outputPath, bool isBinaryAsset, bool skipSettingsDialog = false, object settings = null) + private void Import(string inputPath, string outputPath, bool isInBuilt, bool skipSettingsDialog = false, object settings = null) { lock (_requests) { @@ -254,7 +252,7 @@ namespace FlaxEditor.Modules { InputPath = inputPath, OutputPath = outputPath, - IsBinaryAsset = isBinaryAsset, + IsInBuilt = isInBuilt, SkipSettingsDialog = skipSettingsDialog, Settings = settings, }); diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 6334225fd..86479b696 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -22,14 +22,25 @@ namespace FlaxEditor.Modules /// public class ScenesRootNode : RootNode { + private readonly Editor _editor; + + /// + public ScenesRootNode() + { + _editor = Editor.Instance; + } + /// public override void Spawn(Actor actor, Actor parent) { - Editor.Instance.SceneEditing.Spawn(actor, parent); + _editor.SceneEditing.Spawn(actor, parent); } /// public override Undo Undo => Editor.Instance.Undo; + + /// + public override List Selection => _editor.SceneEditing.Selection; } /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index c64ceaa11..c5145a7aa 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -190,7 +190,7 @@ namespace FlaxEditor.Modules if (isDuringBreakpointHang) { play.Checked = false; - play.Icon = Editor.Icons.Stop32; + play.Icon = Editor.Icons.Stop64; pause.Enabled = false; pause.Checked = true; pause.AutoCheck = false; @@ -199,7 +199,7 @@ namespace FlaxEditor.Modules else if (isPlayMode) { play.Checked = false; - play.Icon = Editor.Icons.Stop32; + play.Icon = Editor.Icons.Stop64; pause.Enabled = true; pause.Checked = Editor.StateMachine.PlayingState.IsPaused; pause.AutoCheck = false; @@ -208,7 +208,7 @@ namespace FlaxEditor.Modules else { play.Checked = Editor.Simulation.IsPlayModeRequested; - play.Icon = Editor.Icons.Play32; + play.Icon = Editor.Icons.Play64; pause.Enabled = canEnterPlayMode; pause.AutoCheck = true; step.Enabled = false; @@ -501,20 +501,20 @@ namespace FlaxEditor.Modules Parent = mainWindow, }; - _toolStripSaveAll = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Save32, Editor.SaveAll).LinkTooltip("Save all (Ctrl+S)"); + _toolStripSaveAll = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Save64, Editor.SaveAll).LinkTooltip("Save all (Ctrl+S)"); ToolStrip.AddSeparator(); - _toolStripUndo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Undo32, Editor.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _toolStripRedo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Redo32, Editor.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _toolStripUndo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Undo64, Editor.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _toolStripRedo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Redo64, Editor.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); ToolStrip.AddSeparator(); _toolStripTranslate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Translate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)"); _toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)"); _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)"); ToolStrip.AddSeparator(); - _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build32, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)"); + _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)"); ToolStrip.AddSeparator(); - _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play32, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)"); - _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause32, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)"); - _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Step32, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game"); + _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)"); + _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)"); + _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game"); UpdateToolstrip(); } diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index f38f6fe3b..5267da01f 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -707,8 +707,9 @@ namespace FlaxEditor.Modules var dpiScale = Platform.DpiScale; var settings = CreateWindowSettings.Default; settings.Title = "Flax Editor"; - settings.Size = new Vector2(1300 * dpiScale, 900 * dpiScale); + settings.Size = Platform.DesktopSize; settings.StartPosition = WindowStartPosition.CenterScreen; + settings.ShowAfterFirstPaint = true; #if PLATFORM_WINDOWS if (!Editor.Instance.Options.Options.Interface.UseNativeWindowSystem) diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 85174030f..c260ae8b8 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -68,6 +68,24 @@ namespace FlaxEditor.Options CompileScripts, } + /// + /// Order of script members show in editor + /// + public enum MembersOrder + { + /// + /// Shows properties/fields in alphabetical order + /// + [Tooltip("Shows properties/fields in alphabetical order")] + Alphabetical, + + /// + /// Shows properties/fields in declaration order + /// + [Tooltip("Shows properties/fields in declaration order")] + Declaration + } + /// /// Gets or sets the scene to load on editor startup. /// @@ -116,6 +134,13 @@ namespace FlaxEditor.Options [EditorDisplay("Scripting", "Force Script Compilation On Startup"), EditorOrder(501), Tooltip("Determines whether automatically compile game scripts before starting the editor.")] public bool ForceScriptCompilationOnStartup { get; set; } = true; + /// + /// Gets or sets an order of script properties/fields in properties panel. + /// + [DefaultValue(MembersOrder.Alphabetical)] + [EditorDisplay("Scripting", "Script Members Order"), EditorOrder(503), Tooltip("Order of script properties/fields in properties panel")] + public MembersOrder ScriptMembersOrder { get; set; } = MembersOrder.Alphabetical; + /// /// Gets or sets a value indicating whether automatically save the Visual Script asset editors when starting the play mode in editor. /// diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 62496823c..ea42ee99b 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -150,7 +150,10 @@ namespace FlaxEditor.Options private void Save() { // Update file - Editor.SaveJsonAsset(_optionsFilePath, Options); + if (Editor.SaveJsonAsset(_optionsFilePath, Options)) + { + MessageBox.Show(string.Format("Failed to save editor option to '{0}'. Ensure that directory exists and program has access to it.", _optionsFilePath), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } // Special case for editor analytics var editorAnalyticsTrackingFile = Path.Combine(Editor.LocalCachePath, "noTracking"); @@ -245,10 +248,11 @@ namespace FlaxEditor.Options Cross = Editor.Icons.Cross12, CheckBoxIntermediate = Editor.Icons.CheckBoxIntermediate12, CheckBoxTick = Editor.Icons.CheckBoxTick12, - StatusBarSizeGrip = Editor.Icons.StatusBarSizeGrip12, - Translate = Editor.Icons.Translate16, - Rotate = Editor.Icons.Rotate16, - Scale = Editor.Icons.Scale16, + StatusBarSizeGrip = Editor.Icons.WindowDrag12, + Translate = Editor.Icons.Translate32, + Rotate = Editor.Icons.Rotate32, + Scale = Editor.Icons.Scale32, + Scalar = Editor.Icons.Scalar32, SharedTooltip = new Tooltip() }; diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index 5558ac89d..0a8d78c26 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -18,6 +18,13 @@ namespace FlaxEditor.Options [EditorDisplay("General"), EditorOrder(100), Tooltip("The mouse movement sensitivity scale applied when using the viewport camera.")] public float MouseSensitivity { get; set; } = 1.0f; + /// + /// Gets or sets the mouse wheel sensitivity applied to zoom in orthographic mode. + /// + [DefaultValue(1.0f), Limit(0.01f, 100.0f)] + [EditorDisplay("General"), EditorOrder(101), Tooltip("The mouse wheel sensitivity applied to zoom in orthographic mode.")] + public float MouseWheelSensitivity { get; set; } = 1.0f; + /// /// Gets or sets the default movement speed for the viewport camera (must match the dropdown menu values in the viewport). /// diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 318d8379b..6703c30a0 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -194,7 +194,7 @@ namespace FlaxEditor.SceneGraph { get { - var scene = _actor.Scene; + var scene = _actor ? _actor.Scene : null; return scene != null ? SceneGraphFactory.FindNode(scene.ID) as SceneNode : null; } } @@ -235,7 +235,6 @@ namespace FlaxEditor.SceneGraph { if (!(value is ActorNode)) throw new InvalidOperationException("ActorNode can have only ActorNode as a parent node."); - base.ParentNode = value; } } @@ -263,6 +262,12 @@ namespace FlaxEditor.SceneGraph return _actor.IntersectsItself(ray.Ray, out distance, out normal); } + /// + public override void GetEditorSphere(out BoundingSphere sphere) + { + Editor.GetActorEditorSphere(_actor, out sphere); + } + /// public override void OnDebugDraw(ViewportDebugDrawData data) { @@ -283,6 +288,13 @@ namespace FlaxEditor.SceneGraph { } + /// + /// Action called after pasting actor in editor. + /// + public virtual void PostPaste() + { + } + /// protected override void OnParentChanged() { @@ -293,6 +305,7 @@ namespace FlaxEditor.SceneGraph // (eg. we build new node for spawned actor and link it to the game) if (_treeNode.Parent != null && !_treeNode.Parent.IsLayoutLocked) { + _treeNode.IndexInParent = _actor.OrderInParent; _treeNode.Parent.SortChildren(); // Update UI @@ -315,5 +328,11 @@ namespace FlaxEditor.SceneGraph base.Dispose(); } + + /// + public override string ToString() + { + return _actor ? _actor.ToString() : base.ToString(); + } } } diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 3d41d0904..7f9e4ca70 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -29,7 +29,7 @@ namespace FlaxEditor.SceneGraph.Actors public override bool CanBeSelectedDirectly => true; - public override bool CanDuplicate => true; + public override bool CanDuplicate => !Root?.Selection.Contains(ParentNode) ?? true; public override bool CanDelete => true; @@ -282,7 +282,13 @@ namespace FlaxEditor.SceneGraph.Actors { // Remove unused points while (srcCount > dstCount) - ActorChildNodes[srcCount-- - 1].Dispose(); + { + var node = ActorChildNodes[srcCount-- - 1]; + // TODO: support selection interface inside SceneGraph nodes (eg. on Root) so prefab editor can handle this too + if (Editor.Instance.SceneEditing.Selection.Contains(node)) + Editor.Instance.SceneEditing.Deselect(); + node.Dispose(); + } // Add new points var id = ID; @@ -361,6 +367,15 @@ namespace FlaxEditor.SceneGraph.Actors } } + /// + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) + { + // Select only spline points + normal = Vector3.Up; + distance = float.MaxValue; + return false; + } + /// public override void OnDispose() { diff --git a/Source/Editor/SceneGraph/Actors/UIControlNode.cs b/Source/Editor/SceneGraph/Actors/UIControlNode.cs index b3f8f5d09..c4f7d1c17 100644 --- a/Source/Editor/SceneGraph/Actors/UIControlNode.cs +++ b/Source/Editor/SceneGraph/Actors/UIControlNode.cs @@ -25,5 +25,20 @@ namespace FlaxEditor.SceneGraph.Actors if (Actor is UIControl uiControl) DebugDraw.DrawWireBox(uiControl.Bounds, Color.BlueViolet); } + + /// + public override void PostPaste() + { + base.PostPaste(); + + var control = ((UIControl)Actor).Control; + if (control != null) + { + if (control.Parent != null) + control.Parent.PerformLayout(); + else + control.PerformLayout(); + } + } } } diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 7b4b35ed1..5e2792dc4 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -86,6 +86,10 @@ namespace FlaxEditor.SceneGraph.GUI } parent.SortChildren(); } + else if (Actor) + { + _orderInParent = Actor.OrderInParent; + } } internal void OnNameChanged() @@ -537,7 +541,7 @@ namespace FlaxEditor.SceneGraph.GUI var customAction = targetActor.HasPrefabLink ? new ReparentAction(targetActor) : null; using (new UndoBlock(ActorNode.Root.Undo, targetActor, "Change actor parent", customAction)) { - targetActor.SetParent(newParent, worldPositionLock); + targetActor.SetParent(newParent, worldPositionLock, true); targetActor.OrderInParent = newOrder; } } @@ -550,7 +554,7 @@ namespace FlaxEditor.SceneGraph.GUI for (int i = 0; i < targetActors.Count; i++) { var targetActor = targetActors[i]; - targetActor.SetParent(newParent, worldPositionLock); + targetActor.SetParent(newParent, worldPositionLock, true); targetActor.OrderInParent = newOrder; } } diff --git a/Source/Editor/SceneGraph/RootNode.cs b/Source/Editor/SceneGraph/RootNode.cs index 10af88489..485c656b1 100644 --- a/Source/Editor/SceneGraph/RootNode.cs +++ b/Source/Editor/SceneGraph/RootNode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; @@ -146,5 +147,10 @@ namespace FlaxEditor.SceneGraph /// Gets the undo. /// public abstract Undo Undo { get; } + + /// + /// Gets the list of selected scene graph nodes in the editor context. + /// + public abstract List Selection { get; } } } diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 01a832c5a..bc8fe84aa 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -57,7 +57,9 @@ namespace FlaxEditor.SceneGraph /// public virtual RootNode Root => ParentNode?.Root; - /// + /// + /// Gets or sets the transform of the node. + /// public abstract Transform Transform { get; set; } /// @@ -110,18 +112,9 @@ namespace FlaxEditor.SceneGraph { if (parentNode != value) { - if (parentNode != null) - { - parentNode.ChildNodes.Remove(this); - } - + parentNode?.ChildNodes.Remove(this); parentNode = value; - - if (parentNode != null) - { - parentNode.ChildNodes.Add(this); - } - + parentNode?.ChildNodes.Add(this); OnParentChanged(); } } @@ -309,6 +302,20 @@ namespace FlaxEditor.SceneGraph return false; } + /// + /// Gets the object bounding sphere (including child actors). + /// + /// The bounding sphere. + public virtual void GetEditorSphere(out BoundingSphere sphere) + { + sphere = new BoundingSphere(Transform.Translation, 15.0f); + for (int i = 0; i < ChildNodes.Count; i++) + { + ChildNodes[i].GetEditorSphere(out var childSphere); + BoundingSphere.Merge(ref sphere, ref childSphere, out sphere); + } + } + /// /// Called when selected nodes should draw debug shapes using interface. /// @@ -358,6 +365,7 @@ namespace FlaxEditor.SceneGraph /// /// Gets or sets the node state. /// + [NoSerialize] public virtual StateData State { get => throw new NotImplementedException(); diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp index ac226fd2c..64f157db0 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp @@ -8,6 +8,9 @@ #include "Editor/Scripting/ScriptsBuilder.h" #include "Engine/Engine/Globals.h" #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" +#if PLATFORM_LINUX +#include +#endif VisualStudioCodeEditor::VisualStudioCodeEditor(const String& execPath, const bool isInsiders) : _execPath(execPath) diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 53c84a1b5..870d68eba 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -40,6 +40,27 @@ namespace FlaxEditor.Scripting /// public string Name => _managed?.Name ?? _custom?.Name; + /// + /// Gets a metadata token for sorting so it may not be the actual token. + /// + public int MetadataToken + { + get + { + int standardToken = _managed?.MetadataToken ?? _custom?.MetadataToken ?? 0; + if (_managed is PropertyInfo && _managed.DeclaringType != null) + { + var field = _managed.DeclaringType.GetField(string.Format("<{0}>k__BackingField", Name), BindingFlags.Instance | BindingFlags.NonPublic); + if (field == null || field.MetadataToken == 0) + { + return standardToken; + } + return field.MetadataToken; + } + return standardToken; + } + } + /// /// Gets a value indicating whether the type is declared public. /// @@ -1444,6 +1465,11 @@ namespace FlaxEditor.Scripting /// string Name { get; } + /// + /// Gets a metadata token for sorting so it may not be the actual token. + /// + int MetadataToken { get; } + /// /// Gets a value indicating whether the type is declared public. /// diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 1c489b2a9..71323dc34 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Types/StringBuilder.h" #include "Engine/Debug/Exceptions/FileNotFoundException.h" #include "Engine/Engine/Engine.h" +#include "Engine/Engine/Globals.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/FileSystemWatcher.h" #include "Engine/Threading/ThreadPool.h" @@ -341,7 +342,21 @@ void ScriptsBuilder::GetBinariesConfiguration(const Char*& target, const Char*& target = platform = architecture = configuration = nullptr; return; } - target = Editor::Project->EditorTarget.GetText(); + + // Pick game target + if (Editor::Project->EditorTarget.HasChars()) + { + target = Editor::Project->EditorTarget.Get(); + } + else if (Editor::Project->GameTarget.HasChars()) + { + target = Editor::Project->GameTarget.Get(); + } + else + { + target = TEXT(""); + LOG(Error, "Missing editor/game targets in project. Please specify EditorTarget and GameTarget properties in .flaxproj file."); + } #if PLATFORM_WINDOWS platform = TEXT("Windows"); @@ -551,19 +566,13 @@ bool ScriptsBuilderService::Init() } // Verify project - if (project->EditorTarget.IsEmpty() || project->GameTarget.IsEmpty()) + if (project->EditorTarget.IsEmpty()) { - const String& name = project->Name; - String codeName; - for (int32 i = 0; i < name.Length(); i++) - { - Char c = name[i]; - if (StringUtils::IsAlnum(c) && c != ' ' && c != '.') - codeName += c; - } - project->GameTarget = codeName + TEXT("Target"); - project->EditorTarget = codeName + TEXT("EditorTarget"); - LOG(Warning, "Missing EditorTarget property in opened project, using deducted target name {0}", Editor::Project->EditorTarget); + LOG(Warning, "Missing {0} property in opened project", TEXT("EditorTarget")); + } + if (project->GameTarget.IsEmpty()) + { + LOG(Warning, "Missing {0} property in opened project", TEXT("GameTarget")); } // Remove any remaining files from previous Editor run hot-reloads diff --git a/Source/Editor/Surface/Archetypes/Comparisons.cs b/Source/Editor/Surface/Archetypes/Comparisons.cs index d77fd5ff7..44c49925d 100644 --- a/Source/Editor/Surface/Archetypes/Comparisons.cs +++ b/Source/Editor/Surface/Archetypes/Comparisons.cs @@ -137,7 +137,7 @@ namespace FlaxEditor.Surface.Archetypes { int* dataValues = (int*)dataPtr; for (int i = 0; i < entries.Count; i++) - dataValues[i] = entries[i].Value; + dataValues[i] = (int)entries[i].Value; } } else diff --git a/Source/Editor/Surface/Archetypes/Flow.cs b/Source/Editor/Surface/Archetypes/Flow.cs index b8c426a5a..ffcfb719a 100644 --- a/Source/Editor/Surface/Archetypes/Flow.cs +++ b/Source/Editor/Surface/Archetypes/Flow.cs @@ -177,7 +177,7 @@ namespace FlaxEditor.Surface.Archetypes { int* dataValues = (int*)dataPtr; for (int i = 0; i < entries.Count; i++) - dataValues[i] = entries[i].Value; + dataValues[i] = (int)entries[i].Value; } } else diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index a47b41a82..4e017248a 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -576,7 +576,7 @@ namespace FlaxEditor.Surface.Archetypes for (int i = 0; i < _parameters.Length; i++) { writer.Write(_parameters[i].Name); // Parameter name - Utilities.Utils.WriteVariantType(writer, TypeUtils.GetType(_parameters[i].Type)); // Box type + Utilities.VariantUtils.WriteVariantType(writer, TypeUtils.GetType(_parameters[i].Type)); // Box type } SetValue(2, stream.ToArray()); } @@ -594,7 +594,7 @@ namespace FlaxEditor.Surface.Archetypes for (int i = 0; i < parametersCount; i++) { var parameterName = reader.ReadString(); // Parameter name - var boxType = Utilities.Utils.ReadVariantType(reader); // Box type + var boxType = Utilities.VariantUtils.ReadVariantType(reader); // Box type MakeBox(i + 1, parameterName, boxType); } } @@ -778,14 +778,14 @@ namespace FlaxEditor.Surface.Archetypes { reader.ReadByte(); // Version signature.IsStatic = reader.ReadBoolean(); // Is Static - signature.ReturnType = Utilities.Utils.ReadVariantScriptType(reader); // Return type + signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return type var parametersCount = reader.ReadInt32(); // Parameters count signature.Params = parametersCount != 0 ? new SignatureParamInfo[parametersCount] : Utils.GetEmptyArray(); for (int i = 0; i < parametersCount; i++) { ref var param = ref signature.Params[i]; param.Name = Utilities.Utils.ReadStr(reader, 11); // Parameter name - param.Type = Utilities.Utils.ReadVariantScriptType(reader); // Parameter type + param.Type = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type param.IsOut = reader.ReadByte() != 0; // Is parameter out } } @@ -799,14 +799,14 @@ namespace FlaxEditor.Surface.Archetypes { reader.ReadByte(); // Version signature.IsStatic = reader.ReadBoolean(); // Is Static - signature.ReturnType = Utilities.Utils.ReadVariantScriptType(reader); // Return type + signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return type var parametersCount = reader.ReadInt32(); // Parameters count signature.Params = parametersCount != 0 ? new SignatureParamInfo[parametersCount] : Utils.GetEmptyArray(); for (int i = 0; i < parametersCount; i++) { ref var param = ref signature.Params[i]; param.Name = reader.ReadString(); // Parameter name - param.Type = Utilities.Utils.ReadVariantScriptType(reader); // Parameter type + param.Type = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type param.IsOut = reader.ReadByte() != 0; // Is parameter out } } @@ -823,13 +823,13 @@ namespace FlaxEditor.Surface.Archetypes { writer.Write((byte)4); // Version writer.Write(methodInfo.IsStatic); // Is Static - Utilities.Utils.WriteVariantType(writer, methodInfo.ValueType); // Return type + Utilities.VariantUtils.WriteVariantType(writer, methodInfo.ValueType); // Return type writer.Write(parameters.Length); // Parameters count for (int i = 0; i < parameters.Length; i++) { ref var param = ref parameters[i]; Utilities.Utils.WriteStr(writer, param.Name, 11); // Parameter name - Utilities.Utils.WriteVariantType(writer, param.Type); // Parameter type + Utilities.VariantUtils.WriteVariantType(writer, param.Type); // Parameter type writer.Write((byte)(param.IsOut ? 1 : 0)); // Is parameter out } return stream.ToArray(); @@ -1434,14 +1434,14 @@ namespace FlaxEditor.Surface.Archetypes if (_signature.IsVirtual) flags |= Flags.Virtual; writer.Write((byte)flags); // Flags - Utilities.Utils.WriteVariantType(writer, _signature.ReturnType); // Return Type + Utilities.VariantUtils.WriteVariantType(writer, _signature.ReturnType); // Return Type var parametersCount = _signature.Parameters?.Length ?? 0; writer.Write(parametersCount); // Parameters count for (int i = 0; i < parametersCount; i++) { ref var param = ref _signature.Parameters[i]; Utilities.Utils.WriteStrAnsi(writer, param.Name, 13); // Parameter name - Utilities.Utils.WriteVariantType(writer, param.Type); // Parameter type + Utilities.VariantUtils.WriteVariantType(writer, param.Type); // Parameter type writer.Write((byte)0); // Is parameter out writer.Write((byte)0); // Has default value } @@ -1470,13 +1470,13 @@ namespace FlaxEditor.Surface.Archetypes var flags = (Flags)reader.ReadByte(); // Flags _signature.IsStatic = (flags & Flags.Static) == Flags.Static; _signature.IsVirtual = (flags & Flags.Virtual) == Flags.Virtual; - _signature.ReturnType = Utilities.Utils.ReadVariantScriptType(reader); // Return Type + _signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return Type var parametersCount = reader.ReadInt32(); // Parameters count _signature.Parameters = new Parameter[parametersCount]; for (int i = 0; i < parametersCount; i++) { var paramName = Utilities.Utils.ReadStrAnsi(reader, 13); // Parameter name - var paramType = Utilities.Utils.ReadVariantScriptType(reader); // Parameter type + var paramType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type var isOut = reader.ReadByte() != 0; // Is parameter out var hasDefaultValue = reader.ReadByte() != 0; // Has default value _signature.Parameters[i] = new Parameter @@ -1993,7 +1993,7 @@ namespace FlaxEditor.Surface.Archetypes } else if (_isBind) { - _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Add48); + _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Add64); _helperButton.Color = Color.Red; _helperButton.TooltipText = "Add new handler function and bind it to this event"; _helperButton.Enabled = _signature != null; diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index 95f503338..0e9fa4ff0 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -171,7 +171,7 @@ namespace FlaxEditor.Surface.Archetypes for (int i = 0; i < fieldsLength; i++) { Utilities.Utils.WriteStr(writer, fields[i].Name, 11); // Field type - Utilities.Utils.WriteVariantType(writer, fields[i].ValueType); // Field type + Utilities.VariantUtils.WriteVariantType(writer, fields[i].ValueType); // Field type } Values[1] = stream.ToArray(); } @@ -188,7 +188,7 @@ namespace FlaxEditor.Surface.Archetypes for (int i = 0; i < fieldsLength; i++) { var fieldName = Utilities.Utils.ReadStr(reader, 11); // Field name - var fieldType = Utilities.Utils.ReadVariantType(reader); // Field type + var fieldType = Utilities.VariantUtils.ReadVariantType(reader); // Field type MakeBox(i + 1, fieldName, new ScriptType(fieldType)); } } diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index 52ffbb5cb..005af20cc 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -104,7 +104,7 @@ namespace FlaxEditor.Surface.Archetypes color *= 1.3f; color.A = 1.0f; var icons = Editor.Instance.Icons; - var icon = isSelected ? icons.VisjectArrowClose : icons.VisjectArrowOpen; + var icon = isSelected ? icons.VisjectArrowClosed32 : icons.VisjectArrowOpen32; Render2D.PushTransform(ref arrowTransform); Render2D.DrawSprite(icon, arrowRect, color); @@ -672,6 +672,24 @@ namespace FlaxEditor.Surface.Archetypes } } + private class ThisNode : SurfaceNode + { + /// + public ThisNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + {} + + /// + public override void OnLoaded() + { + base.OnLoaded(); + var vss = (VisualScriptSurface)this.Context.Surface; + var type = TypeUtils.GetType(vss.Script.ScriptTypeName); + var box = (OutputBox)GetBox(0); + box.CurrentType = type ? type : new ScriptType(typeof(VisualScript)); + } + } + private class AssetReferenceNode : SurfaceNode { /// @@ -1366,6 +1384,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 19, Title = "This Instance", + Create = (id, context, arch, groupArch) => new ThisNode(id, context, arch, groupArch), Description = "Gets the reference to this script object instance (self).", Flags = NodeFlags.VisualScriptGraph, Size = new Vector2(140, 20), diff --git a/Source/Editor/Surface/ContextMenu/ContentFinder.cs b/Source/Editor/Surface/ContextMenu/ContentFinder.cs index 76e8d2e33..fc0f43472 100644 --- a/Source/Editor/Surface/ContextMenu/ContentFinder.cs +++ b/Source/Editor/Surface/ContextMenu/ContentFinder.cs @@ -108,11 +108,13 @@ namespace FlaxEditor.Surface.ContextMenu { _resultPanel.DisposeChildren(); + var dpiScale = DpiScale; + if (items.Count == 0) { Height = _searchBox.Height + 1; _resultPanel.ScrollBars = ScrollBars.None; - RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * Platform.DpiScale); + RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * dpiScale); return; } @@ -146,7 +148,7 @@ namespace FlaxEditor.Surface.ContextMenu MatchedItems.Add(searchItem); } - RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * Platform.DpiScale); + RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * dpiScale); PerformLayout(); } diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index 2a460013c..6a8745fa6 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -578,6 +578,7 @@ namespace FlaxEditor.Surface.Elements BreakConnection(connectedBox); action.End(); Surface.Undo.AddAction(action); + Surface.MarkAsEdited(); } else { diff --git a/Source/Editor/Surface/Elements/EnumValue.cs b/Source/Editor/Surface/Elements/EnumValue.cs index 9513abd78..dbd1620ab 100644 --- a/Source/Editor/Surface/Elements/EnumValue.cs +++ b/Source/Editor/Surface/Elements/EnumValue.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Surface.Elements /// protected override void OnValueChanged() { - if ((int)ParentNode.Values[Archetype.ValueIndex] != Value) + if ((int)ParentNode.Values[Archetype.ValueIndex] != (int)Value) { // Edit value ParentNode.SetValue(Archetype.ValueIndex, Value); diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index ea6e9e34a..c830cf499 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -9,6 +9,8 @@ using FlaxEditor.GUI.Input; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; +using Newtonsoft.Json; +using JsonSerializer = FlaxEngine.Json.JsonSerializer; namespace FlaxEditor.Surface.Elements { @@ -981,6 +983,121 @@ namespace FlaxEditor.Surface.Elements } } + /// + public override bool OnMouseUp(Vector2 location, MouseButton button) + { + if (button == MouseButton.Right && Archetype.ValueIndex != -1) + { + var menu = new FlaxEditor.GUI.ContextMenu.ContextMenu(); + menu.AddButton("Copy value", OnCopyValue); + var paste = menu.AddButton("Paste value", OnPasteValue); + try + { + GetClipboardValue(out _, false); + } + catch + { + paste.Enabled = false; + } + + menu.Show(this, location); + return true; + } + + return base.OnMouseUp(location, button); + } + + private bool GetClipboardValue(out object result, bool deserialize) + { + result = null; + var text = Clipboard.Text; + if (string.IsNullOrEmpty(text)) + return false; + + object obj; + var type = CurrentType; + if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(type)) + { + // Object reference + if (text.Length != 32) + return false; + JsonSerializer.ParseID(text, out var id); + obj = FlaxEngine.Object.Find(ref id); + } + else + { + // Default + obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(type), JsonSerializer.Settings); + } + + if (obj == null || type.IsInstanceOfType(obj)) + { + result = obj; + return true; + } + + return false; + } + + private void OnCopyValue() + { + var value = ParentNode.Values[Archetype.ValueIndex]; + + try + { + string text; + if (value == null) + { + // Missing value + var type = CurrentType; + if (type.Type == typeof(bool)) + text = "false"; + else if (type.Type == typeof(byte) || type.Type == typeof(sbyte) || type.Type == typeof(char) || type.Type == typeof(short) || type.Type == typeof(ushort) || type.Type == typeof(int) || type.Type == typeof(uint) || type.Type == typeof(long) || type.Type == typeof(ulong)) + text = "0"; + else if (type.Type == typeof(float) || type.Type == typeof(double)) + text = "0.0"; + else if (type.Type == typeof(Vector2) || type.Type == typeof(Vector3) || type.Type == typeof(Vector4) || type.Type == typeof(Color)) + text = JsonSerializer.Serialize(TypeUtils.GetDefaultValue(type)); + else if (type.Type == typeof(string)) + text = ""; + else + text = "null"; + } + else if (value is FlaxEngine.Object asObject) + { + // Object reference + text = JsonSerializer.GetStringID(asObject); + } + else + { + // Default + text = JsonSerializer.Serialize(value); + } + Clipboard.Text = text; + } + catch (Exception ex) + { + Editor.LogWarning(ex); + Editor.LogError("Cannot copy property. See log for more info."); + } + } + + private void OnPasteValue() + { + try + { + if (GetClipboardValue(out var value, true)) + { + ParentNode.SetValue(Archetype.ValueIndex, value); + } + } + catch (Exception ex) + { + Editor.LogWarning(ex); + Editor.LogError("Cannot paste property value. See log for more info."); + } + } + /// /// Creates the default value editor control. /// diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index 18de19118..db25b9f09 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -224,10 +224,10 @@ namespace FlaxEditor.Surface }, Icons = { - BoxOpen = editor.Icons.VisjectBoxOpen, - BoxClose = editor.Icons.VisjectBoxClose, - ArrowOpen = editor.Icons.VisjectArrowOpen, - ArrowClose = editor.Icons.VisjectArrowClose, + BoxOpen = editor.Icons.VisjectBoxOpen32, + BoxClose = editor.Icons.VisjectBoxClosed32, + ArrowOpen = editor.Icons.VisjectArrowOpen32, + ArrowClose = editor.Icons.VisjectArrowClosed32, }, Background = editor.UI.VisjectSurfaceBackground, }; diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 996f9d2e0..9b96aa165 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -298,7 +298,6 @@ namespace FlaxEditor.Surface rerouteNode.GetBoxes().First(b => b.IsOutput).CreateConnection(inputBox); addConnectionsAction.End(); - Undo.AddAction(new MultiUndoAction(spawnNodeAction, disconnectBoxesAction, addConnectionsAction)); } else @@ -565,11 +564,10 @@ namespace FlaxEditor.Surface if (key == KeyboardKeys.ArrowUp || key == KeyboardKeys.ArrowDown) { Box selectedBox = GetSelectedBox(SelectedNodes); - if (selectedBox == null) return true; + if (selectedBox == null) + return true; - Box toSelect = (key == KeyboardKeys.ArrowUp) ? - selectedBox?.ParentNode.GetPreviousBox(selectedBox) : - selectedBox?.ParentNode.GetNextBox(selectedBox); + Box toSelect = (key == KeyboardKeys.ArrowUp) ? selectedBox?.ParentNode.GetPreviousBox(selectedBox) : selectedBox?.ParentNode.GetNextBox(selectedBox); if (toSelect != null && toSelect.IsOutput == selectedBox.IsOutput) { @@ -581,10 +579,12 @@ namespace FlaxEditor.Surface if (key == KeyboardKeys.Tab) { Box selectedBox = GetSelectedBox(SelectedNodes); - if (selectedBox == null) return true; + if (selectedBox == null) + return true; int connectionCount = selectedBox.Connections.Count; - if (connectionCount == 0) return true; + if (connectionCount == 0) + return true; if (Root.GetKey(KeyboardKeys.Shift)) { @@ -596,11 +596,11 @@ namespace FlaxEditor.Surface } } - if (key == KeyboardKeys.ArrowRight || key == KeyboardKeys.ArrowLeft) { Box selectedBox = GetSelectedBox(SelectedNodes); - if (selectedBox == null) return true; + if (selectedBox == null) + return true; Box toSelect = null; @@ -633,7 +633,6 @@ namespace FlaxEditor.Surface { Select(toSelect.ParentNode); toSelect.ParentNode.SelectBox(toSelect); - } return true; } @@ -825,10 +824,7 @@ namespace FlaxEditor.Surface xLocation += -120 - distanceBetweenNodes.X; } - return new Vector2( - xLocation, - yLocation - ); + return new Vector2(xLocation, yLocation); } private bool IntersectsConnection(Vector2 mousePosition, out InputBox inputBox, out OutputBox outputBox) diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index b5c502ba5..810f125ae 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -759,12 +759,12 @@ namespace FlaxEditor.Surface _propertiesEditor.Modified += OnPropertyEdited; // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.PageScale32, ShowWholeGraph).LinkTooltip("Show whole graph"); + _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph"); // Setup input actions InputActions.Add(options => options.Undo, _undo.PerformUndo); diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs index c65aa6bf0..c77feb71d 100644 --- a/Source/Editor/Undo/Actions/PasteActorsAction.cs +++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs @@ -140,22 +140,27 @@ namespace FlaxEditor.Actions for (int i = 0; i < nodeParents.Count; i++) { - // Fix name collisions (only for parents) var node = nodeParents[i]; var parent = node.Actor?.Parent; if (parent != null) { + // Fix name collisions string name = node.Name; Actor[] children = parent.Children; if (children.Any(x => x.Name == name)) { - // Generate new name node.Actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x)); } } Editor.Instance.Scene.MarkSceneEdited(node.ParentScene); } + + for (int i = 0; i < nodeParents.Count; i++) + { + var node = nodeParents[i]; + node.PostPaste(); + } } /// diff --git a/Source/Editor/Undo/UndoActionBase.cs b/Source/Editor/Undo/UndoActionBase.cs index 5de052a45..6648096c6 100644 --- a/Source/Editor/Undo/UndoActionBase.cs +++ b/Source/Editor/Undo/UndoActionBase.cs @@ -4,7 +4,6 @@ using System; using FlaxEditor.SceneGraph; using FlaxEngine; using Newtonsoft.Json; -using JsonSerializer = FlaxEngine.Json.JsonSerializer; namespace FlaxEditor { @@ -28,7 +27,6 @@ namespace FlaxEditor var id = Guid.Parse((string)reader.Value); return SceneGraphFactory.FindNode(id); } - return null; } @@ -56,19 +54,11 @@ namespace FlaxEditor /// /// Gets or sets the serialized undo data. /// - /// - /// The data. - /// [NoSerialize] - public TData Data + public virtual TData Data { - get => JsonConvert.DeserializeObject(_data, JsonSerializer.Settings); - protected set => _data = JsonConvert.SerializeObject(value, Formatting.None, JsonSerializer.Settings); - /*protected set - { - _data = JsonConvert.SerializeObject(value, Formatting.Indented, JsonSerializer.Settings); - Debug.Info(_data); - }*/ + get => JsonConvert.DeserializeObject(_data, FlaxEngine.Json.JsonSerializer.Settings); + protected set => _data = JsonConvert.SerializeObject(value, Formatting.None, FlaxEngine.Json.JsonSerializer.Settings); } /// diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index aa26388c1..85d409b92 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "EditorUtilities.h" +#include "Engine/Engine/Globals.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Core/Log.h" diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index a13d441d9..3eab59f80 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -2,15 +2,15 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; -using Newtonsoft.Json; namespace FlaxEditor.Utilities { @@ -29,46 +29,6 @@ namespace FlaxEditor.Utilities "PB" }; - internal enum VariantType - { - Null = 0, - Void, - - Bool, - Int, - Uint, - Int64, - Uint64, - Float, - Double, - Pointer, - - String, - Object, - Structure, - Asset, - Blob, - Enum, - - Vector2, - Vector3, - Vector4, - Color, - Guid, - BoundingBox, - BoundingSphere, - Quaternion, - Transform, - Rectangle, - Ray, - Matrix, - - Array, - Dictionary, - ManagedObject, - Typename, - } - /// /// The name of the Flax Engine C# assembly name. /// @@ -399,151 +359,6 @@ namespace FlaxEditor.Utilities stream.Write(bytes); } - internal static VariantType ToVariantType(this Type type) - { - VariantType variantType; - if (type == null) - variantType = VariantType.Null; - else if (type == typeof(void)) - variantType = VariantType.Void; - else if (type == typeof(bool)) - variantType = VariantType.Bool; - else if (type == typeof(int)) - variantType = VariantType.Int; - else if (type == typeof(uint)) - variantType = VariantType.Uint; - else if (type == typeof(long)) - variantType = VariantType.Int64; - else if (type == typeof(ulong)) - variantType = VariantType.Uint64; - else if (type.IsEnum) - variantType = VariantType.Enum; - else if (type == typeof(float)) - variantType = VariantType.Float; - else if (type == typeof(double)) - variantType = VariantType.Double; - else if (type == typeof(IntPtr)) - variantType = VariantType.Pointer; - else if (type == typeof(string)) - variantType = VariantType.String; - else if (type == typeof(Type) || type == typeof(ScriptType)) - variantType = VariantType.Typename; - else if (typeof(Asset).IsAssignableFrom(type)) - variantType = VariantType.Asset; - else if (typeof(FlaxEngine.Object).IsAssignableFrom(type)) - variantType = VariantType.Object; - else if (type == typeof(BoundingBox)) - variantType = VariantType.BoundingBox; - else if (type == typeof(Transform)) - variantType = VariantType.Transform; - else if (type == typeof(Ray)) - variantType = VariantType.Ray; - else if (type == typeof(Matrix)) - variantType = VariantType.Matrix; - else if (type == typeof(Vector2)) - variantType = VariantType.Vector2; - else if (type == typeof(Vector3)) - variantType = VariantType.Vector3; - else if (type == typeof(Vector4)) - variantType = VariantType.Vector4; - else if (type == typeof(Color)) - variantType = VariantType.Color; - else if (type == typeof(Guid)) - variantType = VariantType.Guid; - else if (type == typeof(Quaternion)) - variantType = VariantType.Quaternion; - else if (type == typeof(Rectangle)) - variantType = VariantType.Rectangle; - else if (type == typeof(BoundingSphere)) - variantType = VariantType.BoundingSphere; - else if (type.IsValueType) - variantType = VariantType.Structure; - else if (type == typeof(byte[])) - variantType = VariantType.Blob; - else if (type == typeof(object[])) - variantType = VariantType.Array; - else if (type == typeof(Dictionary)) - variantType = VariantType.Dictionary; - else if (type.IsPointer || type.IsByRef) - { - // Find underlying type without `*` or `&` - var typeName = type.FullName; - type = TypeUtils.GetManagedType(typeName.Substring(0, typeName.Length - 1)); - return ToVariantType(type); - } - else - variantType = VariantType.ManagedObject; - return variantType; - } - - internal static void WriteVariantType(this BinaryWriter stream, ScriptType type) - { - if (type == ScriptType.Null) - { - stream.Write((byte)0); - stream.Write(0); - return; - } - if (type.Type != null) - { - WriteVariantType(stream, type.Type); - } - else - { - if (type.IsEnum) - stream.Write((byte)VariantType.Enum); - else if (type.IsValueType) - stream.Write((byte)VariantType.Structure); - else - stream.Write((byte)VariantType.Object); - stream.Write(int.MaxValue); - stream.WriteStrAnsi(type.TypeName, 77); - } - } - - internal static void WriteVariantType(this BinaryWriter stream, Type type) - { - if (type == null) - { - stream.Write((byte)0); - stream.Write(0); - return; - } - var variantType = ToVariantType(type); - stream.Write((byte)variantType); - switch (variantType) - { - case VariantType.Object: - if (type != typeof(FlaxEngine.Object)) - { - stream.Write(int.MaxValue); - stream.WriteStrAnsi(type.FullName, 77); - } - else - stream.Write(0); - break; - case VariantType.Asset: - if (type != typeof(Asset)) - { - stream.Write(int.MaxValue); - stream.WriteStrAnsi(type.FullName, 77); - } - else - stream.Write(0); - break; - case VariantType.Enum: - case VariantType.Structure: - case VariantType.ManagedObject: - case VariantType.Typename: - stream.Write(int.MaxValue); - stream.WriteStrAnsi(type.FullName, 77); - break; - default: - stream.Write(0); - break; - } - } - internal static Guid ReadGuid(this BinaryReader stream) { // TODO: use static bytes array to reduce dynamic allocs @@ -682,259 +497,25 @@ namespace FlaxEditor.Utilities new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle())); break; } + case 19: // CommonType::Int2 + { + value = stream.ReadInt2(); + break; + } + case 20: // CommonType::Int3 + { + value = stream.ReadInt3(); + break; + } + case 21: // CommonType::Int4 + { + value = stream.ReadInt4(); + break; + } default: throw new SystemException(); } } - internal static ScriptType ReadVariantScriptType(this BinaryReader stream) - { - var variantType = (VariantType)stream.ReadByte(); - var typeNameLength = stream.ReadInt32(); - if (typeNameLength == int.MaxValue) - { - typeNameLength = stream.ReadInt32(); - var data = new byte[typeNameLength]; - for (int i = 0; i < typeNameLength; i++) - { - var c = stream.ReadByte(); - data[i] = (byte)(c ^ 77); - } - var typeName = System.Text.Encoding.ASCII.GetString(data); - return TypeUtils.GetType(typeName); - } - if (typeNameLength > 0) - { - // [Deprecated on 27.08.2020, expires on 27.08.2021] - var data = new char[typeNameLength]; - for (int i = 0; i < typeNameLength; i++) - { - var c = stream.ReadUInt16(); - data[i] = (char)(c ^ 77); - } - var typeName = new string(data); - return TypeUtils.GetType(typeName); - } - switch (variantType) - { - case VariantType.Null: return ScriptType.Null; - case VariantType.Void: return new ScriptType(typeof(void)); - case VariantType.Bool: return new ScriptType(typeof(bool)); - case VariantType.Int: return new ScriptType(typeof(int)); - case VariantType.Uint: return new ScriptType(typeof(uint)); - case VariantType.Int64: return new ScriptType(typeof(long)); - case VariantType.Uint64: return new ScriptType(typeof(ulong)); - case VariantType.Float: return new ScriptType(typeof(float)); - case VariantType.Double: return new ScriptType(typeof(double)); - case VariantType.Pointer: return new ScriptType(typeof(IntPtr)); - case VariantType.String: return new ScriptType(typeof(string)); - case VariantType.Typename: return new ScriptType(typeof(Type)); - case VariantType.Object: return new ScriptType(typeof(FlaxEngine.Object)); - case VariantType.Asset: return new ScriptType(typeof(Asset)); - case VariantType.Vector2: return new ScriptType(typeof(Vector2)); - case VariantType.Vector3: return new ScriptType(typeof(Vector3)); - case VariantType.Vector4: return new ScriptType(typeof(Vector4)); - case VariantType.Color: return new ScriptType(typeof(Color)); - case VariantType.Guid: return new ScriptType(typeof(Guid)); - case VariantType.BoundingBox: return new ScriptType(typeof(BoundingBox)); - case VariantType.BoundingSphere: return new ScriptType(typeof(BoundingSphere)); - case VariantType.Quaternion: return new ScriptType(typeof(Quaternion)); - case VariantType.Transform: return new ScriptType(typeof(Transform)); - case VariantType.Rectangle: return new ScriptType(typeof(Rectangle)); - case VariantType.Ray: return new ScriptType(typeof(Ray)); - case VariantType.Matrix: return new ScriptType(typeof(Matrix)); - case VariantType.Array: return new ScriptType(typeof(object[])); - case VariantType.Dictionary: return new ScriptType(typeof(Dictionary)); - case VariantType.ManagedObject: return new ScriptType(typeof(object)); - case VariantType.Blob: return new ScriptType(typeof(byte[])); - default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} without typename."); - } - } - - internal static Type ReadVariantType(this BinaryReader stream) - { - var variantType = (VariantType)stream.ReadByte(); - var typeNameLength = stream.ReadInt32(); - if (typeNameLength == int.MaxValue) - { - typeNameLength = stream.ReadInt32(); - var data = new byte[typeNameLength]; - for (int i = 0; i < typeNameLength; i++) - { - var c = stream.ReadByte(); - data[i] = (byte)(c ^ 77); - } - var typeName = System.Text.Encoding.ASCII.GetString(data); - return TypeUtils.GetManagedType(typeName); - } - if (typeNameLength > 0) - { - // [Deprecated on 27.08.2020, expires on 27.08.2021] - var data = new char[typeNameLength]; - for (int i = 0; i < typeNameLength; i++) - { - var c = stream.ReadUInt16(); - data[i] = (char)(c ^ 77); - } - var typeName = new string(data); - return TypeUtils.GetManagedType(typeName); - } - switch (variantType) - { - case VariantType.Null: return null; - case VariantType.Void: return typeof(void); - case VariantType.Bool: return typeof(bool); - case VariantType.Int: return typeof(int); - case VariantType.Uint: return typeof(uint); - case VariantType.Int64: return typeof(long); - case VariantType.Uint64: return typeof(ulong); - case VariantType.Float: return typeof(float); - case VariantType.Double: return typeof(double); - case VariantType.Pointer: return typeof(IntPtr); - case VariantType.String: return typeof(string); - case VariantType.Typename: return typeof(Type); - case VariantType.Object: return typeof(FlaxEngine.Object); - case VariantType.Asset: return typeof(Asset); - case VariantType.Vector2: return typeof(Vector2); - case VariantType.Vector3: return typeof(Vector3); - case VariantType.Vector4: return typeof(Vector4); - case VariantType.Color: return typeof(Color); - case VariantType.Guid: return typeof(Guid); - case VariantType.BoundingBox: return typeof(BoundingBox); - case VariantType.BoundingSphere: return typeof(BoundingSphere); - case VariantType.Quaternion: return typeof(Quaternion); - case VariantType.Transform: return typeof(Transform); - case VariantType.Rectangle: return typeof(Rectangle); - case VariantType.Ray: return typeof(Ray); - case VariantType.Matrix: return typeof(Matrix); - case VariantType.Array: return typeof(object[]); - case VariantType.Dictionary: return typeof(Dictionary); - case VariantType.ManagedObject: return typeof(object); - case VariantType.Blob: return typeof(byte[]); - default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} without typename."); - } - } - - internal static unsafe object ReadVariant(this BinaryReader stream) - { - Type type = null; - var variantType = (VariantType)stream.ReadByte(); - var typeNameLength = stream.ReadInt32(); - if (typeNameLength == int.MaxValue) - { - typeNameLength = stream.ReadInt32(); - var data = new byte[typeNameLength]; - for (int i = 0; i < typeNameLength; i++) - { - var c = stream.ReadByte(); - data[i] = (byte)(c ^ 77); - } - var typeName = System.Text.Encoding.ASCII.GetString(data); - type = TypeUtils.GetManagedType(typeName); - } - else if (typeNameLength > 0) - { - // [Deprecated on 27.08.2020, expires on 27.08.2021] - var data = new char[typeNameLength]; - for (int i = 0; i < typeNameLength; i++) - { - var c = stream.ReadUInt16(); - data[i] = (char)(c ^ 77); - } - var typeName = new string(data); - type = TypeUtils.GetManagedType(typeName); - } - switch (variantType) - { - case VariantType.Null: - case VariantType.ManagedObject: - case VariantType.Void: return null; - case VariantType.Bool: return stream.ReadByte() != 0; - case VariantType.Int: return stream.ReadInt32(); - case VariantType.Uint: return stream.ReadUInt32(); - case VariantType.Int64: return stream.ReadInt64(); - case VariantType.Uint64: return stream.ReadUInt64(); - case VariantType.Float: return stream.ReadSingle(); - case VariantType.Double: return stream.ReadDouble(); - case VariantType.Pointer: return new IntPtr((void*)stream.ReadUInt64()); - case VariantType.String: - { - typeNameLength = stream.ReadInt32(); - var data = new char[typeNameLength]; - for (int i = 0; i < typeNameLength; i++) - { - var c = stream.ReadUInt16(); - data[i] = (char)(c ^ -14); - } - return new string(data); - } - case VariantType.Object: - { - var id = stream.ReadGuid(); - return FlaxEngine.Object.Find(ref id, type ?? typeof(FlaxEngine.Object)); - } - case VariantType.Structure: - { - if (type == null) - throw new Exception("Missing structure type of the Variant."); - var data = stream.ReadBytes(stream.ReadInt32()); - return ByteArrayToStructure(data, type); - } - case VariantType.Asset: - { - var id = stream.ReadGuid(); - return FlaxEngine.Object.Find(ref id, type ?? typeof(Asset)); - } - case VariantType.Blob: return stream.ReadBytes(stream.ReadInt32()); - case VariantType.Enum: - { - if (type == null) - throw new Exception("Missing enum type of the Variant."); - return Enum.ToObject(type, stream.ReadUInt64()); - } - case VariantType.Vector2: return stream.ReadVector2(); - case VariantType.Vector3: return stream.ReadVector3(); - case VariantType.Vector4: return stream.ReadVector4(); - case VariantType.Color: return stream.ReadColor(); - case VariantType.Guid: return stream.ReadGuid(); - case VariantType.BoundingBox: return stream.ReadBoundingBox(); - case VariantType.BoundingSphere: return stream.ReadBoundingSphere(); - case VariantType.Quaternion: return stream.ReadQuaternion(); - case VariantType.Transform: return stream.ReadTransform(); - case VariantType.Rectangle: return stream.ReadRectangle(); - case VariantType.Ray: return stream.ReadRay(); - case VariantType.Matrix: return stream.ReadMatrix(); - case VariantType.Array: - { - var count = stream.ReadInt32(); - var result = new object[count]; - for (int i = 0; i < count; i++) - result[i] = stream.ReadVariant(); - return result; - } - case VariantType.Dictionary: - { - var count = stream.ReadInt32(); - var result = new Dictionary(); - for (int i = 0; i < count; i++) - result.Add(stream.ReadVariant(), stream.ReadVariant()); - return result; - } - case VariantType.Typename: - { - typeNameLength = stream.ReadInt32(); - var data = new byte[typeNameLength]; - for (int i = 0; i < typeNameLength; i++) - { - var c = stream.ReadByte(); - data[i] = (byte)(c ^ -14); - } - var typeName = System.Text.Encoding.ASCII.GetString(data); - return TypeUtils.GetType(typeName); - } - default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType}." + (type != null ? $" Type: {type.FullName}" : string.Empty)); - } - } - internal static void WriteCommonValue(this BinaryWriter stream, object value) { if (value is bool asBool) @@ -1087,567 +668,34 @@ namespace FlaxEditor.Utilities stream.Write(asRay.Direction.Y); stream.Write(asRay.Direction.Z); } + else if (value is Int2 asInt2) + { + stream.Write((byte)19); + stream.Write(asInt2); + } + else if (value is Int3 asInt3) + { + stream.Write((byte)20); + stream.Write(asInt3); + } + else if (value is Int4 asInt4) + { + stream.Write((byte)21); + stream.Write(asInt4); + } else { throw new NotSupportedException(string.Format("Invalid Common Value type {0}", value != null ? value.GetType().ToString() : "null")); } } - internal static void WriteVariant(this BinaryWriter stream, object value) - { - if (value == null) - { - stream.Write((byte)0); - stream.Write(0); - return; - } - var type = value.GetType(); - var variantType = ToVariantType(type); - stream.Write((byte)variantType); - switch (variantType) - { - case VariantType.Object: - if (type != typeof(FlaxEngine.Object)) - { - stream.Write(int.MaxValue); - stream.WriteStrAnsi(type.FullName, 77); - } - else - stream.Write(0); - break; - case VariantType.Asset: - if (type != typeof(Asset)) - { - stream.Write(int.MaxValue); - stream.WriteStrAnsi(type.FullName, 77); - } - else - stream.Write(0); - break; - case VariantType.Enum: - case VariantType.Structure: - stream.Write(int.MaxValue); - stream.WriteStrAnsi(type.FullName, 77); - break; - default: - stream.Write(0); - break; - } - Guid id; - switch (variantType) - { - case VariantType.Bool: - stream.Write((byte)((bool)value ? 1 : 0)); - break; - case VariantType.Int: - stream.Write((int)value); - break; - case VariantType.Uint: - stream.Write((uint)value); - break; - case VariantType.Int64: - stream.Write((long)value); - break; - case VariantType.Uint64: - stream.Write((ulong)value); - break; - case VariantType.Float: - stream.Write((float)value); - break; - case VariantType.Double: - stream.Write((double)value); - break; - case VariantType.Pointer: - stream.Write((ulong)(IntPtr)value); - break; - case VariantType.String: - stream.WriteStr((string)value, -14); - break; - case VariantType.Object: - id = ((FlaxEngine.Object)value).ID; - stream.WriteGuid(ref id); - break; - case VariantType.Structure: - { - var data = StructureToByteArray(value, type); - stream.Write(data.Length); - stream.Write(data); - break; - } - case VariantType.Asset: - id = ((Asset)value).ID; - stream.WriteGuid(ref id); - break; - case VariantType.Blob: - stream.Write(((byte[])value).Length); - stream.Write((byte[])value); - break; - case VariantType.Enum: - stream.Write(Convert.ToUInt64(value)); - break; - case VariantType.Vector2: - stream.Write((Vector2)value); - break; - case VariantType.Vector3: - stream.Write((Vector3)value); - break; - case VariantType.Vector4: - stream.Write((Vector4)value); - break; - case VariantType.Color: - stream.Write((Color)value); - break; - case VariantType.Guid: - id = (Guid)value; - stream.WriteGuid(ref id); - break; - case VariantType.BoundingBox: - stream.Write((BoundingBox)value); - break; - case VariantType.BoundingSphere: - stream.Write((BoundingSphere)value); - break; - case VariantType.Quaternion: - stream.Write((Quaternion)value); - break; - case VariantType.Transform: - stream.Write((Transform)value); - break; - case VariantType.Rectangle: - stream.Write((Rectangle)value); - break; - case VariantType.Ray: - stream.Write((Ray)value); - break; - case VariantType.Matrix: - stream.Write((Matrix)value); - break; - case VariantType.Array: - stream.Write(((object[])value).Length); - foreach (var e in (object[])value) - stream.WriteVariant(e); - break; - case VariantType.Dictionary: - stream.Write(((Dictionary)value).Count); - foreach (var e in (Dictionary)value) - { - stream.WriteVariant(e.Key); - stream.WriteVariant(e.Value); - } - break; - case VariantType.Typename: - if (value is Type) - stream.WriteStrAnsi(((Type)value).FullName, -14); - else if (value is ScriptType) - stream.WriteStrAnsi(((ScriptType)value).TypeName, -14); - break; - } - } - - internal static void WriteVariantType(this JsonWriter stream, Type value) - { - var variantType = ToVariantType(value); - var withoutTypeName = true; - switch (variantType) - { - case VariantType.Object: - if (value != typeof(FlaxEngine.Object)) - withoutTypeName = false; - break; - case VariantType.Asset: - if (value != typeof(Asset)) - withoutTypeName = false; - break; - case VariantType.Enum: - case VariantType.Structure: - withoutTypeName = false; - break; - } - if (withoutTypeName) - { - stream.WriteValue((int)variantType); - } - else - { - stream.WriteStartObject(); - - stream.WritePropertyName("Type"); - stream.WriteValue((int)variantType); - - stream.WritePropertyName("TypeName"); - stream.WriteValue(value.FullName); - - stream.WriteEndObject(); - } - } - - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] - internal static void WriteVariant(this JsonWriter stream, object value) - { - var type = value?.GetType(); - - stream.WriteStartObject(); - - stream.WritePropertyName("Type"); - stream.WriteVariantType(type); - - stream.WritePropertyName("Value"); - var variantType = ToVariantType(type); - switch (variantType) - { - case VariantType.Null: - case VariantType.ManagedObject: - case VariantType.Void: - stream.WriteStartObject(); - stream.WriteEndObject(); - break; - case VariantType.Bool: - stream.WriteValue((bool)value); - break; - case VariantType.Int: - stream.WriteValue((int)value); - break; - case VariantType.Uint: - stream.WriteValue((uint)value); - break; - case VariantType.Int64: - stream.WriteValue((long)value); - break; - case VariantType.Uint64: - stream.WriteValue((ulong)value); - break; - case VariantType.Float: - stream.WriteValue((float)value); - break; - case VariantType.Double: - stream.WriteValue((double)value); - break; - case VariantType.Pointer: - stream.WriteValue((ulong)(IntPtr)value); - break; - case VariantType.String: - stream.WriteValue((string)value); - break; - case VariantType.Object: - stream.WriteValue(((FlaxEngine.Object)value).ID); - break; - case VariantType.Asset: - stream.WriteValue(((Asset)value).ID); - break; - case VariantType.Blob: - stream.WriteValue(Convert.ToBase64String((byte[])value)); - break; - case VariantType.Enum: - stream.WriteValue(Convert.ToUInt64(value)); - break; - case VariantType.Vector2: - { - var asVector2 = (Vector2)value; - stream.WriteStartObject(); - - stream.WritePropertyName("X"); - stream.WriteValue(asVector2.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asVector2.Y); - - stream.WriteEndObject(); - break; - } - case VariantType.Vector3: - { - var asVector3 = (Vector3)value; - stream.WriteStartObject(); - - stream.WritePropertyName("X"); - stream.WriteValue(asVector3.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asVector3.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asVector3.Z); - - stream.WriteEndObject(); - break; - } - case VariantType.Vector4: - { - var asVector4 = (Vector4)value; - stream.WriteStartObject(); - - stream.WritePropertyName("X"); - stream.WriteValue(asVector4.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asVector4.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asVector4.Z); - stream.WritePropertyName("W"); - stream.WriteValue(asVector4.W); - - stream.WriteEndObject(); - break; - } - case VariantType.Color: - { - var asColor = (Color)value; - stream.WriteStartObject(); - - stream.WritePropertyName("R"); - stream.WriteValue(asColor.R); - stream.WritePropertyName("G"); - stream.WriteValue(asColor.G); - stream.WritePropertyName("B"); - stream.WriteValue(asColor.B); - stream.WritePropertyName("A"); - stream.WriteValue(asColor.A); - - stream.WriteEndObject(); - break; - } - case VariantType.Guid: - stream.WriteValue((Guid)value); - break; - case VariantType.BoundingBox: - { - var asBoundingBox = (BoundingBox)value; - stream.WriteStartObject(); - - stream.WritePropertyName("Minimum"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asBoundingBox.Minimum.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asBoundingBox.Minimum.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asBoundingBox.Minimum.Z); - stream.WriteEndObject(); - - stream.WritePropertyName("Maximum"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asBoundingBox.Maximum.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asBoundingBox.Maximum.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asBoundingBox.Maximum.Z); - stream.WriteEndObject(); - - stream.WriteEndObject(); - break; - } - case VariantType.BoundingSphere: - { - var asBoundingSphere = (BoundingSphere)value; - stream.WriteStartObject(); - - stream.WritePropertyName("Center"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asBoundingSphere.Center.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asBoundingSphere.Center.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asBoundingSphere.Center.Z); - stream.WriteEndObject(); - - stream.WritePropertyName("Radius"); - stream.WriteValue(asBoundingSphere.Radius); - - stream.WriteEndObject(); - break; - } - case VariantType.Quaternion: - { - var asQuaternion = (Quaternion)value; - stream.WriteStartObject(); - - stream.WritePropertyName("X"); - stream.WriteValue(asQuaternion.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asQuaternion.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asQuaternion.Z); - stream.WritePropertyName("W"); - stream.WriteValue(asQuaternion.W); - - stream.WriteEndObject(); - break; - } - case VariantType.Transform: - { - var asTransform = (Transform)value; - stream.WriteStartObject(); - - stream.WritePropertyName("Translation"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asTransform.Translation.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asTransform.Translation.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asTransform.Translation.Z); - stream.WriteEndObject(); - - stream.WritePropertyName("Orientation"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asTransform.Orientation.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asTransform.Orientation.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asTransform.Orientation.Z); - stream.WritePropertyName("W"); - stream.WriteValue(asTransform.Orientation.W); - stream.WriteEndObject(); - - stream.WritePropertyName("Scale"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asTransform.Scale.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asTransform.Scale.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asTransform.Scale.Z); - stream.WriteEndObject(); - - stream.WriteEndObject(); - break; - } - case VariantType.Rectangle: - { - var asRectangle = (Rectangle)value; - stream.WriteStartObject(); - - stream.WritePropertyName("Location"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asRectangle.Location.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asRectangle.Location.Y); - stream.WriteEndObject(); - - stream.WritePropertyName("Size"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asRectangle.Size.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asRectangle.Size.Y); - stream.WriteEndObject(); - - stream.WriteEndObject(); - break; - } - case VariantType.Ray: - { - var asRay = (Ray)value; - stream.WriteStartObject(); - - stream.WritePropertyName("Position"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asRay.Position.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asRay.Position.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asRay.Position.Z); - stream.WriteEndObject(); - - stream.WritePropertyName("Direction"); - stream.WriteStartObject(); - stream.WritePropertyName("X"); - stream.WriteValue(asRay.Direction.X); - stream.WritePropertyName("Y"); - stream.WriteValue(asRay.Direction.Y); - stream.WritePropertyName("Z"); - stream.WriteValue(asRay.Direction.Z); - stream.WriteEndObject(); - - stream.WriteEndObject(); - break; - } - case VariantType.Matrix: - { - var asMatrix = (Matrix)value; - stream.WriteStartObject(); - - stream.WritePropertyName("M11"); - stream.WriteValue(asMatrix.M11); - stream.WritePropertyName("M12"); - stream.WriteValue(asMatrix.M12); - stream.WritePropertyName("M13"); - stream.WriteValue(asMatrix.M13); - stream.WritePropertyName("M14"); - stream.WriteValue(asMatrix.M14); - - stream.WritePropertyName("M21"); - stream.WriteValue(asMatrix.M21); - stream.WritePropertyName("M22"); - stream.WriteValue(asMatrix.M22); - stream.WritePropertyName("M23"); - stream.WriteValue(asMatrix.M23); - stream.WritePropertyName("M24"); - stream.WriteValue(asMatrix.M24); - - stream.WritePropertyName("M31"); - stream.WriteValue(asMatrix.M31); - stream.WritePropertyName("M32"); - stream.WriteValue(asMatrix.M32); - stream.WritePropertyName("M33"); - stream.WriteValue(asMatrix.M33); - stream.WritePropertyName("M34"); - stream.WriteValue(asMatrix.M34); - - stream.WritePropertyName("M41"); - stream.WriteValue(asMatrix.M41); - stream.WritePropertyName("M42"); - stream.WriteValue(asMatrix.M42); - stream.WritePropertyName("M43"); - stream.WriteValue(asMatrix.M43); - stream.WritePropertyName("M44"); - stream.WriteValue(asMatrix.M44); - - stream.WriteEndObject(); - break; - } - case VariantType.Array: - { - stream.WriteStartArray(); - foreach (var e in (object[])value) - stream.WriteVariant(e); - stream.WriteEndArray(); - break; - } - case VariantType.Dictionary: - { - stream.WriteStartArray(); - foreach (var e in (Dictionary)value) - { - stream.WritePropertyName("Key"); - stream.WriteVariant(e.Key); - stream.WritePropertyName("Value"); - stream.WriteVariant(e.Value); - } - stream.WriteEndArray(); - break; - } - case VariantType.Typename: - if (value is Type) - stream.WriteValue(((Type)value).FullName); - else if (value is ScriptType) - stream.WriteValue(((ScriptType)value).TypeName); - break; - default: throw new NotImplementedException($"TODO: serialize {variantType} to Json"); - } - - stream.WriteEndObject(); - } - /// /// Shows the source code window. /// /// The source code. /// The window title. - /// The context control used to show source code window popup in a proper location. - public static void ShowSourceCodeWindow(string source, string title, Control control = null) + /// The optional parent window. + public static void ShowSourceCodeWindow(string source, string title, Window parentWindow = null) { if (string.IsNullOrEmpty(source)) { @@ -1661,8 +709,8 @@ namespace FlaxEditor.Utilities settings.AllowMinimize = false; settings.HasSizingFrame = false; settings.StartPosition = WindowStartPosition.CenterParent; - settings.Size = new Vector2(500, 600) * Platform.DpiScale; - settings.Parent = control?.RootWindow?.Window ?? Editor.Instance.Windows.MainWindow; + settings.Size = new Vector2(500, 600) * (parentWindow?.DpiScale ?? Platform.DpiScale); + settings.Parent = parentWindow; settings.Title = title; var dialog = Platform.CreateWindow(ref settings); @@ -1777,5 +825,41 @@ namespace FlaxEditor.Utilities } } } + + /// + /// Creates the search popup with a tree. + /// + /// The search box. + /// The tree control. + /// The created menu to setup and show. + public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree) + { + var menu = new ContextMenuBase + { + Size = new Vector2(320, 220), + }; + searchBox = new TextBox(false, 1, 1) + { + Width = menu.Width - 3, + WatermarkText = "Search...", + Parent = menu, + }; + var panel1 = new Panel(ScrollBars.Vertical) + { + Bounds = new Rectangle(0, searchBox.Bottom + 1, menu.Width, menu.Height - searchBox.Bottom - 2), + Parent = menu + }; + var panel2 = new VerticalPanel + { + Parent = panel1, + AnchorPreset = AnchorPresets.HorizontalStretchTop, + IsScrollable = true, + }; + tree = new Tree(false) + { + Parent = panel2, + }; + return menu; + } } } diff --git a/Source/Editor/Utilities/VariantUtils.cs b/Source/Editor/Utilities/VariantUtils.cs new file mode 100644 index 000000000..eb044c9e9 --- /dev/null +++ b/Source/Editor/Utilities/VariantUtils.cs @@ -0,0 +1,1072 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using FlaxEditor.Scripting; +using FlaxEngine; +using Newtonsoft.Json; + +namespace FlaxEditor.Utilities +{ + /// + /// Editor utilities and helper functions for Variant type. + /// + public static class VariantUtils + { + internal enum VariantType + { + Null = 0, + Void, + + Bool, + Int, + Uint, + Int64, + Uint64, + Float, + Double, + Pointer, + + String, + Object, + Structure, + Asset, + Blob, + Enum, + + Vector2, + Vector3, + Vector4, + Color, + Guid, + BoundingBox, + BoundingSphere, + Quaternion, + Transform, + Rectangle, + Ray, + Matrix, + + Array, + Dictionary, + ManagedObject, + Typename, + + Int2, + Int3, + Int4 + } + + internal static VariantType ToVariantType(this Type type) + { + VariantType variantType; + if (type == null) + variantType = VariantType.Null; + else if (type == typeof(void)) + variantType = VariantType.Void; + else if (type == typeof(bool)) + variantType = VariantType.Bool; + else if (type == typeof(int)) + variantType = VariantType.Int; + else if (type == typeof(uint)) + variantType = VariantType.Uint; + else if (type == typeof(long)) + variantType = VariantType.Int64; + else if (type == typeof(ulong)) + variantType = VariantType.Uint64; + else if (type.IsEnum) + variantType = VariantType.Enum; + else if (type == typeof(float)) + variantType = VariantType.Float; + else if (type == typeof(double)) + variantType = VariantType.Double; + else if (type == typeof(IntPtr)) + variantType = VariantType.Pointer; + else if (type == typeof(string)) + variantType = VariantType.String; + else if (type == typeof(Type) || type == typeof(ScriptType)) + variantType = VariantType.Typename; + else if (typeof(Asset).IsAssignableFrom(type)) + variantType = VariantType.Asset; + else if (typeof(FlaxEngine.Object).IsAssignableFrom(type)) + variantType = VariantType.Object; + else if (type == typeof(BoundingBox)) + variantType = VariantType.BoundingBox; + else if (type == typeof(Transform)) + variantType = VariantType.Transform; + else if (type == typeof(Ray)) + variantType = VariantType.Ray; + else if (type == typeof(Matrix)) + variantType = VariantType.Matrix; + else if (type == typeof(Vector2)) + variantType = VariantType.Vector2; + else if (type == typeof(Vector3)) + variantType = VariantType.Vector3; + else if (type == typeof(Vector4)) + variantType = VariantType.Vector4; + else if (type == typeof(Int2)) + variantType = VariantType.Int2; + else if (type == typeof(Int3)) + variantType = VariantType.Int3; + else if (type == typeof(Int4)) + variantType = VariantType.Int4; + else if (type == typeof(Color)) + variantType = VariantType.Color; + else if (type == typeof(Guid)) + variantType = VariantType.Guid; + else if (type == typeof(Quaternion)) + variantType = VariantType.Quaternion; + else if (type == typeof(Rectangle)) + variantType = VariantType.Rectangle; + else if (type == typeof(BoundingSphere)) + variantType = VariantType.BoundingSphere; + else if (type.IsValueType) + variantType = VariantType.Structure; + else if (type == typeof(byte[])) + variantType = VariantType.Blob; + else if (type == typeof(object[])) + variantType = VariantType.Array; + else if (type == typeof(Dictionary)) + variantType = VariantType.Dictionary; + else if (type.IsPointer || type.IsByRef) + { + // Find underlying type without `*` or `&` + var typeName = type.FullName; + type = TypeUtils.GetManagedType(typeName.Substring(0, typeName.Length - 1)); + return ToVariantType(type); + } + else + variantType = VariantType.ManagedObject; + return variantType; + } + + internal static void WriteVariantType(this BinaryWriter stream, ScriptType type) + { + if (type == ScriptType.Null) + { + stream.Write((byte)0); + stream.Write(0); + return; + } + if (type.Type != null) + { + WriteVariantType(stream, type.Type); + } + else + { + if (type.IsEnum) + stream.Write((byte)VariantType.Enum); + else if (type.IsValueType) + stream.Write((byte)VariantType.Structure); + else + stream.Write((byte)VariantType.Object); + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.TypeName, 77); + } + } + + internal static void WriteVariantType(this BinaryWriter stream, Type type) + { + if (type == null) + { + stream.Write((byte)0); + stream.Write(0); + return; + } + var variantType = ToVariantType(type); + stream.Write((byte)variantType); + switch (variantType) + { + case VariantType.Object: + if (type != typeof(FlaxEngine.Object)) + { + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.FullName, 77); + } + else + stream.Write(0); + break; + case VariantType.Asset: + if (type != typeof(Asset)) + { + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.FullName, 77); + } + else + stream.Write(0); + break; + case VariantType.Enum: + case VariantType.Structure: + case VariantType.ManagedObject: + case VariantType.Typename: + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.FullName, 77); + break; + default: + stream.Write(0); + break; + } + } + + internal static ScriptType ReadVariantScriptType(this BinaryReader stream) + { + var variantType = (VariantType)stream.ReadByte(); + var typeNameLength = stream.ReadInt32(); + if (typeNameLength == int.MaxValue) + { + typeNameLength = stream.ReadInt32(); + var data = new byte[typeNameLength]; + for (int i = 0; i < typeNameLength; i++) + { + var c = stream.ReadByte(); + data[i] = (byte)(c ^ 77); + } + var typeName = System.Text.Encoding.ASCII.GetString(data); + return TypeUtils.GetType(typeName); + } + if (typeNameLength > 0) + { + // [Deprecated on 27.08.2020, expires on 27.08.2021] + var data = new char[typeNameLength]; + for (int i = 0; i < typeNameLength; i++) + { + var c = stream.ReadUInt16(); + data[i] = (char)(c ^ 77); + } + var typeName = new string(data); + return TypeUtils.GetType(typeName); + } + switch (variantType) + { + case VariantType.Null: return ScriptType.Null; + case VariantType.Void: return new ScriptType(typeof(void)); + case VariantType.Bool: return new ScriptType(typeof(bool)); + case VariantType.Int: return new ScriptType(typeof(int)); + case VariantType.Uint: return new ScriptType(typeof(uint)); + case VariantType.Int64: return new ScriptType(typeof(long)); + case VariantType.Uint64: return new ScriptType(typeof(ulong)); + case VariantType.Float: return new ScriptType(typeof(float)); + case VariantType.Double: return new ScriptType(typeof(double)); + case VariantType.Pointer: return new ScriptType(typeof(IntPtr)); + case VariantType.String: return new ScriptType(typeof(string)); + case VariantType.Typename: return new ScriptType(typeof(Type)); + case VariantType.Object: return new ScriptType(typeof(FlaxEngine.Object)); + case VariantType.Asset: return new ScriptType(typeof(Asset)); + case VariantType.Vector2: return new ScriptType(typeof(Vector2)); + case VariantType.Vector3: return new ScriptType(typeof(Vector3)); + case VariantType.Vector4: return new ScriptType(typeof(Vector4)); + case VariantType.Int2: return new ScriptType(typeof(Int2)); + case VariantType.Int3: return new ScriptType(typeof(Int3)); + case VariantType.Int4: return new ScriptType(typeof(Int4)); + case VariantType.Color: return new ScriptType(typeof(Color)); + case VariantType.Guid: return new ScriptType(typeof(Guid)); + case VariantType.BoundingBox: return new ScriptType(typeof(BoundingBox)); + case VariantType.BoundingSphere: return new ScriptType(typeof(BoundingSphere)); + case VariantType.Quaternion: return new ScriptType(typeof(Quaternion)); + case VariantType.Transform: return new ScriptType(typeof(Transform)); + case VariantType.Rectangle: return new ScriptType(typeof(Rectangle)); + case VariantType.Ray: return new ScriptType(typeof(Ray)); + case VariantType.Matrix: return new ScriptType(typeof(Matrix)); + case VariantType.Array: return new ScriptType(typeof(object[])); + case VariantType.Dictionary: return new ScriptType(typeof(Dictionary)); + case VariantType.ManagedObject: return new ScriptType(typeof(object)); + case VariantType.Blob: return new ScriptType(typeof(byte[])); + default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} without typename."); + } + } + + internal static Type ReadVariantType(this BinaryReader stream) + { + var variantType = (VariantType)stream.ReadByte(); + var typeNameLength = stream.ReadInt32(); + if (typeNameLength == int.MaxValue) + { + typeNameLength = stream.ReadInt32(); + var data = new byte[typeNameLength]; + for (int i = 0; i < typeNameLength; i++) + { + var c = stream.ReadByte(); + data[i] = (byte)(c ^ 77); + } + var typeName = System.Text.Encoding.ASCII.GetString(data); + return TypeUtils.GetManagedType(typeName); + } + if (typeNameLength > 0) + { + // [Deprecated on 27.08.2020, expires on 27.08.2021] + var data = new char[typeNameLength]; + for (int i = 0; i < typeNameLength; i++) + { + var c = stream.ReadUInt16(); + data[i] = (char)(c ^ 77); + } + var typeName = new string(data); + return TypeUtils.GetManagedType(typeName); + } + switch (variantType) + { + case VariantType.Null: return null; + case VariantType.Void: return typeof(void); + case VariantType.Bool: return typeof(bool); + case VariantType.Int: return typeof(int); + case VariantType.Uint: return typeof(uint); + case VariantType.Int64: return typeof(long); + case VariantType.Uint64: return typeof(ulong); + case VariantType.Float: return typeof(float); + case VariantType.Double: return typeof(double); + case VariantType.Pointer: return typeof(IntPtr); + case VariantType.String: return typeof(string); + case VariantType.Typename: return typeof(Type); + case VariantType.Object: return typeof(FlaxEngine.Object); + case VariantType.Asset: return typeof(Asset); + case VariantType.Vector2: return typeof(Vector2); + case VariantType.Vector3: return typeof(Vector3); + case VariantType.Vector4: return typeof(Vector4); + case VariantType.Int2: return typeof(Int2); + case VariantType.Int3: return typeof(Int3); + case VariantType.Int4: return typeof(Int4); + case VariantType.Color: return typeof(Color); + case VariantType.Guid: return typeof(Guid); + case VariantType.BoundingBox: return typeof(BoundingBox); + case VariantType.BoundingSphere: return typeof(BoundingSphere); + case VariantType.Quaternion: return typeof(Quaternion); + case VariantType.Transform: return typeof(Transform); + case VariantType.Rectangle: return typeof(Rectangle); + case VariantType.Ray: return typeof(Ray); + case VariantType.Matrix: return typeof(Matrix); + case VariantType.Array: return typeof(object[]); + case VariantType.Dictionary: return typeof(Dictionary); + case VariantType.ManagedObject: return typeof(object); + case VariantType.Blob: return typeof(byte[]); + default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} without typename."); + } + } + + internal static unsafe object ReadVariant(this BinaryReader stream) + { + Type type = null; + var variantType = (VariantType)stream.ReadByte(); + var typeNameLength = stream.ReadInt32(); + if (typeNameLength == int.MaxValue) + { + typeNameLength = stream.ReadInt32(); + var data = new byte[typeNameLength]; + for (int i = 0; i < typeNameLength; i++) + { + var c = stream.ReadByte(); + data[i] = (byte)(c ^ 77); + } + var typeName = System.Text.Encoding.ASCII.GetString(data); + type = TypeUtils.GetManagedType(typeName); + } + else if (typeNameLength > 0) + { + // [Deprecated on 27.08.2020, expires on 27.08.2021] + var data = new char[typeNameLength]; + for (int i = 0; i < typeNameLength; i++) + { + var c = stream.ReadUInt16(); + data[i] = (char)(c ^ 77); + } + var typeName = new string(data); + type = TypeUtils.GetManagedType(typeName); + } + switch (variantType) + { + case VariantType.Null: + case VariantType.ManagedObject: + case VariantType.Void: return null; + case VariantType.Bool: return stream.ReadByte() != 0; + case VariantType.Int: return stream.ReadInt32(); + case VariantType.Uint: return stream.ReadUInt32(); + case VariantType.Int64: return stream.ReadInt64(); + case VariantType.Uint64: return stream.ReadUInt64(); + case VariantType.Float: return stream.ReadSingle(); + case VariantType.Double: return stream.ReadDouble(); + case VariantType.Pointer: return new IntPtr((void*)stream.ReadUInt64()); + case VariantType.String: + { + typeNameLength = stream.ReadInt32(); + var data = new char[typeNameLength]; + for (int i = 0; i < typeNameLength; i++) + { + var c = stream.ReadUInt16(); + data[i] = (char)(c ^ -14); + } + return new string(data); + } + case VariantType.Object: + { + var id = stream.ReadGuid(); + return FlaxEngine.Object.Find(ref id, type ?? typeof(FlaxEngine.Object)); + } + case VariantType.Structure: + { + if (type == null) + throw new Exception("Missing structure type of the Variant."); + var data = stream.ReadBytes(stream.ReadInt32()); + return Utils.ByteArrayToStructure(data, type); + } + case VariantType.Asset: + { + var id = stream.ReadGuid(); + return FlaxEngine.Object.Find(ref id, type ?? typeof(Asset)); + } + case VariantType.Blob: return stream.ReadBytes(stream.ReadInt32()); + case VariantType.Enum: + { + if (type == null) + throw new Exception("Missing enum type of the Variant."); + return Enum.ToObject(type, stream.ReadUInt64()); + } + case VariantType.Vector2: return stream.ReadVector2(); + case VariantType.Vector3: return stream.ReadVector3(); + case VariantType.Vector4: return stream.ReadVector4(); + case VariantType.Int2: return stream.ReadInt2(); + case VariantType.Int3: return stream.ReadInt3(); + case VariantType.Int4: return stream.ReadInt4(); + case VariantType.Color: return stream.ReadColor(); + case VariantType.Guid: return stream.ReadGuid(); + case VariantType.BoundingBox: return stream.ReadBoundingBox(); + case VariantType.BoundingSphere: return stream.ReadBoundingSphere(); + case VariantType.Quaternion: return stream.ReadQuaternion(); + case VariantType.Transform: return stream.ReadTransform(); + case VariantType.Rectangle: return stream.ReadRectangle(); + case VariantType.Ray: return stream.ReadRay(); + case VariantType.Matrix: return stream.ReadMatrix(); + case VariantType.Array: + { + var count = stream.ReadInt32(); + var result = new object[count]; + for (int i = 0; i < count; i++) + result[i] = stream.ReadVariant(); + return result; + } + case VariantType.Dictionary: + { + var count = stream.ReadInt32(); + var result = new Dictionary(); + for (int i = 0; i < count; i++) + result.Add(stream.ReadVariant(), stream.ReadVariant()); + return result; + } + case VariantType.Typename: + { + typeNameLength = stream.ReadInt32(); + var data = new byte[typeNameLength]; + for (int i = 0; i < typeNameLength; i++) + { + var c = stream.ReadByte(); + data[i] = (byte)(c ^ -14); + } + var typeName = System.Text.Encoding.ASCII.GetString(data); + return TypeUtils.GetType(typeName); + } + default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType}." + (type != null ? $" Type: {type.FullName}" : string.Empty)); + } + } + + internal static void WriteVariant(this BinaryWriter stream, object value) + { + if (value == null) + { + stream.Write((byte)0); + stream.Write(0); + return; + } + var type = value.GetType(); + var variantType = ToVariantType(type); + stream.Write((byte)variantType); + switch (variantType) + { + case VariantType.Object: + if (type != typeof(FlaxEngine.Object)) + { + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.FullName, 77); + } + else + stream.Write(0); + break; + case VariantType.Asset: + if (type != typeof(Asset)) + { + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.FullName, 77); + } + else + stream.Write(0); + break; + case VariantType.Enum: + case VariantType.Structure: + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.FullName, 77); + break; + default: + stream.Write(0); + break; + } + Guid id; + switch (variantType) + { + case VariantType.Bool: + stream.Write((byte)((bool)value ? 1 : 0)); + break; + case VariantType.Int: + stream.Write((int)value); + break; + case VariantType.Uint: + stream.Write((uint)value); + break; + case VariantType.Int64: + stream.Write((long)value); + break; + case VariantType.Uint64: + stream.Write((ulong)value); + break; + case VariantType.Float: + stream.Write((float)value); + break; + case VariantType.Double: + stream.Write((double)value); + break; + case VariantType.Pointer: + stream.Write((ulong)(IntPtr)value); + break; + case VariantType.String: + stream.WriteStr((string)value, -14); + break; + case VariantType.Object: + id = ((FlaxEngine.Object)value).ID; + stream.WriteGuid(ref id); + break; + case VariantType.Structure: + { + var data = Utils.StructureToByteArray(value, type); + stream.Write(data.Length); + stream.Write(data); + break; + } + case VariantType.Asset: + id = ((Asset)value).ID; + stream.WriteGuid(ref id); + break; + case VariantType.Blob: + stream.Write(((byte[])value).Length); + stream.Write((byte[])value); + break; + case VariantType.Enum: + stream.Write(Convert.ToUInt64(value)); + break; + case VariantType.Vector2: + stream.Write((Vector2)value); + break; + case VariantType.Vector3: + stream.Write((Vector3)value); + break; + case VariantType.Vector4: + stream.Write((Vector4)value); + break; + case VariantType.Int2: + stream.Write((Int2)value); + break; + case VariantType.Int3: + stream.Write((Int3)value); + break; + case VariantType.Int4: + stream.Write((Int4)value); + break; + case VariantType.Color: + stream.Write((Color)value); + break; + case VariantType.Guid: + id = (Guid)value; + stream.WriteGuid(ref id); + break; + case VariantType.BoundingBox: + stream.Write((BoundingBox)value); + break; + case VariantType.BoundingSphere: + stream.Write((BoundingSphere)value); + break; + case VariantType.Quaternion: + stream.Write((Quaternion)value); + break; + case VariantType.Transform: + stream.Write((Transform)value); + break; + case VariantType.Rectangle: + stream.Write((Rectangle)value); + break; + case VariantType.Ray: + stream.Write((Ray)value); + break; + case VariantType.Matrix: + stream.Write((Matrix)value); + break; + case VariantType.Array: + stream.Write(((object[])value).Length); + foreach (var e in (object[])value) + stream.WriteVariant(e); + break; + case VariantType.Dictionary: + stream.Write(((Dictionary)value).Count); + foreach (var e in (Dictionary)value) + { + stream.WriteVariant(e.Key); + stream.WriteVariant(e.Value); + } + break; + case VariantType.Typename: + if (value is Type) + stream.WriteStrAnsi(((Type)value).FullName, -14); + else if (value is ScriptType) + stream.WriteStrAnsi(((ScriptType)value).TypeName, -14); + break; + } + } + + internal static void WriteVariantType(this JsonWriter stream, Type value) + { + var variantType = ToVariantType(value); + var withoutTypeName = true; + switch (variantType) + { + case VariantType.Object: + if (value != typeof(FlaxEngine.Object)) + withoutTypeName = false; + break; + case VariantType.Asset: + if (value != typeof(Asset)) + withoutTypeName = false; + break; + case VariantType.Enum: + case VariantType.Structure: + withoutTypeName = false; + break; + } + if (withoutTypeName) + { + stream.WriteValue((int)variantType); + } + else + { + stream.WriteStartObject(); + + stream.WritePropertyName("Type"); + stream.WriteValue((int)variantType); + + stream.WritePropertyName("TypeName"); + stream.WriteValue(value.FullName); + + stream.WriteEndObject(); + } + } + + internal static void WriteVariant(this JsonWriter stream, object value) + { + var type = value?.GetType(); + + stream.WriteStartObject(); + + stream.WritePropertyName("Type"); + stream.WriteVariantType(type); + + stream.WritePropertyName("Value"); + var variantType = ToVariantType(type); + // ReSharper disable PossibleNullReferenceException + switch (variantType) + { + case VariantType.Null: + case VariantType.ManagedObject: + case VariantType.Void: + stream.WriteStartObject(); + stream.WriteEndObject(); + break; + case VariantType.Bool: + stream.WriteValue((bool)value); + break; + case VariantType.Int: + stream.WriteValue((int)value); + break; + case VariantType.Uint: + stream.WriteValue((uint)value); + break; + case VariantType.Int64: + stream.WriteValue((long)value); + break; + case VariantType.Uint64: + stream.WriteValue((ulong)value); + break; + case VariantType.Float: + stream.WriteValue((float)value); + break; + case VariantType.Double: + stream.WriteValue((double)value); + break; + case VariantType.Pointer: + stream.WriteValue((ulong)(IntPtr)value); + break; + case VariantType.String: + stream.WriteValue((string)value); + break; + case VariantType.Object: + stream.WriteValue(((FlaxEngine.Object)value).ID); + break; + case VariantType.Asset: + stream.WriteValue(((Asset)value).ID); + break; + case VariantType.Blob: + stream.WriteValue(Convert.ToBase64String((byte[])value)); + break; + case VariantType.Enum: + stream.WriteValue(Convert.ToUInt64(value)); + break; + case VariantType.Vector2: + { + var asVector2 = (Vector2)value; + stream.WriteStartObject(); + + stream.WritePropertyName("X"); + stream.WriteValue(asVector2.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asVector2.Y); + + stream.WriteEndObject(); + break; + } + case VariantType.Vector3: + { + var asVector3 = (Vector3)value; + stream.WriteStartObject(); + + stream.WritePropertyName("X"); + stream.WriteValue(asVector3.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asVector3.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asVector3.Z); + + stream.WriteEndObject(); + break; + } + case VariantType.Vector4: + { + var asVector4 = (Vector4)value; + stream.WriteStartObject(); + + stream.WritePropertyName("X"); + stream.WriteValue(asVector4.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asVector4.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asVector4.Z); + stream.WritePropertyName("W"); + stream.WriteValue(asVector4.W); + + stream.WriteEndObject(); + break; + } + case VariantType.Int2: + { + var asInt2 = (Int2)value; + stream.WriteStartObject(); + + stream.WritePropertyName("X"); + stream.WriteValue(asInt2.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asInt2.Y); + + stream.WriteEndObject(); + break; + } + case VariantType.Int3: + { + var asInt3 = (Int3)value; + stream.WriteStartObject(); + + stream.WritePropertyName("X"); + stream.WriteValue(asInt3.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asInt3.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asInt3.Z); + + stream.WriteEndObject(); + break; + } + case VariantType.Int4: + { + var asInt4 = (Int4)value; + stream.WriteStartObject(); + + stream.WritePropertyName("X"); + stream.WriteValue(asInt4.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asInt4.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asInt4.Z); + stream.WritePropertyName("W"); + stream.WriteValue(asInt4.W); + + stream.WriteEndObject(); + break; + } + case VariantType.Color: + { + var asColor = (Color)value; + stream.WriteStartObject(); + + stream.WritePropertyName("R"); + stream.WriteValue(asColor.R); + stream.WritePropertyName("G"); + stream.WriteValue(asColor.G); + stream.WritePropertyName("B"); + stream.WriteValue(asColor.B); + stream.WritePropertyName("A"); + stream.WriteValue(asColor.A); + + stream.WriteEndObject(); + break; + } + case VariantType.Guid: + stream.WriteValue((Guid)value); + break; + case VariantType.BoundingBox: + { + var asBoundingBox = (BoundingBox)value; + stream.WriteStartObject(); + + stream.WritePropertyName("Minimum"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asBoundingBox.Minimum.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asBoundingBox.Minimum.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asBoundingBox.Minimum.Z); + stream.WriteEndObject(); + + stream.WritePropertyName("Maximum"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asBoundingBox.Maximum.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asBoundingBox.Maximum.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asBoundingBox.Maximum.Z); + stream.WriteEndObject(); + + stream.WriteEndObject(); + break; + } + case VariantType.BoundingSphere: + { + var asBoundingSphere = (BoundingSphere)value; + stream.WriteStartObject(); + + stream.WritePropertyName("Center"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asBoundingSphere.Center.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asBoundingSphere.Center.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asBoundingSphere.Center.Z); + stream.WriteEndObject(); + + stream.WritePropertyName("Radius"); + stream.WriteValue(asBoundingSphere.Radius); + + stream.WriteEndObject(); + break; + } + case VariantType.Quaternion: + { + var asQuaternion = (Quaternion)value; + stream.WriteStartObject(); + + stream.WritePropertyName("X"); + stream.WriteValue(asQuaternion.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asQuaternion.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asQuaternion.Z); + stream.WritePropertyName("W"); + stream.WriteValue(asQuaternion.W); + + stream.WriteEndObject(); + break; + } + case VariantType.Transform: + { + var asTransform = (Transform)value; + stream.WriteStartObject(); + + stream.WritePropertyName("Translation"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asTransform.Translation.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asTransform.Translation.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asTransform.Translation.Z); + stream.WriteEndObject(); + + stream.WritePropertyName("Orientation"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asTransform.Orientation.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asTransform.Orientation.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asTransform.Orientation.Z); + stream.WritePropertyName("W"); + stream.WriteValue(asTransform.Orientation.W); + stream.WriteEndObject(); + + stream.WritePropertyName("Scale"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asTransform.Scale.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asTransform.Scale.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asTransform.Scale.Z); + stream.WriteEndObject(); + + stream.WriteEndObject(); + break; + } + case VariantType.Rectangle: + { + var asRectangle = (Rectangle)value; + stream.WriteStartObject(); + + stream.WritePropertyName("Location"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asRectangle.Location.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asRectangle.Location.Y); + stream.WriteEndObject(); + + stream.WritePropertyName("Size"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asRectangle.Size.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asRectangle.Size.Y); + stream.WriteEndObject(); + + stream.WriteEndObject(); + break; + } + case VariantType.Ray: + { + var asRay = (Ray)value; + stream.WriteStartObject(); + + stream.WritePropertyName("Position"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asRay.Position.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asRay.Position.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asRay.Position.Z); + stream.WriteEndObject(); + + stream.WritePropertyName("Direction"); + stream.WriteStartObject(); + stream.WritePropertyName("X"); + stream.WriteValue(asRay.Direction.X); + stream.WritePropertyName("Y"); + stream.WriteValue(asRay.Direction.Y); + stream.WritePropertyName("Z"); + stream.WriteValue(asRay.Direction.Z); + stream.WriteEndObject(); + + stream.WriteEndObject(); + break; + } + case VariantType.Matrix: + { + var asMatrix = (Matrix)value; + stream.WriteStartObject(); + + stream.WritePropertyName("M11"); + stream.WriteValue(asMatrix.M11); + stream.WritePropertyName("M12"); + stream.WriteValue(asMatrix.M12); + stream.WritePropertyName("M13"); + stream.WriteValue(asMatrix.M13); + stream.WritePropertyName("M14"); + stream.WriteValue(asMatrix.M14); + + stream.WritePropertyName("M21"); + stream.WriteValue(asMatrix.M21); + stream.WritePropertyName("M22"); + stream.WriteValue(asMatrix.M22); + stream.WritePropertyName("M23"); + stream.WriteValue(asMatrix.M23); + stream.WritePropertyName("M24"); + stream.WriteValue(asMatrix.M24); + + stream.WritePropertyName("M31"); + stream.WriteValue(asMatrix.M31); + stream.WritePropertyName("M32"); + stream.WriteValue(asMatrix.M32); + stream.WritePropertyName("M33"); + stream.WriteValue(asMatrix.M33); + stream.WritePropertyName("M34"); + stream.WriteValue(asMatrix.M34); + + stream.WritePropertyName("M41"); + stream.WriteValue(asMatrix.M41); + stream.WritePropertyName("M42"); + stream.WriteValue(asMatrix.M42); + stream.WritePropertyName("M43"); + stream.WriteValue(asMatrix.M43); + stream.WritePropertyName("M44"); + stream.WriteValue(asMatrix.M44); + + stream.WriteEndObject(); + break; + } + case VariantType.Array: + { + stream.WriteStartArray(); + foreach (var e in (object[])value) + stream.WriteVariant(e); + stream.WriteEndArray(); + break; + } + case VariantType.Dictionary: + { + stream.WriteStartArray(); + foreach (var e in (Dictionary)value) + { + stream.WritePropertyName("Key"); + stream.WriteVariant(e.Key); + stream.WritePropertyName("Value"); + stream.WriteVariant(e.Value); + } + stream.WriteEndArray(); + break; + } + case VariantType.Typename: + if (value is Type) + stream.WriteValue(((Type)value).FullName); + else if (value is ScriptType) + stream.WriteValue(((ScriptType)value).TypeName); + break; + default: throw new NotImplementedException($"TODO: serialize {variantType} to Json"); + } + // ReSharper restore PossibleNullReferenceException + + stream.WriteEndObject(); + } + } +} diff --git a/Source/Editor/Viewport/Cameras/ArcBallCamera.cs b/Source/Editor/Viewport/Cameras/ArcBallCamera.cs index 876adc796..e6a72c8b4 100644 --- a/Source/Editor/Viewport/Cameras/ArcBallCamera.cs +++ b/Source/Editor/Viewport/Cameras/ArcBallCamera.cs @@ -94,6 +94,15 @@ namespace FlaxEditor.Viewport.Cameras Viewport.ViewPosition = _orbitCenter + localPosition; } + /// + public override void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) + { + base.SetArcBallView(orientation, orbitCenter, orbitRadius); + + _orbitCenter = orbitCenter; + _orbitRadius = orbitRadius; + } + /// public override void UpdateView(float dt, ref Vector3 moveDelta, ref Vector2 mouseDelta, out bool centerMouse) { diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index 6d0376a89..22abccf69 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -92,36 +92,87 @@ namespace FlaxEditor.Viewport.Cameras /// /// Moves the viewport to visualize selected actors. /// - /// The actors to show. - public void ShowActors(List actors) + /// The actors to show. + /// The used orientation. + public void ShowActor(Actor actor, ref Quaternion orientation) { - if (actors.Count == 0) + Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); + ShowSphere(ref sphere, ref orientation); + } + + /// + /// Moves the viewport to visualize selected actors. + /// + /// The actors to show. + public void ShowActors(List selection) + { + if (selection.Count == 0) return; BoundingSphere mergesSphere = BoundingSphere.Empty; - for (int i = 0; i < actors.Count; i++) + for (int i = 0; i < selection.Count; i++) { - if (actors[i] is ActorNode actor) - { - Editor.GetActorEditorSphere(actor.Actor, out BoundingSphere sphere); - BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); - } + selection[i].GetEditorSphere(out var sphere); + BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); } + if (mergesSphere == BoundingSphere.Empty) + return; ShowSphere(ref mergesSphere); } + /// + /// Moves the viewport to visualize selected actors. + /// + /// The actors to show. + /// The used orientation. + public void ShowActors(List selection, ref Quaternion orientation) + { + if (selection.Count == 0) + return; + + BoundingSphere mergesSphere = BoundingSphere.Empty; + for (int i = 0; i < selection.Count; i++) + { + selection[i].GetEditorSphere(out var sphere); + BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); + } + + if (mergesSphere == BoundingSphere.Empty) + return; + ShowSphere(ref mergesSphere, ref orientation); + } + private void ShowSphere(ref BoundingSphere sphere) { - // Calculate view transform - Quaternion orientation = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); - Vector3 position = sphere.Center - Vector3.Forward * orientation * (sphere.Radius * 2.5f); + var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); + ShowSphere(ref sphere, ref q); + } - // Move viewport + private void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) + { + Vector3 position; + if (Viewport.UseOrthographicProjection) + { + position = sphere.Center + Vector3.Backward * orientation * (sphere.Radius * 5.0f); + Viewport.OrthographicScale = Vector3.Distance(position, sphere.Center) / 1000; + } + else + { + position = sphere.Center - Vector3.Forward * orientation * (sphere.Radius * 2.5f); + } TargetPoint = sphere.Center; MoveViewport(position, orientation); } + /// + public override void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) + { + base.SetArcBallView(orientation, orbitCenter, orbitRadius); + + TargetPoint = orbitCenter; + } + /// public override void Update(float deltaTime) { @@ -160,12 +211,12 @@ namespace FlaxEditor.Viewport.Cameras Viewport.GetInput(out var input); Viewport.GetPrevInput(out var prevInput); - var mainViewport = Viewport as MainEditorGizmoViewport; - bool isUsingGizmo = mainViewport != null && mainViewport.TransformGizmo.ActiveAxis != TransformGizmoBase.Axis.None; + var transformGizmo = (Viewport as EditorGizmoViewport)?.Gizmos.Active as TransformGizmoBase; + var isUsingGizmo = transformGizmo != null && transformGizmo.ActiveAxis != TransformGizmoBase.Axis.None; // Get current view properties - float yaw = Viewport.Yaw; - float pitch = Viewport.Pitch; + var yaw = Viewport.Yaw; + var pitch = Viewport.Pitch; var position = Viewport.ViewPosition; var rotation = Viewport.ViewOrientation; @@ -219,7 +270,7 @@ namespace FlaxEditor.Viewport.Cameras position += forward * (Viewport.MouseWheelZoomSpeedFactor * input.MouseWheelDelta * 25.0f); if (input.IsAltDown) { - position += forward * (Viewport.MouseSpeed * 40 * Viewport.MouseDeltaRight.ValuesSum); + position += forward * (Viewport.MouseSpeed * 40 * Viewport.MousePositionDelta.ValuesSum); } } @@ -227,7 +278,7 @@ namespace FlaxEditor.Viewport.Cameras if (input.IsOrbiting && isUsingGizmo) { centerMouse = false; - Viewport.ViewPosition += mainViewport.TransformGizmo.LastDelta.Translation; + Viewport.ViewPosition += transformGizmo.LastDelta.Translation; return; } @@ -236,7 +287,7 @@ namespace FlaxEditor.Viewport.Cameras Viewport.Pitch = pitch; if (input.IsOrbiting) { - float orbitRadius = Vector3.Distance(ref position, ref TargetPoint); + float orbitRadius = Mathf.Max(Vector3.Distance(ref position, ref TargetPoint), 0.0001f); Vector3 localPosition = Viewport.ViewDirection * (-1 * orbitRadius); Viewport.ViewPosition = TargetPoint + localPosition; } diff --git a/Source/Editor/Viewport/Cameras/ViewportCamera.cs b/Source/Editor/Viewport/Cameras/ViewportCamera.cs index 85b7a917c..6564d16d0 100644 --- a/Source/Editor/Viewport/Cameras/ViewportCamera.cs +++ b/Source/Editor/Viewport/Cameras/ViewportCamera.cs @@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport.Cameras /// The view rotation. /// The orbit center location. /// The orbit radius. - public void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) + public virtual void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) { // Rotate Viewport.ViewOrientation = orientation; diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index ae2db1e9b..93b2c7fbe 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport public bool SnapToGround => Editor.Instance.Options.Options.Input.SnapToGround.Process(Root); /// - public Vector2 MouseDelta => _mouseDeltaLeft * 1000; + public Vector2 MouseDelta => _mouseDelta * 1000; /// public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 5aeba8b28..2dee43614 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -139,7 +139,7 @@ namespace FlaxEditor.Viewport private bool _isControllingMouse; private int _deltaFilteringStep; private Vector2 _startPos; - private Vector2 _mouseDeltaRightLast; + private Vector2 _mouseDeltaLast; private Vector2[] _deltaFilteringBuffer = new Vector2[FpsCameraFilteringFrames]; /// @@ -158,14 +158,9 @@ namespace FlaxEditor.Viewport protected Vector2 _viewMousePos; /// - /// The mouse delta (right button down). + /// The mouse position delta. /// - protected Vector2 _mouseDeltaRight; - - /// - /// The mouse delta (left button down). - /// - protected Vector2 _mouseDeltaLeft; + protected Vector2 _mouseDelta; // Camera @@ -212,14 +207,9 @@ namespace FlaxEditor.Viewport } /// - /// Gets the mouse movement delta for the right button (user press and move). + /// Gets the mouse movement position delta (user press and move). /// - public Vector2 MouseDeltaRight => _mouseDeltaRight; - - /// - /// Gets the mouse movement delta for the left button (user press and move). - /// - public Vector2 MouseDeltaLeft => _mouseDeltaLeft; + public Vector2 MousePositionDelta => _mouseDelta; /// /// Camera's pitch angle clamp range (in degrees). @@ -453,7 +443,7 @@ namespace FlaxEditor.Viewport // Camera speed widget var camSpeed = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var camSpeedCM = new ContextMenu(); - var camSpeedButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.ArrowRightBorder16, camSpeedCM) + var camSpeedButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.CamSpeed32, camSpeedCM) { Tag = this, TooltipText = "Camera speed scale" @@ -494,7 +484,7 @@ namespace FlaxEditor.Viewport // View Flags { var viewFlags = ViewWidgetButtonMenu.AddChildMenu("View Flags").ContextMenu; - viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate16; + viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddSeparator(); for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++) { @@ -538,21 +528,30 @@ namespace FlaxEditor.Viewport { _isOrtho = checkBox.Checked; ViewWidgetButtonMenu.Hide(); + if (_isOrtho) + { + var orient = ViewOrientation; + OrientViewport(ref orient); + } } }; ViewWidgetButtonMenu.VisibleChanged += control => orthoValue.Checked = _isOrtho; } - // Cara Orientation + // Camera Viewpoints { - var cameraView = ViewWidgetButtonMenu.AddChildMenu("Orientation").ContextMenu; - for (int i = 0; i < EditorViewportCameraOrientationValues.Length; i++) + var cameraView = ViewWidgetButtonMenu.AddChildMenu("Viewpoints").ContextMenu; + for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++) { - var co = EditorViewportCameraOrientationValues[i]; + var co = EditorViewportCameraViewpointValues[i]; var button = cameraView.AddButton(co.Name); button.Tag = co.Orientation; } - cameraView.ButtonClicked += button => ViewOrientation = Quaternion.Euler((Vector3)button.Tag); + cameraView.ButtonClicked += button => + { + var orient = Quaternion.Euler((Vector3)button.Tag); + OrientViewport(ref orient); + }; } // Field of View @@ -654,6 +653,23 @@ namespace FlaxEditor.Viewport task.Begin += OnRenderBegin; } + /// + /// Orients the viewport. + /// + /// The orientation. + protected virtual void OrientViewport(ref Quaternion orientation) + { + if (ViewportCamera is FPSCamera fpsCamera) + { + var pos = Vector3.Zero + Vector3.Backward * orientation * 2000.0f; + fpsCamera.MoveViewport(pos, orientation); + } + else + { + ViewportCamera.SetArcBallView(orientation, Vector3.Zero, 2000.0f); + } + } + private void OnEditorOptionsChanged(EditorOptions options) { _mouseSensitivity = options.Viewport.MouseSensitivity; @@ -852,9 +868,14 @@ namespace FlaxEditor.Viewport win.StartTrackingMouse(false); win.Cursor = CursorType.Hidden; - // Center mouse position - //_viewMousePos = Center; - //win.MousePosition = PointToWindow(_viewMousePos); + // Center mouse position if it's too close to the edge + var size = Size; + var center = size * 0.5f; + if (Mathf.Abs(_viewMousePos.X - center.X) > center.X * 0.8f || Mathf.Abs(_viewMousePos.Y - center.Y) > center.Y * 0.8f) + { + _viewMousePos = center; + win.MousePosition = PointToWindow(_viewMousePos); + } } /// @@ -953,7 +974,8 @@ namespace FlaxEditor.Viewport // Get input buttons and keys (skip if viewport has no focus or mouse is over a child control) bool useMouse = Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height); _prevInput = _input; - if (ContainsFocus && GetChildAt(_viewMousePos, c => c.Visible) == null) + var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl)); + if (ContainsFocus && hit == null) _input.Gather(win.Window, useMouse); else _input.Clear(); @@ -991,9 +1013,11 @@ namespace FlaxEditor.Viewport // Check if update mouse Vector2 size = Size; - var options = Editor.Instance.Options.Options.Input; + var options = Editor.Instance.Options.Options; if (_isControllingMouse) { + var rmbWheel = false; + // Gather input { bool isAltDown = _input.IsAltDown; @@ -1009,10 +1033,11 @@ namespace FlaxEditor.Viewport _input.IsOrbiting = isAltDown && lbDown && !mbDown && !rbDown; // Control move speed with RMB+Wheel - if (useMovementSpeed && _input.IsMouseRightDown && wheelInUse) + rmbWheel = useMovementSpeed && _input.IsMouseRightDown && wheelInUse; + if (rmbWheel) { float step = 4.0f; - _wheelMovementChangeDeltaSum += _input.MouseWheelDelta; + _wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity; int camValueIndex = -1; for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) { @@ -1040,27 +1065,27 @@ namespace FlaxEditor.Viewport // Get input movement Vector3 moveDelta = Vector3.Zero; - if (win.GetKey(options.Forward.Key)) + if (win.GetKey(options.Input.Forward.Key)) { moveDelta += Vector3.Forward; } - if (win.GetKey(options.Backward.Key)) + if (win.GetKey(options.Input.Backward.Key)) { moveDelta += Vector3.Backward; } - if (win.GetKey(options.Right.Key)) + if (win.GetKey(options.Input.Right.Key)) { moveDelta += Vector3.Right; } - if (win.GetKey(options.Left.Key)) + if (win.GetKey(options.Input.Left.Key)) { moveDelta += Vector3.Left; } - if (win.GetKey(options.Up.Key)) + if (win.GetKey(options.Input.Up.Key)) { moveDelta += Vector3.Up; } - if (win.GetKey(options.Down.Key)) + if (win.GetKey(options.Input.Down.Key)) { moveDelta += Vector3.Down; } @@ -1074,23 +1099,21 @@ namespace FlaxEditor.Viewport moveDelta *= 0.3f; // Calculate smooth mouse delta not dependant on viewport size - Vector2 offset = _viewMousePos - _startPos; - if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown) + if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel) { offset = Vector2.Zero; } - offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); - _mouseDeltaRight = offset / size; - _mouseDeltaRight.Y *= size.Y / size.X; + _mouseDelta = offset / size; + _mouseDelta.Y *= size.Y / size.X; Vector2 mouseDelta = Vector2.Zero; if (_useMouseFiltering) { // Update delta filtering buffer - _deltaFilteringBuffer[_deltaFilteringStep] = _mouseDeltaRight; + _deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta; _deltaFilteringStep++; // If the step is too far, zero @@ -1104,14 +1127,16 @@ namespace FlaxEditor.Viewport mouseDelta /= FpsCameraFilteringFrames; } else - mouseDelta = _mouseDeltaRight; + { + mouseDelta = _mouseDelta; + } if (_useMouseAcceleration) { // Accelerate the delta var currentDelta = mouseDelta; - mouseDelta += _mouseDeltaRightLast * _mouseAccelerationScale; - _mouseDeltaRightLast = currentDelta; + mouseDelta += _mouseDeltaLast * _mouseAccelerationScale; + _mouseDeltaLast = currentDelta; } // Update @@ -1125,10 +1150,31 @@ namespace FlaxEditor.Viewport Vector2 center = PointToWindow(_startPos); win.MousePosition = center; } + + // Change Ortho size on mouse scroll + if (_isOrtho && !rmbWheel) + { + var scroll = _input.MouseWheelDelta; + if (scroll > Mathf.Epsilon || scroll < -Mathf.Epsilon) + _orthoSize -= scroll * options.Viewport.MouseWheelSensitivity * 0.2f * _orthoSize; + } } else { - _mouseDeltaRight = _mouseDeltaRightLast = Vector2.Zero; + if (_input.IsMouseLeftDown || _input.IsMouseRightDown) + { + // Calculate smooth mouse delta not dependant on viewport size + Vector2 offset = _viewMousePos - _startPos; + offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); + offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); + _mouseDelta = offset / size; + _startPos = _viewMousePos; + } + else + { + _mouseDelta = Vector2.Zero; + } + _mouseDeltaLast = Vector2.Zero; if (ContainsFocus) { @@ -1165,19 +1211,6 @@ namespace FlaxEditor.Viewport UpdateView(dt, ref moveDelta, ref mouseDelta, out _); } } - if (_input.IsMouseLeftDown) - { - // Calculate smooth mouse delta not dependant on viewport size - Vector2 offset = _viewMousePos - _startPos; - offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); - offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); - _mouseDeltaLeft = offset / size; - _startPos = _viewMousePos; - } - else - { - _mouseDeltaLeft = Vector2.Zero; - } _input.MouseWheelDelta = 0; } @@ -1260,26 +1293,26 @@ namespace FlaxEditor.Viewport base.OnDestroy(); } - private struct CameraOrientation + private struct CameraViewpoint { public readonly string Name; public readonly Vector3 Orientation; - public CameraOrientation(string name, Vector3 orientation) + public CameraViewpoint(string name, Vector3 orientation) { Name = name; Orientation = orientation; } } - private readonly CameraOrientation[] EditorViewportCameraOrientationValues = + private readonly CameraViewpoint[] EditorViewportCameraViewpointValues = { - new CameraOrientation("Front", new Vector3(0, 0, 0)), - new CameraOrientation("Back", new Vector3(0, 180, 0)), - new CameraOrientation("Left", new Vector3(0, -90, 0)), - new CameraOrientation("Right", new Vector3(0, 90, 0)), - new CameraOrientation("Top", new Vector3(90, 0, 0)), - new CameraOrientation("Bottom", new Vector3(-90, 0, 0)) + new CameraViewpoint("Front", new Vector3(0, 180, 0)), + new CameraViewpoint("Back", new Vector3(0, 0, 0)), + new CameraViewpoint("Left", new Vector3(0, 90, 0)), + new CameraViewpoint("Right", new Vector3(0, -90, 0)), + new CameraViewpoint("Top", new Vector3(90, 0, 0)), + new CameraViewpoint("Bottom", new Vector3(-90, 0, 0)) }; private readonly float[] EditorViewportCameraSpeedValues = diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index c734b3127..0c895640d 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -9,6 +9,7 @@ using FlaxEditor.GUI.Drag; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.Scripting; +using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Widgets; using FlaxEditor.Windows; using FlaxEngine; @@ -164,6 +165,11 @@ namespace FlaxEditor.Viewport /// public bool DrawDebugDraw = true; + /// + /// Gets the debug draw data for the viewport. + /// + public ViewportDebugDrawData DebugDrawData => _debugDrawData; + /// /// Gets or sets a value indicating whether show navigation mesh. /// @@ -218,7 +224,7 @@ namespace FlaxEditor.Viewport // Transform space widget var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.World16, null, true) + var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, TooltipText = "Gizmo transform space (world or local)", @@ -229,7 +235,7 @@ namespace FlaxEditor.Viewport // Scale snapping widget var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleStep16, null, true) + var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true) { Checked = TransformGizmo.ScaleSnapEnabled, TooltipText = "Enable scale snapping", @@ -256,7 +262,7 @@ namespace FlaxEditor.Viewport // Rotation snapping widget var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateStep16, null, true) + var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true) { Checked = TransformGizmo.RotationSnapEnabled, TooltipText = "Enable rotation snapping", @@ -283,7 +289,7 @@ namespace FlaxEditor.Viewport // Translation snapping widget var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid16, null, true) + var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true) { Checked = TransformGizmo.TranslationSnapEnable, TooltipText = "Enable position snapping", @@ -310,7 +316,7 @@ namespace FlaxEditor.Viewport // Gizmo mode widget var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate16, null, true) + _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, TooltipText = "Translate gizmo mode", @@ -318,14 +324,14 @@ namespace FlaxEditor.Viewport Parent = gizmoMode }; _gizmoModeTranslate.Toggled += OnGizmoModeToggle; - _gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate16, null, true) + _gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, TooltipText = "Rotate gizmo mode", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; - _gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale16, null, true) + _gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, TooltipText = "Scale gizmo mode", @@ -388,10 +394,11 @@ namespace FlaxEditor.Viewport return; // Create actor + var parent = Level.GetScene(0); var actor = new Camera { StaticFlags = StaticFlags.None, - Name = "Camera", + Name = StringUtils.IncrementNameNumber("Camera", x => parent.GetChild(x) == null), Transform = ViewTransform, NearPlane = NearPlane, FarPlane = FarPlane, @@ -401,7 +408,7 @@ namespace FlaxEditor.Viewport }; // Spawn - Editor.Instance.SceneEditing.Spawn(actor); + Editor.Instance.SceneEditing.Spawn(actor, parent); } private void OnBegin(RenderTask task, GPUContext context) @@ -698,6 +705,17 @@ namespace FlaxEditor.Viewport } } + /// + protected override void OrientViewport(ref Quaternion orientation) + { + if (TransformGizmo.SelectedParents.Count != 0) + { + ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); + } + + base.OrientViewport(ref orientation); + } + /// protected override void OnLeftMouseButtonUp() { @@ -797,6 +815,8 @@ namespace FlaxEditor.Viewport return true; if (assetItem.IsOfType()) return true; + if (assetItem.IsOfType()) + return true; if (assetItem.IsOfType()) return true; if (assetItem.IsOfType()) @@ -860,6 +880,15 @@ namespace FlaxEditor.Viewport return location; } + private void Spawn(Actor actor, ref Vector3 hitLocation) + { + actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); + var parent = actor.Parent ?? Level.GetScene(0); + actor.Name = StringUtils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null); + Editor.Instance.SceneEditing.Spawn(actor); + Focus(); + } + private void Spawn(AssetItem item, SceneGraphNode hit, ref Vector2 location, ref Vector3 hitLocation) { if (item is AssetItem assetItem) @@ -872,8 +901,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, ParticleSystem = asset }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -884,8 +912,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Animation = asset }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -921,8 +948,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, SkinnedModel = model }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -933,8 +959,18 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Model = model }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); + return; + } + if (assetItem.IsOfType()) + { + var collisionData = FlaxEngine.Content.LoadAsync(item.ID); + var actor = new MeshCollider + { + Name = item.ShortName, + CollisionData = collisionData + }; + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -945,8 +981,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Clip = clip }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -954,8 +989,7 @@ namespace FlaxEditor.Viewport var prefab = FlaxEngine.Content.LoadAsync(item.ID); var actor = PrefabManager.SpawnPrefab(prefab, null); actor.Name = item.ShortName; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -967,8 +1001,7 @@ namespace FlaxEditor.Viewport { var actor = (Actor)visualScriptItem.ScriptType.CreateInstance(); actor.Name = item.ShortName; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } } @@ -983,8 +1016,7 @@ namespace FlaxEditor.Viewport return; } actor.Name = item.Name; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); } /// @@ -1042,10 +1074,12 @@ namespace FlaxEditor.Viewport DisposeModes(); _debugDrawData.Dispose(); - //Object.Destroy(ref SelectionOutline); - //Object.Destroy(ref EditorPrimitives); - //Object.Destroy(ref _editorSpritesRenderer); - //Object.Destroy(ref _customSelectionOutline); + if (_task != null) + { + // Release if task is not used to save screenshot for project icon + Object.Destroy(ref _task); + ReleaseResources(); + } base.OnDestroy(); } @@ -1062,7 +1096,6 @@ namespace FlaxEditor.Viewport _task = null; _backBuffer = null; - _editorSpritesRenderer = null; } internal void SaveProjectIconEnd() @@ -1070,9 +1103,19 @@ namespace FlaxEditor.Viewport if (_savedTask) { _savedTask.Enabled = false; + Object.Destroy(_savedTask); + ReleaseResources(); _savedTask = null; } Object.Destroy(ref _savedBackBuffer); } + + private void ReleaseResources() + { + Object.Destroy(ref SelectionOutline); + Object.Destroy(ref EditorPrimitives); + Object.Destroy(ref _editorSpritesRenderer); + Object.Destroy(ref _customSelectionOutline); + } } } diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 89c42dc95..6483700b6 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -26,6 +26,7 @@ namespace FlaxEditor.Viewport /// public class PrefabWindowViewport : PrefabPreview, IEditorPrimitivesOwner { + [HideInEditor] private sealed class PrefabSpritesRenderer : MainEditorGizmoViewport.EditorSpritesRenderer { public PrefabWindowViewport Viewport; @@ -117,7 +118,7 @@ namespace FlaxEditor.Viewport // Transform space widget var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.World16, null, true) + var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, TooltipText = "Gizmo transform space (world or local)", @@ -128,7 +129,7 @@ namespace FlaxEditor.Viewport // Scale snapping widget var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleStep16, null, true) + var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleSnap32, null, true) { Checked = TransformGizmo.ScaleSnapEnabled, TooltipText = "Enable scale snapping", @@ -155,7 +156,7 @@ namespace FlaxEditor.Viewport // Rotation snapping widget var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateStep16, null, true) + var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateSnap32, null, true) { Checked = TransformGizmo.RotationSnapEnabled, TooltipText = "Enable rotation snapping", @@ -182,7 +183,7 @@ namespace FlaxEditor.Viewport // Translation snapping widget var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid16, null, true) + var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid32, null, true) { Checked = TransformGizmo.TranslationSnapEnable, TooltipText = "Enable position snapping", @@ -209,7 +210,7 @@ namespace FlaxEditor.Viewport // Gizmo mode widget var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate16, null, true) + _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, TooltipText = "Translate gizmo mode", @@ -217,14 +218,14 @@ namespace FlaxEditor.Viewport Parent = gizmoMode }; _gizmoModeTranslate.Toggled += OnGizmoModeToggle; - _gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate16, null, true) + _gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, TooltipText = "Rotate gizmo mode", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; - _gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale16, null, true) + _gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, TooltipText = "Scale gizmo mode", @@ -342,7 +343,7 @@ namespace FlaxEditor.Viewport public bool SnapToGround => false; /// - public Vector2 MouseDelta => _mouseDeltaLeft * 1000; + public Vector2 MouseDelta => _mouseDelta * 1000; /// public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); @@ -576,7 +577,7 @@ namespace FlaxEditor.Viewport // Selected UI controls outline for (var i = 0; i < _window.Selection.Count; i++) { - if (_window.Selection[i].EditableObject is UIControl controlActor && controlActor.Control != null) + if (_window.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) { var control = controlActor.Control; var bounds = Rectangle.FromPoints(control.PointToParent(this, Vector2.Zero), control.PointToParent(this, control.Size)); @@ -682,10 +683,14 @@ namespace FlaxEditor.Viewport return true; if (assetItem.IsOfType()) return true; + if (assetItem.IsOfType()) + return true; if (assetItem.IsOfType()) return true; if (assetItem.IsOfType()) return true; + if (assetItem is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) + return true; } return false; @@ -746,8 +751,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, ParticleSystem = particleSystem }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (typeof(MaterialBase).IsAssignableFrom(binaryAssetItem.Type)) @@ -773,8 +777,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, SkinnedModel = model }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (typeof(Model).IsAssignableFrom(binaryAssetItem.Type)) @@ -785,8 +788,18 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Model = model }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); + return; + } + if (binaryAssetItem.IsOfType()) + { + var collisionData = FlaxEngine.Content.LoadAsync(item.ID); + var actor = new MeshCollider + { + Name = item.ShortName, + CollisionData = collisionData + }; + Spawn(actor, ref hitLocation); return; } if (typeof(AudioClip).IsAssignableFrom(binaryAssetItem.Type)) @@ -797,8 +810,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Clip = clip }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (typeof(Prefab).IsAssignableFrom(binaryAssetItem.Type)) @@ -806,16 +818,24 @@ namespace FlaxEditor.Viewport var prefab = FlaxEngine.Content.LoadAsync(item.ID); var actor = PrefabManager.SpawnPrefab(prefab, null); actor.Name = item.ShortName; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); return; } } + if (item is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) + { + var actor = (Actor)visualScriptItem.ScriptType.CreateInstance(); + actor.Name = item.ShortName; + Spawn(actor, ref hitLocation); + return; + } } - private void Spawn(Actor actor) + private void Spawn(Actor actor, ref Vector3 hitLocation) { + actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); _window.Spawn(actor); + Focus(); } private void Spawn(ScriptType item, SceneGraphNode hit, ref Vector3 hitLocation) @@ -827,8 +847,18 @@ namespace FlaxEditor.Viewport return; } actor.Name = item.Name; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); + } + + /// + protected override void OrientViewport(ref Quaternion orientation) + { + if (TransformGizmo.SelectedParents.Count != 0) + { + ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); + } + + base.OrientViewport(ref orientation); } /// diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 6f6103a5d..697a7c1c1 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -249,6 +249,19 @@ namespace FlaxEditor.Viewport.Previews } } + /// + public override bool OnKeyDown(KeyboardKeys key) + { + switch (key) + { + case KeyboardKeys.F: + // Pay respect.. + ViewportCamera.SetArcBallView(_previewModel.Box); + break; + } + return base.OnKeyDown(key); + } + /// public override void OnDestroy() { diff --git a/Source/Editor/Viewport/Previews/ModelBasePreview.cs b/Source/Editor/Viewport/Previews/ModelBasePreview.cs new file mode 100644 index 000000000..59ed5f318 --- /dev/null +++ b/Source/Editor/Viewport/Previews/ModelBasePreview.cs @@ -0,0 +1,122 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEditor.GUI.Input; +using FlaxEngine; +using Object = FlaxEngine.Object; + +namespace FlaxEditor.Viewport.Previews +{ + /// + /// Model Base asset preview editor viewport. + /// + /// + public class ModelBasePreview : AssetPreview + { + /// + /// Gets or sets the model asset to preview. + /// + public ModelBase Asset + { + get => (ModelBase)StaticModel.Model ?? AnimatedModel.SkinnedModel; + set + { + StaticModel.Model = value as Model; + AnimatedModel.SkinnedModel = value as SkinnedModel; + } + } + + /// + /// The static model for display. + /// + public StaticModel StaticModel; + + /// + /// The animated model for display. + /// + public AnimatedModel AnimatedModel; + + /// + /// Gets or sets a value indicating whether scale the model to the normalized bounds. + /// + public bool ScaleToFit { get; set; } = true; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true use widgets. + public ModelBasePreview(bool useWidgets) + : base(useWidgets) + { + Task.Begin += OnBegin; + + // Setup preview scene + StaticModel = new StaticModel(); + AnimatedModel = new AnimatedModel(); + + // Link actors for rendering + Task.AddCustomActor(StaticModel); + Task.AddCustomActor(AnimatedModel); + + if (useWidgets) + { + // Preview LOD + { + var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); + var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f) + { + Parent = previewLOD + }; + previewLODValue.ValueChanged += () => + { + StaticModel.ForcedLOD = previewLODValue.Value; + AnimatedModel.ForcedLOD = previewLODValue.Value; + }; + ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = StaticModel.ForcedLOD; + } + } + } + + private void OnBegin(RenderTask task, GPUContext context) + { + var position = Vector3.Zero; + var scale = Vector3.One; + + // Update preview model scale to fit the preview + var model = Asset; + if (ScaleToFit && model && model.IsLoaded) + { + float targetSize = 50.0f; + BoundingBox box = model is Model ? ((Model)model).GetBox() : ((SkinnedModel)model).GetBox(); + float maxSize = Mathf.Max(0.001f, box.Size.MaxValue); + scale = new Vector3(targetSize / maxSize); + position = box.Center * (-0.5f * scale.X) + new Vector3(0, -10, 0); + } + + StaticModel.Transform = new Transform(position, StaticModel.Orientation, scale); + AnimatedModel.Transform = new Transform(position, AnimatedModel.Orientation, scale); + } + + /// + public override bool OnKeyDown(KeyboardKeys key) + { + switch (key) + { + case KeyboardKeys.F: + // Pay respect.. + ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box); + break; + } + return base.OnKeyDown(key); + } + + /// + public override void OnDestroy() + { + // Ensure to cleanup created actor objects + Object.Destroy(ref StaticModel); + Object.Destroy(ref AnimatedModel); + + base.OnDestroy(); + } + } +} diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index 7055edbe9..3f285ddcd 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -85,6 +85,19 @@ namespace FlaxEditor.Viewport.Previews } } + /// + public override bool OnKeyDown(KeyboardKeys key) + { + switch (key) + { + case KeyboardKeys.F: + // Pay respect.. + ViewportCamera.SetArcBallView(_previewModel.Box); + break; + } + return base.OnKeyDown(key); + } + /// public override void OnDestroy() { diff --git a/Source/Editor/Viewport/Previews/PrefabPreview.cs b/Source/Editor/Viewport/Previews/PrefabPreview.cs index 066e98456..09ff34d0d 100644 --- a/Source/Editor/Viewport/Previews/PrefabPreview.cs +++ b/Source/Editor/Viewport/Previews/PrefabPreview.cs @@ -121,6 +121,18 @@ namespace FlaxEditor.Viewport.Previews { } + /// + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + if (_instance != null) + { + // Link UI canvases to the preview (eg. after canvas added to the prefab) + LinkCanvas(_instance); + } + } + /// public override void OnDestroy() { diff --git a/Source/Editor/Viewport/Previews/TexturePreview.cs b/Source/Editor/Viewport/Previews/TexturePreview.cs index 5e1024609..62e19430a 100644 --- a/Source/Editor/Viewport/Previews/TexturePreview.cs +++ b/Source/Editor/Viewport/Previews/TexturePreview.cs @@ -13,6 +13,7 @@ namespace FlaxEditor.Viewport.Previews /// Base class for texture previews. Draws a surface in the UI and supports view moving/zooming. /// /// + [HideInEditor] public abstract class TexturePreviewBase : ContainerControl { private Rectangle _textureRect; diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index bb320fb87..5e43b408b 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -34,7 +34,7 @@ namespace FlaxEditor.Windows { Image icon = new Image(4, 4, 80, 80) { - Brush = new SpriteBrush(Editor.Instance.Icons.Logo128), + Brush = new SpriteBrush(Editor.Instance.Icons.FlaxLogo128), Parent = this }; var nameLabel = new Label(icon.Right + 10, icon.Top, 200, 34) @@ -131,6 +131,7 @@ namespace FlaxEditor.Windows "LZ4 Library - Copyright (c) Yann Collet. All rights reserved.", "fmt - www.fmtlib.net", "minimp3 - www.github.com/lieff/minimp3", + "Tracy Profiler - www.github.com/wolfpld/tracy", "Ogg and Vorbis - Xiph.org Foundation", "OpenAL Soft - www.github.com/kcat/openal-soft", "OpenFBX - www.github.com/nem0/OpenFBX", diff --git a/Source/Editor/Windows/Assets/AnimationGraphFunctionWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphFunctionWindow.cs index 439f61c9f..4be79d36d 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphFunctionWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphFunctionWindow.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more"); // Navigation bar _navigationBar = new NavigationBar diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index f25b51612..634202061 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -251,9 +251,9 @@ namespace FlaxEditor.Windows.Assets _surface.ContextChanged += OnSurfaceContextChanged; // Toolstrip - _toolstrip.AddButton(editor.Icons.Bone32, () => _preview.ShowNodes = !_preview.ShowNodes).SetAutoCheck(true).LinkTooltip("Show animated model nodes debug view"); + _toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).SetAutoCheck(true).LinkTooltip("Show animated model nodes debug view"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/anim-graph/index.html")).LinkTooltip("See documentation to learn more"); // Debug picker var debugPickerContainer = new ContainerControl(); diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index fde6ec3cb..5665a87ce 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -150,12 +150,12 @@ namespace FlaxEditor.Windows.Assets _propertiesPresenter.Modified += MarkAsEdited; // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/animation/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/animation/index.html")).LinkTooltip("See documentation to learn more"); // Setup input actions InputActions.Add(options => options.Undo, _undo.PerformUndo); diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs index 8ad655d01..94b78db7e 100644 --- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs +++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs @@ -53,7 +53,7 @@ namespace FlaxEditor.Windows.Assets { Parent = this }; - _toolstrip.AddButton(editor.Icons.Find32, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window"); + _toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window"); InputActions.Add(options => options.Save, Save); diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs index 1d6850200..cce6985dc 100644 --- a/Source/Editor/Windows/Assets/AudioClipWindow.cs +++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs @@ -152,12 +152,12 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.Select(_properties); // Toolstrip - _toolstrip.AddButton(Editor.Icons.Import32, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); + _toolstrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); _toolstrip.AddSeparator(); - _playButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Play32, OnPlay).LinkTooltip("Play/stop audio"); - _pauseButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Pause32, OnPause).LinkTooltip("Pause audio"); + _playButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Play64, OnPlay).LinkTooltip("Play/stop audio"); + _pauseButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Pause64, OnPause).LinkTooltip("Pause audio"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/audio/audio-clip.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/audio/audio-clip.html")).LinkTooltip("See documentation to learn more"); } private void OnPlay() @@ -244,7 +244,7 @@ namespace FlaxEditor.Windows.Assets { base.UpdateToolstrip(); - _playButton.Icon = _previewSource && _previewSource.State == AudioSource.States.Playing ? Editor.Icons.Stop32 : Editor.Icons.Play32; + _playButton.Icon = _previewSource && _previewSource.State == AudioSource.States.Playing ? Editor.Icons.Stop64 : Editor.Icons.Play64; _pauseButton.Enabled = _previewSource && _previewSource.State == AudioSource.States.Playing; } diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs index 23f9ae5ed..f532ab11f 100644 --- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs +++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; using System.Xml; using FlaxEditor.Content; using FlaxEditor.Content.Create; @@ -10,6 +11,7 @@ using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Previews; using FlaxEngine; using FlaxEngine.GUI; +using Object = FlaxEngine.Object; namespace FlaxEditor.Windows.Assets { @@ -20,6 +22,15 @@ namespace FlaxEditor.Windows.Assets /// public sealed class CollisionDataWindow : AssetEditorWindowBase { + [Flags] + private enum MaterialSlotsMask : uint + { + // @formatter:off + Slot0=1u<<0,Slot1=1u<<1,Slot2=1u<<2,Slot3=1u<<3,Slot4=1u<<4,Slot5=1u<<5,Slot6=1u<<6,Slot7=1u<<7,Slot8=1u<<8,Slot9=1u<<9,Slot10=1u<<10,Slot11=1u<<11,Slot12=1u<<12,Slot13=1u<<13,Slot14=1u<<14,Slot15=1u<<15,Slot16=1u<<16,Slot17=1u<<17,Slot18=1u<<18,Slot19=1u<<19,Slot20=1u<<20,Slot21=1u<<21,Slot22=1u<<22,Slot23=1u<<23,Slot24=1u<<24,Slot25=1u<<25,Slot26=1u<<26,Slot27=1u<<27,Slot28=1u<<28,Slot29=1u<<29,Slot30=1u<<30,Slot31=1u<<31, + // @formatter:on + All = uint.MaxValue, + } + /// /// The asset properties proxy object. /// @@ -34,11 +45,14 @@ namespace FlaxEditor.Windows.Assets public CollisionDataType Type; [EditorOrder(10), EditorDisplay("General"), Tooltip("Source model asset to use for collision data generation")] - public Model Model; + public ModelBase Model; [EditorOrder(20), Limit(0, 5), EditorDisplay("General", "Model LOD Index"), Tooltip("Source model LOD index to use for collision data generation (will be clamped to the actual model LODs collection size)")] public int ModelLodIndex; + [EditorOrder(30), EditorDisplay("General"), Tooltip("The source model material slots mask. One bit per-slot. Can be sued to exclude particular material slots from collision cooking.")] + public MaterialSlotsMask MaterialSlotsMask = MaterialSlotsMask.All; + [EditorOrder(100), EditorDisplay("Convex Mesh", "Convex Flags"), Tooltip("Convex mesh generation flags")] public ConvexMeshGenerationFlags ConvexFlags; @@ -88,28 +102,23 @@ namespace FlaxEditor.Windows.Assets private class CookData : CreateFileEntry { - private PropertiesProxy Proxy; - private CollisionDataType Type; - private Model Model; - private int ModelLodIndex; - private ConvexMeshGenerationFlags ConvexFlags; - private int ConvexVertexLimit; + public PropertiesProxy Proxy; + public CollisionDataType Type; + public ModelBase Model; + public int ModelLodIndex; + public uint MaterialSlotsMask; + public ConvexMeshGenerationFlags ConvexFlags; + public int ConvexVertexLimit; - public CookData(PropertiesProxy proxy, string resultUrl, CollisionDataType type, Model model, int modelLodIndex, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit) + public CookData(string resultUrl) : base("Collision Data", resultUrl) { - Proxy = proxy; - Type = type; - Model = model; - ModelLodIndex = modelLodIndex; - ConvexFlags = convexFlags; - ConvexVertexLimit = convexVertexLimit; } /// public override bool Create() { - bool failed = FlaxEditor.Editor.CookMeshCollision(ResultUrl, Type, Model, ModelLodIndex, ConvexFlags, ConvexVertexLimit); + bool failed = FlaxEditor.Editor.CookMeshCollision(ResultUrl, Type, Model, ModelLodIndex, MaterialSlotsMask, ConvexFlags, ConvexVertexLimit); Proxy._isCooking = false; Proxy.Window.UpdateWiresModel(); @@ -121,7 +130,16 @@ namespace FlaxEditor.Windows.Assets public void Cook() { _isCooking = true; - Window.Editor.ContentImporting.Create(new CookData(this, Asset.Path, Type, Model, ModelLodIndex, ConvexFlags, ConvexVertexLimit)); + Window.Editor.ContentImporting.Create(new CookData(Asset.Path) + { + Proxy = this, + Type = Type, + Model = Model, + ModelLodIndex = ModelLodIndex, + MaterialSlotsMask = (uint)MaterialSlotsMask, + ConvexFlags = ConvexFlags, + ConvexVertexLimit = ConvexVertexLimit, + }); } public void OnLoad(CollisionDataWindow window) @@ -135,7 +153,7 @@ namespace FlaxEditor.Windows.Assets Type = options.Type; if (Type == CollisionDataType.None) Type = CollisionDataType.ConvexMesh; - Model = FlaxEngine.Content.LoadAsync(options.Model); + Model = FlaxEngine.Content.LoadAsync(options.Model); ModelLodIndex = options.ModelLodIndex; ConvexFlags = options.ConvexFlags; ConvexVertexLimit = options.ConvexVertexLimit; @@ -151,7 +169,7 @@ namespace FlaxEditor.Windows.Assets } private readonly SplitPanel _split; - private readonly ModelPreview _preview; + private readonly ModelBasePreview _preview; private readonly CustomEditorPresenter _propertiesPresenter; private readonly PropertiesProxy _properties; private Model _collisionWiresModel; @@ -164,7 +182,7 @@ namespace FlaxEditor.Windows.Assets { // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more"); // Split Panel _split = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.Vertical) @@ -176,7 +194,7 @@ namespace FlaxEditor.Windows.Assets }; // Model preview - _preview = new ModelPreview(true) + _preview = new ModelBasePreview(true) { ViewportCamera = new FPSCamera(), Parent = _split.Panel1 @@ -195,7 +213,7 @@ namespace FlaxEditor.Windows.Assets // Sync helper actor size with actual preview model (preview scales model for better usage experience) if (_collisionWiresShowActor && _collisionWiresShowActor.IsActive) { - _collisionWiresShowActor.Transform = _preview.PreviewActor.Transform; + _collisionWiresShowActor.Transform = _preview.StaticModel.Transform; } base.Update(deltaTime); @@ -230,14 +248,14 @@ namespace FlaxEditor.Windows.Assets } _collisionWiresShowActor.Model = _collisionWiresModel; _collisionWiresShowActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WiresDebugMaterial)); - _preview.Model = FlaxEngine.Content.LoadAsync(_asset.Options.Model); + _preview.Asset = FlaxEngine.Content.LoadAsync(_asset.Options.Model); } /// protected override void UnlinkItem() { _properties.OnClean(); - _preview.Model = null; + _preview.Asset = null; base.UnlinkItem(); } @@ -245,7 +263,7 @@ namespace FlaxEditor.Windows.Assets /// protected override void OnAssetLinked() { - _preview.Model = null; + _preview.Asset = null; base.OnAssetLinked(); } diff --git a/Source/Editor/Windows/Assets/CubeTextureWindow.cs b/Source/Editor/Windows/Assets/CubeTextureWindow.cs index 641441de7..7850f8960 100644 --- a/Source/Editor/Windows/Assets/CubeTextureWindow.cs +++ b/Source/Editor/Windows/Assets/CubeTextureWindow.cs @@ -140,9 +140,9 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.Select(_properties); // Toolstrip - _toolstrip.AddButton(Editor.Icons.Import32, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); + _toolstrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/textures/cube-textures.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/textures/cube-textures.html")).LinkTooltip("See documentation to learn more"); } /// diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index e33076606..63201ea9e 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -111,7 +111,7 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.Select(_proxy); // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save"); } private void OnTextChanged() diff --git a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs index f185e3eec..cca17e825 100644 --- a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs +++ b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs @@ -407,10 +407,10 @@ namespace FlaxEditor.Windows.Assets _proxy = new PropertiesProxy(); _propertiesEditor.Select(_proxy); - _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save32, Save).LinkTooltip("Save asset"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save asset"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); _resetButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Rotate32, Reset).LinkTooltip("Resets the variables values to the default values"); diff --git a/Source/Editor/Windows/Assets/IESProfileWindow.cs b/Source/Editor/Windows/Assets/IESProfileWindow.cs index 81ea9b496..c501a673d 100644 --- a/Source/Editor/Windows/Assets/IESProfileWindow.cs +++ b/Source/Editor/Windows/Assets/IESProfileWindow.cs @@ -27,9 +27,9 @@ namespace FlaxEditor.Windows.Assets }; // Toolstrip - _toolstrip.AddButton(editor.Icons.Import32, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); + _toolstrip.AddButton(editor.Icons.Import64, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.PageScale32, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); } /// diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index da463c501..c48125013 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -17,14 +17,26 @@ namespace FlaxEditor.Windows.Assets { private readonly CustomEditorPresenter _presenter; private readonly ToolStripButton _saveButton; + private readonly ToolStripButton _undoButton; + private readonly ToolStripButton _redoButton; + private readonly Undo _undo; private object _object; + private bool _isRegisteredForScriptsReload; /// public JsonAssetWindow(Editor editor, AssetItem item) : base(editor, item) { + // Undo + _undo = new Undo(); + _undo.UndoDone += OnUndoRedo; + _undo.RedoDone += OnUndoRedo; + // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save"); + _toolstrip.AddSeparator(); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); // Panel var panel = new Panel(ScrollBars.Vertical) @@ -35,15 +47,24 @@ namespace FlaxEditor.Windows.Assets }; // Properties - _presenter = new CustomEditorPresenter(null, "Loading..."); + _presenter = new CustomEditorPresenter(_undo, "Loading..."); _presenter.Panel.Parent = panel; _presenter.Modified += MarkAsEdited; + + // Setup input actions + InputActions.Add(options => options.Undo, _undo.PerformUndo); + InputActions.Add(options => options.Redo, _undo.PerformRedo); + } + + private void OnUndoRedo(IUndoAction action) + { + MarkAsEdited(); + UpdateToolstrip(); } private void OnScriptsReloadBegin() { Close(); - ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; } /// @@ -64,13 +85,14 @@ namespace FlaxEditor.Windows.Assets } ClearEditedFlag(); - _item.RefreshThumbnail(); } /// protected override void UpdateToolstrip() { _saveButton.Enabled = IsEdited; + _undoButton.Enabled = _undo.CanUndo; + _redoButton.Enabled = _undo.CanRedo; base.UpdateToolstrip(); } @@ -80,11 +102,15 @@ namespace FlaxEditor.Windows.Assets { _object = Asset.CreateInstance(); _presenter.Select(_object); + _undo.Clear(); ClearEditedFlag(); // Auto-close on scripting reload if json asset is from game scripts (it might be reloaded) - if (_object != null && FlaxEngine.Scripting.IsTypeFromGameScripts(_object.GetType())) + if (_object != null && FlaxEngine.Scripting.IsTypeFromGameScripts(_object.GetType()) && !_isRegisteredForScriptsReload) + { + _isRegisteredForScriptsReload = true; ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } base.OnAssetLoaded(); } @@ -98,5 +124,17 @@ namespace FlaxEditor.Windows.Assets base.OnItemReimported(item); } + + /// + public override void OnDestroy() + { + if (_isRegisteredForScriptsReload) + { + _isRegisteredForScriptsReload = false; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + } + + base.OnDestroy(); + } } } diff --git a/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs b/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs new file mode 100644 index 000000000..633df8197 --- /dev/null +++ b/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs @@ -0,0 +1,212 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using FlaxEditor.Content; +using FlaxEditor.CustomEditors; +using FlaxEditor.GUI; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Utilities; + +namespace FlaxEditor.Windows.Assets +{ + /// + /// Editor window to view/modify asset. + /// + /// + /// + public sealed class LocalizedStringTableWindow : AssetEditorWindowBase + { + private readonly CustomEditorPresenter _presenter; + private readonly ToolStripButton _saveButton; + private readonly ToolStripButton _undoButton; + private readonly ToolStripButton _redoButton; + private readonly Undo _undo; + private Proxy _proxy; + + private class EntryEditor : CustomEditor + { + private TextBox[] _textBoxes; + private bool _isRefreshing; + + public override void Initialize(LayoutElementsContainer layout) + { + var values = (string[])Values[0]; + if (values == null || values.Length == 0) + { + values = new string[1]; + values[0] = string.Empty; + } + if (_textBoxes == null || _textBoxes.Length != values.Length) + _textBoxes = new TextBox[values.Length]; + for (int i = 0; i < values.Length; i++) + { + var value = values[i]; + var textBox = layout.TextBox(value.IsMultiline()); + textBox.TextBox.Tag = i; + textBox.TextBox.Text = value; + textBox.TextBox.TextBoxEditEnd += OnEditEnd; + _textBoxes[i] = textBox.TextBox; + } + } + + public override void Refresh() + { + base.Refresh(); + + var values = (string[])Values[0]; + if (values != null && values.Length == _textBoxes.Length) + { + _isRefreshing = true; + var style = FlaxEngine.GUI.Style.Current; + var wrongColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f); + var wrongColorBorder = Color.Lerp(wrongColor, style.TextBoxBackground, 0.6f); + for (int i = 0; i < _textBoxes.Length; i++) + { + var textBox = _textBoxes[i]; + if (!textBox.IsEditing) + { + textBox.Text = values[i]; + if (string.IsNullOrEmpty(textBox.Text)) + { + textBox.BorderColor = wrongColorBorder; + textBox.BorderSelectedColor = wrongColor; + } + else + { + textBox.BorderColor = Color.Transparent; + textBox.BorderSelectedColor = style.BackgroundSelected; + } + } + } + _isRefreshing = false; + } + } + + protected override void Deinitialize() + { + base.Deinitialize(); + + _textBoxes = null; + _isRefreshing = false; + } + + private void OnEditEnd(TextBoxBase textBox) + { + if (_isRefreshing) + return; + var values = (string[])Values[0]; + var length = Mathf.Max(values?.Length ?? 0, 1); + var toSet = new string[length]; + if (values != null && values.Length == length) + Array.Copy(values, toSet, length); + var index = (int)textBox.Tag; + toSet[index] = textBox.Text; + SetValue(toSet); + } + } + + private class Proxy + { + [EditorOrder(0), EditorDisplay("General"), Tooltip("The locale of the localized string table (eg. pl-PL)."),] + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.CultureInfoEditor))] + public string Locale; + + [EditorOrder(10), EditorDisplay("Entries", EditorDisplayAttribute.InlineStyle), Tooltip("The string table. Maps the message id into the localized text. For plural messages the list contains separate items for value numbers.")] + [Collection(Spacing = 10, OverrideEditorTypeName = "FlaxEditor.Windows.Assets.LocalizedStringTableWindow+EntryEditor")] + public Dictionary Entries; + } + + /// + public LocalizedStringTableWindow(Editor editor, AssetItem item) + : base(editor, item) + { + // Undo + _undo = new Undo(); + _undo.UndoDone += OnUndoRedo; + _undo.RedoDone += OnUndoRedo; + + // Toolstrip + _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save"); + _toolstrip.AddSeparator(); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + + // Panel + var panel = new Panel(ScrollBars.Vertical) + { + AnchorPreset = AnchorPresets.StretchAll, + Offsets = new Margin(0, 0, _toolstrip.Bottom, 0), + Parent = this + }; + + // Properties + _presenter = new CustomEditorPresenter(_undo, "Loading..."); + _presenter.Panel.Parent = panel; + _presenter.Modified += MarkAsEdited; + + // Setup input actions + InputActions.Add(options => options.Undo, _undo.PerformUndo); + InputActions.Add(options => options.Redo, _undo.PerformRedo); + } + + private void OnUndoRedo(IUndoAction action) + { + MarkAsEdited(); + UpdateToolstrip(); + } + + /// + public override void Save() + { + if (!IsEdited) + return; + + _asset.Locale = _proxy.Locale; + _asset.Entries = _proxy.Entries; + if (_asset.Save(_item.Path)) + { + Editor.LogError("Cannot save asset."); + return; + } + + ClearEditedFlag(); + } + + /// + protected override void UpdateToolstrip() + { + _saveButton.Enabled = IsEdited; + _undoButton.Enabled = _undo.CanUndo; + _redoButton.Enabled = _undo.CanRedo; + + base.UpdateToolstrip(); + } + + /// + protected override void OnAssetLoaded() + { + _proxy = new Proxy + { + Locale = _asset.Locale, + Entries = _asset.Entries, + }; + _presenter.Select(_proxy); + _undo.Clear(); + ClearEditedFlag(); + + base.OnAssetLoaded(); + } + + /// + public override void OnItemReimported(ContentItem item) + { + // Refresh the properties (will get new data in OnAssetLoaded) + _presenter.Deselect(); + ClearEditedFlag(); + + base.OnItemReimported(item); + } + } +} diff --git a/Source/Editor/Windows/Assets/MaterialFunctionWindow.cs b/Source/Editor/Windows/Assets/MaterialFunctionWindow.cs index 20b091a04..6b62d1689 100644 --- a/Source/Editor/Windows/Assets/MaterialFunctionWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialFunctionWindow.cs @@ -29,7 +29,7 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more"); } /// diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs index 4f67e4f72..c5becfe7d 100644 --- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs @@ -327,14 +327,14 @@ namespace FlaxEditor.Windows.Assets _undo.ActionDone += OnAction; // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(Editor.Icons.Rotate32, OnRevertAllParameters).LinkTooltip("Revert all the parameters to the default values"); + _toolstrip.AddButton(Editor.Icons.Rotate64, OnRevertAllParameters).LinkTooltip("Revert all the parameters to the default values"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/instanced-materials/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/instanced-materials/index.html")).LinkTooltip("See documentation to learn more"); // Split Panel _split = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.Vertical) diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index 53d85f493..37dcc40ce 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -233,14 +233,14 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.BracketsSlash32, ShowSourceCode).LinkTooltip("Show generated shader source code"); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode).LinkTooltip("Show generated shader source code"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more"); } private void ShowSourceCode() { var source = Editor.GetShaderSourceCode(_asset); - Utilities.Utils.ShowSourceCodeWindow(source, "Material Source", this); + Utilities.Utils.ShowSourceCodeWindow(source, "Material Source", RootWindow.Window); } /// diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index 74b690f98..7382d727c 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -102,7 +102,7 @@ namespace FlaxEditor.Windows.Assets : base(editor, item) { // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save"); // Split Panel _split = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.None) diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index e8a054222..90b02cbb6 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -399,9 +399,18 @@ namespace FlaxEditor.Windows.Assets ShadowsCastingMode[] shadowsModes = new ShadowsCastingMode[value.Length]; for (int i = 0; i < value.Length; i++) { - materials[i] = value[i].Material; - names[i] = value[i].Name; - shadowsModes[i] = value[i].ShadowsMode; + if (value[i] != null) + { + materials[i] = value[i].Material; + names[i] = value[i].Name; + shadowsModes[i] = value[i].ShadowsMode; + } + else + { + materials[i] = null; + names[i] = "Material " + i; + shadowsModes[i] = ShadowsCastingMode.All; + } } Asset.SetupMaterialSlots(value.Length); @@ -556,7 +565,6 @@ namespace FlaxEditor.Windows.Assets public UVsLayoutPreviewControl() { Offsets = new Margin(4); - AnchorPreset = AnchorPresets.HorizontalStretchMiddle; AutomaticInvalidate = false; } @@ -610,9 +618,9 @@ namespace FlaxEditor.Windows.Assets } /// - protected override void DrawChildren() + public override void DrawSelf() { - base.DrawChildren(); + base.DrawSelf(); var size = Size; if (_channel == UVChannel.None || size.MaxValue < 5.0f) @@ -816,7 +824,7 @@ namespace FlaxEditor.Windows.Assets { // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/models/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/models/index.html")).LinkTooltip("See documentation to learn more"); // Model preview _preview = new Preview(this) diff --git a/Source/Editor/Windows/Assets/ParticleEmitterFunctionWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterFunctionWindow.cs index a449aec82..abad899f4 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterFunctionWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterFunctionWindow.cs @@ -29,7 +29,7 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); } /// diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index ab61a9d68..1968c5735 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -139,14 +139,14 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.BracketsSlash32, ShowSourceCode).LinkTooltip("Show generated shader source code"); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode).LinkTooltip("Show generated shader source code"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); } private void ShowSourceCode() { var source = Editor.GetShaderSourceCode(_asset); - Utilities.Utils.ShowSourceCodeWindow(source, "Particle Emitter GPU Simulation Source", this); + Utilities.Utils.ShowSourceCodeWindow(source, "Particle Emitter GPU Simulation Source", RootWindow.Window); } /// diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs index 929e81b61..acc3c658a 100644 --- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs @@ -107,7 +107,7 @@ namespace FlaxEditor.Windows.Assets { private readonly ParticleSystemWindow _window; - [EditorDisplay("Particle System"), EditorOrder(100), Limit(1), Tooltip("The timeline animation duration in frames.")] + [EditorDisplay("Particle System"), EditorOrder(-100), Limit(1), Tooltip("The timeline animation duration in frames.")] public int TimelineDurationFrames { get => _window.Timeline.DurationFrames; @@ -124,7 +124,7 @@ namespace FlaxEditor.Windows.Assets /// The proxy object for editing particle system track properties. /// [CustomEditor(typeof(EmitterTrackProxyEditor))] - private class EmitterTrackProxy + private class EmitterTrackProxy : GeneralProxy { private readonly ParticleSystemWindow _window; private readonly ParticleEffect _effect; @@ -171,6 +171,7 @@ namespace FlaxEditor.Windows.Assets } public EmitterTrackProxy(ParticleSystemWindow window, ParticleEffect effect, ParticleEmitterTrack track, int emitterIndex) + : base(window) { _window = window; _effect = effect; @@ -238,7 +239,7 @@ namespace FlaxEditor.Windows.Assets /// /// The proxy object for editing folder track properties. /// - private class FolderTrackProxy + private class FolderTrackProxy : GeneralProxy { private readonly FolderTrack _track; @@ -265,7 +266,8 @@ namespace FlaxEditor.Windows.Assets set => _track.IconColor = value; } - public FolderTrackProxy(FolderTrack track) + public FolderTrackProxy(ParticleSystemWindow window, FolderTrack track) + : base(window) { _track = track; } @@ -275,8 +277,7 @@ namespace FlaxEditor.Windows.Assets private readonly SplitPanel _split2; private ParticleSystemTimeline _timeline; private readonly ParticleSystemPreview _preview; - private readonly CustomEditorPresenter _propertiesEditor1; - private readonly CustomEditorPresenter _propertiesEditor2; + private readonly CustomEditorPresenter _propertiesEditor; private ToolStripButton _saveButton; private ToolStripButton _undoButton; private ToolStripButton _redoButton; @@ -347,26 +348,20 @@ namespace FlaxEditor.Windows.Assets _timeline.Modified += OnTimelineModified; _timeline.SelectionChanged += OnTimelineSelectionChanged; - // Properties editor (general) - var propertiesEditor1 = new CustomEditorPresenter(null, string.Empty); - propertiesEditor1.Panel.Parent = _split2.Panel2; - propertiesEditor1.Modified += OnParticleSystemPropertyEdited; - _propertiesEditor1 = propertiesEditor1; - propertiesEditor1.Select(new GeneralProxy(this)); - - // Properties editor (selection) - var propertiesEditor2 = new CustomEditorPresenter(null, string.Empty); - propertiesEditor2.Panel.Parent = _split2.Panel2; - propertiesEditor2.Modified += OnParticleSystemPropertyEdited; - _propertiesEditor2 = propertiesEditor2; + // Properties editor + var propertiesEditor = new CustomEditorPresenter(_undo, string.Empty); + propertiesEditor.Panel.Parent = _split2.Panel2; + propertiesEditor.Modified += OnParticleSystemPropertyEdited; + _propertiesEditor = propertiesEditor; + propertiesEditor.Select(new GeneralProxy(this)); // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); // Setup input actions InputActions.Add(options => options.Undo, _undo.PerformUndo); @@ -380,8 +375,7 @@ namespace FlaxEditor.Windows.Assets if (!_isEditingInstancedParameterValue) { - _propertiesEditor1.BuildLayoutOnUpdate(); - _propertiesEditor2.BuildLayoutOnUpdate(); + _propertiesEditor.BuildLayoutOnUpdate(); } } @@ -399,7 +393,7 @@ namespace FlaxEditor.Windows.Assets { if (_timeline.SelectedTracks.Count == 0) { - _propertiesEditor2.Deselect(); + _propertiesEditor.Select(new GeneralProxy(this)); return; } @@ -414,14 +408,14 @@ namespace FlaxEditor.Windows.Assets } else if (track is FolderTrack folderTrack) { - tracks[i] = new FolderTrackProxy(folderTrack); + tracks[i] = new FolderTrackProxy(this, folderTrack); } else { throw new NotImplementedException("Invalid track type."); } } - _propertiesEditor2.Select(tracks); + _propertiesEditor.Select(tracks); } private void OnParticleSystemPropertyEdited() @@ -443,8 +437,7 @@ namespace FlaxEditor.Windows.Assets if (_timeline.IsModified) { - _propertiesEditor1.BuildLayoutOnUpdate(); - _propertiesEditor2.BuildLayoutOnUpdate(); + _propertiesEditor.BuildLayoutOnUpdate(); _timeline.Save(_asset); } @@ -484,7 +477,7 @@ namespace FlaxEditor.Windows.Assets /// protected override void UnlinkItem() { - _propertiesEditor2.Deselect(); + _propertiesEditor.Deselect(); _preview.System = null; _isWaitingForTimelineLoad = false; @@ -507,7 +500,7 @@ namespace FlaxEditor.Windows.Assets if (_parametersVersion != _preview.PreviewActor.ParametersVersion) { _parametersVersion = _preview.PreviewActor.ParametersVersion; - _propertiesEditor2.BuildLayoutOnUpdate(); + _propertiesEditor.BuildLayoutOnUpdate(); } base.Update(deltaTime); @@ -534,8 +527,7 @@ namespace FlaxEditor.Windows.Assets // Setup _undo.Clear(); _timeline.Enabled = true; - _propertiesEditor1.BuildLayout(); - _propertiesEditor2.Deselect(); + _propertiesEditor.Select(new GeneralProxy(this)); ClearEditedFlag(); } @@ -580,8 +572,7 @@ namespace FlaxEditor.Windows.Assets { if (_undo != null) _undo.Enabled = false; - _propertiesEditor1?.Deselect(); - _propertiesEditor2?.Deselect(); + _propertiesEditor?.Deselect(); _undo?.Clear(); _undo = null; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index e671ba1de..afba74fbc 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -36,6 +36,9 @@ namespace FlaxEditor.Windows.Assets /// public override Undo Undo => _window.Undo; + + /// + public override List Selection => _window.Selection; } /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 8dd4a6ebe..bec9bf7fe 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -154,16 +154,16 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.Modified += MarkAsEdited; // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); _toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)"); _toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)"); _toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)"); _toolstrip.AddSeparator(); - _toolStripLiveReload = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Reload32, () => LiveReload = !LiveReload).SetChecked(true).SetAutoCheck(true).LinkTooltip("Live changes preview (applies prefab changes on modification by auto)"); + _toolStripLiveReload = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, () => LiveReload = !LiveReload).SetChecked(true).SetAutoCheck(true).LinkTooltip("Live changes preview (applies prefab changes on modification by auto)"); Editor.Prefabs.PrefabApplied += OnPrefabApplied; ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; diff --git a/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs b/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs index d1fe73976..a857246da 100644 --- a/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs +++ b/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs @@ -28,7 +28,7 @@ namespace FlaxEditor.Windows.Assets }; // Toolstrip - _toolstrip.AddButton(editor.Icons.PageScale32, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); } /// diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs index c162eac3a..821b5ccc5 100644 --- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs +++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs @@ -634,14 +634,14 @@ namespace FlaxEditor.Windows.Assets _timeline.PlayerChanged += OnTimelinePlayerChanged; // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); - _renderButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Build32, OnRenderButtonClicked).LinkTooltip("Open the scene animation rendering utility..."); + _renderButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Build64, OnRenderButtonClicked).LinkTooltip("Open the scene animation rendering utility..."); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/scene-animations/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/scene-animations/index.html")).LinkTooltip("See documentation to learn more"); // Preview player picker var previewPlayerPickerContainer = new ContainerControl(); diff --git a/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs b/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs index 86066f293..d65a564b2 100644 --- a/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs +++ b/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs @@ -182,9 +182,9 @@ namespace FlaxEditor.Windows.Assets : base(editor, item) { // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save32, Save).LinkTooltip("Save asset to the file"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save asset to the file"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skeleton-mask.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skeleton-mask.html")).LinkTooltip("See documentation to learn more"); // Split Panel _split = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.Vertical) diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index b002aa0fe..9123b8d9a 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -114,9 +114,9 @@ namespace FlaxEditor.Windows.Assets } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); + base.DrawSelf(); var style = Style.Current; var asset = _window.Asset; @@ -511,9 +511,18 @@ namespace FlaxEditor.Windows.Assets ShadowsCastingMode[] shadowsModes = new ShadowsCastingMode[value.Length]; for (int i = 0; i < value.Length; i++) { - materials[i] = value[i].Material; - names[i] = value[i].Name; - shadowsModes[i] = value[i].ShadowsMode; + if (value[i] != null) + { + materials[i] = value[i].Material; + names[i] = value[i].Name; + shadowsModes[i] = value[i].ShadowsMode; + } + else + { + materials[i] = null; + names[i] = "Material " + i; + shadowsModes[i] = ShadowsCastingMode.All; + } } Asset.SetupMaterialSlots(value.Length); @@ -667,7 +676,6 @@ namespace FlaxEditor.Windows.Assets public UVsLayoutPreviewControl() { Offsets = new Margin(4); - AnchorPreset = AnchorPresets.HorizontalStretchMiddle; AutomaticInvalidate = false; } @@ -911,9 +919,9 @@ namespace FlaxEditor.Windows.Assets { // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Bone32, () => _preview.ShowNodes = !_preview.ShowNodes).SetAutoCheck(true).LinkTooltip("Show animated model nodes debug view"); + _toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).SetAutoCheck(true).LinkTooltip("Show animated model nodes debug view"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skinned-model/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skinned-model/index.html")).LinkTooltip("See documentation to learn more"); // Model preview _preview = new Preview(this) diff --git a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs index e82c1dd12..232f69165 100644 --- a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs +++ b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs @@ -1,11 +1,13 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Linq; using System.Xml; using FlaxEditor.Content; using FlaxEditor.Content.Import; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Scripting; using FlaxEditor.Viewport.Previews; using FlaxEngine; @@ -99,18 +101,16 @@ namespace FlaxEditor.Windows.Assets } [EditorOrder(0), EditorDisplay("Sprites")] - [CustomEditor(typeof(SpritesColelctionEditor))] + [CustomEditor(typeof(SpritesCollectionEditor))] public SpriteEntry[] Sprites; [EditorOrder(1000), EditorDisplay("Import Settings", EditorDisplayAttribute.InlineStyle)] - public TextureImportSettings ImportSettings { get; set; } = new TextureImportSettings(); + public TextureImportSettings ImportSettings = new TextureImportSettings(); public sealed class ProxyEditor : GenericEditor { public override void Initialize(LayoutElementsContainer layout) { - var window = ((PropertiesProxy)Values[0])._window; - base.Initialize(layout); layout.Space(10); @@ -119,24 +119,46 @@ namespace FlaxEditor.Windows.Assets } } - public sealed class SpritesColelctionEditor : CustomEditor + public sealed class SpritesCollectionEditor : CustomEditor { public override DisplayStyle Style => DisplayStyle.InlineIntoParent; public override void Initialize(LayoutElementsContainer layout) { var sprites = (SpriteEntry[])Values[0]; - if (sprites != null) { var elementType = new ScriptType(typeof(SpriteEntry)); for (int i = 0; i < sprites.Length; i++) { var group = layout.Group(sprites[i].Name); + group.Panel.Tag = i; + group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked; group.Object(new ListValueContainer(elementType, i, Values)); } } } + + private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Vector2 location) + { + var menu = new ContextMenu(); + + var deleteSprite = menu.AddButton("Delete sprite"); + deleteSprite.Tag = groupPanel.Tag; + deleteSprite.ButtonClicked += OnDeleteSpriteClicked; + + menu.Show(groupPanel, location); + } + + private void OnDeleteSpriteClicked(ContextMenuButton button) + { + var window = ((PropertiesProxy)ParentEditor.Values[0])._window; + var index = (int)button.Tag; + window.Asset.RemoveSprite(index); + window.MarkAsEdited(); + window._properties.UpdateSprites(); + window._propertiesEditor.BuildLayout(); + } } /// @@ -185,6 +207,7 @@ namespace FlaxEditor.Windows.Assets /// public void Reimport() { + ImportSettings.Sprites = null; // Don't override sprites (use sprites from asset) Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)_window.Item, ImportSettings, true); } @@ -241,14 +264,14 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.Modified += MarkAsEdited; // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save32, Save).LinkTooltip("Save"); - _toolstrip.AddButton(editor.Icons.Import32, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save"); + _toolstrip.AddButton(editor.Icons.Import64, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.AddDoc32, () => + _toolstrip.AddButton(editor.Icons.AddFile64, () => { var sprite = new Sprite { - Name = "New Sprite", + Name = StringUtils.IncrementNameNumber("New Sprite", name => Asset.Sprites.All(s => s.Name != name)), Area = new Rectangle(Vector2.Zero, Vector2.One), }; Asset.AddSprite(sprite); @@ -257,7 +280,7 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.BuildLayout(); }).LinkTooltip("Add a new sprite"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.PageScale32, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); } /// diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index 9f6698528..481c48f47 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -140,11 +140,11 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.Select(_properties); // Toolstrip - _toolstrip.AddButton(Editor.Icons.Import32, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); + _toolstrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(Editor.Icons.PageScale32, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(Editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/textures/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/textures/index.html")).LinkTooltip("See documentation to learn more"); } /// diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs index 57041169a..fe87299e2 100644 --- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs +++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs @@ -68,12 +68,12 @@ namespace FlaxEditor.Windows.Assets _undo.ActionDone += OnUndoRedo; // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.PageScale32, ShowWholeGraph).LinkTooltip("Show whole graph"); + _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph"); // Panel _panel = new Panel(ScrollBars.None) diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 8207740f1..b9ddafa87 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -212,7 +212,7 @@ namespace FlaxEditor.Windows.Assets IsScrollable = false, Color = FlaxEngine.GUI.Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(Editor.Instance.Icons.Add48), + Brush = new SpriteBrush(Editor.Instance.Icons.Add64), }; addNewFunction.Clicked += OnAddNewFunctionClicked; @@ -227,7 +227,7 @@ namespace FlaxEditor.Windows.Assets IsScrollable = false, Color = FlaxEngine.GUI.Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(Editor.Instance.Icons.Import32), + Brush = new SpriteBrush(Editor.Instance.Icons.Import64), }; overrideMethod.Clicked += OnOverrideMethodClicked; } @@ -385,23 +385,23 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.Select(_properties); // Toolstrip - _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.PageScale32, ShowWholeGraph).LinkTooltip("Show whole graph"); + _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more"); + _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more"); _debugToolstripControls = new[] { _toolstrip.AddSeparator(), - _toolstrip.AddButton(editor.Icons.Play32, OnDebuggerContinue).LinkTooltip("Continue (F5)"), - _toolstrip.AddButton(editor.Icons.Find32, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"), - _toolstrip.AddButton(editor.Icons.ArrowRight32, OnDebuggerStepOver).LinkTooltip("Step Over (F10)"), - _toolstrip.AddButton(editor.Icons.ArrowDown32, OnDebuggerStepInto).LinkTooltip("Step Into (F11)"), - _toolstrip.AddButton(editor.Icons.ArrowUp32, OnDebuggerStepOut).LinkTooltip("Step Out (Shift+F11)"), - _toolstrip.AddButton(editor.Icons.Stop32, OnDebuggerStop).LinkTooltip("Stop debugging"), + _toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"), + _toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"), + _toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip("Step Over (F10)"), + _toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip("Step Into (F11)"), + _toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip("Step Out (Shift+F11)"), + _toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"), }; foreach (var control in _debugToolstripControls) control.Visible = false; diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 350714bb3..88a976d2b 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -77,11 +77,11 @@ namespace FlaxEditor.Windows { Parent = this, }; - _importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import32, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content"); + _importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content"); _toolStrip.AddSeparator(); - _navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.ArrowLeft32, NavigateBackward).LinkTooltip("Navigate backward"); - _navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.ArrowRight32, NavigateForward).LinkTooltip("Navigate forward"); - _navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.ArrowUp32, NavigateUp).LinkTooltip("Navigate up"); + _navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward"); + _navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward"); + _navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up"); // Navigation bar _navigationBar = new NavigationBar @@ -371,7 +371,7 @@ namespace FlaxEditor.Windows // Focus content window Focus(); - RootWindow?.Window.Focus(); + RootWindow?.Focus(); } // Refresh database and view now diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index 560e507eb..bc80a0ba7 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -312,9 +312,9 @@ namespace FlaxEditor.Windows _clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play").SetAutoCheck(true).SetChecked(true).LinkTooltip("Clears all log entries on enter playmode"); _pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error").SetAutoCheck(true).LinkTooltip("Performs auto pause on error"); toolstrip.AddSeparator(); - _groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages"); - _groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () => UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides warning messages"); - _groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () => UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides info messages"); + _groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error64, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages"); + _groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning64, () => UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides warning messages"); + _groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info64, () => UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides info messages"); UpdateCount(); // Split panel @@ -333,8 +333,9 @@ namespace FlaxEditor.Windows AutoWidth = true, AutoHeight = true, Margin = new Margin(4), + VerticalAlignment = TextAlignment.Near, + HorizontalAlignment = TextAlignment.Near, Offsets = Margin.Zero, - AnchorPreset = AnchorPresets.StretchAll, }; // Entries panel @@ -347,9 +348,9 @@ namespace FlaxEditor.Windows }; // Cache entries icons - IconInfo = Editor.Icons.Info32; - IconWarning = Editor.Icons.Warning32; - IconError = Editor.Icons.Error32; + IconInfo = Editor.Icons.Info64; + IconWarning = Editor.Icons.Warning64; + IconError = Editor.Icons.Error64; // Bind events Editor.Options.OptionsChanged += OnEditorOptionsChanged; diff --git a/Source/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs index 0d3a8bf95..4025f68ab 100644 --- a/Source/Editor/Windows/EditGameWindow.cs +++ b/Source/Editor/Windows/EditGameWindow.cs @@ -243,7 +243,13 @@ namespace FlaxEditor.Windows /// public void ShowSelectedActors() { - ((FPSCamera)Viewport.ViewportCamera).ShowActors(Viewport.TransformGizmo.SelectedParents); + if (Viewport.UseOrthographicProjection) + { + var orient = Viewport.ViewOrientation; + ((FPSCamera)Viewport.ViewportCamera).ShowActors(Viewport.TransformGizmo.SelectedParents, ref orient); + } + else + ((FPSCamera)Viewport.ViewportCamera).ShowActors(Viewport.TransformGizmo.SelectedParents); } /// diff --git a/Source/Editor/Windows/EditorOptionsWindow.cs b/Source/Editor/Windows/EditorOptionsWindow.cs index 01b26a65c..2e700686e 100644 --- a/Source/Editor/Windows/EditorOptionsWindow.cs +++ b/Source/Editor/Windows/EditorOptionsWindow.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Windows { Parent = this }; - _saveButton = (ToolStripButton)toolstrip.AddButton(editor.Icons.Save32, SaveData).LinkTooltip("Save"); + _saveButton = (ToolStripButton)toolstrip.AddButton(editor.Icons.Save64, SaveData).LinkTooltip("Save"); _saveButton.Enabled = false; _tabs = new Tabs diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 278ce69d3..855a0d661 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -43,6 +43,7 @@ namespace FlaxEditor.Windows { PlatformType.PS4, new PS4() }, { PlatformType.XboxScarlett, new XboxScarlett() }, { PlatformType.Android, new Android() }, + { PlatformType.Switch, new Switch() }, }; public BuildTabProxy(GameCookerWindow win, PlatformSelector platformSelector) @@ -57,10 +58,14 @@ namespace FlaxEditor.Windows PerPlatformOptions[PlatformType.PS4].Init("Output/PS4", "PS4"); PerPlatformOptions[PlatformType.XboxScarlett].Init("Output/XboxScarlett", "XboxScarlett"); PerPlatformOptions[PlatformType.Android].Init("Output/Android", "Android"); + PerPlatformOptions[PlatformType.Switch].Init("Output/Switch", "Switch"); } public abstract class Platform { + [HideInEditor] + public bool IsSupported; + [HideInEditor] public bool IsAvailable; @@ -93,6 +98,23 @@ namespace FlaxEditor.Windows { Output = output; + // Check if can build on that platform +#if PLATFORM_WINDOWS + IsSupported = true; +#elif PLATFORM_LINUX + switch (BuildPlatform) + { + case BuildPlatform.LinuxX64: + IsSupported = true; + break; + default: + IsSupported = false; + break; + } +#else +#error "Unknown platform." +#endif + // TODO: restore build settings from the Editor cache! // Check if can find installed tools for this platform @@ -168,6 +190,11 @@ namespace FlaxEditor.Windows protected override BuildPlatform BuildPlatform => BuildPlatform.AndroidARM64; } + public class Switch : Platform + { + protected override BuildPlatform BuildPlatform => BuildPlatform.Switch; + } + public class Editor : CustomEditor { private PlatformType _platform; @@ -179,7 +206,11 @@ namespace FlaxEditor.Windows _platform = proxy.Selector.Selected; var platformObj = proxy.PerPlatformOptions[_platform]; - if (platformObj.IsAvailable) + if (!platformObj.IsSupported) + { + layout.Label("This platform is not supported on this system.", TextAlignment.Center); + } + else if (platformObj.IsAvailable) { string name; switch (_platform) @@ -205,6 +236,9 @@ namespace FlaxEditor.Windows case PlatformType.Android: name = "Android"; break; + case PlatformType.Switch: + name = "Switch"; + break; default: name = CustomEditorsUtil.GetPropertyNameUI(_platform.ToString()); break; diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 2968b0645..26cef2621 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -1,11 +1,11 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; -using FlaxEngine.Utilities; namespace FlaxEditor.Windows { @@ -18,6 +18,7 @@ namespace FlaxEditor.Windows private readonly RenderOutputControl _viewport; private readonly GameRoot _guiRoot; private bool _showGUI = true; + private bool _showDebugDraw = false; private float _gameStartTime; /// @@ -36,14 +37,20 @@ namespace FlaxEditor.Windows if (value != _showGUI) { _showGUI = value; - - // Update root if it's in game - if (Editor.StateMachine.IsPlayMode) - _guiRoot.Visible = value; + _guiRoot.Visible = value; } } } + /// + /// Gets or sets a value indicating whether show Debug Draw shapes in the view or keep it hidden. + /// + public bool ShowDebugDraw + { + get => _showDebugDraw; + set => _showDebugDraw = value; + } + /// /// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor. /// @@ -191,6 +198,7 @@ namespace FlaxEditor.Windows AutoFocus = false, Parent = this }; + task.PostRender += OnPostRender; // Override the game GUI root _guiRoot = new GameRoot @@ -214,6 +222,31 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); } + private void OnPostRender(GPUContext context, RenderContext renderContext) + { + // Debug Draw shapes + if (_showDebugDraw) + { + var task = _viewport.Task; + + // Draw actors debug shapes manually if editor viewport is hidden (game viewport task is always rendered before editor viewports) + var editWindowViewport = Editor.Windows.EditWin.Viewport; + if (editWindowViewport.Task.LastUsedFrame != Engine.FrameCount) + { + unsafe + { + var drawDebugData = editWindowViewport.DebugDrawData; + fixed (IntPtr* actors = drawDebugData.ActorsPtrs) + { + DebugDraw.DrawActors(new IntPtr(actors), drawDebugData.ActorsCount, true); + } + } + } + + DebugDraw.Draw(ref renderContext, task.OutputView); + } + } + private void OnOptionsChanged(EditorOptions options) { CenterMouseOnFocus = options.Interface.CenterMouseOnGameWinFocus; @@ -278,6 +311,22 @@ namespace FlaxEditor.Windows takeScreenshot.Clicked += TakeScreenshot; } + menu.AddSeparator(); + + // Show GUI + { + var button = menu.AddButton("Show GUI"); + var checkbox = new CheckBox(140, 2, ShowGUI) { Parent = button }; + checkbox.StateChanged += x => ShowGUI = x.Checked; + } + + // Show Debug Draw + { + var button = menu.AddButton("Show Debug Draw"); + var checkbox = new CheckBox(140, 2, ShowDebugDraw) { Parent = button }; + checkbox.StateChanged += x => ShowDebugDraw = x.Checked; + } + menu.MinimumWidth = 200; menu.AddSeparator(); } @@ -296,7 +345,7 @@ namespace FlaxEditor.Windows // Selected UI controls outline for (var i = 0; i < Editor.Instance.SceneEditing.Selection.Count; i++) { - if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor.Control != null) + if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) { var control = controlActor.Control; var bounds = Rectangle.FromPoints(control.PointToParent(_viewport, Vector2.Zero), control.PointToParent(_viewport, control.Size)); @@ -408,7 +457,7 @@ namespace FlaxEditor.Windows base.OnStartContainsFocus(); // Center mouse in play mode - if (CenterMouseOnFocus && Editor.StateMachine.IsPlayMode) + if (CenterMouseOnFocus && Editor.StateMachine.IsPlayMode && !Editor.StateMachine.PlayingState.IsPaused) { Vector2 center = PointToWindow(Size * 0.5f); Root.MousePosition = center; diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index 28b6c20a5..a14a38774 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -53,7 +53,7 @@ namespace FlaxEditor.Windows var iconImage = new Image { - Brush = new SpriteBrush(Editor.Instance.Icons.Plugin64), + Brush = new SpriteBrush(Editor.Instance.Icons.Plugin128), Parent = this, Bounds = new Rectangle(margin, margin, iconSize, iconSize), }; diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index 9e79832ab..d33325fa3 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -5,6 +5,29 @@ using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; +namespace FlaxEngine +{ + partial class ProfilerCPU + { + partial struct Event + { + /// + /// Gets the event name. + /// + public unsafe string Name + { + get + { + fixed (char* name = &Name0) + { + return new string(name); + } + } + } + } + } +} + namespace FlaxEditor.Windows.Profiler { /// @@ -204,7 +227,7 @@ namespace FlaxEditor.Windows.Profiler { var e = events[i]; - if (e.Depth == 0 && new string(e.Name) == "Update") + if (e.Depth == 0 && e.Name == "Update") { return new ViewRange(ref e); } @@ -225,7 +248,7 @@ namespace FlaxEditor.Windows.Profiler double scale = 100.0; float x = (float)((e.Start - startTime) * scale); float width = (float)(length * scale); - string name = new string(e.Name).Replace("::", "."); + string name = e.Name.Replace("::", "."); var control = new Timeline.Event(x + xOffset, e.Depth + depthOffset, width) { @@ -399,7 +422,7 @@ namespace FlaxEditor.Windows.Profiler subEventsMemoryTotal += sub.ManagedMemoryAllocation + e.NativeMemoryAllocation; } - string name = new string(e.Name).Replace("::", "."); + string name = e.Name.Replace("::", "."); var row = new Row { diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index e6de1fd69..27a718077 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -92,20 +92,20 @@ namespace FlaxEditor.Windows.Profiler { Parent = this, }; - _liveRecordingButton = toolstrip.AddButton(editor.Icons.Play32); + _liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64); _liveRecordingButton.LinkTooltip("Live profiling events recording"); _liveRecordingButton.AutoCheck = true; _clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear); _clearButton.LinkTooltip("Clear data"); toolstrip.AddSeparator(); - _prevFrameButton = toolstrip.AddButton(editor.Icons.ArrowLeft32, () => ViewFrameIndex--); + _prevFrameButton = toolstrip.AddButton(editor.Icons.Left64, () => ViewFrameIndex--); _prevFrameButton.LinkTooltip("Previous frame"); - _nextFrameButton = toolstrip.AddButton(editor.Icons.ArrowRight32, () => ViewFrameIndex++); + _nextFrameButton = toolstrip.AddButton(editor.Icons.Right64, () => ViewFrameIndex++); _nextFrameButton.LinkTooltip("Next frame"); - _lastFrameButton = toolstrip.AddButton(editor.Icons.Step32, () => ViewFrameIndex = -1); + _lastFrameButton = toolstrip.AddButton(editor.Icons.Skip64, () => ViewFrameIndex = -1); _lastFrameButton.LinkTooltip("Current frame"); toolstrip.AddSeparator(); - _showOnlyLastUpdateEventsButton = toolstrip.AddButton(editor.Icons.PageScale32, () => ShowOnlyLastUpdateEvents = !ShowOnlyLastUpdateEvents); + _showOnlyLastUpdateEventsButton = toolstrip.AddButton(editor.Icons.CenterView64, () => ShowOnlyLastUpdateEvents = !ShowOnlyLastUpdateEvents); _showOnlyLastUpdateEventsButton.LinkTooltip("Show only last update events and hide events from the other callbacks (e.g. draw or fixed update)"); _tabs = new Tabs diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 3db4df799..e33d658e6 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -209,9 +209,11 @@ namespace FlaxEditor.Windows public override void Draw() { var style = Style.Current; + // Draw overlay string overlayText = null; var state = Editor.StateMachine.CurrentState; + var textWrap = TextWrapping.NoWrap; if (state is LoadingState) { overlayText = "Loading..."; @@ -222,11 +224,12 @@ namespace FlaxEditor.Windows } else if (((ContainerControl)_tree.GetChild(0)).ChildrenCount == 0) { - overlayText = "No scene"; + overlayText = "No scene\nOpen one from the content window"; + textWrap = TextWrapping.WrapWords; } if (overlayText != null) { - Render2D.DrawText(Style.Current.FontLarge, overlayText, GetClientArea(), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, overlayText, GetClientArea(), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center, textWrap); } base.Draw(); diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 2d2d26ead..4a107e467 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -2,7 +2,9 @@ #include "SplashScreen.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Engine/Globals.h" #include "Engine/Render2D/FontAsset.h" #include "Engine/Render2D/Font.h" #include "Engine/Render2D/TextLayoutOptions.h" @@ -115,11 +117,21 @@ const Char* SplashScreenQuotes[] = TEXT("Cyberpunk of game engines"), TEXT("That's what she said"), TEXT("Compiling Shaders (93,788)"), - TEXT("Hi There"), + TEXT("Hello There"), TEXT("BAGUETTE"), TEXT("All we had to do was follow the damn train, CJ"), TEXT("28 stab wounds"), TEXT("Here we go again"), + TEXT("@everyone"), + TEXT("Potato"), + TEXT("Python is a programming snek"), + TEXT("Flax will start when pigs will fly"), + TEXT("I'm the android sent by CyberLife"), + TEXT("Fancy-ass ray tracing, rtx on, lighting"), + TEXT("ZOINKS"), + TEXT("Scooby dooby doo"), + TEXT("You shall not load!"), + TEXT("The roof, the roof, the roof is on fire!") }; SplashScreen::~SplashScreen() diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 7084addd1..33827ea93 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -319,10 +319,10 @@ namespace FlaxEditor.Windows Parent = this }; - TabsControl.AddTab(Spawn = new SpawnTab(Editor.Icons.Add48, Editor)); - TabsControl.AddTab(VertexPaint = new VertexPaintingTab(Editor.Icons.Paint48, Editor)); - TabsControl.AddTab(Foliage = new FoliageTab(Editor.Icons.Foliage48, Editor)); - TabsControl.AddTab(Carve = new CarveTab(Editor.Icons.Mountain48, Editor)); + TabsControl.AddTab(Spawn = new SpawnTab(Editor.Icons.Toolbox96, Editor)); + TabsControl.AddTab(VertexPaint = new VertexPaintingTab(Editor.Icons.Paint96, Editor)); + TabsControl.AddTab(Foliage = new FoliageTab(Editor.Icons.Foliage96, Editor)); + TabsControl.AddTab(Carve = new CarveTab(Editor.Icons.Terrain96, Editor)); TabsControl.SelectedTabIndex = 0; } diff --git a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs index b9f20ff2f..f91d0cea9 100644 --- a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs +++ b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs @@ -404,13 +404,13 @@ namespace FlaxEditor.Windows { Parent = this }; - toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more"); + toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more"); _debugToolstripControls = new[] { toolstrip.AddSeparator(), - toolstrip.AddButton(editor.Icons.Play32, OnDebuggerContinue).LinkTooltip("Continue (F5)"), - toolstrip.AddButton(editor.Icons.Find32, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"), - toolstrip.AddButton(editor.Icons.Stop32, OnDebuggerStop).LinkTooltip("Stop debugging"), + toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"), + toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"), + toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"), }; foreach (var control in _debugToolstripControls) control.Visible = Editor.Simulation.IsDuringBreakpointHang; diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h index 139c06aa9..6a782d8d6 100644 --- a/Source/Engine/Animations/AnimationData.h +++ b/Source/Engine/Animations/AnimationData.h @@ -2,7 +2,8 @@ #pragma once -#include "Curve.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Animations/Curve.h" #include "Engine/Core/Math/Transform.h" /// diff --git a/Source/Engine/Animations/AnimationManager.cpp b/Source/Engine/Animations/AnimationManager.cpp index c64a61856..6614c611a 100644 --- a/Source/Engine/Animations/AnimationManager.cpp +++ b/Source/Engine/Animations/AnimationManager.cpp @@ -6,7 +6,8 @@ #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" -Array UpdateList(256); +Array UpdateList; +Array UpdateBones; class AnimationManagerService : public EngineService { @@ -67,10 +68,11 @@ void AnimationManagerService::Update() } animatedModel->GraphInstance.LastUpdateTime = t; - const auto bones = graph->GraphExecutor.Update(animatedModel->GraphInstance, dt); - const bool usePrevFrameBones = animatedModel->PerBoneMotionBlur; - animatedModel->_skinningData.SetData(bones, !usePrevFrameBones); - animatedModel->OnAnimUpdate(); + // Evaluate animated nodes pose + graph->GraphExecutor.Update(animatedModel->GraphInstance, dt); + + // Update gameplay + animatedModel->OnAnimationUpdated(); } } UpdateList.Clear(); @@ -79,6 +81,7 @@ void AnimationManagerService::Update() void AnimationManagerService::Dispose() { UpdateList.Resize(0); + UpdateBones.Resize(0); } void AnimationManager::AddToUpdate(AnimatedModel* obj) diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h index 11b6011e8..121eaee57 100644 --- a/Source/Engine/Animations/AnimationUtils.h +++ b/Source/Engine/Animations/AnimationUtils.h @@ -70,7 +70,7 @@ namespace AnimationUtils FORCE_INLINE void GetTangent(const Quaternion& a, const Quaternion& b, float length, Quaternion& result) { const float oneThird = 1.0f / 3.0f; - Quaternion::Slerp(a, b, length * oneThird, result); + Quaternion::Slerp(a, b, oneThird, result); } template<> @@ -79,7 +79,7 @@ namespace AnimationUtils const float oneThird = 1.0f / 3.0f; const float oneThirdLength = length * oneThird; result.Translation = a.Translation + b.Translation * oneThirdLength; - Quaternion::Slerp(a.Orientation, b.Orientation, oneThirdLength, result.Orientation); + Quaternion::Slerp(a.Orientation, b.Orientation, oneThird, result.Orientation); result.Scale = a.Scale + (b.Scale - a.Scale) * oneThirdLength; } diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp index eaa080f9a..22fc6af28 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp @@ -10,6 +10,7 @@ #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/MException.h" +#include "Engine/Content/Assets/SkinnedModel.h" #include struct InternalInitData @@ -144,6 +145,17 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& box->Cache = value; } +bool AnimGraph::IsReady() const +{ + return BaseModel && BaseModel->IsLoaded(); +} + +bool AnimGraph::CanUseWithSkeleton(SkinnedModel* other) const +{ + // All data loaded and nodes count the same + return IsReady() && other && other->IsLoaded() && other->Skeleton.Nodes.Count() == BaseModel->Skeleton.Nodes.Count(); +} + void AnimGraph::ClearCustomNode(Node* node) { // Clear data diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 2abbb3ff3..2932f7c3f 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -1,8 +1,10 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "AnimGraph.h" -#include "Engine/Engine/Time.h" +#include "Engine/Content/Assets/SkinnedModel.h" +#include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Scripting/Scripting.h" +#include "Engine/Engine/Time.h" RootMotionData RootMotionData::Identity = { Vector3(0.0f), Quaternion(0.0f, 0.0f, 0.0f, 1.0f) }; @@ -173,7 +175,7 @@ AnimGraphExecutor::AnimGraphExecutor(AnimGraph& graph) _perGroupProcessCall[16] = (ProcessBoxHandler)&AnimGraphExecutor::ProcessGroupFunction; } -const Matrix* AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) +void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) { ASSERT(data.Parameters.Count() == _graph.Parameters.Count()); @@ -183,7 +185,6 @@ const Matrix* AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) ANIM_GRAPH_PROFILE_EVENT("Init"); // Prepare graph data for the evaluation - _skeletonBonesCount = skeleton.Bones.Count(); _skeletonNodesCount = skeleton.Nodes.Count(); _graphStack.Clear(); _graphStack.Push((Graph*)&_graph); @@ -263,23 +264,19 @@ const Matrix* AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) _data->RootMotion = animResult->RootMotion; } - // Calculate the final bones transformations - { - ANIM_GRAPH_PROFILE_EVENT("Final Pose"); - - _bonesTransformations.Resize(_skeletonBonesCount, false); - - for (int32 boneIndex = 0; boneIndex < _skeletonBonesCount; boneIndex++) - { - auto& bone = skeleton.Bones[boneIndex]; - _bonesTransformations[boneIndex] = bone.OffsetMatrix * _data->NodesPose[bone.NodeIndex]; - } - } - // Cleanup _data = nullptr; +} - return _bonesTransformations.Get(); +void AnimGraphExecutor::GetInputValue(Box* box, Value& result) +{ + result = eatBox(box->GetParent(), box->FirstConnection()); +} + +void AnimGraphExecutor::ResetBucket(int32 bucketIndex) +{ + auto& stateBucket = _data->State[bucketIndex]; + _graph._bucketInitializerList[bucketIndex](stateBucket); } void AnimGraphExecutor::ResetBuckets(AnimGraphBase* graph) diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 714f6e9b8..8ca4b7247 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -3,9 +3,9 @@ #pragma once #include "Engine/Visject/VisjectGraph.h" -#include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Content/Assets/Animation.h" #include "Engine/Animations/AlphaBlend.h" +#include "Engine/Core/Math/Matrix.h" #include "../Config.h" #define ANIM_GRAPH_PARAM_BASE_MODEL_ID Guid(1000, 0, 0, 0) @@ -21,6 +21,8 @@ class AnimSubGraph; class AnimGraphBase; class AnimGraphNode; class AnimGraphExecutor; +class SkinnedModel; +class SkeletonData; /// /// The root motion data container. Supports displacement and rotation (no scale component). @@ -777,22 +779,14 @@ public: /// /// Determines whether this graph is ready for the animation evaluation. /// - /// True if is ready and can be used for the animation evaluation, otherwise false. - bool IsReady() const - { - return BaseModel && BaseModel->IsLoaded(); - } + bool IsReady() const; /// /// Determines whether this graph can be used with the specified skeleton. /// /// The other skinned model to check. /// True if can perform the update, otherwise false. - bool CanUseWithSkeleton(SkinnedModel* other) const - { - // All data loaded and bones count the same - return IsReady() && other && other->IsLoaded() && other->Skeleton.Bones.Count() == BaseModel->Skeleton.Bones.Count(); - } + bool CanUseWithSkeleton(SkinnedModel* other) const; private: @@ -823,12 +817,10 @@ private: AnimGraph& _graph; float _deltaTime = 0.0f; uint64 _currentFrameIndex = 0; - int32 _skeletonBonesCount = 0; int32 _skeletonNodesCount = 0; RootMotionMode _rootMotionMode = RootMotionMode::NoExtraction; AnimGraphInstanceData* _data = nullptr; AnimGraphImpulse _emptyNodes; - Array _bonesTransformations; AnimGraphTransitionData _transitionData; Array> _callStack; Array> _graphStack; @@ -859,18 +851,13 @@ public: /// /// The instance data. /// The delta time (in seconds). - /// The pointer to the final bones structure as a result of the animation evaluation. - const Matrix* Update(AnimGraphInstanceData& data, float dt); + void Update(AnimGraphInstanceData& data, float dt); - void GetInputValue(Box* box, Value& result) - { - result = eatBox(box->GetParent(), box->FirstConnection()); - } + void GetInputValue(Box* box, Value& result); /// /// Gets the skeleton nodes transformations structure containing identity matrices. /// - /// The data. FORCE_INLINE const AnimGraphImpulse* GetEmptyNodes() const { return &_emptyNodes; @@ -920,11 +907,7 @@ public: /// Resets the state bucket. /// /// The zero-based index of the bucket. - FORCE_INLINE void ResetBucket(int32 bucketIndex) - { - auto& stateBucket = _data->State[bucketIndex]; - _graph._bucketInitializerList[bucketIndex](stateBucket); - } + void ResetBucket(int32 bucketIndex); /// /// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children). diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 1a26318a1..d7baadfc0 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -451,7 +451,69 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu // Get parameter int32 paramIndex; const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex); - value = param ? _data->Parameters[paramIndex].Value : Value::Null; + if (param) + { + value = _data->Parameters[paramIndex].Value; + switch (param->Type.Type) + { + case VariantType::Vector2: + switch (box->ID) + { + case 1: + case 2: + value = value.AsVector2().Raw[box->ID - 1]; + break; + } + break; + case VariantType::Vector3: + switch (box->ID) + { + case 1: + case 2: + case 3: + value = value.AsVector3().Raw[box->ID - 1]; + break; + } + break; + case VariantType::Vector4: + case VariantType::Color: + switch (box->ID) + { + case 1: + case 2: + case 3: + case 4: + value = value.AsVector4().Raw[box->ID - 1]; + break; + } + break; + case VariantType::Matrix: + { + auto& matrix = value.Type.Type == VariantType::Matrix && value.AsBlob.Data ? *(Matrix*)value.AsBlob.Data : Matrix::Identity; + switch (box->ID) + { + case 0: + value = matrix.GetRow1(); + break; + case 1: + value = matrix.GetRow2(); + break; + case 2: + value = matrix.GetRow3(); + break; + case 3: + value = matrix.GetRow4(); + break; + } + break; + } + } + } + else + { + // TODO: add warning that no parameter selected + value = Value::Zero; + } break; } default: @@ -574,7 +636,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu transform.Scale = (Vector3)tryGetValue(node->GetBox(4), Vector3::One); // Skip if no change will be performed - if (boneIndex < 0 || boneIndex >= _skeletonBonesCount || transformMode == BoneTransformMode::None || transform.IsIdentity()) + auto& skeleton = _graph.BaseModel->Skeleton; + if (boneIndex < 0 || boneIndex >= skeleton.Bones.Count() || transformMode == BoneTransformMode::None || (transformMode == BoneTransformMode::Add && transform.IsIdentity())) { // Pass through the input value = Value::Null; @@ -705,8 +768,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const auto copyScale = (bool)node->Values[4]; // Skip if no change will be performed - if (srcBoneIndex < 0 || srcBoneIndex >= _skeletonBonesCount || - dstBoneIndex < 0 || dstBoneIndex >= _skeletonBonesCount || + const auto& skeleton = _graph.BaseModel->Skeleton; + if (srcBoneIndex < 0 || srcBoneIndex >= skeleton.Bones.Count() || + dstBoneIndex < 0 || dstBoneIndex >= skeleton.Bones.Count() || !(copyTranslation || copyRotation || copyScale)) { // Pass through the input @@ -714,7 +778,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu box->Cache = value; return; } - const auto& skeleton = _graph.BaseModel->Skeleton; // Copy bone data Transform srcTransform = nodes->Nodes[skeleton.Bones[srcBoneIndex].NodeIndex]; @@ -738,7 +801,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const auto boneIndex = (int32)node->Values[0]; const auto& skeleton = _graph.BaseModel->Skeleton; const auto input = tryGetValue(node->GetBox(0), Value::Null); - if (ANIM_GRAPH_IS_VALID_PTR(input) && boneIndex >= 0 && boneIndex < _skeletonBonesCount) + if (ANIM_GRAPH_IS_VALID_PTR(input) && boneIndex >= 0 && boneIndex < skeleton.Bones.Count()) value = Variant(((AnimGraphImpulse*)input.AsPointer)->Nodes[skeleton.Bones[boneIndex].NodeIndex]); else value = Variant(Transform::Identity); @@ -1566,7 +1629,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu transform.Scale = (Vector3)tryGetValue(node->GetBox(4), Vector3::One); // Skip if no change will be performed - if (nodeIndex < 0 || nodeIndex >= _skeletonNodesCount || transformMode == BoneTransformMode::None || transform.IsIdentity()) + if (nodeIndex < 0 || nodeIndex >= _skeletonNodesCount || transformMode == BoneTransformMode::None || (transformMode == BoneTransformMode::Add && transform.IsIdentity())) { // Pass through the input value = Value::Null; diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp index 1693a8f4d..f669c72c2 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp @@ -8,7 +8,7 @@ #include "Engine/Audio/AudioClip.h" #include "Engine/Graphics/PostProcessSettings.h" -REGISTER_BINARY_ASSET(SceneAnimation, "FlaxEngine.SceneAnimation", nullptr, false); +REGISTER_BINARY_ASSET(SceneAnimation, "FlaxEngine.SceneAnimation", false); SceneAnimation::SceneAnimation(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index ea24d126f..7d1cf1a78 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -9,6 +9,7 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Audio/AudioClip.h" #include "Engine/Audio/AudioSource.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Script.h" @@ -1132,6 +1133,6 @@ void SceneAnimationPlayer::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h index d8934c126..53c311ff6 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Level/Actor.h" +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Content/AssetReference.h" #include "Engine/Level/Actors/PostFxVolume.h" #include "SceneAnimation.h" diff --git a/Source/Engine/Audio/Audio.Build.cs b/Source/Engine/Audio/Audio.Build.cs index c8ecda06d..6a99a11c2 100644 --- a/Source/Engine/Audio/Audio.Build.cs +++ b/Source/Engine/Audio/Audio.Build.cs @@ -48,8 +48,7 @@ public class Audio : EngineModule break; case TargetPlatform.Switch: options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Switch", "Engine", "Audio")); - //options.CompileEnv.PreprocessorDefinitions.Add("AUDIO_API_SWITCH"); // TODO: impl audio on switch - useNone = true; + options.CompileEnv.PreprocessorDefinitions.Add("AUDIO_API_SWITCH"); break; default: throw new InvalidPlatformException(options.Platform.Target); } diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 29dd2a153..3213850d3 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -4,6 +4,7 @@ #include "Audio.h" #include "AudioSource.h" #include "AudioBackend.h" +#include "Engine/Core/Log.h" #include "Engine/Content/Upgraders/AudioClipUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" @@ -12,7 +13,7 @@ #include "Engine/Tools/AudioTool/OggVorbisDecoder.h" #include "Engine/Tools/AudioTool/AudioTool.h" -REGISTER_BINARY_ASSET(AudioClip, "FlaxEngine.AudioClip", ::New(), false); +REGISTER_BINARY_ASSET_WITH_UPGRADER(AudioClip, "FlaxEngine.AudioClip", AudioClipUpgrader, false); bool AudioClip::StreamingTask::Run() { diff --git a/Source/Engine/Audio/AudioListener.cpp b/Source/Engine/Audio/AudioListener.cpp index 40e7bac3e..e9b1a11ab 100644 --- a/Source/Engine/Audio/AudioListener.cpp +++ b/Source/Engine/Audio/AudioListener.cpp @@ -63,7 +63,7 @@ void AudioListener::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (IsActiveInHierarchy()) diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index b07ea95ee..8969bc4d3 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -461,7 +461,7 @@ void AudioSource::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (IsActiveInHierarchy() && SourceIDs.HasItems()) diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index 89c8f3d02..374ab646f 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -18,7 +18,7 @@ #include #include -#define FLAX_POS_TO_OAL(vec) -vec.X * 0.01f, vec.Y * 0.01f, vec.Z * 0.01f +#define FLAX_POS_TO_OAL(vec) -vec.X * 0.01f, vec.Y * 0.01f, -vec.Z * 0.01f namespace ALC { diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp index aac3979ce..a287f40f2 100644 --- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp +++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp @@ -26,7 +26,7 @@ #define MAX_OUTPUT_CHANNELS 8 #define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS) -#define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(-vec.X * 0.01f, vec.Y * 0.01f, vec.Z * 0.01f) +#define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * 0.01f, vec.Y * 0.01f, vec.Z * 0.01f) #define FLAX_VEC_TO_XAUDIO(vec) (*((X3DAUDIO_VECTOR*)&vec)) namespace XAudio2 diff --git a/Source/Engine/CSG/CSGBuilder.cpp b/Source/Engine/CSG/CSGBuilder.cpp index b62f2a2f0..ca32862d7 100644 --- a/Source/Engine/CSG/CSGBuilder.cpp +++ b/Source/Engine/CSG/CSGBuilder.cpp @@ -7,6 +7,7 @@ #include "Engine/Level/SceneQuery.h" #include "Engine/Level/Actor.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" diff --git a/Source/Engine/CSG/HalfEdge.h b/Source/Engine/CSG/HalfEdge.h index 3a054aff0..e9bd38a34 100644 --- a/Source/Engine/CSG/HalfEdge.h +++ b/Source/Engine/CSG/HalfEdge.h @@ -2,6 +2,8 @@ #pragma once +#include "Engine/Core/Types/BaseTypes.h" + namespace CSG { /// diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 5f11cfb54..7fcd2f405 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -38,9 +38,10 @@ void Asset::OnDeleteObject() if (!IsInternalType()) Content::AssetDisposing(this); - // Cache data const bool wasMarkedToDelete = _deleteFileOnUnload != 0; +#if USE_EDITOR const String path = wasMarkedToDelete ? GetPath() : String::Empty; +#endif const Guid id = GetID(); // Fire unload event (every object referencing this asset or it's data should release reference so later actions are safe) @@ -66,7 +67,7 @@ void Asset::OnDeleteObject() // Base (after it `this` is invalid) ManagedScriptingObject::OnDeleteObject(); - // Check if asset was marked to delete +#if USE_EDITOR if (wasMarkedToDelete) { LOG(Info, "Deleting asset '{0}':{1}.", path, id.ToString()); @@ -77,6 +78,7 @@ void Asset::OnDeleteObject() // Delete file Content::deleteFileSafety(path, id); } +#endif } void Asset::CreateManaged() @@ -131,6 +133,20 @@ void Asset::ChangeID(const Guid& newId) CRASH; } +bool Asset::LastLoadFailed() const +{ + return _loadFailed != 0; +} + +#if USE_EDITOR + +bool Asset::ShouldDeleteFileOnUnload() const +{ + return _deleteFileOnUnload != 0; +} + +#endif + void Asset::Reload() { // It's better to call it from the main thread @@ -225,7 +241,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) while (!Engine::ShouldExit()) { // Try to execute content tasks - while (task->IsQueued()) + while (task->IsQueued() && !Engine::ShouldExit()) { // Pick this task from the queue ContentLoadTask* tmp; @@ -335,13 +351,21 @@ void Asset::startLoading() bool Asset::onLoad(LoadAssetTask* task) { // It may fail when task is cancelled and new one is created later (don't crash but just end with an error) - if (task->GetAsset() != this || _loadingTask == nullptr) + if (task->Asset.Get() != this || _loadingTask == nullptr) return true; Locker.Lock(); // Load asset - const LoadResult result = loadAsset(); + LoadResult result; + { +#if TRACY_ENABLE + ZoneScoped; + const StringView name(GetPath()); + ZoneName(*name, name.Length()); +#endif + result = loadAsset(); + } const bool isLoaded = result == LoadResult::Ok; const bool failed = !isLoaded; _loadFailed = failed; diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index fb62e659c..a831ee773 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -82,7 +82,6 @@ public: /// /// Gets asset's reference count. Asset will be automatically unloaded when this reaches zero. /// - /// The amount of references to that asset. API_PROPERTY() int32 GetReferencesCount() const { return (int32)Platform::AtomicRead(const_cast(&_refCount)); @@ -107,21 +106,18 @@ public: public: /// - /// Gets the path to the asset storage. + /// Gets the path to the asset storage file. In Editor it reflects the actual file, in cooked Game, it fakes the Editor path to be informative for developers. /// - /// The asset file. API_PROPERTY() virtual const String& GetPath() const = 0; /// /// Gets the asset type name. /// - /// The typename. virtual const String& GetTypeName() const = 0; /// /// Returns true if asset is loaded, otherwise false. /// - /// true if this asset is loaded; otherwise, false. API_PROPERTY() FORCE_INLINE bool IsLoaded() const { return _isLoaded != 0; @@ -130,16 +126,11 @@ public: /// /// Returns true if last asset loading failed, otherwise false. /// - /// true if last asset loading failed; otherwise, false. - API_PROPERTY() FORCE_INLINE bool LastLoadFailed() const - { - return _loadFailed != 0; - } + API_PROPERTY() bool LastLoadFailed() const; /// /// Determines whether this asset is virtual (generated or temporary, has no storage so it won't be saved). /// - /// true if this asset is virtual; otherwise, false. API_PROPERTY() FORCE_INLINE bool IsVirtual() const { return _isVirtual != 0; @@ -150,11 +141,7 @@ public: /// /// Determines whether this asset was marked to be deleted on unload. /// - /// true if this asset file was marked to be deleted on asset unload; otherwise, false. - API_PROPERTY() FORCE_INLINE bool ShouldDeleteFileOnUnload() const - { - return _deleteFileOnUnload != 0; - } + API_PROPERTY() bool ShouldDeleteFileOnUnload() const; #endif diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h index 40f57337d..0956e82be 100644 --- a/Source/Engine/Content/AssetReference.h +++ b/Source/Engine/Content/AssetReference.h @@ -274,3 +274,9 @@ public: OnSet(asset); } }; + +template +uint32 GetHash(const AssetReference& key) +{ + return GetHash(key.GetID()); +} diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 963ddaceb..d768eabf0 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -11,7 +11,7 @@ #include "Engine/Serialization/MemoryWriteStream.h" #endif -REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", nullptr, false); +REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false); Animation::Animation(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Content/Assets/AnimationGraph.cpp b/Source/Engine/Content/Assets/AnimationGraph.cpp index 2c0a1c803..7106fb0e0 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.cpp +++ b/Source/Engine/Content/Assets/AnimationGraph.cpp @@ -9,7 +9,7 @@ #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" -REGISTER_BINARY_ASSET(AnimationGraph, "FlaxEngine.AnimationGraph", nullptr, false); +REGISTER_BINARY_ASSET(AnimationGraph, "FlaxEngine.AnimationGraph", false); AnimationGraph::AnimationGraph(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) @@ -74,7 +74,7 @@ BytesContainer AnimationGraph::LoadSurface() return result; } - LOG(Warning, "Animation Graph \'{0}\' surface data is missing.", GetPath()); + LOG(Warning, "Animation Graph \'{0}\' surface data is missing.", ToString()); return BytesContainer(); } diff --git a/Source/Engine/Content/Assets/AnimationGraphFunction.cpp b/Source/Engine/Content/Assets/AnimationGraphFunction.cpp index 7674d93bc..69e611cd4 100644 --- a/Source/Engine/Content/Assets/AnimationGraphFunction.cpp +++ b/Source/Engine/Content/Assets/AnimationGraphFunction.cpp @@ -6,7 +6,7 @@ #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" -REGISTER_BINARY_ASSET(AnimationGraphFunction, "FlaxEngine.AnimationGraphFunction", nullptr, false); +REGISTER_BINARY_ASSET(AnimationGraphFunction, "FlaxEngine.AnimationGraphFunction", false); AnimationGraphFunction::AnimationGraphFunction(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Content/Assets/CubeTexture.cpp b/Source/Engine/Content/Assets/CubeTexture.cpp index 084955a88..1b9095cab 100644 --- a/Source/Engine/Content/Assets/CubeTexture.cpp +++ b/Source/Engine/Content/Assets/CubeTexture.cpp @@ -4,7 +4,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/TextureAssetUpgrader.h" -REGISTER_BINARY_ASSET(CubeTexture, "FlaxEngine.CubeTexture", ::New(), true); +REGISTER_BINARY_ASSET_WITH_UPGRADER(CubeTexture, "FlaxEngine.CubeTexture", TextureAssetUpgrader, true); CubeTexture::CubeTexture(const SpawnParams& params, const AssetInfo* info) : TextureBase(params, info) diff --git a/Source/Engine/Content/Assets/IESProfile.cpp b/Source/Engine/Content/Assets/IESProfile.cpp index 34f0c59c1..1c1d340be 100644 --- a/Source/Engine/Content/Assets/IESProfile.cpp +++ b/Source/Engine/Content/Assets/IESProfile.cpp @@ -4,7 +4,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/TextureAssetUpgrader.h" -REGISTER_BINARY_ASSET(IESProfile, "FlaxEngine.IESProfile", ::New(), false); +REGISTER_BINARY_ASSET_WITH_UPGRADER(IESProfile, "FlaxEngine.IESProfile", TextureAssetUpgrader, false); IESProfile::IESProfile(const SpawnParams& params, const AssetInfo* info) : TextureBase(params, info) diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 4d1e5d283..2be5a6b61 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -16,6 +16,9 @@ #include "Engine/Utilities/Encryption.h" #include "Engine/Tools/MaterialGenerator/MaterialGenerator.h" #include "Engine/ShadersCompilation/Config.h" +#if BUILD_DEBUG +#include "Engine/Engine/Globals.h" +#endif #endif /// @@ -23,7 +26,7 @@ /// #define MATERIAL_AUTO_GENERATE_MISSING_SOURCE (USE_EDITOR) -REGISTER_BINARY_ASSET(Material, "FlaxEngine.Material", ::New(), false); +REGISTER_BINARY_ASSET_WITH_UPGRADER(Material, "FlaxEngine.Material", ShaderAssetUpgrader, false); Material::Material(const SpawnParams& params, const AssetInfo* info) : ShaderAssetTypeBase(params, info) @@ -116,7 +119,7 @@ Asset::LoadResult Material::load() FlaxChunk* materialParamsChunk; // Special case for Null renderer - if (GPUDevice::Instance->GetRendererType() == RendererType::Null) + if (IsNullRenderer()) { // Hack loading MemoryReadStream shaderCacheStream(nullptr, 0); @@ -152,18 +155,16 @@ Asset::LoadResult Material::load() // - If material version is not supported then material cannot be loaded #if COMPILE_WITH_SHADER_COMPILER -#if BUILD_DEBUG - // Materials force reload! - Globals::ConvertLoadedMaterialsByForce = false; -#endif - // Check if current engine has different materials version or convert it by force or has no source generated at all if (_shaderHeader.Material.GraphVersion != MATERIAL_GRAPH_VERSION - || Globals::ConvertLoadedMaterialsByForce #if MATERIAL_AUTO_GENERATE_MISSING_SOURCE || !HasChunk(SHADER_FILE_CHUNK_SOURCE) #endif || HasDependenciesModified() +#if COMPILE_WITH_DEV_ENV + // Set to true to enable force GPU shader regeneration (don't commit it) + || false +#endif ) { // Prepare @@ -310,7 +311,12 @@ Asset::LoadResult Material::load() { // Load material (load shader from cache, load params, setup pipeline stuff) MemoryReadStream shaderCacheStream(shaderCache.Data.Get(), shaderCache.Data.Length()); - _materialShader = MaterialShader::Create(GetPath(), shaderCacheStream, _shaderHeader.Material.Info); +#if GPU_ENABLE_RESOURCE_NAMING + const StringView name(GetPath()); +#else + const StringView name; +#endif + _materialShader = MaterialShader::Create(name, shaderCacheStream, _shaderHeader.Material.Info); if (_materialShader == nullptr) { LOG(Warning, "Cannot load material."); @@ -482,7 +488,7 @@ BytesContainer Material::LoadSurface(bool createDefaultIfMissing) } } - LOG(Warning, "Material \'{0}\' surface data is missing.", GetPath()); + LOG(Warning, "Material \'{0}\' surface data is missing.", ToString()); #if COMPILE_WITH_MATERIAL_GRAPH diff --git a/Source/Engine/Content/Assets/MaterialFunction.cpp b/Source/Engine/Content/Assets/MaterialFunction.cpp index 97af31960..ca3cc58fb 100644 --- a/Source/Engine/Content/Assets/MaterialFunction.cpp +++ b/Source/Engine/Content/Assets/MaterialFunction.cpp @@ -8,7 +8,7 @@ #endif #include "Engine/Content/Factories/BinaryAssetFactory.h" -REGISTER_BINARY_ASSET(MaterialFunction, "FlaxEngine.MaterialFunction", nullptr, false); +REGISTER_BINARY_ASSET(MaterialFunction, "FlaxEngine.MaterialFunction", false); MaterialFunction::MaterialFunction(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 874f607ce..5ea8c9351 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -7,7 +7,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Serialization/MemoryReadStream.h" -REGISTER_BINARY_ASSET(MaterialInstance, "FlaxEngine.MaterialInstance", ::New(), true); +REGISTER_BINARY_ASSET_WITH_UPGRADER(MaterialInstance, "FlaxEngine.MaterialInstance", MaterialInstanceUpgrader, true); MaterialInstance::MaterialInstance(const SpawnParams& params, const AssetInfo* info) : MaterialBase(params, info) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 8cdd0601e..dd9c484e1 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -8,9 +8,11 @@ #include "Engine/Content/Upgraders/ModelAssetUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Models/ModelInstanceEntry.h" #include "Engine/Streaming/StreamingGroup.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Renderer/DrawCall.h" #if GPU_ENABLE_ASYNC_RESOURCES_CREATION #include "Engine/Threading/ThreadPoolTask.h" #define STREAM_TASK_BASE ThreadPoolTask @@ -113,7 +115,7 @@ protected: } }; -REGISTER_BINARY_ASSET(Model, "FlaxEngine.Model", ::New(), true); +REGISTER_BINARY_ASSET_WITH_UPGRADER(Model, "FlaxEngine.Model", ModelAssetUpgrader, true); Model::Model(const SpawnParams& params, const AssetInfo* info) : ModelBase(params, info, StreamingGroups::Instance()->Models()) @@ -396,21 +398,21 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) auto& meshData = meshesData[meshIndex]; // Vertex Buffer 0 (required) - auto task = mesh.ExtractDataAsync(MeshBufferType::Vertex0, meshData.VB0); + auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0); if (task == nullptr) return true; task->Start(); tasks.Add(task); // Vertex Buffer 1 (required) - task = mesh.ExtractDataAsync(MeshBufferType::Vertex1, meshData.VB1); + task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB1); if (task == nullptr) return true; task->Start(); tasks.Add(task); // Vertex Buffer 2 (optional) - task = mesh.ExtractDataAsync(MeshBufferType::Vertex2, meshData.VB2); + task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB2); if (task) { task->Start(); @@ -418,7 +420,7 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) } // Index Buffer (required) - task = mesh.ExtractDataAsync(MeshBufferType::Index, meshData.IB); + task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB); if (task == nullptr) return true; task->Start(); @@ -618,6 +620,19 @@ void Model::SetupMaterialSlots(int32 slotsCount) } } +int32 Model::GetLODsCount() const +{ + return LODs.Count(); +} + +void Model::GetMeshes(Array& meshes, int32 lodIndex) +{ + auto& lod = LODs[lodIndex]; + meshes.Resize(lod.Meshes.Count()); + for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) + meshes[meshIndex] = &lod.Meshes[meshIndex]; +} + void Model::InitAsVirtual() { // Init with a single LOD and one mesh @@ -628,6 +643,21 @@ void Model::InitAsVirtual() BinaryAsset::InitAsVirtual(); } +#if USE_EDITOR + +void Model::GetReferences(Array& output) const +{ + // Base + BinaryAsset::GetReferences(output); + + for (int32 i = 0; i < MaterialSlots.Count(); i++) + { + output.Add(MaterialSlots[i].Material.GetID()); + } +} + +#endif + int32 Model::GetMaxResidency() const { return LODs.Count(); @@ -791,7 +821,7 @@ Asset::LoadResult Model::load() const auto thisSS = LODs[lodIndex].ScreenSize; if (prevSS <= thisSS) { - LOG(Warning, "Model LOD {0} has invalid screen size compared to LOD {1} (asset: {2})", lodIndex, lodIndex - 1, GetPath()); + LOG(Warning, "Model LOD {0} has invalid screen size compared to LOD {1} (asset: {2})", lodIndex, lodIndex - 1, ToString()); } } #endif @@ -837,3 +867,33 @@ AssetChunksFlag Model::getChunksToPreload() const // Note: we don't preload any LODs here because it's done by the Streaming Manager return GET_CHUNK_FLAG(0); } + +void ModelBase::SetupMaterialSlots(int32 slotsCount) +{ + CHECK(slotsCount >= 0 && slotsCount < 4096); + if (!IsVirtual() && WaitForLoaded()) + return; + + ScopeLock lock(Locker); + + const int32 prevCount = MaterialSlots.Count(); + MaterialSlots.Resize(slotsCount, false); + + // Initialize slot names + for (int32 i = prevCount; i < slotsCount; i++) + MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1); +} + +MaterialSlot* ModelBase::GetSlot(const StringView& name) +{ + MaterialSlot* result = nullptr; + for (auto& slot : MaterialSlots) + { + if (slot.Name == name) + { + result = &slot; + break; + } + } + return result; +} diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index e96b47d90..62a2dc6a4 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -55,15 +55,6 @@ public: return LODs.HasItems(); } - /// - /// Gets amount of the level of details in the model - /// - /// Amount of the level of details in the model - FORCE_INLINE int32 GetLODsCount() const - { - return LODs.Count(); - } - /// /// Gets the amount of loaded model LODs. /// @@ -237,18 +228,11 @@ public: // [ModelBase] void SetupMaterialSlots(int32 slotsCount) override; + int32 GetLODsCount() const override; + void GetMeshes(Array& meshes, int32 lodIndex = 0) override; void InitAsVirtual() override; #if USE_EDITOR - void GetReferences(Array& output) const override - { - // Base - BinaryAsset::GetReferences(output); - - for (int32 i = 0; i < MaterialSlots.Count(); i++) - { - output.Add(MaterialSlots[i].Material.GetID()); - } - } + void GetReferences(Array& output) const override; #endif // [StreamableResource] diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index 89805f3c0..527a7ac61 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -7,6 +7,8 @@ #include "Engine/Graphics/Models/MaterialSlot.h" #include "Engine/Streaming/StreamableResource.h" +class MeshBase; + /// /// Base class for asset types that can contain a model resource. /// @@ -44,38 +46,23 @@ public: /// /// Resizes the material slots collection. Updates meshes that were using removed slots. /// - API_FUNCTION() virtual void SetupMaterialSlots(int32 slotsCount) - { - CHECK(slotsCount >= 0 && slotsCount < 4096); - if (!IsVirtual() && WaitForLoaded()) - return; - - ScopeLock lock(Locker); - - const int32 prevCount = MaterialSlots.Count(); - MaterialSlots.Resize(slotsCount, false); - - // Initialize slot names - for (int32 i = prevCount; i < slotsCount; i++) - MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1); - } + API_FUNCTION() virtual void SetupMaterialSlots(int32 slotsCount); /// /// Gets the material slot by the name. /// /// The slot name. /// The material slot with the given name or null if cannot find it (asset may be not loaded yet). - API_FUNCTION() MaterialSlot* GetSlot(const StringView& name) - { - MaterialSlot* result = nullptr; - for (auto& slot : MaterialSlots) - { - if (slot.Name == name) - { - result = &slot; - break; - } - } - return result; - } + API_FUNCTION() MaterialSlot* GetSlot(const StringView& name); + + /// + /// Gets amount of the level of details in the model. + /// + /// Amount of the level of details in the model. + virtual int32 GetLODsCount() const = 0; + + /// + /// Gets the meshes for a particular LOD index. + /// + virtual void GetMeshes(Array& meshes, int32 lodIndex = 0) = 0; }; diff --git a/Source/Engine/Content/Assets/RawDataAsset.cpp b/Source/Engine/Content/Assets/RawDataAsset.cpp index f93395579..98b9e3251 100644 --- a/Source/Engine/Content/Assets/RawDataAsset.cpp +++ b/Source/Engine/Content/Assets/RawDataAsset.cpp @@ -4,7 +4,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Platform/FileSystem.h" -REGISTER_BINARY_ASSET(RawDataAsset, "FlaxEngine.RawDataAsset", nullptr, true); +REGISTER_BINARY_ASSET(RawDataAsset, "FlaxEngine.RawDataAsset", true); RawDataAsset::RawDataAsset(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Content/Assets/Shader.cpp b/Source/Engine/Content/Assets/Shader.cpp index 929abc933..3f54eadbf 100644 --- a/Source/Engine/Content/Assets/Shader.cpp +++ b/Source/Engine/Content/Assets/Shader.cpp @@ -2,11 +2,13 @@ #include "Shader.h" #include "Engine/Core/Log.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Content/Upgraders/ShaderAssetUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Serialization/MemoryReadStream.h" -REGISTER_BINARY_ASSET(Shader, "FlaxEngine.Shader", ::New(), false); +REGISTER_BINARY_ASSET_WITH_UPGRADER(Shader, "FlaxEngine.Shader", ShaderAssetUpgrader, false); Shader::Shader(const SpawnParams& params, const AssetInfo* info) : ShaderAssetTypeBase(params, info) @@ -25,7 +27,7 @@ Shader::~Shader() Asset::LoadResult Shader::load() { // Special case for Null renderer that doesn't need shaders - if (GPUDevice::Instance->GetRendererType() == RendererType::Null) + if (IsNullRenderer()) { return LoadResult::Ok; } diff --git a/Source/Engine/Content/Assets/Shader.h b/Source/Engine/Content/Assets/Shader.h index b97aafd98..9efdf8d6d 100644 --- a/Source/Engine/Content/Assets/Shader.h +++ b/Source/Engine/Content/Assets/Shader.h @@ -3,9 +3,10 @@ #pragma once #include "../BinaryAsset.h" -#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/Cache/ShaderAssetBase.h" +class GPUShader; + /// /// The shader asset. Contains a program that runs on the GPU and is able to perform rendering calculation using textures, vertices and other resources. /// @@ -33,7 +34,6 @@ public: /// /// Gets the GPU shader object. /// - /// The GPU shader object. FORCE_INLINE GPUShader* GetShader() const { return GPU; diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp index 0742e2951..27529df4d 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.cpp +++ b/Source/Engine/Content/Assets/SkeletonMask.cpp @@ -7,7 +7,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/SkeletonMaskUpgrader.h" -REGISTER_BINARY_ASSET(SkeletonMask, "FlaxEngine.SkeletonMask", ::New(), true); +REGISTER_BINARY_ASSET_WITH_UPGRADER(SkeletonMask, "FlaxEngine.SkeletonMask", SkeletonMaskUpgrader, true); SkeletonMask::SkeletonMask(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index dfccfec19..b98ba2e57 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -7,12 +7,14 @@ #include "Engine/Streaming/StreamingGroup.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Models/ModelInstanceEntry.h" #include "Engine/Graphics/Models/Config.h" #include "Engine/Content/WeakAssetReference.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Renderer/DrawCall.h" #define CHECK_INVALID_BUFFER(buffer) \ if (buffer->IsValidFor(this) == false) \ @@ -106,7 +108,7 @@ protected: } }; -REGISTER_BINARY_ASSET(SkinnedModel, "FlaxEngine.SkinnedModel", ::New(), true); +REGISTER_BINARY_ASSET_WITH_UPGRADER(SkinnedModel, "FlaxEngine.SkinnedModel", SkinnedModelAssetUpgrader, true); SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info) : ModelBase(params, info, StreamingGroups::Instance()->SkinnedModels()) @@ -120,6 +122,11 @@ SkinnedModel::~SkinnedModel() ASSERT(_streamingTask == nullptr); } +bool SkinnedModel::HasAnyLODInitialized() const +{ + return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized(); +} + Array SkinnedModel::GetBlendShapes() { Array result; @@ -137,6 +144,18 @@ Array SkinnedModel::GetBlendShapes() return result; } +ContentLoadTask* SkinnedModel::RequestLODDataAsync(int32 lodIndex) +{ + const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex); + return RequestChunkDataAsync(chunkIndex); +} + +void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const +{ + const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex); + GetChunkData(chunkIndex, data); +} + bool SkinnedModel::Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex) { return LODs[lodIndex].Intersects(ray, world, distance, normal, mesh); @@ -389,7 +408,7 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path) { auto& slot = MaterialSlots[materialSlotIndex]; - const auto id =slot.Material.GetID(); + const auto id = slot.Material.GetID(); stream->Write(&id); stream->WriteByte(static_cast(slot.ShadowsMode)); stream->WriteString(slot.Name, 11); @@ -505,14 +524,14 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path) auto& meshData = meshesData[meshIndex]; // Vertex Buffer 0 (required) - auto task = mesh.DownloadDataAsyncGPU(MeshBufferType::Vertex0, meshData.VB0); + auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0); if (task == nullptr) return true; task->Start(); tasks.Add(task); // Index Buffer (required) - task = mesh.DownloadDataAsyncGPU(MeshBufferType::Index, meshData.IB); + task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB); if (task == nullptr) return true; task->Start(); @@ -701,6 +720,19 @@ void SkinnedModel::SetupMaterialSlots(int32 slotsCount) } } +int32 SkinnedModel::GetLODsCount() const +{ + return LODs.Count(); +} + +void SkinnedModel::GetMeshes(Array& meshes, int32 lodIndex) +{ + auto& lod = LODs[lodIndex]; + meshes.Resize(lod.Meshes.Count()); + for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) + meshes[meshIndex] = &lod.Meshes[meshIndex]; +} + void SkinnedModel::InitAsVirtual() { // Init with one mesh and single bone @@ -723,6 +755,21 @@ void SkinnedModel::InitAsVirtual() BinaryAsset::InitAsVirtual(); } +#if USE_EDITOR + +void SkinnedModel::GetReferences(Array& output) const +{ + // Base + BinaryAsset::GetReferences(output); + + for (int32 i = 0; i < MaterialSlots.Count(); i++) + { + output.Add(MaterialSlots[i].Material.GetID()); + } +} + +#endif + int32 SkinnedModel::GetMaxResidency() const { return LODs.Count(); diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index ce5f5f7ac..08cbf23b0 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -61,19 +61,9 @@ public: return LODs.HasItems(); } - /// - /// Gets amount of the level of details in the model - /// - /// Amount of the level of details in the model - FORCE_INLINE int32 GetLODsCount() const - { - return LODs.Count(); - } - /// /// Gets the amount of loaded model LODs. /// - /// Loaded LODs count API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const { return _loadedLODs; @@ -102,7 +92,6 @@ public: /// /// Gets index of the highest resident LOD (may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality) /// - /// LOD index FORCE_INLINE int32 HighestResidentLODIndex() const { return GetLODsCount() - _loadedLODs; @@ -112,10 +101,7 @@ public: /// Determines whether any LOD has been initialized. /// /// True if any LOD has been initialized, otherwise false. - FORCE_INLINE bool HasAnyLODInitialized() const - { - return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized(); - } + bool HasAnyLODInitialized() const; /// /// Determines whether this model can be rendered. @@ -184,22 +170,14 @@ public: /// /// Index of the LOD. /// Task that will gather chunk data or null if already here. - ContentLoadTask* RequestLODDataAsync(int32 lodIndex) - { - const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex); - return RequestChunkDataAsync(chunkIndex); - } + ContentLoadTask* RequestLODDataAsync(int32 lodIndex); /// /// Gets the model LOD data (links bytes). /// /// Index of the LOD. /// The data (may be missing if failed to get it). - void GetLODData(int32 lodIndex, BytesContainer& data) const - { - const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex); - GetChunkData(chunkIndex, data); - } + void GetLODData(int32 lodIndex, BytesContainer& data) const; public: @@ -300,18 +278,11 @@ public: // [ModelBase] void SetupMaterialSlots(int32 slotsCount) override; + int32 GetLODsCount() const override; + void GetMeshes(Array& meshes, int32 lodIndex = 0) override; void InitAsVirtual() override; #if USE_EDITOR - void GetReferences(Array& output) const override - { - // Base - BinaryAsset::GetReferences(output); - - for (int32 i = 0; i < MaterialSlots.Count(); i++) - { - output.Add(MaterialSlots[i].Material.GetID()); - } - } + void GetReferences(Array& output) const override; #endif // [StreamableResource] diff --git a/Source/Engine/Content/Assets/Texture.cpp b/Source/Engine/Content/Assets/Texture.cpp index 22c86028a..23dbff0e2 100644 --- a/Source/Engine/Content/Assets/Texture.cpp +++ b/Source/Engine/Content/Assets/Texture.cpp @@ -10,7 +10,7 @@ #include "Engine/Scripting/MainThreadManagedInvokeAction.h" #include "Engine/Tools/TextureTool/TextureTool.h" -REGISTER_BINARY_ASSET(Texture, "FlaxEngine.Texture", ::New(), true); +REGISTER_BINARY_ASSET_WITH_UPGRADER(Texture, "FlaxEngine.Texture", TextureAssetUpgrader, true); Texture::Texture(const SpawnParams& params, const AssetInfo* info) : TextureBase(params, info) diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 5fd9d6f3f..2da76a840 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -17,6 +17,7 @@ #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/JsonWriter.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Utilities/StringConverter.h" #include "FlaxEngine.Gen.h" @@ -1276,7 +1277,7 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val } } -REGISTER_BINARY_ASSET(VisualScript, "FlaxEngine.VisualScript", nullptr, false); +REGISTER_BINARY_ASSET(VisualScript, "FlaxEngine.VisualScript", false); VisualScript::VisualScript(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) @@ -1329,6 +1330,7 @@ Asset::LoadResult VisualScript::load() { case GRAPH_NODE_MAKE_TYPE(16, 3): { + // Override method auto& method = _methods.AddOne(); method.Script = this; method.Node = &node; @@ -1342,6 +1344,7 @@ Asset::LoadResult VisualScript::load() } case GRAPH_NODE_MAKE_TYPE(16, 6): { + // Function auto& method = _methods.AddOne(); method.Script = this; method.Node = &node; @@ -1380,6 +1383,17 @@ Asset::LoadResult VisualScript::load() } } } +#if COMPILE_WITH_PROFILER + for (auto& method : _methods) + { + const StringView assetName(StringUtils::GetFileNameWithoutExtension(GetPath())); + method.ProfilerName.Resize(assetName.Length() + 2 + method.Name.Length()); + StringUtils::ConvertUTF162ANSI(assetName.Get(), method.ProfilerName.Get(), assetName.Length()); + 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()); + } +#endif // Setup fields list _fields.Resize(Graph.Parameters.Count()); @@ -2132,7 +2146,7 @@ BytesContainer VisualScript::LoadSurface() return result; } - LOG(Warning, "\'{0}\' surface data is missing.", GetPath()); + LOG(Warning, "\'{0}\' surface data is missing.", ToString()); return BytesContainer(); } @@ -2284,6 +2298,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); // Add to the calling stack ScopeContext scope; diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 6beeca5cc..1a1201191 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -115,6 +115,9 @@ public: MethodFlags MethodFlags; ScriptingTypeMethodSignature Signature; Array> ParamNames; +#if COMPILE_WITH_PROFILER + StringAnsi ProfilerName; +#endif }; struct Field diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 1dbc672f2..ef22bd3c9 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "BinaryAsset.h" +#include "Cache/AssetsCache.h" #include "Storage/ContentStorageManager.h" #include "Loading/Tasks/LoadAssetDataTask.h" #include "Engine/ContentImporters/AssetsImportingManager.h" @@ -131,6 +132,13 @@ void BinaryAsset::GetImportMetadata(String& path, String& username) const } } +String BinaryAsset::GetImportPath() const +{ + String path, username; + GetImportMetadata(path, username); + return path; +} + void BinaryAsset::ClearDependencies() { for (auto& e : Dependencies) @@ -293,7 +301,12 @@ bool BinaryAsset::LoadChunks(AssetChunksFlag chunks) #if USE_EDITOR -bool BinaryAsset::SaveAsset(const StringView& path, AssetInitData& data, bool silentMode) +bool BinaryAsset::SaveAsset(AssetInitData& data, bool silentMode) const +{ + return SaveAsset(GetPath(), data, silentMode); +} + +bool BinaryAsset::SaveAsset(const StringView& path, AssetInitData& data, bool silentMode) const { data.Header = _header; data.Metadata.Link(Metadata); @@ -303,9 +316,13 @@ bool BinaryAsset::SaveAsset(const StringView& path, AssetInitData& data, bool si bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool silentMode) { + // Ensure path is in a valid format + String pathNorm(path); + FileSystem::NormalizePath(pathNorm); + // Find target storage container and the asset - auto storage = ContentStorageManager::TryGetStorage(path); - auto asset = Content::GetAsset(path); + auto storage = ContentStorageManager::TryGetStorage(pathNorm); + auto asset = Content::GetAsset(pathNorm); auto binaryAsset = dynamic_cast(asset); if (asset && !binaryAsset) { @@ -351,8 +368,8 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool } else { - ASSERT(path.HasChars()); - result = FlaxStorage::Create(path, data, silentMode); + ASSERT(pathNorm.HasChars()); + result = FlaxStorage::Create(pathNorm, data, silentMode); } if (binaryAsset) binaryAsset->_isSaving = false; @@ -429,7 +446,12 @@ void BinaryAsset::OnDeleteObject() const String& BinaryAsset::GetPath() const { +#if USE_EDITOR return Storage ? Storage->GetPath() : String::Empty; +#else + // In build all assets are packed into packages so use ID for original path lookup + return Content::GetRegistry()->GetEditorAssetPath(_id); +#endif } /// @@ -487,7 +509,6 @@ protected: return Result::Ok; } - void OnEnd() override { _dataLock.Release(); diff --git a/Source/Engine/Content/BinaryAsset.h b/Source/Engine/Content/BinaryAsset.h index c533399d2..207faadfa 100644 --- a/Source/Engine/Content/BinaryAsset.h +++ b/Source/Engine/Content/BinaryAsset.h @@ -109,13 +109,7 @@ public: /// /// Gets the imported file path from the asset metadata (can be empty if not available). /// - /// The imported source file path. - API_PROPERTY() String GetImportPath() const - { - String path, username; - GetImportMetadata(path, username); - return path; - } + API_PROPERTY() String GetImportPath() const; /// /// Clears the asset dependencies list and unregisters from tracking their changes. @@ -131,7 +125,6 @@ public: /// /// Determines whether any of the dependency assets was modified after last modification time of this asset (last file write time check). /// - /// true if one or more dependencies were modified; otherwise, false. bool HasDependenciesModified() const; protected: @@ -277,10 +270,7 @@ public: /// Asset data. /// In silent mode don't reload opened storage container that is using target file. /// True if failed, otherwise false. - FORCE_INLINE bool SaveAsset(AssetInitData& data, bool silentMode = false) - { - return SaveAsset(GetPath(), data, silentMode); - } + bool SaveAsset(AssetInitData& data, bool silentMode = false) const; /// /// Saves this asset to the file. @@ -289,7 +279,7 @@ public: /// Asset path (will be used to override the asset or create a new one). /// In silent mode don't reload opened storage container that is using target file. /// True if failed, otherwise false. - bool SaveAsset(const StringView& path, AssetInitData& data, bool silentMode = false); + bool SaveAsset(const StringView& path, AssetInitData& data, bool silentMode = false) const; /// /// Saves asset data to the storage container. Asset unique ID is handled by auto. diff --git a/Source/Engine/Content/Cache/AssetsCache.cpp b/Source/Engine/Content/Cache/AssetsCache.cpp index 71faef533..35863be60 100644 --- a/Source/Engine/Content/Cache/AssetsCache.cpp +++ b/Source/Engine/Content/Cache/AssetsCache.cpp @@ -3,6 +3,7 @@ #include "AssetsCache.h" #include "Engine/Core/Log.h" #include "Engine/Core/DeleteMe.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Serialization/FileReadStream.h" @@ -10,12 +11,12 @@ #include "Engine/Content/Storage/ContentStorageManager.h" #include "Engine/Content/Storage/JsonStorageProxy.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Engine/Globals.h" #include "FlaxEngine.Gen.h" AssetsCache::AssetsCache() : _isDirty(false) , _registry(4096) - , _pathsMapping(256) { } @@ -91,8 +92,8 @@ void AssetsCache::Init() #if ENABLE_ASSETS_DISCOVERY stream->Read(&e.FileModified); #else - DateTime tmp1; - stream->Read(&tmp1); + DateTime tmp1; + stream->Read(&tmp1); #endif if (flags & AssetsCacheFlags::RelativePaths && e.Info.Path.HasChars()) @@ -207,7 +208,7 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa #if ENABLE_ASSETS_DISCOVERY stream->Write(&e.FileModified); #else - stream->WriteInt64(0); + stream->WriteInt64(0); #endif index++; @@ -231,6 +232,21 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa return false; } +const String& AssetsCache::GetEditorAssetPath(const Guid& id) const +{ +#if USE_EDITOR + auto e = _registry.TryGet(id); + return e ? e->Info.Path : String::Empty; +#else + for (auto& e : _pathsMapping) + { + if (e.Value == id) + return e.Key; + } + return String::Empty; +#endif +} + bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info) { PROFILE_CPU(); @@ -402,41 +418,55 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage) _isDirty = true; } +void AssetsCache::RegisterAsset(const AssetHeader& header, const StringView& path) +{ + RegisterAsset(header.ID, header.TypeName, path); +} + +void AssetsCache::RegisterAssets(const FlaxStorageReference& storage) +{ + RegisterAssets(storage.Get()); +} + void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const StringView& path) { PROFILE_CPU(); - ScopeLock lock(_locker); - // Mark registry as draft - _isDirty = true; - // Check if asset has been already added to the registry bool isMissing = true; for (auto i = _registry.Begin(); i.IsNotEnd(); ++i) { auto& e = i->Value; - // Compare IDs if (e.Info.ID == id) { - // Update registry entry - e.Info.Path = path; - e.Info.TypeName = typeName; - - // Back + if (e.Info.Path != path) + { + e.Info.Path = path; + _isDirty = true; + } + if (e.Info.TypeName != typeName) + { + e.Info.TypeName = typeName; + _isDirty = true; + } isMissing = false; break; } - // Compare paths if (e.Info.Path == path) { - // Update registry entry - e.Info.ID = id; - e.Info.TypeName = typeName; - - // Back + if (e.Info.ID != id) + { + e.Info.Path = path; + _isDirty = true; + } + if (e.Info.TypeName != typeName) + { + e.Info.TypeName = typeName; + _isDirty = true; + } isMissing = false; break; } @@ -445,9 +475,8 @@ void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const St if (isMissing) { LOG(Info, "Register asset {0}:{1} \'{2}\'", id, typeName, path); - - // Add new asset entry _registry.Add(id, Entry(id, typeName, path)); + _isDirty = true; } } @@ -567,8 +596,8 @@ bool AssetsCache::IsEntryValid(Entry& e) #else // In game we don't care about it because all cached asset entries are valid (precached) - // Skip only entries with missing file - return e.Info.Path.HasChars(); + // Skip only entries with missing file + return e.Info.Path.HasChars(); #endif } diff --git a/Source/Engine/Content/Cache/AssetsCache.h b/Source/Engine/Content/Cache/AssetsCache.h index ba0ee0303..cee2e0e8f 100644 --- a/Source/Engine/Content/Cache/AssetsCache.h +++ b/Source/Engine/Content/Cache/AssetsCache.h @@ -2,13 +2,19 @@ #pragma once +#include "../AssetInfo.h" +#include "../Config.h" #include "Engine/Core/Types/Guid.h" +#if ENABLE_ASSETS_DISCOVERY +#include "Engine/Core/Types/DateTime.h" +#endif #include "Engine/Core/Types/String.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Platform/CriticalSection.h" -#include "Engine/Content/Storage/FlaxStorageReference.h" -#include "../AssetInfo.h" -#include "../Config.h" + +struct AssetHeader; +struct FlaxStorageReference; +class FlaxStorage; /// /// Assets cache flags. @@ -31,7 +37,7 @@ DECLARE_ENUM_OPERATORS(AssetsCacheFlags); /// /// Flax Game Engine assets cache container /// -class AssetsCache +class FLAXENGINE_API AssetsCache { public: @@ -46,19 +52,17 @@ public: AssetInfo Info; #if ENABLE_ASSETS_DISCOVERY - /// /// The file modified date. /// DateTime FileModified; - #endif Entry() { } - Entry(const Guid& id, const String& typeName, const StringView& path) + Entry(const Guid& id, const StringView& typeName, const StringView& path) : Info(id, typeName, path) #if ENABLE_ASSETS_DISCOVERY , FileModified(DateTime::NowUTC()) @@ -124,6 +128,13 @@ public: public: + /// + /// Finds the asset path by id. In editor it returns the actual asset path, at runtime it returns the mapped asset path. + /// + /// The asset id. + /// The asset path, or empty if failed to find. + const String& GetEditorAssetPath(const Guid& id) const; + /// /// Finds the asset info by path. /// @@ -167,16 +178,13 @@ public: /// /// The asset typename. /// The result array. - void GetAllByTypeName(const StringView& typeName, Array& result) const; + void GetAllByTypeName(const StringView& typeName, Array& result) const; /// /// Register assets in the cache /// /// Flax assets container reference - FORCE_INLINE void RegisterAssets(const FlaxStorageReference& storage) - { - RegisterAssets(storage.Get()); - } + void RegisterAssets(const FlaxStorageReference& storage); /// /// Register assets in the cache @@ -189,10 +197,7 @@ public: /// /// Flax asset file header /// Asset path - FORCE_INLINE void RegisterAsset(const AssetHeader& header, const StringView& path) - { - RegisterAsset(header.ID, header.TypeName, path); - } + void RegisterAsset(const AssetHeader& header, const StringView& path); /// /// Register asset in the cache diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 4b1e32f16..0b3f9a1d7 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -14,6 +14,7 @@ #include "Engine/Threading/Threading.h" #include "Engine/Graphics/Graphics.h" #include "Engine/Engine/Time.h" +#include "Engine/Engine/Globals.h" #include "Engine/Level/Types.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ManagedCLR/MClass.h" @@ -408,16 +409,20 @@ Asset* Content::LoadAsync(const StringView& path, MClass* type) Asset* Content::LoadAsync(const StringView& path, const ScriptingTypeHandle& type) { + // Ensure path is in a valid format + String pathNorm(path); + FileSystem::NormalizePath(pathNorm); + #if USE_EDITOR - if (!FileSystem::FileExists(path)) + if (!FileSystem::FileExists(pathNorm)) { - LOG(Error, "Missing file \'{0}\'", path); + LOG(Error, "Missing file \'{0}\'", pathNorm); return nullptr; } #endif AssetInfo assetInfo; - if (GetAssetInfo(path, assetInfo)) + if (GetAssetInfo(pathNorm, assetInfo)) { return LoadAsync(assetInfo.ID, type); } diff --git a/Source/Engine/Content/Factories/BinaryAssetFactory.cpp b/Source/Engine/Content/Factories/BinaryAssetFactory.cpp index c3c4e8178..220ea5ed5 100644 --- a/Source/Engine/Content/Factories/BinaryAssetFactory.cpp +++ b/Source/Engine/Content/Factories/BinaryAssetFactory.cpp @@ -4,6 +4,7 @@ #include "../BinaryAsset.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Content/Storage/ContentStorageManager.h" #if USE_EDITOR @@ -14,28 +15,23 @@ bool BinaryAssetFactoryBase::Init(BinaryAsset* asset) { ASSERT(asset && asset->Storage); - - // Prepare auto storage = asset->Storage; - AssetInfo info; - info.ID = asset->GetID(); - info.TypeName = asset->GetTypeName(); - info.Path = storage->GetPath(); // Load serialized asset data AssetInitData initData; - if (storage->LoadAssetHeader(info.ID, initData)) + if (storage->LoadAssetHeader(asset->GetID(), initData)) { - LOG(Error, "Cannot load asset header.\nInfo: {0}", info.ToString()); + LOG(Error, "Cannot load asset header.\nInfo: {0}", AssetInfo(asset->GetID(), asset->GetTypeName(), storage->GetPath()).ToString()); return true; } -#if COMPILE_WITH_ASSET_UPGRADERS +#if USE_EDITOR // Check if need to perform data conversion to the newer version (only in Editor) const auto upgrader = GetUpgrader(); if (storage->AllowDataModifications() && upgrader && upgrader->ShouldUpgrade(initData.SerializedVersion)) { const auto startTime = DateTime::NowUTC(); + const AssetInfo info(asset->GetID(), asset->GetTypeName(), storage->GetPath()); LOG(Info, "Starting asset \'{0}\' conversion", info.Path); // Backup source file (in case of conversion failure) @@ -99,21 +95,21 @@ bool BinaryAssetFactoryBase::Init(BinaryAsset* asset) // Check if serialized asset version is supported if (!IsVersionSupported(initData.SerializedVersion)) { - LOG(Warning, "Asset version {1} is not supported.\nInfo: {0}", info.ToString(), initData.SerializedVersion); + LOG(Warning, "Asset version {1} is not supported.\nInfo: {0}", AssetInfo(asset->GetID(), asset->GetTypeName(), storage->GetPath()).ToString(), initData.SerializedVersion); return true; } // Initialize asset if (asset->Init(initData)) { - LOG(Error, "Cannot initialize asset.\nInfo: {0}", info.ToString()); + LOG(Error, "Cannot initialize asset.\nInfo: {0}", AssetInfo(asset->GetID(), asset->GetTypeName(), storage->GetPath()).ToString()); return true; } return false; } -#if COMPILE_WITH_ASSET_UPGRADERS +#if USE_EDITOR bool BinaryAssetFactoryBase::UpgradeAsset(const AssetInfo& info, FlaxStorage* storage, AssetMigrationContext& context) { diff --git a/Source/Engine/Content/Factories/BinaryAssetFactory.h b/Source/Engine/Content/Factories/BinaryAssetFactory.h index 25dd38aeb..1040ea3c5 100644 --- a/Source/Engine/Content/Factories/BinaryAssetFactory.h +++ b/Source/Engine/Content/Factories/BinaryAssetFactory.h @@ -3,7 +3,9 @@ #pragma once #include "IAssetFactory.h" +#if USE_EDITOR #include "Engine/Content/Upgraders/BinaryAssetUpgrader.h" +#endif #include "Engine/Content/AssetInfo.h" #include "Engine/Scripting/ScriptingObject.h" @@ -14,7 +16,7 @@ class FlaxStorage; /// The binary assets factory base class. /// /// -class BinaryAssetFactoryBase : public IAssetFactory +class FLAXENGINE_API BinaryAssetFactoryBase : public IAssetFactory { public: @@ -29,7 +31,7 @@ protected: virtual BinaryAsset* Create(const AssetInfo& info) = 0; virtual bool IsVersionSupported(uint32 serializedVersion) const = 0; -#if COMPILE_WITH_ASSET_UPGRADERS +#if USE_EDITOR bool UpgradeAsset(const AssetInfo& info, FlaxStorage* storage, AssetMigrationContext& context); #endif @@ -65,16 +67,31 @@ protected: } }; -#define REGISTER_BINARY_ASSET(type, typeName, upgrader, supportsVirtualAssets) \ +#define REGISTER_BINARY_ASSET(type, typeName, supportsVirtualAssets) \ + const String type::TypeName = TEXT(typeName); \ + class CONCAT_MACROS(Factory, type) : public BinaryAssetFactory \ + { \ + public: \ + CONCAT_MACROS(Factory, type)() { IAssetFactory::Get().Add(type::TypeName, this); } \ + ~CONCAT_MACROS(Factory, type)() { IAssetFactory::Get().Remove(type::TypeName); } \ + bool SupportsVirtualAssets() const override { return supportsVirtualAssets; } \ + }; \ + static CONCAT_MACROS(Factory, type) CONCAT_MACROS(CFactory, type) + +#if USE_EDITOR +#define REGISTER_BINARY_ASSET_WITH_UPGRADER(type, typeName, upgrader, supportsVirtualAssets) \ const String type::TypeName = TEXT(typeName); \ class CONCAT_MACROS(Factory, type) : public BinaryAssetFactory \ { \ private: \ - IAssetUpgrader* _upgrader = upgrader; \ + IAssetUpgrader* _upgrader = ::New(); \ public: \ CONCAT_MACROS(Factory, type)() { IAssetFactory::Get().Add(type::TypeName, this); } \ - ~CONCAT_MACROS(Factory, type)() { if (_upgrader) Delete(_upgrader); IAssetFactory::Get().Remove(type::TypeName); } \ + ~CONCAT_MACROS(Factory, type)() { Delete(_upgrader); IAssetFactory::Get().Remove(type::TypeName); } \ bool SupportsVirtualAssets() const override { return supportsVirtualAssets; } \ IAssetUpgrader* GetUpgrader() const override { return _upgrader; } \ }; \ static CONCAT_MACROS(Factory, type) CONCAT_MACROS(CFactory, type) +#else +#define REGISTER_BINARY_ASSET_WITH_UPGRADER(type, typeName, upgrader, supportsVirtualAssets) REGISTER_BINARY_ASSET(type, typeName, supportsVirtualAssets) +#endif diff --git a/Source/Engine/Content/Factories/IAssetFactory.h b/Source/Engine/Content/Factories/IAssetFactory.h index dbc7b1e90..a4f5681b4 100644 --- a/Source/Engine/Content/Factories/IAssetFactory.h +++ b/Source/Engine/Content/Factories/IAssetFactory.h @@ -9,13 +9,10 @@ struct AssetInfo; class Asset; class IAssetUpgrader; -// Enables upgrading asset files from the older version format -#define COMPILE_WITH_ASSET_UPGRADERS (USE_EDITOR) - /// /// The asset objects factory. /// -class IAssetFactory +class FLAXENGINE_API IAssetFactory { public: diff --git a/Source/Engine/Content/Factories/JsonAssetFactory.h b/Source/Engine/Content/Factories/JsonAssetFactory.h index 0269ec596..988776186 100644 --- a/Source/Engine/Content/Factories/JsonAssetFactory.h +++ b/Source/Engine/Content/Factories/JsonAssetFactory.h @@ -10,7 +10,7 @@ /// The Json assets factory base class. /// /// -class JsonAssetFactoryBase : public IAssetFactory +class FLAXENGINE_API JsonAssetFactoryBase : public IAssetFactory { protected: @@ -23,7 +23,6 @@ public: { return Create(info); } - Asset* NewVirtual(const AssetInfo& info) override { return Create(info); @@ -47,12 +46,13 @@ protected: } }; -#define REGISTER_JSON_ASSET(type, typeName) \ +#define REGISTER_JSON_ASSET(type, typeName, supportsVirtualAssets) \ const String type::TypeName = TEXT(typeName); \ class CONCAT_MACROS(Factory, type) : public JsonAssetFactory \ { \ public: \ CONCAT_MACROS(Factory, type)() { IAssetFactory::Get().Add(type::TypeName, this); } \ ~CONCAT_MACROS(Factory, type)() { IAssetFactory::Get().Remove(type::TypeName); } \ + bool SupportsVirtualAssets() const override { return supportsVirtualAssets; } \ }; \ static CONCAT_MACROS(Factory, type) CONCAT_MACROS(CFactory, type) diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 821c43f87..44baecb12 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -1,12 +1,16 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "JsonAsset.h" -#include "Storage/ContentStorageManager.h" #include "Engine/Threading/Threading.h" #if USE_EDITOR #include "Engine/Platform/File.h" +#include "Engine/Core/Types/DataContainer.h" +#else +#include "Storage/ContentStorageManager.h" #endif +#include "Content.h" #include "FlaxEngine.Gen.h" +#include "Cache/AssetsCache.h" #include "Engine/Core/Log.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Content/Factories/JsonAssetFactory.h" @@ -38,7 +42,12 @@ String JsonAssetBase::GetData() const const String& JsonAssetBase::GetPath() const { +#if USE_EDITOR return _path; +#else + // In build all assets are packed into packages so use ID for original path lookup + return Content::GetRegistry()->GetEditorAssetPath(_id); +#endif } #if USE_EDITOR @@ -93,9 +102,8 @@ Asset::LoadResult JsonAssetBase::loadAsset() { // Load data (raw json file in editor, cooked asset in build game) #if USE_EDITOR - BytesContainer data; - if (File::ReadAllBytes(GetPath(), data)) + if (File::ReadAllBytes(_path, data)) { LOG(Warning, "Filed to load json asset data. {0}", ToString()); return LoadResult::CannotLoadData; @@ -104,11 +112,9 @@ Asset::LoadResult JsonAssetBase::loadAsset() { return LoadResult::MissingDataChunk; } - #else - // Get the asset storage container but don't load it now - const auto storage = ContentStorageManager::GetStorage(GetPath(), true); + const auto storage = ContentStorageManager::GetStorage(_path, true); if (!storage) return LoadResult::CannotLoadStorage; @@ -124,7 +130,6 @@ Asset::LoadResult JsonAssetBase::loadAsset() if (storage->LoadAssetChunk(chunk)) return LoadResult::CannotLoadData; auto& data = chunk->Data; - #endif // Parse json document @@ -176,7 +181,7 @@ void JsonAssetBase::onRename(const StringView& newPath) #endif -REGISTER_JSON_ASSET(JsonAsset, "FlaxEngine.JsonAsset"); +REGISTER_JSON_ASSET(JsonAsset, "FlaxEngine.JsonAsset", true); JsonAsset::JsonAsset(const SpawnParams& params, const AssetInfo* info) : JsonAssetBase(params, info) diff --git a/Source/Engine/Content/Loading/ContentLoadTask.h b/Source/Engine/Content/Loading/ContentLoadTask.h index 94e8d5ec7..df13c8c43 100644 --- a/Source/Engine/Content/Loading/ContentLoadTask.h +++ b/Source/Engine/Content/Loading/ContentLoadTask.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Threading/Task.h" +#include "Engine/Core/Types/String.h" class Asset; class LoadingThread; diff --git a/Source/Engine/Content/Loading/ContentLoadingManager.cpp b/Source/Engine/Content/Loading/ContentLoadingManager.cpp index 6e6ff7a65..5f75e8cd1 100644 --- a/Source/Engine/Content/Loading/ContentLoadingManager.cpp +++ b/Source/Engine/Content/Loading/ContentLoadingManager.cpp @@ -165,7 +165,7 @@ bool ContentLoadingManagerService::Init() // Calculate amount of loading threads to use const CPUInfo cpuInfo = Platform::GetCPUInfo(); - const int32 count = static_cast(Math::Clamp(LOADING_THREAD_PER_PHYSICAL_CORE * cpuInfo.ProcessorCoreCount, 1.0f, 4.0f)); + const int32 count = Math::Clamp(static_cast(LOADING_THREAD_PER_PHYSICAL_CORE * (float)cpuInfo.ProcessorCoreCount), 1, 6); LOG(Info, "Creating {0} content loading threads...", count); // Create loading threads diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h index 5d552b996..752fa7bc4 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h @@ -53,6 +53,9 @@ protected: AssetReference ref = _asset.Get(); if (ref == nullptr) return Result::MissingReferences; +#if TRACY_ENABLE + const StringView name(ref->GetPath()); +#endif // Load chunks for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) @@ -62,11 +65,14 @@ protected: const auto chunk = ref->GetChunk(i); if (chunk != nullptr) { - // Check for cancel if (IsCancelRequested()) return Result::Ok; // Load it +#if TRACY_ENABLE + ZoneScoped; + ZoneName(*name, name.Length()); +#endif if (ref->Storage->LoadAssetChunk(chunk)) { LOG(Warning, "Cannot load asset \'{0}\' chunk {1}.", ref->ToString(), i); diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index 7a7ea285f..ae8dab9e6 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -13,10 +13,6 @@ /// class LoadAssetTask : public ContentLoadTask { -private: - - WeakAssetReference _asset; - public: /// @@ -25,27 +21,20 @@ public: /// The asset to load. LoadAssetTask(Asset* asset) : ContentLoadTask(Type::LoadAsset) - , _asset(asset) + , Asset(asset) { } public: - /// - /// Gets the asset. - /// - /// The asset. - FORCE_INLINE Asset* GetAsset() const - { - return _asset.Get(); - } + WeakAssetReference Asset; public: // [ContentLoadTask] bool HasReference(Object* obj) const override { - return obj == _asset; + return obj == Asset; } protected: @@ -55,7 +44,8 @@ protected: { PROFILE_CPU(); - AssetReference ref = _asset.Get(); + // Keep valid ref to the asset + AssetReference<::Asset> ref = Asset.Get(); if (ref == nullptr) return Result::MissingReferences; @@ -68,7 +58,7 @@ protected: void OnEnd() override { - _asset = nullptr; + Asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/Storage/AssetHeader.h b/Source/Engine/Content/Storage/AssetHeader.h index 19609fd2c..03d8c6028 100644 --- a/Source/Engine/Content/Storage/AssetHeader.h +++ b/Source/Engine/Content/Storage/AssetHeader.h @@ -5,7 +5,9 @@ #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/Pair.h" #include "Engine/Core/Types/String.h" -#include "Engine/Core/Types/DataContainer.h" +#if USE_EDITOR +#include "Engine/Core/Collections/Array.h" +#endif #include "FlaxChunk.h" /// @@ -54,7 +56,8 @@ public: /// Gets the chunks. /// /// The output data. - void GetChunks(Array& output) const + template + void GetChunks(Array& output) const { for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) { @@ -67,7 +70,8 @@ public: /// Gets the chunks that are loaded. /// /// The output data. - void GetLoadedChunks(Array& output) const + template + void GetLoadedChunks(Array& output) const { for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) { @@ -130,7 +134,7 @@ struct FLAXENGINE_API AssetInitData /// /// The serialized asset version /// - uint32 SerializedVersion; + uint32 SerializedVersion = 0; /// /// The custom asset data (should be small, for eg. texture description structure). @@ -138,7 +142,6 @@ struct FLAXENGINE_API AssetInitData BytesContainer CustomData; #if USE_EDITOR - /// /// The asset metadata information. Stored in a Json format. /// @@ -148,24 +151,16 @@ struct FLAXENGINE_API AssetInitData /// Asset dependencies list used by the asset for tracking (eg. material functions used by material asset). The pair of asset ID and cached file edit time (for tracking modification). /// Array> Dependencies; - #endif public: - AssetInitData() - : SerializedVersion(0) - { - } - /// /// Gets the hash code. /// - /// Hash Code uint32 GetHashCode() const { // Note: do not use Metadata/Dependencies because it may not be loaded (it's optional) - uint32 hashCode = GetHash(Header.ID); hashCode = (hashCode * 397) ^ SerializedVersion; hashCode = (hashCode * 397) ^ CustomData.Length(); diff --git a/Source/Engine/Content/Storage/ContentStorageManager.h b/Source/Engine/Content/Storage/ContentStorageManager.h index 2d7ce5fd7..e18ead571 100644 --- a/Source/Engine/Content/Storage/ContentStorageManager.h +++ b/Source/Engine/Content/Storage/ContentStorageManager.h @@ -3,6 +3,7 @@ #pragma once #include "FlaxStorageReference.h" +#include "Engine/Core/Types/TimeSpan.h" class FlaxFile; class FlaxPackage; diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index d4da1d951..50ed82595 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -5,10 +5,16 @@ #include "FlaxPackage.h" #include "ContentStorageManager.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Platform/File.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/FileWriteStream.h" +#if USE_EDITOR #include "Engine/Serialization/JsonWriter.h" #include "Engine/Serialization/JsonWriters.h" +#else +#include "Engine/Engine/Globals.h" +#endif #include String AssetHeader::ToString() const @@ -194,13 +200,13 @@ void FlaxStorage::AddRef() void FlaxStorage::RemoveRef() { - ASSERT(_refCount > 0); - - _refCount--; - - if (_refCount == 0) + if (_refCount > 0) { - _lastRefLostTime = DateTime::NowUTC(); + _refCount--; + if (_refCount == 0) + { + _lastRefLostTime = DateTime::NowUTC(); + } } } @@ -623,6 +629,7 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk) stream->ReadBytes(tmpBuf.Get(), size); // Decompress data + PROFILE_CPU_NAMED("DecompressLZ4"); chunk->Data.Allocate(originalSize); const int32 res = LZ4_decompress_safe((const char*)tmpBuf.Get(), chunk->Data.Get(), size, originalSize); if (res <= 0) @@ -823,6 +830,7 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d const FlaxChunk* chunk = chunks[i]; if (chunk->Flags & FlaxChunkFlags::CompressedLZ4) { + PROFILE_CPU_NAMED("CompressLZ4"); const int32 srcSize = chunk->Data.Length(); const int32 maxSize = LZ4_compressBound(srcSize); auto& chunkCompressed = compressedChunks[i]; diff --git a/Source/Engine/Content/Storage/FlaxStorageReference.h b/Source/Engine/Content/Storage/FlaxStorageReference.h index d690227ae..7297bc779 100644 --- a/Source/Engine/Content/Storage/FlaxStorageReference.h +++ b/Source/Engine/Content/Storage/FlaxStorageReference.h @@ -5,7 +5,7 @@ #include "FlaxStorage.h" /// -/// Flax Storage Container Reference +/// Flax Storage container reference. /// struct FLAXENGINE_API FlaxStorageReference { @@ -44,10 +44,8 @@ public: public: - // Assignment operator FlaxStorageReference& operator=(const FlaxStorageReference& other) { - // Protect against invalid self-assignment if (this != &other) { if (_storage) @@ -56,7 +54,6 @@ public: if (_storage) _storage->AddRef(); } - return *this; } diff --git a/Source/Engine/Content/Storage/JsonStorageProxy.cpp b/Source/Engine/Content/Storage/JsonStorageProxy.cpp index 8b2f07944..1d5dbdf19 100644 --- a/Source/Engine/Content/Storage/JsonStorageProxy.cpp +++ b/Source/Engine/Content/Storage/JsonStorageProxy.cpp @@ -31,7 +31,7 @@ bool JsonStorageProxy::GetAssetInfo(const StringView& path, Guid& resultId, Stri document.Parse((const char*)fileData.Get(), fileData.Count()); if (document.HasParseError()) { - Log::JsonParseException(document.GetParseError(), document.GetErrorOffset(), String(path)); + Log::JsonParseException(document.GetParseError(), document.GetErrorOffset(), path); return false; } @@ -94,7 +94,7 @@ bool JsonStorageProxy::ChangeId(const StringView& path, const Guid& newId) document.Parse((const char*)fileData.Get(), fileData.Count()); if (document.HasParseError()) { - Log::JsonParseException(document.GetParseError(), document.GetErrorOffset(), String(path)); + Log::JsonParseException(document.GetParseError(), document.GetErrorOffset(), path); return false; } diff --git a/Source/Engine/Content/Upgraders/AudioClipUpgrader.h b/Source/Engine/Content/Upgraders/AudioClipUpgrader.h index 5e2555958..ee0d131b7 100644 --- a/Source/Engine/Content/Upgraders/AudioClipUpgrader.h +++ b/Source/Engine/Content/Upgraders/AudioClipUpgrader.h @@ -2,6 +2,8 @@ #pragma once +#if USE_EDITOR + #include "BinaryAssetUpgrader.h" #include "Engine/Audio/AudioClip.h" #include "Engine/Tools/AudioTool/OggVorbisDecoder.h" @@ -130,3 +132,5 @@ private: return false; } }; + +#endif diff --git a/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h b/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h index 2c2d0f03e..551c4e1e7 100644 --- a/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h @@ -2,6 +2,8 @@ #pragma once +#if USE_EDITOR + #include "IAssetUpgrader.h" #include "Engine/Content/Storage/AssetHeader.h" #include "Engine/Core/Log.h" @@ -9,7 +11,7 @@ /// /// Binary asset upgrading context structure. /// -struct AssetMigrationContext +struct FLAXENGINE_API AssetMigrationContext { /// /// The input data. @@ -63,7 +65,7 @@ typedef bool (*UpgradeHandler)(AssetMigrationContext& context); /// Binary Assets Upgrader base class /// /// -class BinaryAssetUpgrader : public IAssetUpgrader +class FLAXENGINE_API BinaryAssetUpgrader : public IAssetUpgrader { public: @@ -205,3 +207,5 @@ public: return false; } }; + +#endif diff --git a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h index cb26fa8d2..8be02b973 100644 --- a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h @@ -2,6 +2,8 @@ #pragma once +#if USE_EDITOR + #include "BinaryAssetUpgrader.h" #include "Engine/Render2D/FontAsset.h" @@ -71,3 +73,5 @@ private: return CopyChunk(context, 0); } }; + +#endif diff --git a/Source/Engine/Content/Upgraders/IAssetUpgrader.h b/Source/Engine/Content/Upgraders/IAssetUpgrader.h index f54616a45..edff613e7 100644 --- a/Source/Engine/Content/Upgraders/IAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/IAssetUpgrader.h @@ -2,12 +2,14 @@ #pragma once +#if USE_EDITOR + #include "Engine/Core/Types/BaseTypes.h" /// /// The assets upgrading objects interface. /// -class IAssetUpgrader +class FLAXENGINE_API IAssetUpgrader { public: @@ -27,3 +29,5 @@ public: /// True if perform conversion, otherwise false. virtual bool ShouldUpgrade(uint32 serializedVersion) const = 0; }; + +#endif diff --git a/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h b/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h index 31caeb230..6fd5093f5 100644 --- a/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h +++ b/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h @@ -2,6 +2,8 @@ #pragma once +#if USE_EDITOR + #include "BinaryAssetUpgrader.h" #include "Engine/Core/Core.h" #include "Engine/Platform/Platform.h" @@ -105,3 +107,5 @@ private: return false; } }; + +#endif diff --git a/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h index 17b2bda38..b09d1c280 100644 --- a/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h @@ -2,12 +2,15 @@ #pragma once +#if USE_EDITOR + #include "BinaryAssetUpgrader.h" #include "Engine/Core/Core.h" #include "Engine/Platform/Platform.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Graphics/Models/ModelData.h" +#include "Engine/Content/Asset.h" /// /// Model Asset Upgrader @@ -1222,3 +1225,5 @@ private: return false; } }; + +#endif diff --git a/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h b/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h index 827197009..eb84649ad 100644 --- a/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h @@ -2,8 +2,11 @@ #pragma once +#if USE_EDITOR + #include "BinaryAssetUpgrader.h" #include "Engine/Platform/Platform.h" +#include "Engine/Graphics/Shaders/Cache/ShaderStorage.h" /// /// Material Asset and Shader Asset Upgrader @@ -85,3 +88,5 @@ private: return CopyChunks(context); } }; + +#endif diff --git a/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h b/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h index 5058aed68..431c71014 100644 --- a/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h +++ b/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h @@ -2,6 +2,8 @@ #pragma once +#if USE_EDITOR + #include "BinaryAssetUpgrader.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Serialization/MemoryReadStream.h" @@ -97,3 +99,5 @@ private: return false; } }; + +#endif diff --git a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h index 9a5b4c5ae..c026daebe 100644 --- a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h @@ -2,10 +2,17 @@ #pragma once +#if USE_EDITOR + #include "BinaryAssetUpgrader.h" #include "Engine/Platform/Platform.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" +#include "Engine/Graphics/Models/Types.h" +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/BoundingSphere.h" +#include "Engine/Core/Math/Matrix.h" +#include "Engine/Core/Math/Transform.h" /// /// Skinned Model Asset Upgrader @@ -418,3 +425,5 @@ private: return false; } }; + +#endif diff --git a/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h b/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h index e1d75dba5..a598538f9 100644 --- a/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h @@ -2,6 +2,8 @@ #pragma once +#if USE_EDITOR + #include "BinaryAssetUpgrader.h" #include "Engine/Core/Core.h" #include "Engine/Platform/Platform.h" @@ -825,3 +827,5 @@ private: return true; } }; + +#endif diff --git a/Source/Engine/Content/WeakAssetReference.h b/Source/Engine/Content/WeakAssetReference.h index 4943fd0b5..49401bf25 100644 --- a/Source/Engine/Content/WeakAssetReference.h +++ b/Source/Engine/Content/WeakAssetReference.h @@ -229,3 +229,9 @@ public: OnSet(asset); } }; + +template +uint32 GetHash(const WeakAssetReference& key) +{ + return GetHash(key.GetID()); +} diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index f1d048ca5..b4f002b42 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -31,6 +31,7 @@ #include "CreateParticleEmitterFunction.h" #include "CreateAnimationGraphFunction.h" #include "CreateVisualScript.h" +#include "CreateJson.h" // Tags used to detect asset creation mode const String AssetsImportingManager::CreateTextureTag(TEXT("Texture")); @@ -91,6 +92,10 @@ CreateAssetResult CreateAssetContext::Run(const CreateAssetFunction& callback) if (result != CreateAssetResult::Ok) return result; + // Skip for non-flax assets (eg. json resource or custom asset type) + if (!TargetAssetPath.EndsWith(ASSET_FILES_EXTENSION)) + return CreateAssetResult::Ok; + // Validate assigned TypeID if (Data.Header.TypeName.IsEmpty()) { @@ -112,8 +117,7 @@ CreateAssetResult CreateAssetContext::Run(const CreateAssetFunction& callback) } // Save file - result = Save(); - + result = FlaxStorage::Create(OutputPath, Data) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok; if (result == CreateAssetResult::Ok) { _applyChangesResult = CreateAssetResult::Abort; @@ -161,11 +165,6 @@ void CreateAssetContext::AddMeta(JsonWriter& writer) const writer.String(Platform::GetUserName()); } -CreateAssetResult CreateAssetContext::Save() -{ - return FlaxStorage::Create(OutputPath, Data) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok; -} - void CreateAssetContext::ApplyChanges() { // Get access @@ -231,8 +230,6 @@ bool AssetsImportingManager::Create(const String& tag, const StringView& outputP bool AssetsImportingManager::Import(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg) { - ASSERT(outputPath.EndsWith(StringView(ASSET_FILES_EXTENSION))); - LOG(Info, "Importing file '{0}' to '{1}'...", inputPath, outputPath); // Check if input file exists @@ -246,8 +243,7 @@ bool AssetsImportingManager::Import(const StringView& inputPath, const StringVie const String extension = FileSystem::GetExtension(inputPath).ToLower(); // Special case for raw assets - const String assetExtension = ASSET_FILES_EXTENSION; - if (assetExtension.Compare(extension, StringSearchCase::IgnoreCase) == 0) + if (StringView(ASSET_FILES_EXTENSION).Compare(StringView(extension), StringSearchCase::IgnoreCase) == 0) { // Simply copy file (content layer will resolve duplicated IDs, etc.) return FileSystem::CopyFile(outputPath, inputPath); @@ -266,8 +262,6 @@ bool AssetsImportingManager::Import(const StringView& inputPath, const StringVie bool AssetsImportingManager::ImportIfEdited(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg) { - ASSERT(outputPath.EndsWith(StringView(ASSET_FILES_EXTENSION))); - // Check if asset not exists if (!FileSystem::FileExists(outputPath)) { @@ -338,7 +332,7 @@ bool AssetsImportingManager::Create(const Function {1}", typeName, String(dataTypename.Get())); } } + else + { + const String directory = StringUtils::GetDirectoryName(path); + if (!FileSystem::DirectoryExists(directory)) + { + if (FileSystem::CreateDirectory(directory)) + { + LOG(Warning, "Failed to create directory"); + return true; + } + } + } rapidjson_flax::StringBuffer buffer; @@ -76,8 +93,184 @@ bool CreateJson::Create(const StringView& path, StringAnsiView& data, StringAnsi { asset->Reload(); } + else + { + Content::GetRegistry()->RegisterAsset(id, String(dataTypename), path); + } return false; } +void FormatPoValue(String& value) +{ + value.Replace(TEXT("\\n"), TEXT("\n")); + value.Replace(TEXT("%s"), TEXT("{}")); + value.Replace(TEXT("%d"), TEXT("{}")); +} + +CreateAssetResult CreateJson::ImportPo(CreateAssetContext& context) +{ + // Base + IMPORT_SETUP(LocalizedStringTable, 1); + + // Load file (UTF-16) + String inputData; + if (File::ReadAllText(context.InputPath, inputData)) + { + return CreateAssetResult::InvalidPath; + } + + // Use virtual asset for data storage and serialization + AssetReference asset = Content::CreateVirtualAsset(); + if (!asset) + return CreateAssetResult::Error; + + // Parse PO format + int32 pos = 0; + int32 pluralCount = 0; + int32 lineNumber = 0; + bool fuzzy = false, hasNewContext = false; + StringView msgctxt, msgid; + String idTmp; + while (pos < inputData.Length()) + { + // Read line + const int32 startPos = pos; + while (pos < inputData.Length() && inputData[pos] != '\n') + pos++; + const StringView line(&inputData[startPos], pos - startPos); + lineNumber++; + pos++; + const int32 valueStart = line.Find('\"') + 1; + const int32 valueEnd = line.FindLast('\"'); + const StringView value(line.Get() + valueStart, Math::Max(valueEnd - valueStart, 0)); + + if (line.StartsWith(StringView(TEXT("msgid_plural")))) + { + // Plural form + } + else if (line.StartsWith(StringView(TEXT("msgid")))) + { + // Id + msgid = value; + + // Reset context if already used + if (!hasNewContext) + msgctxt = StringView(); + hasNewContext = false; + } + else if (line.StartsWith(StringView(TEXT("msgstr")))) + { + // String + if (msgid.HasChars()) + { + // Format message + String msgstr(value); + FormatPoValue(msgstr); + + // Get message id + StringView id = msgid; + if (msgctxt.HasChars()) + { + idTmp = String(msgctxt) + TEXT(".") + String(msgid); + id = idTmp; + } + + int32 indexStart = line.Find('['); + if (indexStart != -1 && indexStart < valueStart) + { + indexStart++; + while (indexStart < line.Length() && StringUtils::IsWhitespace(line[indexStart])) + indexStart++; + int32 indexEnd = line.Find(']'); + while (indexEnd > indexStart && StringUtils::IsWhitespace(line[indexEnd - 1])) + indexEnd--; + int32 index = -1; + StringUtils::Parse(line.Get() + indexStart, (uint32)(indexEnd - indexStart), &index); + if (pluralCount <= 0) + { + LOG(Error, "Missing 'nplurals'. Cannot use plural message at line {0}", lineNumber); + return CreateAssetResult::Error; + } + if (index < 0 || index > pluralCount) + { + LOG(Error, "Invalid plural message index at line {0}", lineNumber); + return CreateAssetResult::Error; + } + + // Plural message + asset->AddPluralString(id, msgstr, index); + } + else + { + // Message + asset->AddString(id, msgstr); + } + } + } + else if (line.StartsWith(StringView(TEXT("msgctxt")))) + { + // Context + msgctxt = value; + hasNewContext = true; + } + else if (line.StartsWith('\"')) + { + // Config + const Char* pluralForms = StringUtils::Find(line.Get(), TEXT("Plural-Forms")); + if (pluralForms != nullptr && pluralForms < line.Get() + line.Length() - 1) + { + // Process plural forms rule + const Char* nplurals = StringUtils::Find(pluralForms, TEXT("nplurals")); + if (nplurals && nplurals < line.Get() + line.Length()) + { + while (*nplurals && *nplurals != '=') + nplurals++; + while (*nplurals && (StringUtils::IsWhitespace(*nplurals) || *nplurals == '=')) + nplurals++; + const Char* npluralsStart = nplurals; + while (*nplurals && !StringUtils::IsWhitespace(*nplurals) && *nplurals != ';') + nplurals++; + StringUtils::Parse(npluralsStart, (uint32)(nplurals - npluralsStart), &pluralCount); + if (pluralCount < 0 || pluralCount > 100) + { + LOG(Error, "Invalid 'nplurals' at line {0}", lineNumber); + return CreateAssetResult::Error; + } + } + // TODO: parse plural forms rule + } + const Char* language = StringUtils::Find(line.Get(), TEXT("Language")); + if (language != nullptr && language < line.Get() + line.Length() - 1) + { + // Process language locale + while (*language && *language != ':') + language++; + language++; + while (*language && StringUtils::IsWhitespace(*language)) + language++; + const Char* languageStart = language; + while (*language && !StringUtils::IsWhitespace(*language) && *language != '\\' && *language != '\"') + language++; + asset->Locale.Set(languageStart, (int32)(language - languageStart)); + if (asset->Locale == TEXT("English")) + asset->Locale = TEXT("en"); + if (asset->Locale.Length() > 5) + LOG(Warning, "Imported .po file uses invalid locale '{0}'", asset->Locale); + } + } + else if (line.StartsWith('#') || line.IsEmpty()) + { + // Comment + const Char* fuzzyPos = StringUtils::Find(line.Get(), TEXT("fuzzy")); + fuzzy |= fuzzyPos != nullptr && fuzzyPos < line.Get() + line.Length() - 1; + } + } + if (asset->Locale.IsEmpty()) + LOG(Warning, "Imported .po file has missing locale"); + + // Save asset + return asset->Save(context.TargetAssetPath) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok; +} + #endif diff --git a/Source/Engine/ContentImporters/CreateJson.h b/Source/Engine/ContentImporters/CreateJson.h index ccc15a792..38ca4b187 100644 --- a/Source/Engine/ContentImporters/CreateJson.h +++ b/Source/Engine/ContentImporters/CreateJson.h @@ -18,6 +18,7 @@ public: static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const String& dataTypename); static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const char* dataTypename); static bool Create(const StringView& path, StringAnsiView& data, StringAnsiView& dataTypename); + static CreateAssetResult ImportPo(CreateAssetContext& context); }; #endif diff --git a/Source/Engine/ContentImporters/ImportModelFile.cpp b/Source/Engine/ContentImporters/ImportModelFile.cpp index 073f3edbd..32fd38e4d 100644 --- a/Source/Engine/ContentImporters/ImportModelFile.cpp +++ b/Source/Engine/ContentImporters/ImportModelFile.cpp @@ -115,7 +115,8 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context) // Import model file ModelData modelData; String errorMsg; - if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, StringUtils::GetDirectoryName(context.TargetAssetPath) / StringUtils::GetFileNameWithoutExtension(context.InputPath))) + String autoImportOutput = String(StringUtils::GetDirectoryName(context.TargetAssetPath)) / StringUtils::GetFileNameWithoutExtension(context.InputPath); + if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput)) { LOG(Error, "Cannot import model file. {0}", errorMsg); return CreateAssetResult::Error; diff --git a/Source/Engine/ContentImporters/Types.h b/Source/Engine/ContentImporters/Types.h index 051b865d8..92aa7f533 100644 --- a/Source/Engine/ContentImporters/Types.h +++ b/Source/Engine/ContentImporters/Types.h @@ -111,12 +111,6 @@ public: /// The json metadata writer. void AddMeta(JsonWriter& writer) const; - /// - /// Save asset file data to the hard drive - /// - /// Saving result - CreateAssetResult Save(); - private: void ApplyChanges(); @@ -130,12 +124,17 @@ struct AssetImporter public: /// - /// Extension of the file to import with that importer + /// Extension of the file to import with that importer (without leading dot). /// String FileExtension; /// - /// Call asset importing process + /// Extension of the output file as output with that importer (without leading dot). + /// + String ResultExtension; + + /// + /// Callback for the asset importing process. /// CreateAssetFunction Callback; }; diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 6a841bac5..9b8c41245 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -2,6 +2,7 @@ #pragma once +#include #include "Engine/Platform/Platform.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Memory/Allocation.h" @@ -47,6 +48,20 @@ public: _allocation.Allocate(capacity); } + /// + /// Initializes a new instance of the class. + /// + /// The initial values defined in the array. + Array(std::initializer_list initList) + { + _count = _capacity = (int32)initList.size(); + if (_count > 0) + { + _allocation.Allocate(_count); + Memory::ConstructItems(Get(), initList.begin(), _count); + } + } + /// /// Initializes a new instance of the class. /// @@ -123,6 +138,24 @@ public: _allocation.Swap(other._allocation); } + /// + /// The assignment operator that deletes the current collection of items and the copies items from the initializer list. + /// + /// The other collection to copy. + /// The reference to this. + Array& operator=(std::initializer_list initList) noexcept + { + Memory::DestructItems(Get(), _count); + + _count = _capacity = (int32)initList.size(); + if (_capacity > 0) + { + _allocation.Allocate(_capacity); + Memory::ConstructItems(Get(), initList.begin(), _count); + } + return *this; + } + /// /// The assignment operator that deletes the current collection of items and the copies items from the other array. /// @@ -725,7 +758,6 @@ public: /// /// Performs pop from stack operation (stack grows at the end of the collection). /// - /// The item. T Pop() { T item(Last()); @@ -736,19 +768,19 @@ public: /// /// Peeks items which is at the top of the stack (stack grows at the end of the collection). /// - /// The item. FORCE_INLINE T& Peek() { - return Last(); + ASSERT(_count > 0); + return Get()[_count - 1]; } /// /// Peeks items which is at the top of the stack (stack grows at the end of the collection). /// - /// The item. FORCE_INLINE const T& Peek() const { - return Last(); + ASSERT(_count > 0); + return Get()[_count - 1]; } public: diff --git a/Source/Engine/Core/Collections/BitArray.h b/Source/Engine/Core/Collections/BitArray.h index c3051c678..dc20d1cd7 100644 --- a/Source/Engine/Core/Collections/BitArray.h +++ b/Source/Engine/Core/Collections/BitArray.h @@ -236,7 +236,7 @@ public: return; ASSERT(capacity >= 0); const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0; - _allocation.Relocate(Math::Max(capacity / sizeof(ItemType), 1), _count, count); + _allocation.Relocate(Math::Max(capacity / sizeof(ItemType), 1), _count / sizeof(ItemType), count / sizeof(ItemType)); _capacity = capacity; _count = count; } diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 99bad0bef..08b404bfd 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -2,7 +2,7 @@ #pragma once -#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Collections/Config.h" @@ -781,7 +781,8 @@ public: /// Gets the keys collection to the output array (will contain unique items). /// /// The result. - void GetKeys(Array& result) const + template + void GetKeys(Array& result) const { for (auto i = Begin(); i.IsNotEnd(); ++i) result.Add(i->Key); @@ -791,7 +792,8 @@ public: /// Gets the values collection to the output array (may contain duplicates). /// /// The result. - void GetValues(Array& result) const + template + void GetValues(Array& result) const { for (auto i = Begin(); i.IsNotEnd(); ++i) result.Add(i->Value); diff --git a/Source/Engine/Core/Collections/Sorting.h b/Source/Engine/Core/Collections/Sorting.h index 4fd26f794..6aa584ace 100644 --- a/Source/Engine/Core/Collections/Sorting.h +++ b/Source/Engine/Core/Collections/Sorting.h @@ -17,7 +17,7 @@ public: /// /// Helper collection used by the sorting algorithms. Implements stack using single linear allocation with variable capacity. /// - class SortingStack + class FLAXENGINE_API SortingStack { public: diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index 424eddaa1..5b333b227 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -13,6 +13,7 @@ #include "Engine/Input/InputSettings.h" #include "Engine/Audio/AudioSettings.h" #include "Engine/Navigation/NavigationSettings.h" +#include "Engine/Localization/LocalizationSettings.h" #include "Engine/Content/Content.h" #include "Engine/Content/JsonAsset.h" #include "Engine/Content/AssetReference.h" @@ -100,7 +101,12 @@ bool GameSettings::Load() auto settings = Get(); if (!settings) { +#if USE_EDITOR + // Allow lack of Game Settings in Editor + return false; +#else return true; +#endif } // Preload all settings assets @@ -122,6 +128,7 @@ bool GameSettings::Load() PRELOAD_SETTINGS(Input); PRELOAD_SETTINGS(Graphics); PRELOAD_SETTINGS(Navigation); + PRELOAD_SETTINGS(Localization); PRELOAD_SETTINGS(GameCooking); #undef PRELOAD_SETTINGS @@ -153,6 +160,7 @@ void GameSettings::Apply() APPLY_SETTINGS(InputSettings); APPLY_SETTINGS(GraphicsSettings); APPLY_SETTINGS(NavigationSettings); + APPLY_SETTINGS(LocalizationSettings); APPLY_SETTINGS(BuildSettings); APPLY_SETTINGS(PlatformSettings); #undef APPLY_SETTINGS @@ -192,6 +200,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE(Input); DESERIALIZE(Graphics); DESERIALIZE(Navigation); + DESERIALIZE(Localization); DESERIALIZE(GameCooking); // Per-platform settings containers diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index 0767d795a..ed96ef4e6 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -79,6 +79,12 @@ namespace FlaxEditor.Content.Settings [EditorOrder(1045), EditorDisplay("Other Settings"), AssetReference(typeof(NavigationSettings), true), Tooltip("Reference to Navigation Settings asset")] public JsonAsset Navigation; + /// + /// Reference to asset. + /// + [EditorOrder(1046), EditorDisplay("Other Settings"), AssetReference(typeof(LocalizationSettings), true), Tooltip("Reference to Localization Settings asset")] + public JsonAsset Localization; + /// /// Reference to asset. /// @@ -219,6 +225,8 @@ namespace FlaxEditor.Content.Settings return LoadAsset(gameSettings.Graphics) as T; if (type == typeof(NavigationSettings)) return LoadAsset(gameSettings.Navigation) as T; + if (type == typeof(LocalizationSettings)) + return LoadAsset(gameSettings.Localization) as T; if (type == typeof(BuildSettings)) return LoadAsset(gameSettings.GameCooking) as T; if (type == typeof(InputSettings)) @@ -321,6 +329,8 @@ namespace FlaxEditor.Content.Settings return SaveAsset(gameSettings, ref gameSettings.Graphics, obj); if (type == typeof(NavigationSettings)) return SaveAsset(gameSettings, ref gameSettings.Navigation, obj); + if (type == typeof(LocalizationSettings)) + return SaveAsset(gameSettings, ref gameSettings.Localization, obj); if (type == typeof(BuildSettings)) return SaveAsset(gameSettings, ref gameSettings.GameCooking, obj); if (type == typeof(InputSettings)) diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index 63745d9d8..8016bdbe4 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -68,6 +68,7 @@ public: Guid Input; Guid Graphics; Guid Navigation; + Guid Localization; Guid GameCooking; // Per-platform settings containers diff --git a/Source/Engine/Core/Config/LayersTagsSettings.h b/Source/Engine/Core/Config/LayersTagsSettings.h index 066b5cdd5..c72ab3974 100644 --- a/Source/Engine/Core/Config/LayersTagsSettings.h +++ b/Source/Engine/Core/Config/LayersTagsSettings.h @@ -3,6 +3,8 @@ #pragma once #include "Engine/Core/Config/Settings.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Collections/Array.h" /// /// Layers and objects tags settings. diff --git a/Source/Engine/Core/Config/TimeSettings.h b/Source/Engine/Core/Config/TimeSettings.h index 7e383380f..bdfcd927a 100644 --- a/Source/Engine/Core/Config/TimeSettings.h +++ b/Source/Engine/Core/Config/TimeSettings.h @@ -15,31 +15,31 @@ public: /// /// The target amount of the game logic updates per second (script updates frequency). /// - API_FIELD(Attributes="EditorOrder(1), DefaultValue(30.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Update FPS\")") - float UpdateFPS = 30.0f; + API_FIELD(Attributes="EditorOrder(1), Limit(0, 1000), EditorDisplay(\"General\", \"Update FPS\")") + float UpdateFPS = 60.0f; /// /// The target amount of the physics simulation updates per second (also fixed updates frequency). /// - API_FIELD(Attributes="EditorOrder(2), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Physics FPS\")") + API_FIELD(Attributes="EditorOrder(2), Limit(0, 1000), EditorDisplay(\"General\", \"Physics FPS\")") float PhysicsFPS = 60.0f; /// /// The target amount of the frames rendered per second (actual game FPS). /// - API_FIELD(Attributes="EditorOrder(3), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Draw FPS\")") + API_FIELD(Attributes="EditorOrder(3), Limit(0, 1000), EditorDisplay(\"General\", \"Draw FPS\")") float DrawFPS = 60.0f; /// /// The game time scale factor. Default is 1. /// - API_FIELD(Attributes="EditorOrder(10), DefaultValue(1.0f), Limit(0, 1000.0f, 0.1f), EditorDisplay(\"General\")") + API_FIELD(Attributes="EditorOrder(10), Limit(0, 1000.0f, 0.1f), EditorDisplay(\"General\")") float TimeScale = 1.0f; /// /// The maximum allowed delta time (in seconds) for the game logic update step. /// - API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.1f), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")") + API_FIELD(Attributes="EditorOrder(20), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")") float MaxUpdateDeltaTime = 0.1f; public: diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index d4118c3da..17c0c6afe 100644 --- a/Source/Engine/Core/Delegate.h +++ b/Source/Engine/Core/Delegate.h @@ -4,11 +4,6 @@ #include "Engine/Core/Memory/Allocation.h" -template -class Function; -template -class Delegate; - /// /// The function object. /// diff --git a/Source/Engine/Core/DeleteMe.h b/Source/Engine/Core/DeleteMe.h index fddb16f7a..7dc366b98 100644 --- a/Source/Engine/Core/DeleteMe.h +++ b/Source/Engine/Core/DeleteMe.h @@ -2,6 +2,8 @@ #pragma once +#include "Engine/Core/Compiler.h" + /// /// Helper class used to delete another object. /// diff --git a/Source/Engine/Core/ISerializable.h b/Source/Engine/Core/ISerializable.h index cfc5e1976..aaa2c241b 100644 --- a/Source/Engine/Core/ISerializable.h +++ b/Source/Engine/Core/ISerializable.h @@ -37,21 +37,21 @@ public: virtual ~ISerializable() = default; /// - /// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. + /// Serializes object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. /// /// The output stream. /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0; /// - /// Deserialize object from the input stream + /// Deserializes object from the input stream. /// /// The input stream. /// The deserialization modifier object. Always valid. virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; /// - /// Deserialize object from the input stream child member. Won't deserialize it if member is missing. + /// Deserializes object from the input stream child member. Won't deserialize it if member is missing. /// /// The input stream. /// The input stream member to lookup. diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index cb73a79e1..29099ad80 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -156,9 +156,9 @@ void Log::Logger::Dispose() WriteFloor(); // Close - LogAfterInit = false; if (LogAfterInit) { + LogAfterInit = false; LogFile->Close(); Delete(LogFile); LogFile = nullptr; diff --git a/Source/Engine/Core/Math/BoundingBox.cpp b/Source/Engine/Core/Math/BoundingBox.cpp index d7932e1b1..439b4341f 100644 --- a/Source/Engine/Core/Math/BoundingBox.cpp +++ b/Source/Engine/Core/Math/BoundingBox.cpp @@ -6,7 +6,7 @@ #include "../Types/String.h" const BoundingBox BoundingBox::Empty(Vector3(MAX_float), Vector3(MIN_float)); -const BoundingBox BoundingBox::Zero(Vector3(0.0f), Vector3(0.0f)); +const BoundingBox BoundingBox::Zero(Vector3(0.0f)); String BoundingBox::ToString() const { diff --git a/Source/Engine/Core/Math/BoundingBox.h b/Source/Engine/Core/Math/BoundingBox.h index f669370ff..8a349e022 100644 --- a/Source/Engine/Core/Math/BoundingBox.h +++ b/Source/Engine/Core/Math/BoundingBox.h @@ -45,6 +45,16 @@ public: { } + /// + /// Initializes a new instance of the struct. + /// + /// The location of the empty bounding box. + BoundingBox(const Vector3& point) + : Minimum(point) + , Maximum(point) + { + } + /// /// Initializes a new instance of the struct. /// diff --git a/Source/Engine/Core/Math/CollisionsHelper.cs b/Source/Engine/Core/Math/CollisionsHelper.cs index 7dd660587..d6e0392c8 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cs +++ b/Source/Engine/Core/Math/CollisionsHelper.cs @@ -125,7 +125,7 @@ namespace FlaxEngine public static class CollisionsHelper { /// - /// Determines the closest point between a point and a line. + /// Determines the closest point between a point and a line segment. /// /// The point to test. /// The line first point. diff --git a/Source/Engine/Core/Math/Int2.cpp b/Source/Engine/Core/Math/Int2.cpp new file mode 100644 index 000000000..1293d23ac --- /dev/null +++ b/Source/Engine/Core/Math/Int2.cpp @@ -0,0 +1,51 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Int2.h" +#include "Int3.h" +#include "Int4.h" +#include "Vector2.h" +#include "Vector3.h" +#include "Vector4.h" +#include "Engine/Core/Types/String.h" + +static_assert(sizeof(Int2) == 8, "Invalid Int2 type size."); + +const Int2 Int2::Zero(0); +const Int2 Int2::One(1); +const Int2 Int2::Minimum(MIN_int32); +const Int2 Int2::Maximum(MAX_int32); + +Int2::Int2(const Int3& xyz) + : X(xyz.X) + , Y(xyz.Y) +{ +} + +Int2::Int2(const Int4& xyzw) + : X(xyzw.X) + , Y(xyzw.Y) +{ +} + +Int2::Int2(const Vector2& xy) + : X(static_cast(xy.X)) + , Y(static_cast(xy.Y)) +{ +} + +Int2::Int2(const Vector3& xyz) + : X(static_cast(xyz.X)) + , Y(static_cast(xyz.Y)) +{ +} + +Int2::Int2(const Vector4& xyzw) + : X(static_cast(xyzw.X)) + , Y(static_cast(xyzw.Y)) +{ +} + +String Int2::ToString() const +{ + return String::Format(TEXT("{}"), *this); +} diff --git a/Source/Engine/Core/Math/Int2.cs b/Source/Engine/Core/Math/Int2.cs index 0bf38e95f..a063e1bbf 100644 --- a/Source/Engine/Core/Math/Int2.cs +++ b/Source/Engine/Core/Math/Int2.cs @@ -12,9 +12,8 @@ namespace FlaxEngine /// Represents a two dimensional mathematical vector (signed integers). /// [Serializable] - [StructLayout(LayoutKind.Sequential, Pack = 4)] [TypeConverter(typeof(TypeConverters.Int2Converter))] - public struct Int2 : IEquatable, IFormattable + partial struct Int2 : IEquatable, IFormattable { private static readonly string _formatString = "X:{0} Y:{1}"; @@ -53,16 +52,6 @@ namespace FlaxEngine /// public static readonly Int2 Maximum = new Int2(int.MaxValue); - /// - /// The X component of the vector. - /// - public int X; - - /// - /// The Y component of the vector. - /// - public int Y; - /// /// Initializes a new instance of the struct. /// diff --git a/Source/Engine/Core/Math/Int2.h b/Source/Engine/Core/Math/Int2.h index a6038d1b0..236768254 100644 --- a/Source/Engine/Core/Math/Int2.h +++ b/Source/Engine/Core/Math/Int2.h @@ -6,26 +6,27 @@ #include "Engine/Core/Formatting.h" #include "Engine/Core/Templates.h" -struct Vector2; -struct Vector3; -struct Vector4; - /// /// Two-components vector (32 bit integer type). /// -API_STRUCT(InBuild) struct FLAXENGINE_API Int2 +API_STRUCT() struct FLAXENGINE_API Int2 { +DECLARE_SCRIPTING_TYPE_MINIMAL(Int2); public: union { struct { - // X component - int32 X; + /// + /// The X component. + /// + API_FIELD() int32 X; - // Y component - int32 Y; + /// + /// The Y component. + /// + API_FIELD() int32 Y; }; // Raw values @@ -40,6 +41,12 @@ public: // Vector with all components equal 1 static const Int2 One; + // A minimum Int2 + static const Int2 Minimum; + + // A maximum Int2 + static const Int2 Maximum; + public: /// @@ -67,9 +74,25 @@ public: } // Init - // @param v Vector to use X and Y components - explicit Int2(const Vector2& v); + // @param xyz Int3 to use X and Y components + Int2(const Int3& xyz); + // Init + // @param xyzw Int4 to use X and Y components + Int2(const Int4& xyzw); + + // Init + // @param xy Vector2 to use X and Y components + explicit Int2(const Vector2& xy); + + // Init + // @param xyz Vector3 to use X and Y components + explicit Int2(const Vector3& xyz); + + // Init + // @param xyzw Vector4 to use X and Y components + explicit Int2(const Vector4& xyzw); + public: String ToString() const; @@ -211,29 +234,29 @@ public: public: - static void Add(const Int2& a, const Int2& b, Int2* result) + static void Add(const Int2& a, const Int2& b, Int2& result) { - result->X = a.X + b.X; - result->Y = a.Y + b.Y; + result.X = a.X + b.X; + result.Y = a.Y + b.Y; } static Int2 Add(const Int2& a, const Int2& b) { Int2 result; - Add(a, b, &result); + Add(a, b, result); return result; } - static void Subtract(const Int2& a, const Int2& b, Int2* result) + static void Subtract(const Int2& a, const Int2& b, Int2& result) { - result->X = a.X - b.X; - result->Y = a.Y - b.Y; + result.X = a.X - b.X; + result.Y = a.Y - b.Y; } static Int2 Subtract(const Int2& a, const Int2& b) { Int2 result; - Subtract(a, b, &result); + Subtract(a, b, result); return result; } @@ -257,17 +280,112 @@ public: return Int2(a.X / b, a.Y / b); } - // Creates vector from minimum components of two vectors + /// + /// Gets a value indicting whether this vector is zero. + /// + /// True if the vector is zero, otherwise false. + bool IsZero() const + { + return X == 0 && Y == 0; + } + + /// + /// Gets a value indicting whether any vector component is zero. + /// + /// True if a component is zero, otherwise false. + bool IsAnyZero() const + { + return X == 0 || Y == 0; + } + + /// + /// Gets a value indicting whether this vector is one. + /// + /// True if the vector is one, otherwise false. + bool IsOne() const + { + return X == 1 && Y == 1; + } + + /// + /// Calculates a vector with values being opposite to values of that vector + /// + /// Negative vector + Int2 GetNegative() const + { + return Int2(-X, -Y); + } + + /// + /// Returns average arithmetic of all the components + /// + /// Average arithmetic of all the components + float AverageArithmetic() const + { + return (X + Y) * 0.5f; + } + + /// + /// Gets sum of all vector components values + /// + /// Sum of X, Y, Z and W + int32 SumValues() const + { + return X + Y; + } + + /// + /// Returns minimum value of all the components + /// + /// Minimum value + int32 MinValue() const + { + return Math::Min(X, Y); + } + + /// + /// Returns maximum value of all the components + /// + /// Maximum value + int32 MaxValue() const + { + return Math::Max(X, Y); + } + + + // Returns a vector containing the smallest components of the specified vectors + // @param a The first source vector + // @param b The second source vector static Int2 Min(const Int2& a, const Int2& b) { return Int2(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y); } - // Creates vector from maximum components of two vectors + // Returns a vector containing the largest components of the specified vectors + // @param a The first source vector + // @param b The second source vector static Int2 Max(const Int2& a, const Int2& b) { return Int2(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y); } + + // Returns a vector containing the smallest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + // @param result When the method completes, contains an new vector composed of the smallest components of the source vectors + static void Min(const Int2& a, const Int2& b, Int2& result) + { + result = Int2(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y); + } + + // Returns a vector containing the largest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + // @param result When the method completes, contains an new vector composed of the largest components of the source vectors + static void Max(const Int2& a, const Int2& b, Int2& result) + { + result = Int2(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y); + } }; template<> diff --git a/Source/Engine/Core/Math/Int3.cpp b/Source/Engine/Core/Math/Int3.cpp new file mode 100644 index 000000000..327437d70 --- /dev/null +++ b/Source/Engine/Core/Math/Int3.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Int2.h" +#include "Int3.h" +#include "Int4.h" +#include "Vector2.h" +#include "Vector3.h" +#include "Vector4.h" +#include "Engine/Core/Types/String.h" + +static_assert(sizeof(Int3) == 12, "Invalid Int3 type size."); + +const Int3 Int3::Zero(0); +const Int3 Int3::One(1); +const Int3 Int3::Minimum(MIN_int32); +const Int3 Int3::Maximum(MAX_int32); + +Int3::Int3(const Int2& xy, int32 z) + : X(xy.X) + , Y(xy.Y) + , Z(z) +{ +} + +Int3::Int3(const Int4& xyzw) + : X(xyzw.X) + , Y(xyzw.Y) + , Z(xyzw.Z) +{ +} + +Int3::Int3(const Vector2& xy, int32 z) + : X(static_cast(xy.X)) + , Y(static_cast(xy.Y)) + , Z(z) +{ +} + +Int3::Int3(const Vector3& xyz) + : X(static_cast(xyz.X)) + , Y(static_cast(xyz.Y)) + , Z(static_cast(xyz.Z)) +{ +} + +Int3::Int3(const Vector4& xyzw) + : X(static_cast(xyzw.X)) + , Y(static_cast(xyzw.Y)) + , Z(static_cast(xyzw.Z)) +{ +} + +String Int3::ToString() const +{ + return String::Format(TEXT("{}"), *this); +} diff --git a/Source/Engine/Core/Math/Int3.cs b/Source/Engine/Core/Math/Int3.cs index 93193881a..e0c67bd33 100644 --- a/Source/Engine/Core/Math/Int3.cs +++ b/Source/Engine/Core/Math/Int3.cs @@ -12,9 +12,8 @@ namespace FlaxEngine /// Represents a three dimensional mathematical vector (signed integers). /// [Serializable] - [StructLayout(LayoutKind.Sequential, Pack = 4)] [TypeConverter(typeof(TypeConverters.Int3Converter))] - public struct Int3 : IEquatable, IFormattable + partial struct Int3 : IEquatable, IFormattable { private static readonly string _formatString = "X:{0} Y:{1} Z:{2}"; @@ -58,21 +57,6 @@ namespace FlaxEngine /// public static readonly Int3 Maximum = new Int3(int.MaxValue); - /// - /// The X component of the vector. - /// - public int X; - - /// - /// The Y component of the vector. - /// - public int Y; - - /// - /// The Z component of the vector. - /// - public int Z; - /// /// Initializes a new instance of the struct. /// diff --git a/Source/Engine/Core/Math/Int3.h b/Source/Engine/Core/Math/Int3.h index 6886acc6b..92311e2b1 100644 --- a/Source/Engine/Core/Math/Int3.h +++ b/Source/Engine/Core/Math/Int3.h @@ -6,29 +6,33 @@ #include "Engine/Core/Formatting.h" #include "Engine/Core/Templates.h" -struct Vector2; -struct Vector3; -struct Vector4; /// /// Three-components vector (32 bit integer type). /// -API_STRUCT(InBuild) struct FLAXENGINE_API Int3 +API_STRUCT() struct FLAXENGINE_API Int3 { +DECLARE_SCRIPTING_TYPE_MINIMAL(Int3); public: union { struct { - // X component - int32 X; + /// + /// The X component. + /// + API_FIELD() int32 X; - // Y component - int32 Y; + /// + /// The Y component. + /// + API_FIELD() int32 Y; - // Y component - int32 Z; + /// + /// The Z component. + /// + API_FIELD() int32 Z; }; // Raw values @@ -43,6 +47,12 @@ public: // Vector with all components equal 1 static const Int3 One; + // A minimum Int3 + static const Int3 Minimum; + + // A maximum Int3 + static const Int3 Maximum; + public: /// @@ -73,19 +83,293 @@ public: } // Init - // @param v Vector to use X, Y and Z components - explicit Int3(const Vector3& v); + // @param v Int2 to use X and Y components + // @param z Z component value + Int3(const Int2& xy, int32 z); + // Init + // @param v Int4 to use X and Y components + Int3(const Int4& xyzw); + + // Init + // @param v Vector2 to use X and Y components + // @param z Z component value + explicit Int3(const Vector2& xy, int32 z); + + // Init + // @param v Vector3 to use X, Y and Z components + explicit Int3(const Vector3& xyz); + + // Init + // @param v Vector4 to use X and Y components + explicit Int3(const Vector4& xyzw); + public: String ToString() const; public: + // Arithmetic operators with Int2 + + Int3 operator+(const Int3& b) const + { + return Add(*this, b); + } + + Int3 operator-(const Int3& b) const + { + return Subtract(*this, b); + } + + Int3 operator*(const Int3& b) const + { + return Multiply(*this, b); + } + + Int3 operator/(const Int3& b) const + { + return Divide(*this, b); + } + + Int3 operator-() const + { + return Int3(-X, -Y, -Z); + } + + // op= operators with Int2 + + Int3& operator+=(const Int3& b) + { + *this = Add(*this, b); + return *this; + } + + Int3& operator-=(const Int3& b) + { + *this = Subtract(*this, b); + return *this; + } + + Int3& operator*=(const Int3& b) + { + *this = Multiply(*this, b); + return *this; + } + + Int3& operator/=(const Int3& b) + { + *this = Divide(*this, b); + return *this; + } + + // Arithmetic operators with int32 + + Int3 operator+(int32 b) const + { + return Add(*this, b); + } + + Int3 operator-(int32 b) const + { + return Subtract(*this, b); + } + + Int3 operator*(int32 b) const + { + return Multiply(*this, b); + } + + Int3 operator/(int32 b) const + { + return Divide(*this, b); + } + + // op= operators with int32 + + Int3& operator+=(int32 b) + { + *this = Add(*this, b); + return *this; + } + + Int3& operator-=(int32 b) + { + *this = Subtract(*this, b); + return *this; + } + + Int3& operator*=(int32 b) + { + *this = Multiply(*this, b); + return *this; + } + + Int3& operator/=(int32 b) + { + *this = Divide(*this, b); + return *this; + } + + // Comparison operators + + bool operator==(const Int3& b) const + { + return X == b.X && Y == b.Y; + } + + bool operator!=(const Int3& b) const + { + return X != b.X || Y != b.Y; + } + + bool operator>(const Int3& b) const + { + return X > b.X && Y > b.Y; + } + + bool operator>=(const Int3& b) const + { + return X >= b.X && Y >= b.Y; + } + + bool operator<(const Int3& b) const + { + return X < b.X && Y < b.Y; + } + + bool operator<=(const Int3& b) const + { + return X <= b.X && Y <= b.Y; + } + +public: + + static void Add(const Int3& a, const Int3& b, Int3& result) + { + result.X = a.X + b.X; + result.Y = a.Y + b.Y; + result.Z = a.Z + b.Z; + } + + static Int3 Add(const Int3& a, const Int3& b) + { + Int3 result; + Add(a, b, result); + return result; + } + + static void Subtract(const Int3& a, const Int3& b, Int3& result) + { + result.X = a.X - b.X; + result.Y = a.Y - b.Y; + result.Z = a.Z - b.Z; + } + + static Int3 Subtract(const Int3& a, const Int3& b) + { + Int3 result; + Subtract(a, b, result); + return result; + } + + static Int3 Multiply(const Int3& a, const Int3& b) + { + return Int3(a.X * b.X, a.Y * b.Y, a.Z * b.Z); + } + + static Int3 Multiply(const Int3& a, int32 b) + { + return Int3(a.X * b, a.Y * b, a.Z * b); + } + + static Int3 Divide(const Int3& a, const Int3& b) + { + return Int3(a.X / b.X, a.Y / b.Y, a.Z / b.Z); + } + + static Int3 Divide(const Int3& a, int32 b) + { + return Int3(a.X / b, a.Y / b, a.Z / b); + } + +public: + + /// + /// Gets a value indicting whether this vector is zero. + /// + /// True if the vector is zero, otherwise false. + bool IsZero() const + { + return X == 0 && Y == 0 && Z == 0; + } + + /// + /// Gets a value indicting whether any vector component is zero. + /// + /// True if a component is zero, otherwise false. + bool IsAnyZero() const + { + return X == 0 || Y == 0 || Z == 0; + } + + /// + /// Gets a value indicting whether this vector is one. + /// + /// True if the vector is one, otherwise false. + bool IsOne() const + { + return X == 1 && Y == 1 && Z == 1; + } + + /// + /// Calculates a vector with values being opposite to values of that vector + /// + /// Negative vector + Int3 GetNegative() const + { + return Int3(-X, -Y, -Z); + } + + /// + /// Returns average arithmetic of all the components + /// + /// Average arithmetic of all the components + float AverageArithmetic() const + { + return (X + Y + Z) / 3.0f; + } + + /// + /// Gets sum of all vector components values + /// + /// Sum of X, Y, Z and W + int32 SumValues() const + { + return X + Y + Z; + } + + /// + /// Returns minimum value of all the components + /// + /// Minimum value + int32 MinValue() const + { + return Math::Min(X, Y, Z); + } + + /// + /// Returns maximum value of all the components + /// + /// Maximum value + int32 MaxValue() const + { + return Math::Max(X, Y, Z); + } + // Returns a vector containing the largest components of the specified vectors // @param a The first source vector // @param b The second source vector - // @param result When the method completes, contains an new vector composed of the largest components of the source vectors static Int3 Max(const Int3& a, const Int3& b) { return Int3(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z); @@ -94,7 +378,6 @@ public: // Returns a vector containing the smallest components of the specified vectors // @param a The first source vector // @param b The second source vector - // @param result When the method completes, contains an new vector composed of the smallest components of the source vectors static Int3 Min(const Int3& a, const Int3& b) { return Int3(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z); @@ -104,18 +387,18 @@ public: // @param a The first source vector // @param b The second source vector // @param result When the method completes, contains an new vector composed of the largest components of the source vectors - static void Max(const Int3& a, const Int3& b, Int3* result) + static void Max(const Int3& a, const Int3& b, Int3& result) { - *result = Int3(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z); + result = Int3(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z); } // Returns a vector containing the smallest components of the specified vectors // @param a The first source vector // @param b The second source vector // @param result When the method completes, contains an new vector composed of the smallest components of the source vectors - static void Min(const Int3& a, const Int3& b, Int3* result) + static void Min(const Int3& a, const Int3& b, Int3 result) { - *result = Int3(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z); + result = Int3(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z); } }; diff --git a/Source/Engine/Core/Math/VectorInt.cpp b/Source/Engine/Core/Math/Int4.cpp similarity index 56% rename from Source/Engine/Core/Math/VectorInt.cpp rename to Source/Engine/Core/Math/Int4.cpp index 300343da5..a8f6be236 100644 --- a/Source/Engine/Core/Math/VectorInt.cpp +++ b/Source/Engine/Core/Math/Int4.cpp @@ -1,42 +1,51 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -#include "VectorInt.h" +#include "Int2.h" +#include "Int3.h" +#include "Int4.h" #include "Vector2.h" #include "Vector3.h" #include "Vector4.h" #include "Engine/Core/Types/String.h" -const Int2 Int2::Zero(0); -const Int2 Int2::One(1); - -Int2::Int2(const Vector2& v) - : X(static_cast(v.X)) - , Y(static_cast(v.Y)) -{ -} - -String Int2::ToString() const -{ - return String::Format(TEXT("{}"), *this); -} - -const Int3 Int3::Zero(0); -const Int3 Int3::One(1); - -Int3::Int3(const Vector3& v) - : X(static_cast(v.X)) - , Y(static_cast(v.Y)) - , Z(static_cast(v.Z)) -{ -} - -String Int3::ToString() const -{ - return String::Format(TEXT("{}"), *this); -} +static_assert(sizeof(Int4) == 16, "Invalid Int4 type size."); const Int4 Int4::Zero(0); const Int4 Int4::One(1); +const Int4 Int4::Minimum(MIN_int32); +const Int4 Int4::Maximum(MAX_int32); + +Int4::Int4(const Int2& xy, int32 z, int32 w) + : X(xy.X) + , Y(xy.Y) + , Z(z) + , W(w) +{ +} + +Int4::Int4(const Int3& xyz, int32 w) + : X(xyz.X) + , Y(xyz.Y) + , Z(xyz.Z) + , W(w) +{ +} + +Int4::Int4(const Vector2& v, int32 z, int32 w) + : X(static_cast(v.X)) + , Y(static_cast(v.Y)) + , Z(z) + , W(w) +{ +} + +Int4::Int4(const Vector3& v, int32 w) + : X(static_cast(v.X)) + , Y(static_cast(v.Y)) + , Z(static_cast(v.Z)) + , W(w) +{ +} Int4::Int4(const Vector4& v) : X(static_cast(v.X)) diff --git a/Source/Engine/Core/Math/Int4.cs b/Source/Engine/Core/Math/Int4.cs index 0617f7b71..5328dcc73 100644 --- a/Source/Engine/Core/Math/Int4.cs +++ b/Source/Engine/Core/Math/Int4.cs @@ -12,9 +12,8 @@ namespace FlaxEngine /// Represents a four dimensional mathematical vector (signed integers). /// [Serializable] - [StructLayout(LayoutKind.Sequential, Pack = 4)] [TypeConverter(typeof(TypeConverters.Int4Converter))] - public struct Int4 : IEquatable, IFormattable + partial struct Int4 : IEquatable, IFormattable { private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}"; @@ -63,26 +62,6 @@ namespace FlaxEngine /// public static readonly Int4 Maximum = new Int4(int.MaxValue); - /// - /// The X component of the vector. - /// - public int X; - - /// - /// The Y component of the vector. - /// - public int Y; - - /// - /// The Z component of the vector. - /// - public int Z; - - /// - /// The W component of the vector. - /// - public int W; - /// /// Initializes a new instance of the struct. /// diff --git a/Source/Engine/Core/Math/Int4.h b/Source/Engine/Core/Math/Int4.h index c8e9eebd8..8c7f148be 100644 --- a/Source/Engine/Core/Math/Int4.h +++ b/Source/Engine/Core/Math/Int4.h @@ -6,32 +6,37 @@ #include "Engine/Core/Formatting.h" #include "Engine/Core/Templates.h" -struct Vector2; -struct Vector3; -struct Vector4; - /// /// Four-components vector (32 bit integer type). /// -API_STRUCT(InBuild) struct FLAXENGINE_API Int4 +API_STRUCT() struct FLAXENGINE_API Int4 { +DECLARE_SCRIPTING_TYPE_MINIMAL(Int4); public: union { struct { - // X component - int32 X; + /// + /// The X component. + /// + API_FIELD() int32 X; - // Y component - int32 Y; + /// + /// The Y component. + /// + API_FIELD() int32 Y; - // Z component - int32 Z; + /// + /// The Z component. + /// + API_FIELD() int32 Z; - // W component - int32 W; + /// + /// The W component. + /// + API_FIELD() int32 W; }; // Raw values @@ -46,6 +51,12 @@ public: // Vector with all components equal 1 static const Int4 One; + // A minimum Int4 + static const Int4 Minimum; + + // A maximum Int4 + static const Int4 Maximum; + public: /// @@ -78,9 +89,31 @@ public: { } + // Init + // @param v Int2 to use X and Y components + // @param z Z component value + // @param w W component value + Int4(const Int2& xy, int32 z, int32 w); + + // Init + // @param v Int3 to use X , Y and Z components + // @param w W component value + Int4(const Int3& xyz, int32 w); + + // Init + // @param v Vector2 to use X and Y components + // @param z Z component value + // @param w W component value + explicit Int4(const Vector2& xy, int32 z, int32 w); + + // Init + // @param v Vector3 to use X , Y and Z components + // @param w W component value + explicit Int4(const Vector3& xyz, int32 w); + // Init // @param v Vector to use X, Y, Z and W components - explicit Int4(const Vector4& v); + explicit Int4(const Vector4& xyzw); public: @@ -88,6 +121,229 @@ public: public: + // Arithmetic operators with Int2 + + Int4 operator+(const Int4& b) const + { + return Add(*this, b); + } + + Int4 operator-(const Int4& b) const + { + return Subtract(*this, b); + } + + Int4 operator*(const Int4& b) const + { + return Multiply(*this, b); + } + + Int4 operator/(const Int4& b) const + { + return Divide(*this, b); + } + + Int4 operator-() const + { + return Int4(-X, -Y, -Z, -W); + } + + // op= operators with Int2 + + Int4& operator+=(const Int4& b) + { + *this = Add(*this, b); + return *this; + } + + Int4& operator-=(const Int4& b) + { + *this = Subtract(*this, b); + return *this; + } + + Int4& operator*=(const Int4& b) + { + *this = Multiply(*this, b); + return *this; + } + + Int4& operator/=(const Int4& b) + { + *this = Divide(*this, b); + return *this; + } + + // Arithmetic operators with int32 + + Int4 operator+(int32 b) const + { + return Add(*this, b); + } + + Int4 operator-(int32 b) const + { + return Subtract(*this, b); + } + + Int4 operator*(int32 b) const + { + return Multiply(*this, b); + } + + Int4 operator/(int32 b) const + { + return Divide(*this, b); + } + + // op= operators with int32 + + Int4& operator+=(int32 b) + { + *this = Add(*this, b); + return *this; + } + + Int4& operator-=(int32 b) + { + *this = Subtract(*this, b); + return *this; + } + + Int4& operator*=(int32 b) + { + *this = Multiply(*this, b); + return *this; + } + + Int4& operator/=(int32 b) + { + *this = Divide(*this, b); + return *this; + } + + // Comparison operators + + bool operator==(const Int4& b) const + { + return X == b.X && Y == b.Y; + } + + bool operator!=(const Int4& b) const + { + return X != b.X || Y != b.Y; + } + + bool operator>(const Int4& b) const + { + return X > b.X && Y > b.Y; + } + + bool operator>=(const Int4& b) const + { + return X >= b.X && Y >= b.Y; + } + + bool operator<(const Int4& b) const + { + return X < b.X && Y < b.Y; + } + + bool operator<=(const Int4& b) const + { + return X <= b.X && Y <= b.Y; + } + +public: + + static void Add(const Int4& a, const Int4& b, Int4& result) + { + result.X = a.X + b.X; + result.Y = a.Y + b.Y; + result.Z = a.Z + b.Z; + result.W = a.W + b.W; + } + + static Int4 Add(const Int4& a, const Int4& b) + { + Int4 result; + Add(a, b, result); + return result; + } + + static void Subtract(const Int4& a, const Int4& b, Int4& result) + { + result.X = a.X - b.X; + result.Y = a.Y - b.Y; + result.Z = a.Z - b.Z; + result.W = a.W - b.W; + } + + static Int4 Subtract(const Int4& a, const Int4& b) + { + Int4 result; + Subtract(a, b, result); + return result; + } + + static Int4 Multiply(const Int4& a, const Int4& b) + { + return Int4(a.X * b.X, a.Y * b.Y, a.Z * b.Z, a.W * b.W); + } + + static Int4 Multiply(const Int4& a, int32 b) + { + return Int4(a.X * b, a.Y * b, a.Z * b, a.W * b); + } + + static Int4 Divide(const Int4& a, const Int4& b) + { + return Int4(a.X / b.X, a.Y / b.Y, a.Z / b.Z, a.W / b.W); + } + + static Int4 Divide(const Int4& a, int32 b) + { + return Int4(a.X / b, a.Y / b, a.Z / b, a.Y / b); + } + +public: + + /// + /// Gets a value indicting whether this vector is zero. + /// + /// True if the vector is zero, otherwise false. + bool IsZero() const + { + return X == 0 && Y == 0 && Z == 0 && W == 0; + } + + /// + /// Gets a value indicting whether any vector component is zero. + /// + /// True if a component is zero, otherwise false. + bool IsAnyZero() const + { + return X == 0 || Y == 0 || Z == 0 || W == 0; + } + + /// + /// Gets a value indicting whether this vector is one. + /// + /// True if the vector is one, otherwise false. + bool IsOne() const + { + return X == 1 && Y == 1 && Z == 1 && W == 1; + } + + /// + /// Calculates a vector with values being opposite to values of that vector + /// + /// Negative vector + Int4 GetNegative() const + { + return Int4(-X, -Y, -Z, -W); + } + /// /// Returns average arithmetic of all the components /// @@ -123,6 +379,40 @@ public: { return Math::Max(X, Y, Z, W); } + + // Returns a vector containing the largest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + static Int4 Max(const Int4& a, const Int4& b) + { + return Int4(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z, a.W > b.W ? a.W : b.W); + } + + // Returns a vector containing the smallest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + static Int4 Min(const Int4& a, const Int4& b) + { + return Int4(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z, a.W < b.W ? a.W : b.W); + } + + // Returns a vector containing the largest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + // @param result When the method completes, contains an new vector composed of the largest components of the source vectors + static void Max(const Int4& a, const Int4& b, Int4& result) + { + result = Int4(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z, a.W > b.W ? a.W : b.W); + } + + // Returns a vector containing the smallest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + // @param result When the method completes, contains an new vector composed of the smallest components of the source vectors + static void Min(const Int4& a, const Int4& b, Int4& result) + { + result = Int4(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z, a.W < b.W ? a.W : b.W); + } }; template<> diff --git a/Source/Engine/Core/Math/Math.h b/Source/Engine/Core/Math/Math.h index ee3b34b9a..e9bd6e58f 100644 --- a/Source/Engine/Core/Math/Math.h +++ b/Source/Engine/Core/Math/Math.h @@ -13,6 +13,7 @@ #define PI_OVER_2 1.57079632679f #define PI_OVER_4 0.78539816339f #define PI_HALF PI_OVER_2 +#define GOLDEN_RATIO 1.6180339887f // The value for which all absolute numbers smaller than are considered equal to zero. #define ZeroTolerance 1e-6f @@ -648,9 +649,9 @@ namespace Math { float delta = a2 - a1; if (delta > PI) - delta = delta - PI * 2.0f; + delta = delta - TWO_PI; else if (delta < -PI) - delta = delta + PI * 2.0f; + delta = delta + TWO_PI; return delta; } @@ -658,9 +659,9 @@ namespace Math static float UnwindRadians(float a) { while (a > PI) - a -= (float)PI * 2.0f; + a -= TWO_PI; while (a < -PI) - a += (float)PI * 2.0f; + a += TWO_PI; return a; } diff --git a/Source/Engine/Core/Math/Matrix3x3.cs b/Source/Engine/Core/Math/Matrix3x3.cs index f440eb799..e65d9b036 100644 --- a/Source/Engine/Core/Math/Matrix3x3.cs +++ b/Source/Engine/Core/Math/Matrix3x3.cs @@ -1566,7 +1566,7 @@ namespace FlaxEngine } /// - /// Creates a Matrix3x3 that scales along the x-axis, y-axis, and y-axis. + /// Creates a Matrix3x3 that scales along the x-axis, y-axis, and z-axis. /// /// Scaling factor for all three axes. /// The created scaling Matrix3x3. @@ -1577,7 +1577,7 @@ namespace FlaxEngine } /// - /// Creates a Matrix3x3 that scales along the x-axis, y-axis, and y-axis. + /// Creates a Matrix3x3 that scales along the x-axis, y-axis, and z-axis. /// /// Scaling factor that is applied along the x-axis. /// Scaling factor that is applied along the y-axis. @@ -1605,9 +1605,9 @@ namespace FlaxEngine } /// - /// Creates a Matrix3x3 that uniformly scales along all three axis. + /// Creates a Matrix3x3 that uniformly scales along all three axes. /// - /// The uniform scale that is applied along all axis. + /// The uniform scale that is applied along all axes. /// When the method completes, contains the created scaling Matrix3x3. public static void Scaling(float scale, out Matrix3x3 result) { @@ -1616,9 +1616,9 @@ namespace FlaxEngine } /// - /// Creates a Matrix3x3 that uniformly scales along all three axis. + /// Creates a Matrix3x3 that uniformly scales along all three axes. /// - /// The uniform scale that is applied along all axis. + /// The uniform scale that is applied along all axes. /// The created scaling Matrix3x3. public static Matrix3x3 Scaling(float scale) { diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index 125b5b36c..684fb26b4 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -697,6 +697,27 @@ namespace FlaxEngine return result; } + /// + /// Calculates the orientation from the direction vector. + /// + /// The direction vector (normalized). + /// The orientation. + public static Quaternion FromDirection(Vector3 direction) + { + Quaternion orientation; + if (Vector3.Dot(direction, Vector3.Up) >= 0.999f) + { + orientation = RotationAxis(Vector3.Left, Mathf.PiOverTwo); + } + else + { + Vector3 right = Vector3.Cross(direction, Vector3.Up); + Vector3 up = Vector3.Cross(right, direction); + orientation = LookRotation(direction, up); + } + return orientation; + } + /// /// Performs a linear interpolation between two quaternions. /// diff --git a/Source/Engine/Core/Math/Rectangle.cs b/Source/Engine/Core/Math/Rectangle.cs index 50f9adce4..d1fb16abc 100644 --- a/Source/Engine/Core/Math/Rectangle.cs +++ b/Source/Engine/Core/Math/Rectangle.cs @@ -285,6 +285,19 @@ namespace FlaxEngine return new Rectangle(Location - toExpand * 0.5f, Size + toExpand); } + /// + /// Calculates a rectangle that includes the margins (inside). + /// + /// The rectangle. + /// The margin to apply to the rectangle. + /// Rectangle inside the given rectangle after applying margins inside it. + public static Rectangle Margin(Rectangle value, GUI.Margin margin) + { + value.Location += margin.Location; + value.Size -= margin.Size; + return value; + } + /// /// Calculates a rectangle that contains the union of a and b rectangles /// diff --git a/Source/Engine/Core/Math/Vector2.cpp b/Source/Engine/Core/Math/Vector2.cpp index c23e79d19..2c6979575 100644 --- a/Source/Engine/Core/Math/Vector2.cpp +++ b/Source/Engine/Core/Math/Vector2.cpp @@ -5,6 +5,8 @@ #include "Vector4.h" #include "Color.h" #include "Int2.h" +#include "Int3.h" +#include "Int4.h" #include "../Types/String.h" static_assert(sizeof(Vector2) == 8, "Invalid Vector2 type size."); @@ -16,21 +18,33 @@ const Vector2 Vector2::UnitY(0, 1); const Vector2 Vector2::Minimum(MIN_float); const Vector2 Vector2::Maximum(MAX_float); -Vector2::Vector2(const Int2& v) - : X((float)v.X) - , Y((float)v.Y) +Vector2::Vector2(const Int2& xy) + : X(static_cast(xy.X)) + , Y(static_cast(xy.Y)) { } -Vector2::Vector2(const Vector3& v) - : X(v.X) - , Y(v.Y) +Vector2::Vector2(const Int3& xyz) + : X(static_cast(xyz.X)) + , Y(static_cast(xyz.Y)) { } -Vector2::Vector2(const Vector4& v) - : X(v.X) - , Y(v.Y) +Vector2::Vector2(const Int4& xyzw) + : X(static_cast(xyzw.X)) + , Y(static_cast(xyzw.Y)) +{ +} + +Vector2::Vector2(const Vector3& xyz) + : X(xyz.X) + , Y(xyz.Y) +{ +} + +Vector2::Vector2(const Vector4& xyzw) + : X(xyzw.X) + , Y(xyzw.Y) { } diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index c0b84ca04..bc193a034 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -197,6 +197,16 @@ namespace FlaxEngine /// public float ValuesSum => X + Y; + /// + /// Gets a vector with values being absolute values of that vector. + /// + public Vector2 Absolute => new Vector2(Mathf.Abs(X), Mathf.Abs(Y)); + + /// + /// Gets a vector with values being opposite to values of that vector. + /// + public Vector2 Negative => new Vector2(-X, -Y); + /// /// Gets or sets the component at the specified index. /// diff --git a/Source/Engine/Core/Math/Vector2.h b/Source/Engine/Core/Math/Vector2.h index 7a1b1519b..aaea1d73c 100644 --- a/Source/Engine/Core/Math/Vector2.h +++ b/Source/Engine/Core/Math/Vector2.h @@ -8,6 +8,9 @@ struct Vector3; struct Vector4; +struct Int2; +struct Int3; +struct Int4; struct Color; struct Matrix; @@ -85,16 +88,24 @@ public: } // Init - // @param v Vector to use X and Y components - explicit Vector2(const Int2& v); + // @param v Int2 to use X and Y components + explicit Vector2(const Int2& xy); // Init - // @param v Vector to use X and Y components - explicit Vector2(const Vector3& v); + // @param v Int3 to use X and Y components + explicit Vector2(const Int3& xyz); + + // Init + // @param v Int4 to use X and Y components + explicit Vector2(const Int4& xyzw); + + // Init + // @param v Vector3 to use X and Y components + explicit Vector2(const Vector3& xyz); // Init // @param v Vector4 to use X and Y components - explicit Vector2(const Vector4& v); + explicit Vector2(const Vector4& xyzw); // Init // @param color Color value diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index f66bbcbbd..9c8783dad 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -6,7 +6,9 @@ #include "Color.h" #include "Quaternion.h" #include "Matrix.h" +#include "Int2.h" #include "Int3.h" +#include "Int4.h" #include "../Types/String.h" static_assert(sizeof(Vector3) == 12, "Invalid Vector3 type size."); @@ -40,10 +42,24 @@ Vector3::Vector3(const Vector2& xy) { } +Vector3::Vector3(const Int2& xy, float z) + : X(static_cast(xy.X)) + , Y(static_cast(xy.Y)) + , Z(static_cast(z)) +{ +} + Vector3::Vector3(const Int3& xyz) - : X((float)xyz.X) - , Y((float)xyz.Y) - , Z((float)xyz.Z) + : X(static_cast(xyz.X)) + , Y(static_cast(xyz.Y)) + , Z(static_cast(xyz.Z)) +{ +} + +Vector3::Vector3(const Int4& xyzw) + : X(static_cast(xyzw.X)) + , Y(static_cast(xyzw.Y)) + , Z(static_cast(xyzw.Z)) { } diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index 1f4792e31..761977c96 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -12,6 +12,8 @@ struct Vector2; struct Vector4; struct Color; class String; +struct Int3; +struct Int4; /// /// Represents a three dimensional mathematical vector. @@ -138,10 +140,19 @@ public: // @param xy Vector3 value explicit Vector3(const Vector2& xy); + // Init + // @param xy Int22 with X and Y components values + // @param z Z component value + explicit Vector3(const Int2& xy, float z); + // Init // @param xyz Int3 value explicit Vector3(const Int3& xyz); + // Init + // @param xyzw Int4 value + explicit Vector3(const Int4& xyzw); + // Init // @param xyz Vector4 value explicit Vector3(const Vector4& xyz); @@ -901,6 +912,7 @@ public: /// The third triangle vertex. /// The triangle area. static float TriangleArea(const Vector3& v0, const Vector3& v1, const Vector3& v2); + }; inline Vector3 operator+(float a, const Vector3& b) diff --git a/Source/Engine/Core/Math/Vector4.cpp b/Source/Engine/Core/Math/Vector4.cpp index 34fa27aac..5da725c20 100644 --- a/Source/Engine/Core/Math/Vector4.cpp +++ b/Source/Engine/Core/Math/Vector4.cpp @@ -3,10 +3,12 @@ #include "Vector4.h" #include "Vector2.h" #include "Vector3.h" +#include "Int2.h" +#include "Int3.h" +#include "Int4.h" #include "Color.h" #include "Matrix.h" #include "Rectangle.h" -#include "Int4.h" #include "../Types/String.h" static_assert(sizeof(Vector4) == 16, "Invalid Vector4 type size."); @@ -49,11 +51,27 @@ Vector4::Vector4(const Vector3& xyz, float w) { } +Vector4::Vector4(const Int2& xy, float z, float w) + : X(static_cast(xy.X)) + , Y(static_cast(xy.Y)) + , Z(z) + , W(w) +{ +} + +Vector4::Vector4(const Int3& xyz, float w) + : X(static_cast(xyz.X)) + , Y(static_cast(xyz.Y)) + , Z(static_cast(xyz.Z)) + , W(w) +{ +} + Vector4::Vector4(const Int4& xyzw) - : X((float)xyzw.X) - , Y((float)xyzw.Y) - , Z((float)xyzw.X) - , W((float)xyzw.Y) + : X(static_cast(xyzw.X)) + , Y(static_cast(xyzw.Y)) + , Z(static_cast(xyzw.X)) + , W(static_cast(xyzw.Y)) { } diff --git a/Source/Engine/Core/Math/Vector4.cs b/Source/Engine/Core/Math/Vector4.cs index 99430072d..2a62df067 100644 --- a/Source/Engine/Core/Math/Vector4.cs +++ b/Source/Engine/Core/Math/Vector4.cs @@ -227,6 +227,16 @@ namespace FlaxEngine /// public float ValuesSum => X + Y + Z + W; + /// + /// Gets a vector with values being absolute values of that vector. + /// + public Vector4 Absolute => new Vector4(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z), Mathf.Abs(W)); + + /// + /// Gets a vector with values being opposite to values of that vector. + /// + public Vector4 Negative => new Vector4(-X, -Y, -Z, -W); + /// /// Gets or sets the component at the specified index. /// diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h index c7771aa67..fe25a80bd 100644 --- a/Source/Engine/Core/Math/Vector4.h +++ b/Source/Engine/Core/Math/Vector4.h @@ -13,6 +13,9 @@ struct Color; struct Matrix; struct Rectangle; class String; +struct Int2; +struct Int3; +struct Int4; /// /// Represents a four dimensional mathematical vector. @@ -133,6 +136,17 @@ public: // @param w W component value Vector4(const Vector3& xyz, float w); + // Init + // @param xy X and Y values in the vector + // @param z Z component value + // @param w W component value + explicit Vector4(const Int2& xy, float z, float w); + + // Init + // @param xyz X, Y and Z values in the vector + // @param w W component value + explicit Vector4(const Int3& xyz, float w); + // Init // @param color Int4 value explicit Vector4(const Int4& xyzw); diff --git a/Source/Engine/Core/Math/VectorInt.h b/Source/Engine/Core/Math/VectorInt.h deleted file mode 100644 index d9611d615..000000000 --- a/Source/Engine/Core/Math/VectorInt.h +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Int2.h" -#include "Int3.h" -#include "Int4.h" diff --git a/Source/Engine/Core/Memory/Memory.h b/Source/Engine/Core/Memory/Memory.h index 6341ca21f..59d316287 100644 --- a/Source/Engine/Core/Memory/Memory.h +++ b/Source/Engine/Core/Memory/Memory.h @@ -3,6 +3,8 @@ #pragma once #include "Engine/Core/Templates.h" +#include "Engine/Platform/Platform.h" +#include #include "CrtAllocator.h" typedef CrtAllocator Allocator; @@ -24,10 +26,7 @@ namespace AllocatorExt return nullptr; } if (!ptr) - { return Allocator::Allocate(newSize); - } - void* result = Allocator::Allocate(newSize); if (result) { @@ -53,14 +52,9 @@ namespace AllocatorExt return nullptr; } if (!ptr) - { return Allocator::Allocate(newSize); - } if (newSize <= oldSize) - { return ptr; - } - void* result = Allocator::Allocate(newSize); if (result) { diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h index b57f139ff..90ad934c2 100644 --- a/Source/Engine/Core/Types/BaseTypes.h +++ b/Source/Engine/Core/Types/BaseTypes.h @@ -97,6 +97,10 @@ template class Pair; template class Dictionary; +template +class Function; +template +class Delegate; // Declares full set of operators for the enum type (using binary operation on integer values) #define DECLARE_ENUM_OPERATORS(T) \ diff --git a/Source/Engine/Core/Types/DataContainer.h b/Source/Engine/Core/Types/DataContainer.h index 8b504e58f..5767ca7b9 100644 --- a/Source/Engine/Core/Types/DataContainer.h +++ b/Source/Engine/Core/Types/DataContainer.h @@ -3,10 +3,9 @@ #pragma once #include "Span.h" -#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Templates.h" +#include "Engine/Core/Memory/Memory.h" #include "Engine/Platform/Platform.h" -#include "Engine/Serialization/WriteStream.h" -#include "Engine/Serialization/ReadStream.h" /// /// Universal utility class that can store the chunk of data or just reference to the memory. @@ -49,7 +48,8 @@ public: /// Initializes a new instance of the class. /// /// The data array to link. - DataContainer(const Array& data) + template + DataContainer(const Array& data) : Base((T*)data.Get(), data.Count()) , _isAllocated(false) { @@ -60,6 +60,7 @@ public: /// /// Other container to copy DataContainer(const DataContainer& other) + : DataContainer() { if (other.IsAllocated()) Copy(other); @@ -71,12 +72,11 @@ public: /// Initializes a new instance of the class. /// /// The other collection to move. - FORCE_INLINE DataContainer(DataContainer&& other) + DataContainer(DataContainer&& other) noexcept { Base::_data = other._data; Base::_length = other._length; _isAllocated = other._isAllocated; - other._data = nullptr; other._length = 0; other._isAllocated = false; @@ -87,16 +87,14 @@ public: /// /// The other collection to move. /// The reference to this. - DataContainer& operator=(DataContainer&& other) + DataContainer& operator=(DataContainer&& other) noexcept { if (this != &other) { Release(); - Base::_data = other._data; Base::_length = other._length; _isAllocated = other._isAllocated; - other._data = nullptr; other._length = 0; other._isAllocated = false; @@ -122,7 +120,7 @@ public: } /// - /// Destructor + /// Releases any allocated memory. /// ~DataContainer() { @@ -133,47 +131,45 @@ public: public: /// - /// Returns true if data is allocated by container itself + /// Returns true if data is allocated by container itself, otherwise it's just linked. /// - /// True if is allocated by container, otherwise false FORCE_INLINE bool IsAllocated() const { return _isAllocated; } -public: - /// - /// Link external data + /// Links the external data. /// /// Data array to link - void Link(const Array& data) + template + void Link(const Array& data) { Link(data.Get(), data.Count()); } /// - /// Link external data + /// Links the external data. /// - /// Data to link + /// Data to link. FORCE_INLINE void Link(const Span& data) { Link(data.Get(), data.Length()); } /// - /// Link external data + /// Links the external data. /// - /// Data to link + /// Data to link. FORCE_INLINE void Link(const DataContainer& data) { Link(data.Get(), data.Length()); } /// - /// Link external data + /// Links external data. /// - /// Data to link + /// Data to link. template FORCE_INLINE void Link(const U* data) { @@ -181,30 +177,27 @@ public: } /// - /// Link external data + /// Links the external data. /// - /// Data pointer - /// Data length + /// Data pointer. + /// Data length. void Link(const T* data, int32 length) { Release(); - _isAllocated = false; Base::_length = length; Base::_data = (T*)data; } /// - /// Allocate new memory chunk + /// Allocate a new memory chunk. /// - /// Length of the data (amount of T elements) + /// Length of the data (amount of T elements). void Allocate(const int32 length) { if (_isAllocated && Base::_length == length) return; - Release(); - if (length > 0) { _isAllocated = true; @@ -214,10 +207,11 @@ public: } /// - /// Copies the data to a new allocated chunk + /// Copies the data to a new allocated chunk. /// - /// Data array to copy - FORCE_INLINE void Copy(const Array& data) + /// Data array to copy. + template + FORCE_INLINE void Copy(const Array& data) { if (data.HasItems()) Copy(data.Get(), data.Count()); @@ -226,10 +220,10 @@ public: } /// - /// Copies the data to a new allocated chunk + /// Copies the data to a new allocated chunk. /// - /// Data to copy - FORCE_INLINE void Copy(const DataContainer& data) + /// Data to copy. + void Copy(const DataContainer& data) { if (data.IsValid()) Copy(data.Get(), data.Length()); @@ -238,10 +232,10 @@ public: } /// - /// Copies the data to a new allocated chunk + /// Copies the data to a new allocated chunk. /// - /// Data to copy - FORCE_INLINE void Copy(const Span& data) + /// Data to copy. + void Copy(const Span& data) { if (data.IsValid()) Copy(data.Get(), data.Length()); @@ -250,9 +244,9 @@ public: } /// - /// Copies the data to a new allocated chunk + /// Copies the data to a new allocated chunk. /// - /// Pointer to data to copy + /// Pointer to data to copy. template FORCE_INLINE void Copy(const U* data) { @@ -260,14 +254,13 @@ public: } /// - /// Copies the data to a new allocated chunk + /// Copies the data to a new allocated chunk. /// - /// Pointer to data to copy - /// Length of the data (amount of T elements) + /// Pointer to data to copy. + /// Length of the data (amount of T elements). void Copy(const T* data, int32 length) { ASSERT(data && length > 0); - Allocate(length); ASSERT(Base::_data); Platform::MemoryCopy(Base::_data, data, length * sizeof(T)); @@ -347,10 +340,9 @@ public: public: + template void Read(ReadStream* stream, int32 length) { - // Note: this may not work for the objects, use with primitive types and structures - ASSERT(stream != nullptr); Allocate(length); if (length > 0) @@ -359,10 +351,9 @@ public: } } + template void Write(WriteStream* stream) const { - // Note: this may not work for the objects, use with primitive types and structures - ASSERT(stream != nullptr); if (Base::_length > 0) { diff --git a/Source/Engine/Core/Types/DateTime.cpp b/Source/Engine/Core/Types/DateTime.cpp index 5090c62a1..c4d2124ad 100644 --- a/Source/Engine/Core/Types/DateTime.cpp +++ b/Source/Engine/Core/Types/DateTime.cpp @@ -6,8 +6,8 @@ #include "Engine/Platform/Platform.h" #include "Engine/Core/Math/Math.h" -const int32 DateTime::CachedDaysPerMonth[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; -const int32 DateTime::CachedDaysToMonth[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; +const int32 CachedDaysPerMonth[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; +const int32 CachedDaysToMonth[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; DateTime::DateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second, int32 millisecond) { @@ -25,6 +25,11 @@ DateTime::DateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, + millisecond * Constants::TicksPerMillisecond; } +DateTime DateTime::GetDate() const +{ + return DateTime(Ticks - Ticks % Constants::TicksPerDay); +} + void DateTime::GetDate(int32& year, int32& month, int32& day) const { // Based on: @@ -68,6 +73,11 @@ int32 DateTime::GetDayOfYear() const return day; } +int32 DateTime::GetHour() const +{ + return static_cast(Ticks / Constants::TicksPerHour % 24); +} + int32 DateTime::GetHour12() const { const int32 hour = GetHour(); @@ -78,6 +88,26 @@ int32 DateTime::GetHour12() const return hour; } +double DateTime::GetJulianDay() const +{ + return 1721425.5 + static_cast(Ticks) / Constants::TicksPerDay; +} + +double DateTime::GetModifiedJulianDay() const +{ + return GetJulianDay() - 2400000.5; +} + +int32 DateTime::GetMillisecond() const +{ + return static_cast(Ticks / Constants::TicksPerMillisecond % 1000); +} + +int32 DateTime::GetMinute() const +{ + return static_cast(Ticks / Constants::TicksPerMinute % 60); +} + int32 DateTime::GetMonth() const { int32 year, month, day; @@ -85,6 +115,21 @@ int32 DateTime::GetMonth() const return month; } +MonthOfYear DateTime::GetMonthOfYear() const +{ + return static_cast(GetMonth()); +} + +int32 DateTime::GetSecond() const +{ + return static_cast(Ticks / Constants::TicksPerSecond % 60); +} + +TimeSpan DateTime::GetTimeOfDay() const +{ + return TimeSpan(Ticks % Constants::TicksPerDay); +} + int32 DateTime::GetYear() const { int32 year, month, day; @@ -92,6 +137,11 @@ int32 DateTime::GetYear() const return year; } +int32 DateTime::ToUnixTimestamp() const +{ + return static_cast((Ticks - DateTime(1970, 1, 1).Ticks) / Constants::TicksPerSecond); +} + int32 DateTime::DaysInMonth(int32 year, int32 month) { ASSERT_LOW_LAYER((month >= 1) && (month <= 12)); @@ -105,6 +155,16 @@ int32 DateTime::DaysInYear(int32 year) return IsLeapYear(year) ? 366 : 365; } +DateTime DateTime::FromJulianDay(double julianDay) +{ + return DateTime(static_cast((julianDay - 1721425.5) * Constants::TicksPerDay)); +} + +DateTime DateTime::FromUnixTimestamp(int32 unixTime) +{ + return DateTime(1970, 1, 1) + TimeSpan(static_cast(unixTime) * Constants::TicksPerSecond); +} + bool DateTime::IsLeapYear(int32 year) { if ((year % 4) == 0) @@ -114,6 +174,11 @@ bool DateTime::IsLeapYear(int32 year) return false; } +DateTime DateTime::MaxValue() +{ + return DateTime(3652059 * Constants::TicksPerDay - 1); +} + DateTime DateTime::Now() { int32 year, month, day, dayOfWeek, hour, minute, second, millisecond; @@ -144,3 +209,30 @@ String DateTime::ToFileNameString() const GetDate(year, month, day); return String::Format(TEXT("{0}_{1:0>2}_{2:0>2}_{3:0>2}_{4:0>2}_{5:0>2}"), year, month, day, GetHour(), GetMinute(), GetSecond()); } + +DateTime DateTime::operator+(const TimeSpan& other) const +{ + return DateTime(Ticks + other.Ticks); +} + +DateTime& DateTime::operator+=(const TimeSpan& other) +{ + Ticks += other.Ticks; + return *this; +} + +TimeSpan DateTime::operator-(const DateTime& other) const +{ + return TimeSpan(Ticks - other.Ticks); +} + +DateTime DateTime::operator-(const TimeSpan& other) const +{ + return DateTime(Ticks - other.Ticks); +} + +DateTime& DateTime::operator-=(const TimeSpan& other) +{ + Ticks -= other.Ticks; + return *this; +} diff --git a/Source/Engine/Core/Types/DateTime.h b/Source/Engine/Core/Types/DateTime.h index a1bf245e1..08817f78f 100644 --- a/Source/Engine/Core/Types/DateTime.h +++ b/Source/Engine/Core/Types/DateTime.h @@ -3,7 +3,8 @@ #pragma once #include "BaseTypes.h" -#include "TimeSpan.h" +#include "Engine/Core/Formatting.h" +#include "Engine/Core/Templates.h" #include "Engine/Core/Enums.h" /// @@ -21,11 +22,6 @@ DECLARE_ENUM_EX_12(MonthOfYear, int32, 1, January, February, March, April, May, /// API_STRUCT(InBuild, Namespace="System") struct FLAXENGINE_API DateTime { -private: - - static const int32 CachedDaysPerMonth[]; - static const int32 CachedDaysToMonth[]; - public: /// @@ -77,32 +73,11 @@ public: public: - DateTime operator+(const TimeSpan& other) const - { - return DateTime(Ticks + other.Ticks); - } - - DateTime& operator+=(const TimeSpan& other) - { - Ticks += other.Ticks; - return *this; - } - - TimeSpan operator-(const DateTime& other) const - { - return TimeSpan(Ticks - other.Ticks); - } - - DateTime operator-(const TimeSpan& other) const - { - return DateTime(Ticks - other.Ticks); - } - - DateTime& operator-=(const TimeSpan& other) - { - Ticks -= other.Ticks; - return *this; - } + DateTime operator+(const TimeSpan& other) const; + DateTime& operator+=(const TimeSpan& other); + TimeSpan operator-(const DateTime& other) const; + DateTime operator-(const TimeSpan& other) const; + DateTime& operator-=(const TimeSpan& other); bool operator==(const DateTime& other) const { @@ -139,11 +114,7 @@ public: /// /// Gets the date part of this date. The time part is truncated and becomes 00:00:00.000. /// - /// A DateTime object containing the date. - DateTime GetDate() const - { - return DateTime(Ticks - Ticks % Constants::TicksPerDay); - } + DateTime GetDate() const; /// /// Gets the date components of this date. @@ -156,106 +127,68 @@ public: /// /// Gets this date's day part (1 to 31). /// - /// The day of the month. int32 GetDay() const; /// /// Calculates this date's day of the week (Sunday - Saturday). /// - /// The week day. DayOfWeek GetDayOfWeek() const; /// /// Gets this date's day of the year. /// - /// The day of year. int32 GetDayOfYear() const; /// /// Gets this date's hour part in 24-hour clock format (0 to 23). /// - /// The hour. - int32 GetHour() const - { - return static_cast(Ticks / Constants::TicksPerHour % 24); - } + int32 GetHour() const; /// /// Gets this date's hour part in 12-hour clock format (1 to 12). /// - /// The hour in AM/PM format. int32 GetHour12() const; /// /// Gets the Julian Day for this date. /// - /// - /// The Julian Day is the number of days since the inception of the Julian calendar at noon on Monday, January 1, 4713 B.C.E. The minimum Julian Day that can be represented in DateTime is 1721425.5, which corresponds to Monday, January 1, 0001 in the Gregorian calendar. - /// - /// Julian Day. - double GetJulianDay() const - { - return 1721425.5 + static_cast(Ticks) / Constants::TicksPerDay; - } + /// The Julian Day is the number of days since the inception of the Julian calendar at noon on Monday, January 1, 4713 B.C.E. The minimum Julian Day that can be represented in DateTime is 1721425.5, which corresponds to Monday, January 1, 0001 in the Gregorian calendar. + double GetJulianDay() const; /// /// Gets the Modified Julian day. /// - /// - /// The Modified Julian Day is calculated by subtracting 2400000.5, which corresponds to midnight UTC on November 17, 1858 in the Gregorian calendar. - /// - /// The Modified Julian Day. - double GetModifiedJulianDay() const - { - return GetJulianDay() - 2400000.5; - } + /// The Modified Julian Day is calculated by subtracting 2400000.5, which corresponds to midnight UTC on November 17, 1858 in the Gregorian calendar. + double GetModifiedJulianDay() const; /// /// Gets this date's millisecond part (0 to 999). /// - /// The millisecond. - int32 GetMillisecond() const - { - return static_cast(Ticks / Constants::TicksPerMillisecond % 1000); - } + int32 GetMillisecond() const; /// /// Gets this date's minute part (0 to 59). /// - /// The minute. - int32 GetMinute() const - { - return static_cast(Ticks / Constants::TicksPerMinute % 60); - } + int32 GetMinute() const; /// /// Gets this date's the month part (1 to 12). /// - /// The month. int32 GetMonth() const; /// /// Gets the date's month of the year (January to December). /// - /// The month of year. - MonthOfYear GetMonthOfYear() const - { - return static_cast(GetMonth()); - } + MonthOfYear GetMonthOfYear() const; /// /// Gets this date's second part. /// - /// The second. - int32 GetSecond() const - { - return static_cast(Ticks / Constants::TicksPerSecond % 60); - } + int32 GetSecond() const; /// - /// Gets this date's representation as number of ticks. + /// Gets this date's representation as number of ticks since midnight, January 1, 0001. /// - /// The number of ticks since midnight, January 1, 0001. int64 GetTicks() const { return Ticks; @@ -264,26 +197,17 @@ public: /// /// Gets the time elapsed since midnight of this date. /// - /// The time span since midnight. - TimeSpan GetTimeOfDay() const - { - return TimeSpan(Ticks % Constants::TicksPerDay); - } + TimeSpan GetTimeOfDay() const; /// /// Gets this date's year part. /// - /// The year. int32 GetYear() const; /// /// Gets this date as the number of seconds since the Unix Epoch (January 1st of 1970). /// - /// The time. - int32 ToUnixTimestamp() const - { - return static_cast((Ticks - DateTime(1970, 1, 1).Ticks) / Constants::TicksPerSecond); - } + int32 ToUnixTimestamp() const; public: @@ -307,27 +231,19 @@ public: /// /// The Julian Day. /// Gregorian date and time. - static DateTime FromJulianDay(double julianDay) - { - return DateTime(static_cast((julianDay - 1721425.5) * Constants::TicksPerDay)); - } + static DateTime FromJulianDay(double julianDay); /// /// Returns the date from Unix time (seconds from midnight 1970-01-01). /// /// The Unix time (seconds from midnight 1970-01-01). /// The Gregorian date and time. - static DateTime FromUnixTimestamp(int32 unixTime) - { - return DateTime(1970, 1, 1) + TimeSpan(static_cast(unixTime) * Constants::TicksPerSecond); - } + static DateTime FromUnixTimestamp(int32 unixTime); /// /// Determines whether the specified year is a leap year. /// - /// - /// A leap year is a year containing one additional day in order to keep the calendar synchronized with the astronomical year. - /// + /// A leap year is a year containing one additional day in order to keep the calendar synchronized with the astronomical year. /// The year. /// true if the specified year os a leap year; otherwise, false. static bool IsLeapYear(int32 year); @@ -335,22 +251,15 @@ public: /// /// Returns the maximum date value. /// - /// - /// The maximum date value is December 31, 9999, 23:59:59.9999999. - /// - /// The date. - static DateTime MaxValue() - { - return DateTime(3652059 * Constants::TicksPerDay - 1); - } + /// The maximum date value is December 31, 9999, 23:59:59.9999999. + /// The maximum valid date. + static DateTime MaxValue(); /// /// Returns the minimum date value. /// - /// - /// The minimum date value is January 1, 0001, 00:00:00.0. - /// - /// The date. + /// The minimum date value is January 1, 0001, 00:00:00.0. + /// The minimum valid date. static DateTime MinValue() { return DateTime(1, 1, 1, 0, 0, 0, 0); @@ -359,18 +268,14 @@ public: /// /// Gets the local date and time on this computer. /// - /// - ///This method takes into account the local computer's time zone and daylight saving settings. For time zone independent time comparisons, and when comparing times between different computers, use NowUTC() instead. - /// + /// This method takes into account the local computer's time zone and daylight saving settings. For time zone independent time comparisons, and when comparing times between different computers, use NowUTC() instead. /// The current date and time. static DateTime Now(); /// /// Gets the UTC date and time on this computer. /// - /// - /// This method returns the Coordinated Universal Time (UTC), which does not take the local computer's time zone and daylight savings settings into account. It should be used when comparing dates and times that should be independent of the user's locale. To get the date and time in the current locale, use Now() instead. - /// + /// This method returns the Coordinated Universal Time (UTC), which does not take the local computer's time zone and daylight savings settings into account. It should be used when comparing dates and times that should be independent of the user's locale. To get the date and time in the current locale, use Now() instead. /// The current date and time. static DateTime NowUTC(); diff --git a/Source/Engine/Core/Types/Guid.cpp b/Source/Engine/Core/Types/Guid.cpp index 323949646..c8aaf8aa1 100644 --- a/Source/Engine/Core/Types/Guid.cpp +++ b/Source/Engine/Core/Types/Guid.cpp @@ -101,8 +101,8 @@ FORCE_INLINE bool GuidParse(const StringViewType& text, Guid& value) // FormatType::D case 36: { - StringType b = text.Substring(9, 4) + text.Substring(14, 4); - StringType c = text.Substring(19, 4) + text.Substring(24, 4); + StringType b = StringType(text.Substring(9, 4)) + text.Substring(14, 4); + StringType c = StringType(text.Substring(19, 4)) + text.Substring(24, 4); return StringUtils::ParseHex(*text + 0, 8, &value.A) || StringUtils::ParseHex(*b, &value.B) || @@ -113,8 +113,8 @@ FORCE_INLINE bool GuidParse(const StringViewType& text, Guid& value) // FormatType::P case 38: { - StringType b = text.Substring(10, 4) + text.Substring(15, 4); - StringType c = text.Substring(20, 4) + text.Substring(25, 4); + StringType b = StringType(text.Substring(10, 4)) + text.Substring(15, 4); + StringType c = StringType(text.Substring(20, 4)) + text.Substring(25, 4); return text[0] != text[text.Length() - 1] || StringUtils::ParseHex(*text + 1, 8, &value.A) || diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index 040bb9ab2..7919d513d 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -13,7 +13,18 @@ String::String(const StringAnsi& str) String::String(const StringView& str) { - Set(str.Get(), str.Length()); + _length = str.Length(); + if (_length != 0) + { + ASSERT(_length > 0); + _data = (Char*)Platform::Allocate((_length + 1) * sizeof(Char), 16); + _data[_length] = 0; + Platform::MemoryCopy(_data, str.Get(), _length * sizeof(Char)); + } + else + { + _data = nullptr; + } } String::String(const StringAnsiView& str) @@ -38,7 +49,6 @@ void String::Set(const Char* chars, int32 length) } _length = length; } - Platform::MemoryCopy(_data, chars, length * sizeof(Char)); } @@ -58,7 +68,6 @@ void String::Set(const char* chars, int32 length) } _length = length; } - if (chars) StringUtils::ConvertANSI2UTF16(chars, _data, length); } diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index 87babc637..d7d42f211 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -5,6 +5,7 @@ #include "Engine/Platform/Platform.h" #include "Engine/Platform/StringUtils.h" #include "Engine/Core/Formatting.h" +#include "Engine/Core/Templates.h" /// /// Represents text as a sequence of characters. Container uses a single dynamic memory allocation to store the characters data. Characters sequence is always null-terminated. @@ -225,9 +226,7 @@ public: const T* start = _data; if (startPosition != -1) start += startPosition < Length() ? startPosition : Length(); - const T* tmp = searchCase == StringSearchCase::IgnoreCase - ? StringUtils::FindIgnoreCase(start, subStr) - : StringUtils::Find(start, subStr); + const T* tmp = searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(start, subStr) : StringUtils::Find(start, subStr); return tmp ? static_cast(tmp - **this) : -1; } @@ -498,18 +497,19 @@ public: } /// - /// Resizes string contents, works like: *this = Left(length) but is faster. + /// Resizes string contents. /// - /// New length of the string (amount of characters from left side to preserve). + /// New length of the string. void Resize(int32 length) { - ASSERT(length >= 0 && length <= _length); + ASSERT(length >= 0); if (_length != length) { const auto oldData = _data; + const auto minLength = _length < length ? _length : length; _length = length; _data = (T*)Platform::Allocate((length + 1) * sizeof(T), 16); - Platform::MemoryCopy(_data, oldData, length * sizeof(T)); + Platform::MemoryCopy(_data, oldData, minLength * sizeof(T)); _data[length] = 0; Platform::Free(oldData); } @@ -540,6 +540,7 @@ public: /// /// The reference to the string. String(const String& str) + : String() { Set(str.Get(), str.Length()); } @@ -577,6 +578,7 @@ public: /// The ANSI string. /// The ANSI string length. explicit String(const char* str, int32 length) + : String() { Set(str, length); } @@ -586,8 +588,9 @@ public: /// /// The UTF-16 string. String(const Char* str) - : String(str, StringUtils::Length(str)) + : String() { + Set(str, StringUtils::Length(str)); } /// @@ -596,6 +599,7 @@ public: /// The UTF-16 string. /// The UTF-16 string length. String(const Char* str, int32 length) + : String() { Set(str, length); } @@ -604,7 +608,7 @@ public: /// Initializes a new instance of the class. /// /// The other string. - explicit String(const StringView& str); + String(const StringView& str); /// /// Initializes a new instance of the class. @@ -1111,7 +1115,17 @@ public: /// The combined path. FORCE_INLINE String operator/(const String& str) const { - return operator/(*str); + return String(*this) /= str; + } + + /// + /// Concatenates this path with given path ensuring the '/' character is used between them. + /// + /// The string to be concatenated onto the end of this. + /// The combined path. + FORCE_INLINE String operator/(const StringView& str) const + { + return String(*this) /= str; } public: diff --git a/Source/Engine/Core/Types/StringView.cpp b/Source/Engine/Core/Types/StringView.cpp index d6cbb3a6a..8b30cc755 100644 --- a/Source/Engine/Core/Types/StringView.cpp +++ b/Source/Engine/Core/Types/StringView.cpp @@ -27,16 +27,28 @@ bool StringView::operator!=(const String& other) const return StringUtils::Compare(this->GetText(), *other) != 0; } -String StringView::Substring(int32 startIndex) const +StringView StringView::Left(int32 count) const { - ASSERT(startIndex >= 0 && startIndex < Length()); - return String(Get() + startIndex, Length() - startIndex); + const int32 countClamped = count < 0 ? 0 : count < Length() ? count : Length(); + return StringView(**this, countClamped); } -String StringView::Substring(int32 startIndex, int32 count) const +StringView StringView::Right(int32 count) const +{ + const int32 countClamped = count < 0 ? 0 : count < Length() ? count : Length(); + return StringView(**this + Length() - countClamped); +} + +StringView StringView::Substring(int32 startIndex) const +{ + ASSERT(startIndex >= 0 && startIndex < Length()); + return StringView(Get() + startIndex, Length() - startIndex); +} + +StringView StringView::Substring(int32 startIndex, int32 count) const { ASSERT(startIndex >= 0 && startIndex + count <= Length() && count >= 0); - return String(Get() + startIndex, count); + return StringView(Get() + startIndex, count); } String StringView::ToString() const diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index 5d5ad2799..e267b760e 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -66,7 +66,6 @@ public: /// /// Returns true if string is empty. /// - /// True if string is empty, otherwise false. FORCE_INLINE bool IsEmpty() const { return _length == 0; @@ -75,7 +74,6 @@ public: /// /// Returns true if string isn't empty. /// - /// True if string isn't empty, otherwise false. FORCE_INLINE bool HasChars() const { return _length != 0; @@ -84,7 +82,6 @@ public: /// /// Gets the length of the string. /// - /// The string length. FORCE_INLINE int32 Length() const { return _length; @@ -93,7 +90,6 @@ public: /// /// Gets the pointer to the string. /// - /// The string. FORCE_INLINE const T* operator*() const { return _data; @@ -102,7 +98,6 @@ public: /// /// Gets the pointer to the string. /// - /// The string. FORCE_INLINE const T* Get() const { return _data; @@ -111,7 +106,6 @@ public: /// /// Gets the pointer to the string or to the static empty text if string is null. Returned pointer is always valid (read-only). /// - /// The string handle. FORCE_INLINE const T* GetText() const { return _data ? _data : (const T*)TEXT(""); @@ -160,16 +154,16 @@ public: { const int32 length = Length(); if (searchCase == StringSearchCase::IgnoreCase) - return length > 0 && _data[0] == c; - return length > 0 && StringUtils::ToLower(_data[0]) == StringUtils::ToLower(c); + return length > 0 && StringUtils::ToLower(_data[0]) == StringUtils::ToLower(c); + return length > 0 && _data[0] == c; } bool EndsWith(T c, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const { const int32 length = Length(); if (searchCase == StringSearchCase::IgnoreCase) - return length > 0 && _data[length - 1] == c; - return length > 0 && StringUtils::ToLower(_data[length - 1]) == StringUtils::ToLower(c); + return length > 0 && StringUtils::ToLower(_data[length - 1]) == StringUtils::ToLower(c); + return length > 0 && _data[length - 1] == c; } bool StartsWith(const StringViewBase& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const @@ -321,12 +315,26 @@ public: public: + /// + /// Gets the left most given number of characters. + /// + /// The characters count. + /// The substring. + StringView Left(int32 count) const; + + /// + /// Gets the string of characters from the right (end of the string). + /// + /// The characters count. + /// The substring. + StringView Right(int32 count) const; + /// /// Retrieves substring created from characters starting from startIndex to the String end. /// /// The index of the first character to subtract. /// The substring created from String data. - String Substring(int32 startIndex) const; + StringView Substring(int32 startIndex) const; /// /// Retrieves substring created from characters starting from start index. @@ -334,7 +342,7 @@ public: /// The index of the first character to subtract. /// The amount of characters to retrieve. /// The substring created from String data. - String Substring(int32 startIndex, int32 count) const; + StringView Substring(int32 startIndex, int32 count) const; public: diff --git a/Source/Engine/Core/Types/TimeSpan.h b/Source/Engine/Core/Types/TimeSpan.h index 0b56d8ee1..cb663d80c 100644 --- a/Source/Engine/Core/Types/TimeSpan.h +++ b/Source/Engine/Core/Types/TimeSpan.h @@ -3,7 +3,6 @@ #pragma once #include "BaseTypes.h" -#include "Engine/Platform/Platform.h" #include "Engine/Core/Formatting.h" #include "Engine/Core/Templates.h" diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index db71c2f77..97bcfc412 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -11,6 +11,9 @@ #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Math/Int2.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Math/Int4.h" #include "Engine/Core/Math/Quaternion.h" #include "Engine/Core/Math/Color.h" #include "Engine/Core/Math/Matrix.h" @@ -551,6 +554,24 @@ Variant::Variant(const Vector4& v) *(Vector4*)AsData = v; } +Variant::Variant(const Int2& v) + : Type(VariantType::Int2) +{ + *(Int2*)AsData = v; +} + +Variant::Variant(const Int3& v) + : Type(VariantType::Int3) +{ + *(Int3*)AsData = v; +} + +Variant::Variant(const Int4& v) + : Type(VariantType::Int4) +{ + *(Int4*)AsData = v; +} + Variant::Variant(const Color& v) : Type(VariantType::Color) { @@ -691,7 +712,7 @@ Variant::Variant(const CommonValue& value) *this = Variant(value.AsRay); break; default: - CRASH; + CRASH; } } @@ -1504,6 +1525,132 @@ Variant::operator Vector4() const } } +Variant::operator Int2() const +{ + switch (Type.Type) + { + case VariantType::Bool: + return Int2((int32)(AsBool ? 1.0f : 0.0f)); + case VariantType::Int: + return Int2((int32)AsInt); + case VariantType::Uint: + return Int2((int32)AsUint); + case VariantType::Int64: + return Int2((int32)AsInt64); + case VariantType::Uint64: + case VariantType::Enum: + return Int2((int32)AsUint64); + case VariantType::Float: + return Int2((int32)AsFloat); + case VariantType::Double: + return Int2((int32)AsDouble); + case VariantType::Pointer: + return Int2((int32)(intptr)AsPointer); + case VariantType::Vector2: + return Int2(*(Vector2*)AsData); + case VariantType::Vector3: + return Int2(*(Vector3*)AsData); + case VariantType::Vector4: + return Int2(*(Vector4*)AsData); + case VariantType::Int2: + return Int2(*(Int2*)AsData); + case VariantType::Int3: + return Int2(*(Int3*)AsData); + case VariantType::Int4: + case VariantType::Color: + return Int2(*(Int4*)AsData); + case VariantType::Structure: + if (StringUtils::Compare(Type.TypeName, Int2::TypeInitializer.GetType().Fullname.Get()) == 0) + return *(Int2*)AsBlob.Data; + default: + return Int3::Zero; + } +} + +Variant::operator Int3() const +{ + switch (Type.Type) + { + case VariantType::Bool: + return Int3((int32)(AsBool ? 1 : 0)); + case VariantType::Int: + return Int3((int32)AsInt); + case VariantType::Uint: + return Int3((int32)AsUint); + case VariantType::Int64: + return Int3((int32)AsInt64); + case VariantType::Uint64: + case VariantType::Enum: + return Int3((int32)AsUint64); + case VariantType::Float: + return Int3((int32)AsFloat); + case VariantType::Double: + return Int3((int32)AsDouble); + case VariantType::Pointer: + return Int3((int32)(intptr)AsPointer); + case VariantType::Vector2: + return Int3(*(Vector2*)AsData, 0); + case VariantType::Vector3: + return Int3(*(Vector3*)AsData); + case VariantType::Vector4: + return Int3(*(Vector4*)AsData); + case VariantType::Int2: + return Int3(*(Int2*)AsData, 0); + case VariantType::Int3: + return Int3(*(Int3*)AsData); + case VariantType::Int4: + case VariantType::Color: + return Int3(*(Int4*)AsData); + case VariantType::Structure: + if (StringUtils::Compare(Type.TypeName, Int3::TypeInitializer.GetType().Fullname.Get()) == 0) + return *(Int3*)AsBlob.Data; + default: + return Int3::Zero; + } +} + +Variant::operator Int4() const +{ + switch (Type.Type) + { + case VariantType::Bool: + return Int4((int32)(AsBool ? 1 : 0)); + case VariantType::Int: + return Int4(AsInt); + case VariantType::Uint: + return Int4((int32)AsUint); + case VariantType::Int64: + return Int4((int32)AsInt64); + case VariantType::Uint64: + case VariantType::Enum: + return Int4((int32)AsUint64); + case VariantType::Float: + return Int4((int32)AsFloat); + case VariantType::Double: + return Int4((int32)AsDouble); + case VariantType::Pointer: + return Int4((int32)(intptr)AsPointer); + case VariantType::Vector2: + return Int4(*(Vector2*)AsData, 0, 0); + case VariantType::Vector3: + return Int4(*(Vector3*)AsData, 0); + case VariantType::Vector4: + return Int4(*(Vector4*)AsData); + case VariantType::Int2: + return Int4(*(Int2*)AsData, 0, 0); + case VariantType::Int3: + return Int4(*(Int3*)AsData, 0); + case VariantType::Int4: + case VariantType::Color: + return *(Int4*)AsData; + case VariantType::Structure: + if (StringUtils::Compare(Type.TypeName, Int4::TypeInitializer.GetType().Fullname.Get()) == 0) + return *(Int4*)AsBlob.Data; + default: + return Int4::Zero; + } +} + Variant::operator Color() const { switch (Type.Type) @@ -1670,6 +1817,21 @@ const Vector4& Variant::AsVector4() const return *(const Vector4*)AsData; } +const Int2& Variant::AsInt2() const +{ + return *(const Int2*)AsData; +} + +const Int3& Variant::AsInt3() const +{ + return *(const Int3*)AsData; +} + +const Int4& Variant::AsInt4() const +{ + return *(const Int4*)AsData; +} + const Color& Variant::AsColor() const { return *(const Color*)AsData; diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 98fd9cf24..ac9af0e79 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -54,6 +54,10 @@ API_STRUCT(InBuild) struct FLAXENGINE_API VariantType ManagedObject, Typename, + Int2, + Int3, + Int4, + MAX }; @@ -198,6 +202,9 @@ public: Variant(const Vector2& v); Variant(const Vector3& v); Variant(const Vector4& v); + Variant(const Int2& v); + Variant(const Int3& v); + Variant(const Int4& v); Variant(const Color& v); Variant(const Quaternion& v); Variant(const BoundingSphere& v); @@ -262,6 +269,9 @@ public: explicit operator Vector2() const; explicit operator Vector3() const; explicit operator Vector4() const; + explicit operator Int2() const; + explicit operator Int3() const; + explicit operator Int4() const; explicit operator Color() const; explicit operator Quaternion() const; explicit operator Guid() const; @@ -275,6 +285,9 @@ public: const Vector2& AsVector2() const; const Vector3& AsVector3() const; const Vector4& AsVector4() const; + const Int2& AsInt2() const; + const Int3& AsInt3() const; + const Int4& AsInt4() const; const Color& AsColor() const; const Quaternion& AsQuaternion() const; diff --git a/Source/Engine/Core/Types/Version.h b/Source/Engine/Core/Types/Version.h index 6d0bac6b1..323c894a5 100644 --- a/Source/Engine/Core/Types/Version.h +++ b/Source/Engine/Core/Types/Version.h @@ -4,11 +4,10 @@ #include "BaseTypes.h" #include "Engine/Core/Math/Math.h" -#include "Engine/Core/Formatting.h" #include "Engine/Core/Templates.h" /// -/// Represents the version number made of major, minor, patch and build numbers. +/// Represents the version number made of major, minor, build and revision numbers. /// struct FLAXENGINE_API Version { diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 01f0b711c..b443577fb 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -12,11 +12,13 @@ #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/AssetReference.h" #include "Engine/Graphics/GPUContext.h" -#include "Engine/Graphics/RenderTask.h" -#include "Engine/Graphics/DynamicBuffer.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUPipelineState.h" -#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/DynamicBuffer.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Animations/AnimationUtils.h" #include "Engine/Profiler/Profiler.h" #include "Engine/Debug/DebugLog.h" @@ -271,7 +273,6 @@ namespace DynamicVertexBuffer* DebugDrawVB = nullptr; Vector3 SphereCache[DEBUG_DRAW_SPHERE_VERTICES]; Vector3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES]; - Vector3 CylinderCache[DEBUG_DRAW_CYLINDER_VERTICES]; Array SphereTriangleCache; }; @@ -1242,80 +1243,98 @@ void DebugDraw::DrawWireTube(const Vector3& position, const Quaternion& orientat } } +namespace +{ + void DrawCylinder(Array* list, const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration) + { + // Setup cache + Vector3 CylinderCache[DEBUG_DRAW_CYLINDER_VERTICES]; + const float angleBetweenFacets = TWO_PI / DEBUG_DRAW_CYLINDER_RESOLUTION; + const float verticalOffset = height * 0.5f; + int32 index = 0; + for (int32 i = 0; i < DEBUG_DRAW_CYLINDER_RESOLUTION; i++) + { + const float theta = i * angleBetweenFacets; + const float x = Math::Cos(theta) * radius; + const float z = Math::Sin(theta) * radius; + + // Top cap + CylinderCache[index++] = Vector3(x, verticalOffset, z); + + // Top part of body + CylinderCache[index++] = Vector3(x, verticalOffset, z); + + // Bottom part of body + CylinderCache[index++] = Vector3(x, -verticalOffset, z); + + // Bottom cap + CylinderCache[index++] = Vector3(x, -verticalOffset, z); + } + + DebugTriangle t; + t.Color = Color32(color); + t.TimeLeft = duration; + const Matrix world = Matrix::RotationQuaternion(orientation) * Matrix::Translation(position); + + // Write triangles + for (uint32 i = 0; i < DEBUG_DRAW_CYLINDER_VERTICES; i += 4) + { + // Each iteration, the loop advances to the next vertex column + // Four triangles per column (except for the four degenerate cap triangles) + + // Top cap triangles + auto nextIndex = (uint16)((i + 4) % DEBUG_DRAW_CYLINDER_VERTICES); + if (nextIndex != 0) + { + Vector3::Transform(CylinderCache[i], world, t.V0); + Vector3::Transform(CylinderCache[nextIndex], world, t.V1); + Vector3::Transform(CylinderCache[0], world, t.V2); + list->Add(t); + } + + // Body triangles + nextIndex = (uint16)((i + 5) % DEBUG_DRAW_CYLINDER_VERTICES); + Vector3::Transform(CylinderCache[(i + 1)], world, t.V0); + Vector3::Transform(CylinderCache[(i + 2)], world, t.V1); + Vector3::Transform(CylinderCache[nextIndex], world, t.V2); + list->Add(t); + + Vector3::Transform(CylinderCache[nextIndex], world, t.V0); + Vector3::Transform(CylinderCache[(i + 2)], world, t.V1); + Vector3::Transform(CylinderCache[((i + 6) % DEBUG_DRAW_CYLINDER_VERTICES)], world, t.V2); + list->Add(t); + + // Bottom cap triangles + nextIndex = (uint16)((i + 7) % DEBUG_DRAW_CYLINDER_VERTICES); + if (nextIndex != 3) + { + Vector3::Transform(CylinderCache[(i + 3)], world, t.V0); + Vector3::Transform(CylinderCache[3], world, t.V1); + Vector3::Transform(CylinderCache[nextIndex], world, t.V2); + list->Add(t); + } + } + } +} + +void DebugDraw::DrawCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration, bool depthTest) +{ + Array* list; + if (depthTest) + list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles; + else + list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles; + ::DrawCylinder(list, position, orientation, radius, height, color, duration); +} + void DebugDraw::DrawWireCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration, bool depthTest) { - // Setup - // TODO: move this to init!! - const float angleBetweenFacets = TWO_PI / DEBUG_DRAW_CYLINDER_RESOLUTION; - float verticalOffset = height * 0.5f; - int32 index = 0; - for (int32 i = 0; i < DEBUG_DRAW_CYLINDER_RESOLUTION; i++) - { - // Cache data - float theta = i * angleBetweenFacets; - float x = Math::Cos(theta) * radius; - float z = Math::Sin(theta) * radius; - - // Top cap - CylinderCache[index++] = Vector3(x, verticalOffset, z); - - // Top part of body - CylinderCache[index++] = Vector3(x, verticalOffset, z); - - // Bottom part of body - CylinderCache[index++] = Vector3(x, -verticalOffset, z); - - // Bottom cap - CylinderCache[index++] = Vector3(x, -verticalOffset, z); - } - - MISSING_CODE("missing rendering cylinder"); - - // Write lines - // TODO: optimize this to draw less lines - /*for (uint32 i = 0; i < DEBUG_DRAW_CYLINDER_VERTICES; i += 4) - { - // Each iteration, the loop advances to the next vertex column - // Four triangles per column (except for the four degenerate cap triangles) - - // Top cap triangles - auto nextIndex = (ushort)((i + 4) % DEBUG_DRAW_CYLINDER_VERTICES); - if (nextIndex != 0) //Don't add cap indices if it's going to be a degenerate triangle. - { - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[i])); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[nextIndex])); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[0])); - } - - // Body triangles - nextIndex = (ushort)((i + 5) % DEBUG_DRAW_CYLINDER_VERTICES); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[(i + 1)])); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[(i + 2)])); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[nextIndex])); - - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[nextIndex])); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[(i + 2)])); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[((i + 6) % DEBUG_DRAW_CYLINDER_VERTICES)])); - - // Bottom cap triangles - nextIndex = (ushort)((i + 7) % DEBUG_DRAW_CYLINDER_VERTICES); - if (nextIndex != 3) //Don't add cap indices if it's going to be a degenerate triangle. - { - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[(i + 3)])); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[3])); - _vertexList.Items.Add(new VertexMeshInput(CylinderCache[nextIndex])); - } - } - _vertexList.UpdateResource(); - - // Draw - Matrix world = Matrix.RotationQuaternion(orientation) * Matrix.Translation(position); - bool posOnly; - auto material = _boxMaterial.Material; - material.Params[0].Value = color; - material.Params[1].Value = brightness; - material.Apply(view, ref world, out posOnly); - _vertexList.Draw(PrimitiveType.TriangleList, vertices);*/ + Array* list; + if (depthTest) + list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultWireTriangles : &Context->DebugDrawDepthTest.OneFrameWireTriangles; + else + list = duration > 0 ? &Context->DebugDrawDefault.DefaultWireTriangles : &Context->DebugDrawDefault.OneFrameWireTriangles; + ::DrawCylinder(list, position, orientation, radius, height, color, duration); } void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration, bool depthTest) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 13aa5c3ac..31cdf88a4 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -268,6 +268,18 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the cylinder. + /// + /// The center position. + /// The orientation. + /// The radius. + /// The height. + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the wireframe cylinder. /// @@ -349,6 +361,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, color, duration, depthTest) #define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawSphere(sphere, color, duration, depthTest) #define DEBUG_DRAW_BOX(box, color, duration, depthTest) DebugDraw::DrawBox(box, color, duration, depthTest) +#define DEBUG_DRAW_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawCylinder(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawWireTriangle(v0, v1, v2, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, indices, color, duration, depthTest) @@ -356,7 +369,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) DebugDraw::DrawWireFrustum(frustum, color, duration, depthTest) #define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, radius, length, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, radius, height, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireCylinder(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, color, duration, depthTest) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration) @@ -371,6 +384,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) #define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) #define DEBUG_DRAW_BOX(box, color, duration, depthTest) +#define DEBUG_DRAW_CYLINDER(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) diff --git a/Source/Engine/Debug/DebugLog.cpp b/Source/Engine/Debug/DebugLog.cpp index 9263651b9..fb0ab901b 100644 --- a/Source/Engine/Debug/DebugLog.cpp +++ b/Source/Engine/Debug/DebugLog.cpp @@ -7,6 +7,7 @@ #include "Engine/Scripting/ManagedCLR/MDomain.h" #include "Engine/Scripting/ManagedCLR/MAssembly.h" #include "Engine/Scripting/ManagedCLR/MClass.h" +#include "Engine/Threading/Threading.h" #include "FlaxEngine.Gen.h" #include #include diff --git a/Source/Engine/Debug/Exceptions/JsonParseException.h b/Source/Engine/Debug/Exceptions/JsonParseException.h index 765d7855e..4e4b35962 100644 --- a/Source/Engine/Debug/Exceptions/JsonParseException.h +++ b/Source/Engine/Debug/Exceptions/JsonParseException.h @@ -25,7 +25,7 @@ namespace Log /// Parsing error code. /// Parsing error location. JsonParseException(ErrorCode error, size_t offset) - : JsonParseException(error, offset, String::Empty) + : JsonParseException(error, offset, StringView::Empty) { } @@ -35,7 +35,7 @@ namespace Log /// Parsing error code. /// Parsing error location. /// Additional information that help describe error - JsonParseException(ErrorCode error, size_t offset, const String& additionalInfo) + JsonParseException(ErrorCode error, size_t offset, const StringView& additionalInfo) : Exception(String::Format(TEXT("Parsing Json failed with error code {0} (offset {2}). {1}"), static_cast(error), GetParseError_En(error), offset), additionalInfo) { } diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp index 62e187e7d..a4b10adf9 100644 --- a/Source/Engine/Engine/Base/GameBase.cpp +++ b/Source/Engine/Engine/Base/GameBase.cpp @@ -6,6 +6,7 @@ #include "Engine/Engine/Time.h" #include "Engine/Engine/Engine.h" +#include "Engine/Engine/Globals.h" #include "Engine/Platform/Window.h" #include "Engine/Profiler/Profiler.h" #include "Engine/Level/Level.h" @@ -17,6 +18,7 @@ #include "Engine/Graphics/RenderTask.h" #include "Engine/Render2D/Render2D.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Utilities/Encryption.h" #include "Engine/Core/Log.h" #include "FlaxEngine.Gen.h" @@ -241,7 +243,7 @@ void GameBaseImpl::OnPostRender(GPUContext* context, RenderContext& renderContex const Rectangle screenRect(viewport.X, viewport.Y, viewport.Width, viewport.Height); Rectangle imageArea = screenRect; imageArea.Scale(0.6f); - const float aspectRatio = static_cast(splashScreen->Width()) / splashScreen->Height(); + const float aspectRatio = static_cast(splashScreen->Width()) / static_cast(splashScreen->Height()); const float height = imageArea.GetWidth() / aspectRatio; imageArea.Location.Y += (imageArea.GetHeight() - height) * 0.5f; imageArea.Size.Y = height; diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index edfc9c268..d6c03b070 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -109,7 +109,10 @@ bool CommandLine::Parse(const Char* cmdLine) PARSE_BOOL_SWITCH("-novsync ", NoVSync); PARSE_BOOL_SWITCH("-nolog ", NoLog); PARSE_BOOL_SWITCH("-std ", Std); +#if !BUILD_RELEASE PARSE_ARG_SWITCH("-debug ", DebuggerAddress); + PARSE_BOOL_SWITCH("-debugwait ", WaitForDebugger); +#endif #if PLATFORM_HAS_HEADLESS_MODE PARSE_BOOL_SWITCH("-headless ", Headless); #endif diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index 278620bf5..62b38ca99 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -49,11 +49,20 @@ public: /// Nullable Std; +#if !BUILD_RELEASE + /// /// -debug !ip:port! (Mono debugger address) /// Nullable DebuggerAddress; + /// + /// -debugwait (instructs Mono debugger to wait for client attach for 5 seconds) + /// + Nullable WaitForDebugger; + +#endif + #if PLATFORM_HAS_HEADLESS_MODE /// diff --git a/Source/Engine/Engine/Engine.Build.cs b/Source/Engine/Engine/Engine.Build.cs index f425ef41f..242637613 100644 --- a/Source/Engine/Engine/Engine.Build.cs +++ b/Source/Engine/Engine/Engine.Build.cs @@ -36,6 +36,7 @@ public class Engine : EngineModule options.PublicDependencies.Add("UI"); options.PublicDependencies.Add("Utilities"); options.PublicDependencies.Add("Visject"); + options.PublicDependencies.Add("Localization"); // Use source folder per platform group switch (options.Platform.Target) diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 709997eac..24d691c56 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -166,6 +166,7 @@ int32 Engine::Main(const Char* cmdLine) } } #endif + // App paused logic if (Platform::GetIsPaused()) { @@ -202,6 +203,7 @@ int32 Engine::Main(const Char* cmdLine) { OnDraw(); Time::OnEndDraw(); + FrameMark; canDraw = false; } @@ -413,24 +415,6 @@ bool Engine::HasGameViewportFocus() #endif } -Vector2 Engine::ScreenToGameViewport(const Vector2& screenPos) -{ -#if USE_EDITOR - return Editor::Managed->ScreenToGameViewport(screenPos); -#else - return MainWindow ? MainWindow->ScreenToClient(screenPos) : Vector2::Minimum; -#endif -} - -Vector2 Engine::GameViewportToScreen(const Vector2& viewportPos) -{ -#if USE_EDITOR - return Editor::Managed->GameViewportToScreen(viewportPos); -#else - return MainWindow ? MainWindow->ClientToScreen(viewportPos) : Vector2::Minimum; -#endif -} - void Engine::OnPause() { LOG(Info, "App paused"); @@ -640,7 +624,7 @@ void EngineImpl::InitMainWindow() if (exception) { MException ex(exception); - ex.Log(LogType::Fatal, TEXT("FlaxEngine.ClassLibraryInitializer.SetWindow")); + ex.Log(LogType::Fatal, TEXT("FlaxEngine.Scripting.SetWindow")); } #endif } diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 22aeac9f0..13f3e1fe8 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -153,20 +153,6 @@ public: /// True if game viewport is focused, otherwise false. static bool HasGameViewportFocus(); - /// - /// Converts the screen-space position to the game viewport position. - /// - /// The screen-space position. - /// The game viewport position. - static Vector2 ScreenToGameViewport(const Vector2& screenPos); - - /// - /// Converts the game viewport position to the screen-space position. - /// - /// The game viewport position. - /// The screen-space position. - static Vector2 GameViewportToScreen(const Vector2& viewportPos); - private: static void OnPause(); diff --git a/Source/Engine/Engine/EngineService.cpp b/Source/Engine/Engine/EngineService.cpp index 90e9a8d78..9f653a0b8 100644 --- a/Source/Engine/Engine/EngineService.cpp +++ b/Source/Engine/Engine/EngineService.cpp @@ -4,6 +4,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Sorting.h" +#include static bool CompareEngineServices(EngineService* const& a, EngineService* const& b) { @@ -14,6 +15,7 @@ static bool CompareEngineServices(EngineService* const& a, EngineService* const& void EngineService::name() { } \ void EngineService::On##name() \ { \ + ZoneScoped; \ auto& services = GetServices(); \ for (int32 i = 0; i < services.Count(); i++) \ services[i]->name(); \ @@ -22,6 +24,7 @@ static bool CompareEngineServices(EngineService* const& a, EngineService* const& void EngineService::name() { } \ void EngineService::On##name() \ { \ + ZoneScoped; \ auto& services = GetServices(); \ for (int32 i = 0; i < services.Count(); i++) \ services[i]->name(); \ @@ -63,18 +66,33 @@ bool EngineService::Init() void EngineService::OnInit() { + ZoneScoped; Sort(); // Init services from front to back auto& services = GetServices(); +#if TRACY_ENABLE + Char nameBuffer[100]; +#endif for (int32 i = 0; i < services.Count(); i++) { const auto service = services[i]; - LOG(Info, "Initialize {0}...", service->Name); + const StringView name(service->Name); +#if TRACY_ENABLE + ZoneScoped; + int32 nameBufferLength = 0; + for (int32 j = 0; j < name.Length(); j++) + if (name[j] != ' ') + nameBuffer[nameBufferLength++] = name[j]; + Platform::MemoryCopy(nameBuffer + nameBufferLength, TEXT("::Init"), 7 * sizeof(Char)); + nameBufferLength += 7; + ZoneName(nameBuffer, nameBufferLength); +#endif + LOG(Info, "Initialize {0}...", name); service->IsInitialized = true; if (service->Init()) { - Platform::Fatal(String::Format(TEXT("Failed to initialize {0}."), service->Name)); + Platform::Fatal(String::Format(TEXT("Failed to initialize {0}."), name)); } } @@ -87,6 +105,7 @@ void EngineService::Dispose() void EngineService::OnDispose() { + ZoneScoped; // Dispose services from back to front auto& services = GetServices(); for (int32 i = services.Count() - 1; i >= 0; i--) diff --git a/Source/Engine/Engine/GameplayGlobals.cpp b/Source/Engine/Engine/GameplayGlobals.cpp index 944253b4a..0525f3169 100644 --- a/Source/Engine/Engine/GameplayGlobals.cpp +++ b/Source/Engine/Engine/GameplayGlobals.cpp @@ -8,6 +8,8 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/BinaryAssetUpgrader.h" +#if USE_EDITOR + class GameplayGlobalsUpgrader : public BinaryAssetUpgrader { public: @@ -50,7 +52,9 @@ private: } }; -REGISTER_BINARY_ASSET(GameplayGlobals, "FlaxEngine.GameplayGlobals", ::New(), true); +#endif + +REGISTER_BINARY_ASSET_WITH_UPGRADER(GameplayGlobals, "FlaxEngine.GameplayGlobals", GameplayGlobalsUpgrader, true); GameplayGlobals::GameplayGlobals(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) @@ -59,6 +63,7 @@ GameplayGlobals::GameplayGlobals(const SpawnParams& params, const AssetInfo* inf Dictionary GameplayGlobals::GetValues() const { + ScopeLock lock(Locker); Dictionary result; for (auto& e : Variables) result.Add(e.Key, e.Value.Value); @@ -67,6 +72,7 @@ Dictionary GameplayGlobals::GetValues() const void GameplayGlobals::SetValues(const Dictionary& values) { + ScopeLock lock(Locker); for (auto& e : values) { bool hasKey = false; @@ -97,6 +103,7 @@ void GameplayGlobals::SetValues(const Dictionary& values) Dictionary GameplayGlobals::GetDefaultValues() const { + ScopeLock lock(Locker); Dictionary result; for (auto& e : Variables) result.Add(e.Key, e.Value.DefaultValue); @@ -105,6 +112,7 @@ Dictionary GameplayGlobals::GetDefaultValues() const void GameplayGlobals::SetDefaultValues(const Dictionary& values) { + ScopeLock lock(Locker); for (auto& e : values) { bool hasKey = false; @@ -135,12 +143,14 @@ void GameplayGlobals::SetDefaultValues(const Dictionary& values Variant GameplayGlobals::GetValue(const StringView& name) const { + ScopeLock lock(Locker); auto e = Variables.TryGet(name); return e ? e->Value : Variant::Zero; } void GameplayGlobals::SetValue(const StringView& name, const Variant& value) { + ScopeLock lock(Locker); auto e = Variables.TryGet(name); if (e) { @@ -150,6 +160,7 @@ void GameplayGlobals::SetValue(const StringView& name, const Variant& value) void GameplayGlobals::ResetValues() { + ScopeLock lock(Locker); for (auto& e : Variables) { e.Value.Value = e.Value.DefaultValue; diff --git a/Source/Engine/Engine/Globals.cpp b/Source/Engine/Engine/Globals.cpp index 9f180fa12..e65823350 100644 --- a/Source/Engine/Engine/Globals.cpp +++ b/Source/Engine/Engine/Globals.cpp @@ -25,4 +25,3 @@ int32 Globals::EngineBuildNumber = FLAXENGINE_VERSION_BUILD; String Globals::ProductName; String Globals::CompanyName; int32 Globals::ContentKey; -bool Globals::ConvertLoadedMaterialsByForce; diff --git a/Source/Engine/Engine/Globals.h b/Source/Engine/Engine/Globals.h index 16c47d8ee..6d88b11bf 100644 --- a/Source/Engine/Engine/Globals.h +++ b/Source/Engine/Engine/Globals.h @@ -94,7 +94,4 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals); /// The content data keycode. /// static int32 ContentKey; - - // True if convert all loaded material by force - static bool ConvertLoadedMaterialsByForce; }; diff --git a/Source/Engine/Engine/Linux/LinuxGame.cpp b/Source/Engine/Engine/Linux/LinuxGame.cpp index 1f59a66a9..3fe7c6a92 100644 --- a/Source/Engine/Engine/Linux/LinuxGame.cpp +++ b/Source/Engine/Engine/Linux/LinuxGame.cpp @@ -7,6 +7,7 @@ #include "Engine/Platform/FileSystem.h" #include "Engine/Core/Config/PlatformSettings.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Engine/Globals.h" #include "Engine/Graphics/Textures/TextureData.h" // hack using TextureTool in Platform module -> TODO: move texture data sampling to texture data itself #define COMPILE_WITH_TEXTURE_TOOL 1 diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 999dc28cb..11db5dd42 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -66,6 +66,26 @@ void Screen::SetSize(const Vector2& value) Size = value; } +Vector2 Screen::ScreenToGameViewport(const Vector2& screenPos) +{ +#if USE_EDITOR + return Editor::Managed->ScreenToGameViewport(screenPos); +#else + auto win = Engine::MainWindow; + return win ? win->ScreenToClient(screenPos) : Vector2::Minimum; +#endif +} + +Vector2 Screen::GameViewportToScreen(const Vector2& viewportPos) +{ +#if USE_EDITOR + return Editor::Managed->GameViewportToScreen(viewportPos); +#else + auto win = Engine::MainWindow; + return win ? win->ClientToScreen(viewportPos) : Vector2::Minimum; +#endif +} + bool Screen::GetCursorVisible() { #if USE_EDITOR diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 88a44da43..4fa39a324 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -33,6 +33,20 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); /// The value API_PROPERTY() static Vector2 GetSize(); + /// + /// Converts the screen-space position to the game viewport position. + /// + /// The screen-space position. + /// The game viewport position. + API_FUNCTION() static Vector2 ScreenToGameViewport(const Vector2& screenPos); + + /// + /// Converts the game viewport position to the screen-space position. + /// + /// The game viewport position. + /// The screen-space position. + API_FUNCTION() static Vector2 GameViewportToScreen(const Vector2& viewportPos); + /// /// Sets the window size. /// diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index f26f4f759..2dd348207 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -17,7 +17,7 @@ namespace bool Time::_gamePaused = false; float Time::_physicsMaxDeltaTime = 0.1f; DateTime Time::StartupTime; -float Time::UpdateFPS = 30.0f; +float Time::UpdateFPS = 60.0f; float Time::PhysicsFPS = 60.0f; float Time::DrawFPS = 60.0f; float Time::TimeScale = 1.0f; diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h index eb176ae7c..af0545184 100644 --- a/Source/Engine/Engine/Time.h +++ b/Source/Engine/Engine/Time.h @@ -22,7 +22,7 @@ public: /// Engine subsystem updating data. /// Used to invoke game logic updates, physics updates and rendering with possibly different frequencies. /// - class TickData + class FLAXENGINE_API TickData { public: diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index c38191adf..bb809d4e0 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -5,6 +5,7 @@ #include "FoliageCluster.h" #include "Engine/Core/Random.h" #include "Engine/Engine/Engine.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneQuery.h" #include "Engine/Profiler/ProfilerCPU.h" diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 51d92cd3f..8f30dc85a 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -6,6 +6,7 @@ #include "RenderTask.h" #include "RenderTools.h" #include "Graphics.h" +#include "Shaders/GPUShader.h" #include "Async/DefaultGPUTasksExecutor.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Assets/Material.h" diff --git a/Source/Engine/Graphics/Graphics.Build.cs b/Source/Engine/Graphics/Graphics.Build.cs index 7d7f49d3d..82e279360 100644 --- a/Source/Engine/Graphics/Graphics.Build.cs +++ b/Source/Engine/Graphics/Graphics.Build.cs @@ -76,7 +76,7 @@ public class Graphics : EngineModule options.PrivateDependencies.Add("GraphicsDeviceVulkan"); break; case TargetPlatform.Switch: - options.PrivateDependencies.Add("GraphicsDeviceNull"); // TODO: use Vulkan on Switch + options.PrivateDependencies.Add("GraphicsDeviceVulkan"); break; default: throw new InvalidPlatformException(options.Platform.Target); } diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 69170dca0..292e6c570 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -171,6 +171,14 @@ bool GraphicsService::Init() device = CreateGPUDeviceVulkan(); #endif +#elif PLATFORM_SWITCH + + // Switch +#if GRAPHICS_API_VULKAN + if (!device) + device = CreateGPUDeviceVulkan(); +#endif + #elif !defined(GRAPHICS_API_NULL) #error "Platform does not support GPU devices." diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.h b/Source/Engine/Graphics/Materials/DecalMaterialShader.h index 0baf88b42..a84960576 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.h +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.h @@ -33,7 +33,7 @@ public: /// Init /// /// Material resource name - DecalMaterialShader(const String& name) + DecalMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h index 537547c6a..b8886769d 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h @@ -56,7 +56,7 @@ private: public: - DeferredMaterialShader(const String& name) + DeferredMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.h b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h index f2c796a23..c3040ded3 100644 --- a/Source/Engine/Graphics/Materials/DeformableMaterialShader.h +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h @@ -44,7 +44,7 @@ private: public: - DeformableMaterialShader(const String& name) + DeformableMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.h b/Source/Engine/Graphics/Materials/ForwardMaterialShader.h index bcba48259..dd053717f 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.h +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.h @@ -58,7 +58,7 @@ public: /// Init /// /// Material resource name - ForwardMaterialShader(const String& name) + ForwardMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.h b/Source/Engine/Graphics/Materials/GUIMaterialShader.h index c84373c90..ead9cf4a9 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.h +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.h @@ -33,7 +33,7 @@ public: /// Init /// /// Material resource name - GUIMaterialShader(const String& name) + GUIMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 5ce55f1c4..b5ff66c3b 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -2,6 +2,8 @@ #include "MaterialParams.h" #include "MaterialInfo.h" +#include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Math/Matrix.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Engine/GameplayGlobals.h" @@ -96,11 +98,11 @@ Variant MaterialParameter::GetValue() const case MaterialParameterType::Vector3: return _asVector3; case MaterialParameterType::Vector4: - return _asVector4; + return *(Vector4*)&AsData; case MaterialParameterType::Color: return _asColor; case MaterialParameterType::Matrix: - return Variant(_asMatrix); + return Variant(*(Matrix*)&AsData); case MaterialParameterType::NormalMap: case MaterialParameterType::Texture: case MaterialParameterType::CubeTexture: @@ -140,13 +142,13 @@ void MaterialParameter::SetValue(const Variant& value) _asVector3 = (Vector3)value; break; case MaterialParameterType::Vector4: - _asVector4 = (Vector4)value; + *(Vector4*)&AsData = (Vector4)value; break; case MaterialParameterType::Color: _asColor = (Color)value; break; case MaterialParameterType::Matrix: - _asMatrix = (Matrix)value; + *(Matrix*)&AsData = (Matrix)value; break; case MaterialParameterType::NormalMap: case MaterialParameterType::Texture: @@ -245,13 +247,13 @@ void MaterialParameter::Bind(BindMeta& meta) const *((Vector3*)(meta.Constants + _offset)) = _asVector3; break; case MaterialParameterType::Vector4: - *((Vector4*)(meta.Constants + _offset)) = _asVector4; + *((Vector4*)(meta.Constants + _offset)) = *(Vector4*)&AsData; break; case MaterialParameterType::Color: *((Color*)(meta.Constants + _offset)) = _asColor; break; case MaterialParameterType::Matrix: - Matrix::Transpose(_asMatrix, *(Matrix*)(meta.Constants + _offset)); + Matrix::Transpose(*(Matrix*)&AsData, *(Matrix*)(meta.Constants + _offset)); break; case MaterialParameterType::NormalMap: { @@ -406,13 +408,13 @@ void MaterialParameter::clone(const MaterialParameter* param) _asVector3 = param->_asVector3; break; case MaterialParameterType::Vector4: - _asVector4 = param->_asVector4; + *(Vector4*)&AsData = *(Vector4*)¶m->AsData; break; case MaterialParameterType::Color: _asColor = param->_asColor; break; case MaterialParameterType::Matrix: - _asMatrix = param->_asMatrix; + *(Matrix*)&AsData = *(Matrix*)¶m->AsData; break; default: break; @@ -585,13 +587,13 @@ bool MaterialParams::Load(ReadStream* stream) stream->Read(¶m->_asVector3); break; case MaterialParameterType::Vector4: - stream->Read(¶m->_asVector4); + stream->Read((Vector4*)¶m->AsData); break; case MaterialParameterType::Color: stream->Read(¶m->_asColor); break; case MaterialParameterType::Matrix: - stream->Read(¶m->_asMatrix); + stream->Read((Matrix*)¶m->AsData); break; case MaterialParameterType::NormalMap: case MaterialParameterType::Texture: @@ -658,13 +660,13 @@ bool MaterialParams::Load(ReadStream* stream) stream->Read(¶m->_asVector3); break; case MaterialParameterType::Vector4: - stream->Read(¶m->_asVector4); + stream->Read((Vector4*)¶m->AsData); break; case MaterialParameterType::Color: stream->Read(¶m->_asColor); break; case MaterialParameterType::Matrix: - stream->Read(¶m->_asMatrix); + stream->Read((Matrix*)¶m->AsData); break; case MaterialParameterType::NormalMap: case MaterialParameterType::Texture: @@ -732,13 +734,13 @@ bool MaterialParams::Load(ReadStream* stream) stream->Read(¶m->_asVector3); break; case MaterialParameterType::Vector4: - stream->Read(¶m->_asVector4); + stream->Read((Vector4*)¶m->AsData); break; case MaterialParameterType::Color: stream->Read(¶m->_asColor); break; case MaterialParameterType::Matrix: - stream->Read(¶m->_asMatrix); + stream->Read((Matrix*)¶m->AsData); break; case MaterialParameterType::NormalMap: case MaterialParameterType::Texture: @@ -824,13 +826,13 @@ void MaterialParams::Save(WriteStream* stream) stream->Write(¶m->_asVector3); break; case MaterialParameterType::Vector4: - stream->Write(¶m->_asVector4); + stream->Write((Vector4*)¶m->AsData); break; case MaterialParameterType::Color: stream->Write(¶m->_asColor); break; case MaterialParameterType::Matrix: - stream->Write(¶m->_asMatrix); + stream->Write((Matrix*)¶m->AsData); break; case MaterialParameterType::NormalMap: case MaterialParameterType::Texture: @@ -898,13 +900,13 @@ void MaterialParams::Save(WriteStream* stream, const ArrayWrite(¶m.AsVector3); break; case MaterialParameterType::Vector4: - stream->Write(¶m.AsVector4); + stream->Write((Vector4*)¶m.AsData); break; case MaterialParameterType::Color: stream->Write(¶m.AsColor); break; case MaterialParameterType::Matrix: - stream->Write(¶m.AsMatrix); + stream->Write((Matrix*)¶m.AsData); break; case MaterialParameterType::NormalMap: case MaterialParameterType::Texture: diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index c51ffb8f9..d4fef1966 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -2,7 +2,9 @@ #pragma once -#include "Engine/Core/Math/Matrix.h" +#include "Engine/Core/Math/Color.h" +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingObjectReference.h" @@ -12,6 +14,7 @@ class MaterialInstance; class MaterialParams; class GPUContext; +class GPUTextureView; class RenderBuffers; struct MaterialParamsLink @@ -143,10 +146,9 @@ struct SerializedMaterialParam float AsFloat; Vector2 AsVector2; Vector3 AsVector3; - Vector4 AsVector4; Color AsColor; Guid AsGuid; - Matrix AsMatrix; + byte AsData[16 * 4]; }; byte RegisterIndex; @@ -181,9 +183,8 @@ private: float _asFloat; Vector2 _asVector2; Vector3 _asVector3; - Vector4 _asVector4; Color _asColor; - Matrix _asMatrix; + byte AsData[16 * 4]; }; AssetReference _asAsset; diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index 5191357ad..91c4fe8b8 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -54,7 +54,7 @@ GPUPipelineState* MaterialShader::PipelineStateCache::InitPS(CullMode mode, bool return ps; } -MaterialShader::MaterialShader(const String& name) +MaterialShader::MaterialShader(const StringView& name) : _isLoaded(false) , _shader(nullptr) { @@ -68,7 +68,7 @@ MaterialShader::~MaterialShader() SAFE_DELETE_GPU_RESOURCE(_shader); } -MaterialShader* MaterialShader::Create(const String& name, MemoryReadStream& shaderCacheStream, const MaterialInfo& info) +MaterialShader* MaterialShader::Create(const StringView& name, MemoryReadStream& shaderCacheStream, const MaterialInfo& info) { MaterialShader* material; switch (info.Domain) diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 8c49389a7..af0045f04 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -72,7 +72,7 @@ protected: /// Init /// /// Material resource name - MaterialShader(const String& name); + MaterialShader(const StringView& name); public: @@ -90,7 +90,7 @@ public: /// Stream with compiled shader data /// Loaded material info structure /// The created and loaded material or null if failed. - static MaterialShader* Create(const String& name, MemoryReadStream& shaderCacheStream, const MaterialInfo& info); + static MaterialShader* Create(const StringView& name, MemoryReadStream& shaderCacheStream, const MaterialInfo& info); /// /// Creates the dummy material used by the Null rendering backend to mock object but not perform any rendering. diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index abc28a0de..2c5dc147a 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -2,6 +2,7 @@ #include "MaterialShaderFeatures.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/ShadowsPass.h" #if USE_EDITOR diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.h b/Source/Engine/Graphics/Materials/ParticleMaterialShader.h index a38bf56f0..28a52ddf5 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.h +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.h @@ -54,7 +54,7 @@ public: /// Init /// /// Material resource name - ParticleMaterialShader(const String& name) + ParticleMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.h b/Source/Engine/Graphics/Materials/PostFxMaterialShader.h index 85767736f..0fdc5d416 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.h +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.h @@ -31,7 +31,7 @@ public: /// Init /// /// Material resource name - PostFxMaterialShader(const String& name) + PostFxMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.h b/Source/Engine/Graphics/Materials/TerrainMaterialShader.h index f69fc48ba..519233807 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.h +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.h @@ -48,7 +48,7 @@ public: /// Init /// /// Material resource name - TerrainMaterialShader(const String& name) + TerrainMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h index e38944342..5cf69fecb 100644 --- a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h +++ b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h @@ -19,7 +19,7 @@ public: /// Init /// /// Material resource name - VolumeParticleMaterialShader(const String& name) + VolumeParticleMaterialShader(const StringView& name) : MaterialShader(name) { } diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 5d7f712a8..dddbd6a3e 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -5,6 +5,8 @@ #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Serialization/MemoryReadStream.h" @@ -131,22 +133,29 @@ namespace } } +bool Mesh::HasVertexColors() const +{ + return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated(); +} + bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices) { + auto model = (Model*)_model; + Unload(); // Setup GPU resources - _model->LODs[_lodIndex]._verticesCount -= _vertices; + model->LODs[_lodIndex]._verticesCount -= _vertices; const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices); if (!failed) { - _model->LODs[_lodIndex]._verticesCount += _vertices; + model->LODs[_lodIndex]._verticesCount += _vertices; // Calculate mesh bounds SetBounds(BoundingBox::FromPoints((Vector3*)vb0, vertexCount)); // Send event (actors using this model can update bounds, etc.) - _model->onLoaded(); + model->onLoaded(); } return failed; @@ -214,17 +223,6 @@ Mesh::~Mesh() SAFE_DELETE_GPU_RESOURCE(_indexBuffer); } -void Mesh::SetMaterialSlotIndex(int32 value) -{ - if (value < 0 || value >= _model->MaterialSlots.Count()) - { - LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count()); - return; - } - - _materialSlotIndex = value; -} - bool Mesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* vb2, void* ib, bool use16BitIndexBuffer) { // Cache data @@ -443,7 +441,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float // TODO: cache vertexOffset within the model LOD per-mesh uint32 vertexOffset = 0; for (int32 meshIndex = 0; meshIndex < _index; meshIndex++) - vertexOffset += _model->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount(); + vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount(); drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex]; drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType); } @@ -464,7 +462,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float renderContext.List->AddDrawCall(drawModes, info.Flags, drawCall, entry.ReceiveDecals); } -bool Mesh::ExtractData(MeshBufferType type, BytesContainer& result) const +bool Mesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const { GPUBuffer* buffer = nullptr; switch (type) @@ -485,7 +483,7 @@ bool Mesh::ExtractData(MeshBufferType type, BytesContainer& result) const return buffer && buffer->DownloadData(result); } -Task* Mesh::ExtractDataAsync(MeshBufferType type, BytesContainer& result) const +Task* Mesh::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const { GPUBuffer* buffer = nullptr; switch (type) @@ -506,6 +504,15 @@ Task* Mesh::ExtractDataAsync(MeshBufferType type, BytesContainer& result) const return buffer ? buffer->DownloadDataAsync(result) : nullptr; } +bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result) const +{ +#if !BUILD_RELEASE + // TODO: implement this + LOG(Error, "Mesh::DownloadDataCPU not implemented."); +#endif + return true; +} + ScriptingObject* Mesh::GetParentModel() { return _model; @@ -661,7 +668,7 @@ bool Mesh::DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI) // TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer BytesContainer data; - auto task = mesh->ExtractDataAsync(bufferType, data); + auto task = mesh->DownloadDataGPUAsync(bufferType, data); if (task == nullptr) return true; diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 83c37c530..ca5174f7b 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -2,38 +2,29 @@ #pragma once -#include "Engine/Core/Math/BoundingBox.h" -#include "Engine/Core/Math/BoundingSphere.h" -#include "Engine/Scripting/ScriptingObject.h" -#include "Engine/Renderer/RenderList.h" -#include "Engine/Graphics/RenderTask.h" +#include "MeshBase.h" #include "ModelInstanceEntry.h" #include "Config.h" #include "Types.h" +#include "Engine/Level/Types.h" #if USE_PRECISE_MESH_INTERSECTS #include "CollisionProxy.h" #endif +struct GeometryDrawStateData; +class Lightmap; class GPUBuffer; /// /// Represents part of the model that is made of vertices and can be rendered using custom material and transformation. /// -API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public PersistentScriptingObject +API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public MeshBase { -DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, PersistentScriptingObject); +DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, MeshBase); protected: - - Model* _model; int32 _index; int32 _lodIndex; - int32 _materialSlotIndex; - bool _use16BitIndexBuffer; bool _hasLightmapUVs; - BoundingBox _box; - BoundingSphere _sphere; - uint32 _vertices; - uint32 _triangles; GPUBuffer* _vertexBuffers[3]; GPUBuffer* _indexBuffer; #if USE_PRECISE_MESH_INTERSECTS @@ -41,7 +32,6 @@ protected: #endif public: - Mesh(const Mesh& other) : Mesh() { @@ -56,13 +46,12 @@ public: ~Mesh(); public: - /// /// Gets the model owning this mesh. /// FORCE_INLINE Model* GetModel() const { - return _model; + return (Model*)_model; } /// @@ -81,35 +70,6 @@ public: return _index; } - /// - /// Gets the index of the material slot to use during this mesh rendering. - /// - API_PROPERTY() FORCE_INLINE int32 GetMaterialSlotIndex() const - { - return _materialSlotIndex; - } - - /// - /// Sets the index of the material slot to use during this mesh rendering. - /// - API_PROPERTY() void SetMaterialSlotIndex(int32 value); - - /// - /// Gets the triangle count. - /// - API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const - { - return _triangles; - } - - /// - /// Gets the vertex count. - /// - API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const - { - return _vertices; - } - /// /// Gets the index buffer. /// @@ -138,23 +98,11 @@ public: return _vertexBuffers[0] != nullptr; } - /// - /// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit. - /// - /// True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer. - API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const - { - return _use16BitIndexBuffer; - } - /// /// Determines whether this mesh has a vertex colors buffer. /// /// True if this mesh has a vertex colors buffers. - API_PROPERTY() FORCE_INLINE bool HasVertexColors() const - { - return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated(); - } + API_PROPERTY() bool HasVertexColors() const; /// /// Determines whether this mesh contains valid lightmap texture coordinates data. @@ -165,34 +113,6 @@ public: return _hasLightmapUVs; } - /// - /// Sets the mesh bounds. - /// - /// The bounding box. - void SetBounds(const BoundingBox& box) - { - _box = box; - BoundingSphere::FromBox(box, _sphere); - } - - /// - /// Gets the box. - /// - /// The bounding box. - API_PROPERTY() FORCE_INLINE const BoundingBox& GetBox() const - { - return _box; - } - - /// - /// Gets the sphere. - /// - /// The bounding sphere. - API_PROPERTY() FORCE_INLINE const BoundingSphere& GetSphere() const - { - return _sphere; - } - #if USE_PRECISE_MESH_INTERSECTS /// @@ -207,7 +127,6 @@ public: #endif public: - /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). /// @@ -286,7 +205,6 @@ public: bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint32* triangles, Vector3* normals = nullptr, Vector3* tangents = nullptr, Vector2* uvs = nullptr, Color32* colors = nullptr); public: - /// /// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load). /// @@ -319,7 +237,6 @@ public: bool UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices); public: - /// /// Initializes instance of the class. /// @@ -351,7 +268,6 @@ public: void Unload(); public: - /// /// Determines if there is an intersection between the mesh and a ray in given world /// @@ -372,7 +288,6 @@ public: } public: - /// /// Gets the draw call geometry for this mesh. Sets the index and vertex buffers. /// @@ -472,25 +387,12 @@ public: void Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const; public: - - /// - /// Extract mesh buffer data (cannot be called from the main thread!). - /// - /// Buffer type - /// The result data - /// True if failed, otherwise false - bool ExtractData(MeshBufferType type, BytesContainer& result) const; - - /// - /// Extracts mesh buffer data in the async task. - /// - /// Buffer type - /// The result data - /// Created async task used to gather the buffer data. - Task* ExtractDataAsync(MeshBufferType type, BytesContainer& result) const; + // [MeshBase] + bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const override; + Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const override; + bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const override; 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); diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h new file mode 100644 index 000000000..bedb57c90 --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -0,0 +1,134 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/BoundingSphere.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Graphics/Models/Types.h" +#include "Engine/Scripting/ScriptingObject.h" + +class Task; +class ModelBase; + +/// +/// Base class for model resources meshes. +/// +API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public PersistentScriptingObject +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(MeshBase); +protected: + + ModelBase* _model; + bool _use16BitIndexBuffer; + BoundingBox _box; + BoundingSphere _sphere; + uint32 _vertices; + uint32 _triangles; + int32 _materialSlotIndex; + + explicit MeshBase(const SpawnParams& params) + : PersistentScriptingObject(params) + { + } + +public: + + /// + /// Gets the model owning this mesh. + /// + FORCE_INLINE ModelBase* GetModelBase() const + { + return _model; + } + + /// + /// Gets the triangle count. + /// + /// The triangles + API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const + { + return _triangles; + } + + /// + /// Gets the vertex count. + /// + /// The vertices + API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const + { + return _vertices; + } + + /// + /// Gets the box. + /// + /// The bounding box. + API_PROPERTY() FORCE_INLINE const BoundingBox& GetBox() const + { + return _box; + } + + /// + /// Gets the sphere. + /// + /// The bounding sphere. + API_PROPERTY() FORCE_INLINE const BoundingSphere& GetSphere() const + { + return _sphere; + } + + /// + /// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit. + /// + /// True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer. + API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const + { + return _use16BitIndexBuffer; + } + + /// + /// Gets the index of the material slot to use during this mesh rendering. + /// + API_PROPERTY() FORCE_INLINE int32 GetMaterialSlotIndex() const + { + return _materialSlotIndex; + } + + /// + /// Sets the index of the material slot to use during this mesh rendering. + /// + API_PROPERTY() void SetMaterialSlotIndex(int32 value); + + /// + /// Sets the mesh bounds. + /// + /// The bounding box. + void SetBounds(const BoundingBox& box); + +public: + + /// + /// Extract mesh buffer data from GPU. Cannot be called from the main thread. + /// + /// Buffer type + /// The result data + /// True if failed, otherwise false + virtual bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const = 0; + + /// + /// Extracts mesh buffer data from GPU in the async task. + /// + /// Buffer type + /// The result data + /// Created async task used to gather the buffer data. + virtual Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const = 0; + + /// + /// Extract mesh buffer data from CPU. Cached internally. + /// + /// Buffer type + /// The result data + /// True if failed, otherwise false + virtual bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const = 0; +}; diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp index 8439a30d1..8faa9dcaa 100644 --- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp +++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Utilities.h" #include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Collections/BitArray.h" #include "Engine/Tools/ModelTool/ModelTool.h" #include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h" diff --git a/Source/Engine/Graphics/Models/SkeletonData.h b/Source/Engine/Graphics/Models/SkeletonData.h index 5f336d8be..3e5700304 100644 --- a/Source/Engine/Graphics/Models/SkeletonData.h +++ b/Source/Engine/Graphics/Models/SkeletonData.h @@ -5,6 +5,8 @@ #include "Engine/Core/Math/Transform.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Collections/Array.h" /// /// Describes a single skeleton node data. Used by the runtime. diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 9e26867ab..f2d1134d9 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -4,6 +4,8 @@ #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/SkinnedModel.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Serialization/MemoryReadStream.h" @@ -34,17 +36,6 @@ SkinnedMesh::~SkinnedMesh() SAFE_DELETE_GPU_RESOURCE(_indexBuffer); } -void SkinnedMesh::SetMaterialSlotIndex(int32 value) -{ - if (value < 0 || value >= _model->MaterialSlots.Count()) - { - LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count()); - return; - } - - _materialSlotIndex = value; -} - bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* ib, bool use16BitIndexBuffer) { // Cache data @@ -101,6 +92,8 @@ void SkinnedMesh::Unload() bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices) { + auto model = (SkinnedModel*)_model; + // Setup GPU resources const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices); if (!failed) @@ -109,7 +102,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0Skinne SetBounds(BoundingBox::FromPoints((Vector3*)vb, vertexCount)); // Send event (actors using this model can update bounds, etc.) - _model->onLoaded(); + model->onLoaded(); } return failed; @@ -218,7 +211,7 @@ bool SkinnedMesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) c return buffer && buffer->DownloadData(result); } -Task* SkinnedMesh::DownloadDataAsyncGPU(MeshBufferType type, BytesContainer& result) const +Task* SkinnedMesh::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const { GPUBuffer* buffer = nullptr; switch (type) @@ -535,7 +528,7 @@ bool SkinnedMesh::DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 type { // Get data from GPU // TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer - auto task = mesh->DownloadDataAsyncGPU(bufferType, data); + auto task = mesh->DownloadDataGPUAsync(bufferType, data); if (task == nullptr) return true; task->Start(); diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index bbdf38014..eb854189e 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -2,34 +2,25 @@ #pragma once -#include "Engine/Core/Math/BoundingBox.h" -#include "Engine/Core/Math/BoundingSphere.h" -#include "Engine/Scripting/ScriptingObject.h" -#include "Engine/Renderer/RenderList.h" -#include "Engine/Graphics/RenderTask.h" -#include "ModelInstanceEntry.h" +#include "MeshBase.h" #include "Types.h" #include "BlendShape.h" +struct GeometryDrawStateData; +struct RenderContext; class GPUBuffer; +class SkinnedMeshDrawData; /// /// Represents part of the skinned model that is made of vertices and can be rendered using custom material, transformation and skeleton bones hierarchy. /// -API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedMesh : public PersistentScriptingObject +API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedMesh : public MeshBase { -DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedMesh, PersistentScriptingObject); +DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedMesh, MeshBase); protected: - SkinnedModel* _model; int32 _index; int32 _lodIndex; - int32 _materialSlotIndex; - bool _use16BitIndexBuffer; - BoundingBox _box; - BoundingSphere _sphere; - uint32 _vertices; - uint32 _triangles; GPUBuffer* _vertexBuffer; GPUBuffer* _indexBuffer; mutable Array _cachedIndexBuffer; @@ -40,7 +31,7 @@ public: SkinnedMesh(const SkinnedMesh& other) : SkinnedMesh() { -#if !!BUILD_RELEASE +#if !BUILD_RELEASE CRASH; // Not used #endif } @@ -58,7 +49,7 @@ public: /// The skinned model FORCE_INLINE SkinnedModel* GetSkinnedModel() const { - return _model; + return (SkinnedModel*)_model; } /// @@ -70,39 +61,6 @@ public: return _index; } - /// - /// Gets the material slot index. - /// - /// The material slot index - API_PROPERTY() FORCE_INLINE int32 GetMaterialSlotIndex() const - { - return _materialSlotIndex; - } - - /// - /// Sets the index of the material slot index. - /// - /// The value. - API_PROPERTY() void SetMaterialSlotIndex(int32 value); - - /// - /// Gets the triangle count. - /// - /// The triangles - API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const - { - return _triangles; - } - - /// - /// Gets the vertex count. - /// - /// The vertices - API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const - { - return _vertices; - } - /// /// Determines whether this mesh is initialized (has vertex and index buffers initialized). /// @@ -112,15 +70,6 @@ public: return _vertexBuffer != nullptr; } - /// - /// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit. - /// - /// True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer. - API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const - { - return _use16BitIndexBuffer; - } - /// /// Blend shapes used by this mesh. /// @@ -194,36 +143,6 @@ public: /// True if failed, otherwise false. bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices); -public: - - /// - /// Sets the mesh bounds. - /// - /// The bounding box. - void SetBounds(const BoundingBox& box) - { - _box = box; - BoundingSphere::FromBox(box, _sphere); - } - - /// - /// Gets the box. - /// - /// The bounding box. - API_PROPERTY() FORCE_INLINE BoundingBox GetBox() const - { - return _box; - } - - /// - /// Gets the sphere. - /// - /// The bounding sphere. - API_PROPERTY() FORCE_INLINE BoundingSphere GetSphere() const - { - return _sphere; - } - public: /// @@ -319,29 +238,10 @@ public: public: - /// - /// Extract mesh buffer data from the GPU (cannot be called from the main thread!). - /// - /// Buffer type - /// The result data - /// True if failed, otherwise false - bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const; - - /// - /// Extracts mesh buffer data from the GPU in the async task. - /// - /// Buffer type - /// The result data - /// Created async task used to gather the buffer data. - Task* DownloadDataAsyncGPU(MeshBufferType type, BytesContainer& result) const; - - /// - /// Extract mesh buffer data from the CPU. - /// - /// Buffer type - /// The result data - /// True if failed, otherwise false - bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const; + // [MeshBase] + bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const override; + Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const override; + bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const override; private: diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp index db0e8b86e..e2a43d085 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp @@ -5,6 +5,12 @@ #include "Engine/Content/Assets/Model.h" #include "Engine/Serialization/MemoryReadStream.h" +bool SkinnedModelLOD::HasAnyMeshInitialized() const +{ + // Note: we initialize all meshes at once so the last one can be used to check it. + return Meshes.HasItems() && Meshes.Last().IsInitialized(); +} + bool SkinnedModelLOD::Load(MemoryReadStream& stream) { // Load LOD for each mesh diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h index 673bbb4e2..758db0a1b 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.h @@ -33,12 +33,7 @@ public: /// /// Determines whether any mesh has been initialized. /// - /// True if any mesh has been initialized, otherwise false. - FORCE_INLINE bool HasAnyMeshInitialized() const - { - // Note: we initialize all meshes at once so the last one can be used to check it. - return Meshes.HasItems() && Meshes.Last().IsInitialized(); - } + bool HasAnyMeshInitialized() const; public: diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index 400deb3c1..e1607906f 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Math/Vector4.h" #include "Engine/Content/AssetReference.h" #include "Engine/Serialization/ISerializable.h" #include "Engine/Content/Assets/Texture.h" diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index 5d6a47861..86fa20856 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -84,7 +84,7 @@ public: /// /// The index of the frame when this task was last time rendered. /// - uint64 LastUsedFrame = 0; + API_FIELD(ReadOnly) uint64 LastUsedFrame = 0; /// /// Action fired on task rendering. @@ -251,7 +251,7 @@ public: /// /// The scene rendering camera. Can be used to override the rendering view properties based on the current camera setup. /// - API_FIELD() Camera* Camera = nullptr; + API_FIELD() ScriptingObjectReference Camera; /// /// The render view description. diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index 4fc5e2e1d..142906192 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -6,6 +6,7 @@ #include "PixelFormat.h" #include "RenderView.h" #include "GPUDevice.h" +#include "RenderTask.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Engine/Time.h" @@ -553,3 +554,20 @@ float ViewToCenterLessRadius(const RenderView& view, const Vector3& center, floa // Calculate result return viewToCenter - radius; } + +void MeshBase::SetMaterialSlotIndex(int32 value) +{ + if (value < 0 || value >= _model->MaterialSlots.Count()) + { + LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count()); + return; + } + + _materialSlotIndex = value; +} + +void MeshBase::SetBounds(const BoundingBox& box) +{ + _box = box; + BoundingSphere::FromBox(box, _sphere); +} diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index 4d8b8cf1d..8ce11ec70 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -4,6 +4,8 @@ #include "ShaderStorage.h" #include "ShaderCacheManager.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/ShadowsOfMordor/AtlasChartsPacker.h" @@ -31,6 +33,16 @@ ShaderStorage::CachingMode ShaderStorage::GetCachingMode() #endif +bool ShaderAssetBase::IsNullRenderer() +{ + return GPUDevice::Instance->GetRendererType() == RendererType::Null; +} + +int32 ShaderAssetBase::GetCacheChunkIndex() +{ + return GetCacheChunkIndex(GPUDevice::Instance->GetShaderProfile()); +} + int32 ShaderAssetBase::GetCacheChunkIndex(ShaderProfile profile) { int32 result; @@ -61,6 +73,28 @@ int32 ShaderAssetBase::GetCacheChunkIndex(ShaderProfile profile) return result; } +bool ShaderAssetBase::initBase(AssetInitData& initData) +{ + // Validate version + if (initData.SerializedVersion != ShaderStorage::Header::Version) + { + LOG(Warning, "Invalid shader serialized version."); + return true; + } + + // Validate data + if (initData.CustomData.Length() != sizeof(_shaderHeader)) + { + LOG(Warning, "Invalid shader header."); + return true; + } + + // Load header 'as-is' + Platform::MemoryCopy(&_shaderHeader, initData.CustomData.Get(), sizeof(_shaderHeader)); + + return false; +} + #if USE_EDITOR bool ShaderAssetBase::Save() diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h index ab1f79aee..a2b81bf5e 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h @@ -4,7 +4,6 @@ #include "Engine/Core/Types/DataContainer.h" #include "Engine/Content/BinaryAsset.h" -#include "Engine/Graphics/GPUDevice.h" #include "ShaderStorage.h" /// @@ -18,17 +17,15 @@ protected: public: - /// - /// Gets internal shader cache chunk index - /// - /// Chunk index - FORCE_INLINE static int32 GetCacheChunkIndex() - { - return GetCacheChunkIndex(GPUDevice::Instance->GetShaderProfile()); - } + static bool IsNullRenderer(); /// - /// Gets internal shader cache chunk index + /// Gets internal shader cache chunk index (for current GPU device shader profile). + /// + static int32 GetCacheChunkIndex(); + + /// + /// Gets internal shader cache chunk index. /// /// Shader profile /// Chunk index @@ -47,11 +44,12 @@ public: #endif protected: - + + bool initBase(AssetInitData& initData); + /// /// Gets the parent asset. /// - /// The asset. virtual BinaryAsset* GetShaderAsset() const = 0; #if USE_EDITOR @@ -110,7 +108,7 @@ protected: }; /// -/// Base class for assets that can contain shader +/// Base class for binary assets that can contain shader. /// template class ShaderAssetTypeBase : public BaseType, public ShaderAssetBase @@ -121,11 +119,6 @@ public: protected: - /// - /// Init - /// - /// Asset scripting class metadata - /// Asset information explicit ShaderAssetTypeBase(const ScriptingObjectSpawnParams& params, const AssetInfo* info) : BaseType(params, info) { @@ -134,38 +127,22 @@ protected: protected: // [BaseType] - BinaryAsset* GetShaderAsset() const override - { - return (BinaryAsset*)this; - } bool init(AssetInitData& initData) override { - // Validate version - if (initData.SerializedVersion != ShadersSerializedVersion) - { - LOG(Warning, "Invalid shader serialized version."); - return true; - } - - // Validate data - if (initData.CustomData.Length() != sizeof(_shaderHeader)) - { - LOG(Warning, "Invalid shader header."); - return true; - } - - // Load header 'as-is' - Platform::MemoryCopy(&_shaderHeader, initData.CustomData.Get(), sizeof(_shaderHeader)); - - return false; + return initBase(initData); } - AssetChunksFlag getChunksToPreload() const override { AssetChunksFlag result = 0; const auto cachingMode = ShaderStorage::GetCachingMode(); - if (cachingMode == ShaderStorage::CachingMode::AssetInternal && GPUDevice::Instance->GetRendererType() != RendererType::Null) + if (cachingMode == ShaderStorage::CachingMode::AssetInternal && !IsNullRenderer()) result |= GET_CHUNK_FLAG(GetCacheChunkIndex()); return result; } + + // [ShaderAssetBase] + BinaryAsset* GetShaderAsset() const override + { + return (BinaryAsset*)this; + } }; diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp index 92fa47e8c..cd63b3ec4 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp @@ -233,7 +233,7 @@ bool ShaderCacheManagerService::Init() { LOG(Warning, "Shaders cache database is invalid. Performing reset."); - if (FileSystem::DeleteDirectory(rootDir)) + if (FileSystem::DirectoryExists(rootDir) && FileSystem::DeleteDirectory(rootDir)) { LOG(Warning, "Failed to reset the shaders cache database."); } diff --git a/Source/Engine/Graphics/Textures/ITextureOwner.h b/Source/Engine/Graphics/Textures/ITextureOwner.h index 8f66298ba..196b76664 100644 --- a/Source/Engine/Graphics/Textures/ITextureOwner.h +++ b/Source/Engine/Graphics/Textures/ITextureOwner.h @@ -18,8 +18,7 @@ public: /// /// Gets the texture owner mutex used to synchronize texture logic. /// - /// The mutex. - virtual CriticalSection* GetOwnerLocker() const = 0; + virtual CriticalSection& GetOwnerLocker() const = 0; /// /// Get texture mip map data diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 15b9c540c..d5a1e9339 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -33,6 +33,23 @@ StreamingTexture::~StreamingTexture() ASSERT(_streamingTasksCount == 0); } +Vector2 StreamingTexture::Size() const +{ + return _texture->Size(); +} + +int32 StreamingTexture::TextureMipIndexToTotalIndex(int32 textureMipIndex) const +{ + const int32 missingMips = TotalMipLevels() - _texture->MipLevels(); + return textureMipIndex + missingMips; +} + +int32 StreamingTexture::TotalIndexToTextureMipIndex(int32 mipIndex) const +{ + const int32 missingMips = TotalMipLevels() - _texture->MipLevels(); + return mipIndex - missingMips; +} + bool StreamingTexture::Create(const TextureHeader& header) { // Validate header (further validation is performed by the Texture.Init) @@ -90,13 +107,27 @@ uint64 StreamingTexture::GetTotalMemoryUsage() const return CalculateTextureMemoryUsage(_header.Format, _header.Width, _header.Height, _header.MipLevels) * arraySize; } +String StreamingTexture::ToString() const +{ + return _texture->ToString(); +} + +int32 StreamingTexture::GetCurrentResidency() const +{ + return _texture->ResidentMipLevels(); +} + +int32 StreamingTexture::GetAllocatedResidency() const +{ + return _texture->MipLevels(); +} + bool StreamingTexture::CanBeUpdated() const { // Streaming Texture cannot be updated if: // - is not initialized // - mip data uploading job running // - resize texture job running - return IsInitialized() && Platform::AtomicRead(&_streamingTasksCount) == 0; } @@ -146,7 +177,6 @@ protected: return Result::Ok; } - void OnEnd() override { Platform::InterlockedDecrement(&_streamingTexture->_streamingTasksCount); @@ -154,7 +184,6 @@ protected: // Base GPUTask::OnEnd(); } - void OnSync() override { Swap(_streamingTexture->_texture, _newTexture); @@ -298,7 +327,6 @@ protected: return Result::Ok; } - void OnEnd() override { _dataLock.Release(); diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.h b/Source/Engine/Graphics/Textures/StreamingTexture.h index 3b6e94959..1dda71ba4 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.h +++ b/Source/Engine/Graphics/Textures/StreamingTexture.h @@ -2,7 +2,6 @@ #pragma once -#include "GPUTexture.h" #include "ITextureOwner.h" #include "Engine/Streaming/StreamableResource.h" #include "Types.h" @@ -14,7 +13,6 @@ class FLAXENGINE_API StreamingTexture : public Object, public StreamableResource { friend class StreamTextureMipTask; friend class StreamTextureResizeTask; - protected: ITextureOwner* _owner; @@ -25,16 +23,7 @@ protected: public: - /// - /// Init - /// - /// Parent object - /// Texture object name StreamingTexture(ITextureOwner* owner, const String& name); - - /// - /// Destructor - /// ~StreamingTexture(); public: @@ -42,31 +31,23 @@ public: /// /// Gets the owner. /// - /// The owner. FORCE_INLINE ITextureOwner* GetOwner() const { return _owner; } /// - /// Gets texture object handle + /// Gets texture object handle. /// - /// Texture object FORCE_INLINE GPUTexture* GetTexture() const { return _texture; } /// - /// Gets texture size of Vector2::Zero if not loaded + /// Gets texture size of Vector2::Zero if not loaded. /// - /// Texture size - FORCE_INLINE Vector2 Size() const - { - return _texture->Size(); - } - -public: + Vector2 Size() const; /// /// Gets a value indicating whether this instance is initialized. @@ -88,7 +69,6 @@ public: /// /// Gets total texture height (in texels) /// - /// Texture width FORCE_INLINE int32 TotalHeight() const { return _header.Height; @@ -97,7 +77,6 @@ public: /// /// Gets total texture array size /// - /// Texture array size FORCE_INLINE int32 TotalArraySize() const { return IsCubeMap() ? 6 : 1; @@ -106,7 +85,6 @@ public: /// /// Gets total texture mip levels count /// - /// Texture mip levels FORCE_INLINE int32 TotalMipLevels() const { return _header.MipLevels; @@ -115,7 +93,6 @@ public: /// /// Returns texture format type /// - /// Texture format type FORCE_INLINE TextureFormatType GetFormatType() const { return _header.Type; @@ -124,7 +101,6 @@ public: /// /// Returns true if it's a cube map texture /// - /// True if i's a cubemap FORCE_INLINE bool IsCubeMap() const { return _header.IsCubeMap; @@ -133,7 +109,6 @@ public: /// /// Returns true if texture cannot be used during GPU resources streaming system /// - /// True if texture cannot be used during GPU resources streaming system FORCE_INLINE bool NeverStream() const { return _header.NeverStream; @@ -142,7 +117,6 @@ public: /// /// Gets the texture header. /// - /// Header FORCE_INLINE const TextureHeader* GetHeader() const { return &_header; @@ -163,22 +137,14 @@ public: /// /// Index of the texture mip. /// The index of the mip map. - FORCE_INLINE int32 TextureMipIndexToTotalIndex(int32 textureMipIndex) const - { - const int32 missingMips = TotalMipLevels() - _texture->MipLevels(); - return textureMipIndex + missingMips; - } + int32 TextureMipIndexToTotalIndex(int32 textureMipIndex) const; /// /// Converts absolute mip map index to the allocated texture mip index. /// /// Index of the texture mip. /// The index of the mip map. - FORCE_INLINE int32 TotalIndexToTextureMipIndex(int32 mipIndex) const - { - const int32 missingMips = TotalMipLevels() - _texture->MipLevels(); - return mipIndex - missingMips; - } + int32 TotalIndexToTextureMipIndex(int32 mipIndex) const; public: @@ -211,27 +177,15 @@ public: public: // [Object] - String ToString() const override - { - return _texture->ToString(); - } + String ToString() const override; // [StreamableResource] int32 GetMaxResidency() const override { return _header.MipLevels; } - - int32 GetCurrentResidency() const override - { - return _texture->ResidentMipLevels(); - } - - int32 GetAllocatedResidency() const override - { - return _texture->MipLevels(); - } - + int32 GetCurrentResidency() const override; + int32 GetAllocatedResidency() const override; bool CanBeUpdated() const override; Task* UpdateAllocation(int32 residency) override; Task* CreateStreamingTask(int32 residency) override; diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp index 83e781a5a..d21d61f2f 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.cpp +++ b/Source/Engine/Graphics/Textures/TextureBase.cpp @@ -3,10 +3,11 @@ #include "TextureBase.h" #include "TextureData.h" #include "Engine/Graphics/GPUDevice.h" -#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" -#include "Engine/Debug/Exceptions/InvalidOperationException.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/PixelFormatExtensions.h" +#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Debug/Exceptions/InvalidOperationException.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" @@ -22,6 +23,36 @@ TextureBase::TextureBase(const SpawnParams& params, const AssetInfo* info) { } +Vector2 TextureBase::Size() const +{ + return Vector2(static_cast(_texture.TotalWidth()), static_cast(_texture.TotalHeight())); +} + +int32 TextureBase::GetArraySize() const +{ + return StreamingTexture()->TotalArraySize(); +} + +int32 TextureBase::GetMipLevels() const +{ + return StreamingTexture()->TotalMipLevels(); +} + +int32 TextureBase::GetResidentMipLevels() const +{ + return GetTexture()->ResidentMipLevels(); +} + +uint64 TextureBase::GetCurrentMemoryUsage() const +{ + return GetTexture()->GetMemoryUsage(); +} + +uint64 TextureBase::GetTotalMemoryUsage() const +{ + return StreamingTexture()->GetTotalMemoryUsage(); +} + BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch) { BytesContainer result; @@ -193,9 +224,9 @@ int32 TextureBase::calculateChunkIndex(int32 mipIndex) const return mipIndex; } -CriticalSection* TextureBase::GetOwnerLocker() const +CriticalSection& TextureBase::GetOwnerLocker() const { - return &_parent->Locker; + return _parent->Locker; } void TextureBase::unload(bool isReloading) diff --git a/Source/Engine/Graphics/Textures/TextureBase.h b/Source/Engine/Graphics/Textures/TextureBase.h index 2cb895a23..7839f940a 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.h +++ b/Source/Engine/Graphics/Textures/TextureBase.h @@ -6,6 +6,8 @@ #include "StreamingTexture.h" #include "Engine/Core/Log.h" +class TextureData; + /// /// Base class for , , and other assets that can contain texture data. /// @@ -103,50 +105,32 @@ public: /// /// Gets the total size of the texture. Actual resident size may be different due to dynamic content streaming. Returns Vector2::Zero if texture is not loaded. /// - API_PROPERTY() FORCE_INLINE Vector2 Size() const - { - return Vector2(static_cast(_texture.TotalWidth()), static_cast(_texture.TotalHeight())); - } + API_PROPERTY() Vector2 Size() const; /// /// Gets the total array size of the texture. /// - API_PROPERTY() int32 GetArraySize() const - { - return StreamingTexture()->TotalArraySize(); - } + API_PROPERTY() int32 GetArraySize() const; /// /// Gets the total mip levels count of the texture. Actual resident mipmaps count may be different due to dynamic content streaming. /// - API_PROPERTY() int32 GetMipLevels() const - { - return StreamingTexture()->TotalMipLevels(); - } + API_PROPERTY() int32 GetMipLevels() const; /// /// Gets the current mip levels count of the texture that are on GPU ready to use. /// - API_PROPERTY() int32 GetResidentMipLevels() const - { - return GetTexture()->ResidentMipLevels(); - } + API_PROPERTY() int32 GetResidentMipLevels() const; /// /// Gets the amount of the memory used by this resource. Exact value may differ due to memory alignment and resource allocation policy. /// - API_PROPERTY() uint64 GetCurrentMemoryUsage() const - { - return GetTexture()->GetMemoryUsage(); - } + API_PROPERTY() uint64 GetCurrentMemoryUsage() const; /// /// Gets the total memory usage that texture may have in use (if loaded to the maximum quality). Exact value may differ due to memory alignment and resource allocation policy. /// - API_PROPERTY() uint64 GetTotalMemoryUsage() const - { - return StreamingTexture()->GetTotalMemoryUsage(); - } + API_PROPERTY() uint64 GetTotalMemoryUsage() const; public: @@ -186,7 +170,7 @@ private: public: // [ITextureOwner] - CriticalSection* GetOwnerLocker() const override; + CriticalSection& GetOwnerLocker() const override; Task* RequestMipDataAsync(int32 mipIndex) override; FlaxStorage::LockData LockData() override; void GetMipData(int32 mipIndex, BytesContainer& data) const override; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp index 6ebd55384..e9d83bfc3 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp @@ -99,8 +99,6 @@ void GPUSwapChainDX11::SetFullscreen(bool isFullscreen) swapChainDesc.BufferDesc = outputDX.DesktopViewMode; } - releaseBackBuffer(); - if (FAILED(_swapChain->ResizeTarget(&swapChainDesc.BufferDesc))) { LOG(Warning, "Swapchain resize failed."); @@ -110,10 +108,6 @@ void GPUSwapChainDX11::SetFullscreen(bool isFullscreen) { LOG(Warning, "Cannot change fullscreen mode for '{0}' to {1}.", ToString(), isFullscreen); } - - VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, _width, _height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags)); - - getBackBuffer(); } #else LOG(Info, "Cannot change fullscreen mode on this platform"); @@ -208,7 +202,7 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height) ASSERT(_swapChain); // Disable DXGI changes to the window - VALIDATE_DIRECTX_RESULT(dxgi->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER)); + VALIDATE_DIRECTX_RESULT(dxgi->MakeWindowAssociation(_windowHandle, 0)); #else auto dxgiFactory = (IDXGIFactory2*)_device->GetDXGIFactory(); VALIDATE_DIRECTX_RESULT(dxgiFactory->CreateSwapChainForCoreWindow(_device->GetDevice(), static_cast(_windowHandle), &swapChainDesc, nullptr, &_swapChain)); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp index 669b217e4..b8ac22bba 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp @@ -124,8 +124,6 @@ void GPUSwapChainDX12::SetFullscreen(bool isFullscreen) swapChainDesc.BufferDesc = outputDX.DesktopViewMode; } - releaseBackBuffer(); - if (FAILED(_swapChain->ResizeTarget(&swapChainDesc.BufferDesc))) { LOG(Warning, "Swapchain resize failed."); @@ -136,10 +134,6 @@ void GPUSwapChainDX12::SetFullscreen(bool isFullscreen) LOG(Warning, "Cannot change fullscreen mode for '{0}' to {1}.", ToString(), isFullscreen); } - VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, _width, _height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags)); - - getBackBuffer(); - _isFullscreen = isFullscreen; } #else @@ -223,7 +217,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height) _backBuffers.Resize(swapChainDesc.BufferCount); // Disable DXGI changes to the window - dxgiFactory->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER); + dxgiFactory->MakeWindowAssociation(_windowHandle, 0); } else { diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp index f9a39574c..ac56a2a9f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp @@ -43,7 +43,7 @@ void CmdBufferVulkan::End() { ASSERT(IsOutsideRenderPass()); -#if GPU_ALLOW_PROFILE_EVENTS +#if GPU_ALLOW_PROFILE_EVENTS && VK_EXT_debug_utils // End remaining events while (_eventsBegin--) vkCmdEndDebugUtilsLabelEXT(GetHandle()); @@ -89,6 +89,7 @@ void CmdBufferVulkan::AcquirePoolSet() void CmdBufferVulkan::BeginEvent(const Char* name) { +#if VK_EXT_debug_utils if (!vkCmdBeginDebugUtilsLabelEXT) return; @@ -108,15 +109,18 @@ void CmdBufferVulkan::BeginEvent(const Char* name) RenderToolsVulkan::ZeroStruct(label, VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT); label.pLabelName = buffer; vkCmdBeginDebugUtilsLabelEXT(GetHandle(), &label); +#endif } void CmdBufferVulkan::EndEvent() { +#if VK_EXT_debug_utils if (_eventsBegin == 0 || !vkCmdEndDebugUtilsLabelEXT) return; _eventsBegin--; vkCmdEndDebugUtilsLabelEXT(GetHandle()); +#endif } #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp index 1cd90b11b..2acd2712c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp @@ -17,14 +17,17 @@ void GPUBufferViewVulkan::Init(GPUDeviceVulkan* device, GPUBufferVulkan* owner, Buffer = buffer; Size = size; - VkBufferViewCreateInfo viewInfo; - RenderToolsVulkan::ZeroStruct(viewInfo, VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO); - viewInfo.buffer = Buffer; - viewInfo.format = RenderToolsVulkan::ToVulkanFormat(format); - viewInfo.offset = 0; - viewInfo.range = Size; - - VALIDATE_VULKAN_RESULT(vkCreateBufferView(device->Device, &viewInfo, nullptr, &View)); + if (owner->IsShaderResource() && !(owner->GetDescription().Flags & GPUBufferFlags::Structured)) + { + VkBufferViewCreateInfo viewInfo; + RenderToolsVulkan::ZeroStruct(viewInfo, VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO); + viewInfo.buffer = Buffer; + viewInfo.format = RenderToolsVulkan::ToVulkanFormat(format); + viewInfo.offset = 0; + viewInfo.range = Size; + ASSERT_LOW_LAYER(viewInfo.format != VK_FORMAT_UNDEFINED); + VALIDATE_VULKAN_RESULT(vkCreateBufferView(device->Device, &viewInfo, nullptr, &View)); + } } void GPUBufferViewVulkan::Release() @@ -32,19 +35,18 @@ void GPUBufferViewVulkan::Release() if (View != VK_NULL_HANDLE) { Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::BufferView, View); - View = VK_NULL_HANDLE; - -#if BUILD_DEBUG - Device = nullptr; - Owner = nullptr; - Buffer = VK_NULL_HANDLE; -#endif } +#if BUILD_DEBUG + Device = nullptr; + Owner = nullptr; + Buffer = VK_NULL_HANDLE; +#endif } void GPUBufferViewVulkan::DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) { + ASSERT_LOW_LAYER(View != VK_NULL_HANDLE); bufferView = &View; context->AddBufferBarrier(Owner, VK_ACCESS_SHADER_READ_BIT); @@ -52,6 +54,7 @@ void GPUBufferViewVulkan::DescriptorAsUniformTexelBuffer(GPUContextVulkan* conte void GPUBufferViewVulkan::DescriptorAsStorageBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range) { + ASSERT_LOW_LAYER(Buffer); buffer = Buffer; offset = 0; range = Size; @@ -88,7 +91,7 @@ bool GPUBufferVulkan::OnInit() bufferInfo.size = _desc.Size; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; - if (useSRV) + if (useSRV && !(_desc.Flags & GPUBufferFlags::Structured)) bufferInfo.usage |= VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; if (useUAV || _desc.Flags & GPUBufferFlags::RawBuffer) bufferInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; @@ -165,7 +168,7 @@ bool GPUBufferVulkan::OnInit() } } // Check if need to bind buffer to the shaders - else if (useSRV) + else if (useSRV || useUAV) { // Create buffer view _view.Init(_device, this, _buffer, _desc.Size, _desc.Format); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index aa169e8f5..a0b64a3bb 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -27,6 +27,7 @@ #include "Engine/Core/Utilities.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Engine/Engine.h" +#include "Engine/Engine/Globals.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Profiler/ProfilerCPU.h" @@ -52,6 +53,8 @@ VkDebugReportCallbackEXT MsgCallback = VK_NULL_HANDLE; extern VulkanValidationLevel ValidationLevel; +#if VK_EXT_debug_report + static VKAPI_ATTR VkBool32 VKAPI_PTR DebugReportFunction(VkDebugReportFlagsEXT msgFlags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, size_t location, int32 msgCode, const char* layerPrefix, const char* msg, void* userData) { const Char* msgPrefix = TEXT("UNKNOWN"); @@ -126,6 +129,8 @@ static VKAPI_ATTR VkBool32 VKAPI_PTR DebugReportFunction(VkDebugReportFlagsEXT m return VK_FALSE; } +#endif + #if VK_EXT_debug_utils static VKAPI_ATTR VkBool32 VKAPI_PTR DebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT msgSeverity, VkDebugUtilsMessageTypeFlagsEXT msgType, const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData) @@ -140,8 +145,8 @@ static VKAPI_ATTR VkBool32 VKAPI_PTR DebugUtilsCallback(VkDebugUtilsMessageSever case 2: // Fragment shader writes to output location 0 with no matching attachment case 3: // Attachment 2 not written by fragment shader case 5: // SPIR-V module not valid: MemoryBarrier: Vulkan specification requires Memory Semantics to have one of the following bits set: Acquire, Release, AcquireRelease or SequentiallyConsistent -#if PLATFORM_ANDROID case -1666394502: // After query pool creation, each query must be reset before it is used. Queries must also be reset between uses. +#if PLATFORM_ANDROID case 602160055: // Attachment 4 not written by fragment shader; undefined values will be written to attachment. TODO: investigate it for PS_GBuffer shader from Deferred material with USE_LIGHTMAP=1 #endif return VK_FALSE; @@ -270,6 +275,7 @@ void SetupDebugLayerCallback() if (SupportsDebugCallbackExt) #endif { +#if VK_EXT_debug_report if (vkCreateDebugReportCallbackEXT) { VkDebugReportCallbackCreateInfoEXT createInfo; @@ -303,6 +309,7 @@ void SetupDebugLayerCallback() { LOG(Warning, "GetProcAddr: Unable to find vkDbgCreateMsgCallback; debug reporting skipped!"); } +#endif } else { @@ -324,8 +331,10 @@ void RemoveDebugLayerCallback() if (MsgCallback != VK_NULL_HANDLE) #endif { +#if VK_EXT_debug_report if (vkDestroyDebugReportCallbackEXT) vkDestroyDebugReportCallbackEXT(GPUDeviceVulkan::Instance, MsgCallback, nullptr); +#endif MsgCallback = VK_NULL_HANDLE; } } @@ -344,7 +353,7 @@ DeferredDeletionQueueVulkan::~DeferredDeletionQueueVulkan() void DeferredDeletionQueueVulkan::ReleaseResources(bool deleteImmediately) { - ScopeLock lock(&_locker); + ScopeLock lock(_locker); const uint64 checkFrame = Engine::FrameCount - VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT; for (int32 i = 0; i < _entries.Count(); i++) { @@ -1032,7 +1041,7 @@ void StagingManagerVulkan::Dispose() ScopeLock lock(_locker); #if !BUILD_RELEASE - LOG(Info, "Vulakn staging buffers peek memory usage: {0}, allocs: {1}, frees: {2}", Utilities::BytesToText(_allBuffersPeekSize), Utilities::BytesToText(_allBuffersAllocSize), Utilities::BytesToText(_allBuffersFreeSize)); + LOG(Info, "Vulkan staging buffers peek memory usage: {0}, allocs: {1}, frees: {2}", Utilities::BytesToText(_allBuffersPeekSize), Utilities::BytesToText(_allBuffersAllocSize), Utilities::BytesToText(_allBuffersFreeSize)); #endif // Release buffers and clear memory @@ -1085,13 +1094,17 @@ GPUDevice* GPUDeviceVulkan::Create() } #endif + VkResult result; + +#if !PLATFORM_SWITCH // Initialize bindings - VkResult result = volkInitialize(); + result = volkInitialize(); if (result != VK_SUCCESS) { LOG(Warning, "Graphics Device init failed with error {0}", RenderToolsVulkan::GetVkErrorString(result)); return nullptr; } +#endif // Engine registration const StringAsANSI<256> appName(*Globals::ProductName); @@ -1170,10 +1183,12 @@ GPUDevice* GPUDeviceVulkan::Create() return nullptr; } +#if !PLATFORM_SWITCH // Setup bindings volkLoadInstance(Instance); +#endif - // Setup debug layer +// Setup debug layer #if VULKAN_USE_DEBUG_LAYER SetupDebugLayerCallback(); #endif @@ -1658,8 +1673,10 @@ bool GPUDeviceVulkan::Init() // Create the device VALIDATE_VULKAN_RESULT(vkCreateDevice(gpu, &deviceInfo, nullptr, &Device)); +#if !PLATFORM_SWITCH // Optimize bindings volkLoadDevice(Device); +#endif // Create queues if (graphicsQueueFamilyIndex == -1) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs index 68eb5ffcc..4779beb4f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs +++ b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs @@ -108,7 +108,15 @@ public class GraphicsDeviceVulkan : GraphicsDeviceBaseModule options.PublicDefinitions.Add("GRAPHICS_API_VULKAN"); - options.PrivateDependencies.Add("volk"); options.PrivateDependencies.Add("VulkanMemoryAllocator"); + + if (options.Platform.Target == TargetPlatform.Switch) + { + options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Switch", "Engine", "GraphicsDevice", "Vulkan")); + } + else + { + options.PrivateDependencies.Add("volk"); + } } } diff --git a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h index 3e00a9e87..fec251eca 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h +++ b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h @@ -8,6 +8,19 @@ #include "Engine/Core/Math/Math.h" +#if PLATFORM_SWITCH + +#define VK_USE_PLATFORM_VI_NN 1 +#include +#undef VK_EXT_debug_utils +#undef VK_EXT_debug_report +#undef VK_EXT_validation_cache +#define VMA_DEDICATED_ALLOCATION 0 +#pragma clang diagnostic ignored "-Wpointer-bool-conversion" +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" + +#else + #if PLATFORM_WIN32 #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" #endif @@ -17,6 +30,8 @@ // License: MIT #include +#endif + // Use Vulkan Memory Allocator for buffer and image memory allocations // Source: https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/ // License: MIT diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp index e974396a4..d24c5742d 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp @@ -241,7 +241,9 @@ String RenderToolsVulkan::GetVkErrorString(VkResult result) VKERR(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR); VKERR(VK_ERROR_VALIDATION_FAILED_EXT); VKERR(VK_ERROR_INVALID_SHADER_NV); +#if VK_HEADER_VERSION >= 89 VKERR(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT); +#endif VKERR(VK_ERROR_FRAGMENTATION_EXT); VKERR(VK_ERROR_NOT_PERMITTED_EXT); #if VK_HEADER_VERSION < 140 diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h index cf9b7e9cf..ec5d882bc 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h @@ -78,6 +78,9 @@ public: case 0: stageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; break; + case VK_ACCESS_INDIRECT_COMMAND_READ_BIT: + stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT; + break; case VK_ACCESS_TRANSFER_WRITE_BIT: stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT; break; diff --git a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h index 8e585c78e..bc216190b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h +++ b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h @@ -8,4 +8,6 @@ #include "Linux/LinuxVulkanPlatform.h" #elif PLATFORM_ANDROID #include "Android/AndroidVulkanPlatform.h" +#elif PLATFORM_SWITCH +#include "Platforms/Switch/Engine/GraphicsDevice/Vulkan/SwitchVulkanPlatform.h" #endif diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 485d5a891..d7796691b 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -339,14 +339,14 @@ bool Input::GetKeyUp(const KeyboardKeys key) Vector2 Input::GetMousePosition() { - return Mouse ? Engine::ScreenToGameViewport(Mouse->GetPosition()) : Vector2::Minimum; + return Mouse ? Screen::ScreenToGameViewport(Mouse->GetPosition()) : Vector2::Minimum; } void Input::SetMousePosition(const Vector2& position) { if (Mouse && Engine::HasGameViewportFocus()) { - const auto pos = Engine::GameViewportToScreen(position); + const auto pos = Screen::GameViewportToScreen(position); if (pos > Vector2::Minimum) Mouse->SetMousePosition(pos); } diff --git a/Source/Engine/Input/InputDevice.h b/Source/Engine/Input/InputDevice.h index 81b4eceb7..27f6125ba 100644 --- a/Source/Engine/Input/InputDevice.h +++ b/Source/Engine/Input/InputDevice.h @@ -113,7 +113,7 @@ public: /// Captures the input since the last call and triggers the input events. /// /// The input events queue. - /// True if device has been disconnected, otherwise false. + /// True if device has been disconnected, otherwise false. virtual bool Update(EventQueue& queue) { if (UpdateState()) @@ -126,7 +126,7 @@ public: /// /// Updates only the current state of the device. /// - /// True if device has been disconnected, otherwise false. + /// True if device has been disconnected, otherwise false. virtual bool UpdateState() { return false; diff --git a/Source/Engine/Input/InputSettings.h b/Source/Engine/Input/InputSettings.h index 7771028e8..fd594f3ee 100644 --- a/Source/Engine/Input/InputSettings.h +++ b/Source/Engine/Input/InputSettings.h @@ -5,6 +5,7 @@ #include "Engine/Core/Config/Settings.h" #include "Engine/Serialization/JsonTools.h" #include "VirtualInput.h" +#include "Engine/Core/Collections/Array.h" /// /// Input settings container. diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 3753f6140..20ec4cb7a 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -15,7 +15,9 @@ #include "Engine/Core/Cache.h" #include "Engine/Core/Collections/CollectionPoolCache.h" #include "Engine/Debug/Exceptions/JsonParseException.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderView.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Serialization/ISerializeModifier.h" #include "Engine/Serialization/Serialization.h" @@ -203,6 +205,13 @@ void Actor::SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefa LOG(Error, "Editing scene hierarchy is only allowed on a main thread."); return; } +#if USE_EDITOR || !BUILD_RELEASE + if (Is()) + { + LOG(Error, "Cannot change parent of the Scene. Use Level to manage scenes."); + return; + } +#endif // Peek the previous state const Transform prevTransform = _transform; @@ -340,6 +349,12 @@ void Actor::SetOrderInParent(int32 index) } } +Actor* Actor::GetChild(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < Children.Count(), nullptr); + return Children[index]; +} + Actor* Actor::GetChild(const StringView& name) const { for (int32 i = 0; i < Children.Count(); i++) @@ -347,7 +362,6 @@ Actor* Actor::GetChild(const StringView& name) const if (Children[i]->GetName() == name) return Children[i]; } - return nullptr; } @@ -475,6 +489,12 @@ void Actor::SetName(const StringView& value) Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, this, nullptr); } +Script* Actor::GetScript(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < Scripts.Count(), nullptr); + return Scripts[index]; +} + Script* Actor::GetScript(const MClass* type) const { CHECK_RETURN(type, nullptr); @@ -945,28 +965,34 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) DESERIALIZE_MEMBER(Name, _name); DESERIALIZE_MEMBER(Transform, _localTransform); - Guid parentId = Guid::Empty; - DESERIALIZE_MEMBER(ParentID, parentId); - const auto parent = Scripting::FindObject(parentId); - if (_parent != parent) { - if (IsDuringPlay()) + const auto member = SERIALIZE_FIND_MEMBER(stream, "ParentID"); + if (member != stream.MemberEnd()) { - SetParent(parent, false, false); + Guid parentId; + Serialization::Deserialize(member->value, parentId, modifier); + const auto parent = Scripting::FindObject(parentId); + if (_parent != parent) + { + if (IsDuringPlay()) + { + SetParent(parent, false, false); + } + else + { + if (_parent) + _parent->Children.RemoveKeepOrder(this); + _parent = parent; + if (_parent) + _parent->Children.Add(this); + OnParentChanged(); + } + } + else if (!parent && parentId.IsValid()) + { + LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); + } } - else - { - if (_parent) - _parent->Children.RemoveKeepOrder(this); - _parent = parent; - if (_parent) - _parent->Children.Add(this); - OnParentChanged(); - } - } - else if (!parent && parentId.IsValid()) - { - LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); } // StaticFlags update - added StaticFlags::Navigation @@ -1133,20 +1159,28 @@ BoundingBox Actor::GetBoxWithChildren() const #if USE_EDITOR +BoundingBox Actor::GetEditorBox() const +{ + return GetBox(); +} + BoundingBox Actor::GetEditorBoxChildren() const { BoundingBox result = GetEditorBox(); - for (int32 i = 0; i < Children.Count(); i++) { BoundingBox::Merge(result, Children[i]->GetEditorBoxChildren(), result); } - return result; } #endif +bool Actor::HasContentLoaded() const +{ + return true; +} + void Actor::UnregisterObjectHierarchy() { if (IsRegistered()) @@ -1164,6 +1198,10 @@ void Actor::UnregisterObjectHierarchy() } } +void Actor::Draw(RenderContext& renderContext) +{ +} + void Actor::DrawGeneric(RenderContext& renderContext) { // Generic drawing uses only GBuffer Fill Pass and simple frustum culling (see SceneRendering for more optimized drawing) @@ -1210,12 +1248,6 @@ void Actor::OnDebugDraw() for (auto* script : Scripts) if (script->GetEnabled()) script->OnDebugDraw(); - - for (auto& child : Children) - { - if (child->GetIsActive()) - child->OnDebugDraw(); - } } void Actor::OnDebugDrawSelected() @@ -1458,6 +1490,7 @@ void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer, bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output) { + PROFILE_CPU(); if (actors.IsEmpty()) { // Cannot serialize empty list @@ -1517,6 +1550,7 @@ Array Actor::ToBytes(const Array& actors) bool Actor::FromBytes(const Span& data, Array& output, ISerializeModifier* modifier) { + PROFILE_CPU(); output.Clear(); ASSERT(modifier); @@ -1561,7 +1595,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Order in parent int32 orderInParent; stream.ReadInt32(&orderInParent); - order.At(i) = orderInParent; + order[i] = orderInParent; // Load JSON rapidjson_flax::Document document; @@ -1601,7 +1635,6 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Order in parent int32 orderInParent; stream.ReadInt32(&orderInParent); - order.Add(orderInParent); // Load JSON rapidjson_flax::Document document; @@ -1619,17 +1652,19 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM Scripting::ObjectsLookupIdMapping.Set(nullptr); // Link objects - for (int32 i = 0; i < objectsCount; i++) + //for (int32 i = 0; i < objectsCount; i++) { - SceneObject* obj = sceneObjects->At(i); - obj->PostLoad(); + //SceneObject* obj = sceneObjects->At(i); + // TODO: post load or post spawn? + //obj->PostLoad(); } // Update objects order - for (int32 i = 0; i < objectsCount; i++) + //for (int32 i = 0; i < objectsCount; i++) { - SceneObject* obj = sceneObjects->At(i); - obj->SetOrderInParent(order[i]); + //SceneObject* obj = sceneObjects->At(i); + // TODO: remove order from saved data? + //obj->SetOrderInParent(order[i]); } // Call events (only for parents because they will propagate events down the tree) @@ -1646,6 +1681,10 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM } } for (int32 i = 0; i < parents->Count(); i++) + { + parents->At(i)->PostSpawn(); + } + for (int32 i = 0; i < parents->Count(); i++) { Actor* actor = parents->At(i); actor->OnTransformChanged(); @@ -1687,6 +1726,7 @@ Array Actor::FromBytes(const Span& data, const Dictionary Actor::TryGetSerializedObjectsIds(const Span& data) { + PROFILE_CPU(); Array result; if (data.Length() > 0) { @@ -1707,6 +1747,7 @@ Array Actor::TryGetSerializedObjectsIds(const Span& data) String Actor::ToJson() { + PROFILE_CPU(); rapidjson_flax::StringBuffer buffer; CompactJsonWriter writer(buffer); writer.SceneObject(this); @@ -1718,6 +1759,8 @@ String Actor::ToJson() void Actor::FromJson(const StringAnsiView& json) { + PROFILE_CPU(); + // Load JSON rapidjson_flax::Document document; document.Parse(json.Get(), json.Length()); diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 2d9c27b9a..fce009549 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -124,7 +124,7 @@ namespace FlaxEngine public Actor AddChild(Type type) { var result = (Actor)New(type); - result.SetParent(this, false); + result.SetParent(this, false, false); return result; } @@ -136,7 +136,7 @@ namespace FlaxEngine public T AddChild() where T : Actor { var result = New(); - result.SetParent(this, false); + result.SetParent(this, false, false); return result; } @@ -173,7 +173,7 @@ namespace FlaxEngine if (result == null) { result = New(); - result.SetParent(this, false); + result.SetParent(this, false, false); } return result; } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 69d4c8681..00aeb17ab 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -176,7 +176,7 @@ public: /// New parent /// Should actor world positions remain the same after parent change? /// True if can break prefab link on changing the parent. - void SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefabLink); + API_FUNCTION() void SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefabLink); /// /// Gets amount of child actors. @@ -192,10 +192,7 @@ public: /// /// The child actor index. /// The child actor (always valid). - API_FUNCTION() FORCE_INLINE Actor* GetChild(int32 index) const - { - return Children[index]; - } + API_FUNCTION() Actor* GetChild(int32 index) const; /// /// Gets the child actor with the given name. @@ -266,10 +263,7 @@ public: /// /// The script index. /// The script (always valid). - API_FUNCTION() FORCE_INLINE Script* GetScript(int32 index) const - { - return Scripts[index]; - } + API_FUNCTION() Script* GetScript(int32 index) const; /// /// Gets the script of the given type from this actor. @@ -629,10 +623,7 @@ public: /// /// Gets actor bounding box (single actor, no children included) for editor tools. /// - API_PROPERTY() virtual BoundingBox GetEditorBox() const - { - return GetBox(); - } + API_PROPERTY() virtual BoundingBox GetEditorBox() const; /// /// Gets actor bounding box of the actor including all child actors for editor tools. @@ -644,10 +635,7 @@ public: /// /// Returns true if actor has loaded content. /// - API_PROPERTY() virtual bool HasContentLoaded() const - { - return true; - } + API_PROPERTY() virtual bool HasContentLoaded() const; /// /// Calls UnregisterObject for all objects in the actor hierarchy. @@ -660,9 +648,7 @@ public: /// Draws this actor. Called by Scene Rendering service. This call is more optimized than generic Draw (eg. models are rendered during all passed but other actors are invoked only during GBufferFill pass). /// /// The rendering context. - virtual void Draw(RenderContext& renderContext) - { - } + virtual void Draw(RenderContext& renderContext); /// /// Draws this actor. Called during custom actor rendering or any other generic rendering from code. @@ -679,12 +665,12 @@ public: #if USE_EDITOR /// - /// Draws debug shapes for the actor and all child actors. + /// Draws debug shapes for the actor and all child scripts. /// virtual void OnDebugDraw(); /// - /// Draws debug shapes for the selected actor and all child actors. + /// Draws debug shapes for the selected actor and all child scripts. /// virtual void OnDebugDrawSelected(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 85ee6d333..a62aad592 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -8,10 +8,13 @@ #include "Editor/Editor.h" #endif #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Serialization/Serialization.h" +extern Array UpdateBones; + AnimatedModel::AnimatedModel(const SpawnParams& params) : ModelInstanceActor(params) , _actualMode(AnimationUpdateMode::Never) @@ -41,7 +44,8 @@ void AnimatedModel::UpdateAnimation() || !IsActiveInHierarchy() || SkinnedModel == nullptr || !SkinnedModel->IsLoaded() - || _lastUpdateFrame == Engine::FrameCount) + || _lastUpdateFrame == Engine::FrameCount + || _masterPose) return; _lastUpdateFrame = Engine::FrameCount; @@ -131,6 +135,22 @@ void AnimatedModel::GetCurrentPose(Array& nodesTransformation, bool worl } } +void AnimatedModel::SetCurrentPose(const Array& nodesTransformation, bool worldSpace) +{ + if (GraphInstance.NodesPose.Count() == 0) + return; + CHECK(nodesTransformation.Count() == GraphInstance.NodesPose.Count()); + GraphInstance.NodesPose = nodesTransformation; + if (worldSpace) + { + Matrix invWorld; + Matrix::Invert(_world, invWorld); + for (auto& m : GraphInstance.NodesPose) + m = invWorld * m; + } + OnAnimationUpdated(); +} + void AnimatedModel::GetNodeTransformation(int32 nodeIndex, Matrix& nodeTransformation, bool worldSpace) const { if (nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count()) @@ -146,6 +166,35 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace); } +int32 AnimatedModel::FindClosestNode(const Vector3& location, bool worldSpace) const +{ + const Vector3 pos = worldSpace ? _transform.WorldToLocal(location) : location; + int32 result = -1; + float closest = MAX_float; + for (int32 nodeIndex = 0; nodeIndex < GraphInstance.NodesPose.Count(); nodeIndex++) + { + const Vector3 node = GraphInstance.NodesPose[nodeIndex].GetTranslation(); + const float dst = Vector3::DistanceSquared(node, pos); + if (dst < closest) + { + closest = dst; + result = nodeIndex; + } + } + return result; +} + +void AnimatedModel::SetMasterPoseModel(AnimatedModel* masterPose) +{ + if (masterPose == _masterPose) + return; + if (_masterPose) + _masterPose->AnimationUpdated.Unbind(this); + _masterPose = masterPose; + if (_masterPose) + _masterPose->AnimationUpdated.Bind(this); +} + #define CHECK_ANIM_GRAPH_PARAM_ACCESS() \ if (!AnimationGraph) \ { \ @@ -233,6 +282,8 @@ void AnimatedModel::SetParameterValue(const Guid& id, const Variant& value) LOG(Warning, "Failed to set animated model '{0}' missing parameter '{1}'", ToString(), id.ToString()); } +#undef CHECK_ANIM_GRAPH_PARAM_ACCESS + float AnimatedModel::GetBlendShapeWeight(const StringView& name) { for (auto& e : _blendShapes.Weights) @@ -339,6 +390,7 @@ void AnimatedModel::BeginPlay(SceneBeginData* data) void AnimatedModel::EndPlay() { AnimationManager::RemoveFromUpdate(this); + SetMasterPoseModel(nullptr); // Base ModelInstanceActor::EndPlay(); @@ -383,7 +435,7 @@ void AnimatedModel::UpdateLocalBounds() } else { - box = BoundingBox(Vector3::Zero, Vector3::Zero); + box = BoundingBox(Vector3::Zero); } // Scale bounds @@ -401,12 +453,38 @@ void AnimatedModel::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void AnimatedModel::OnAnimUpdate() +void AnimatedModel::OnAnimationUpdated() { + ANIM_GRAPH_PROFILE_EVENT("OnAnimationUpdated"); + auto& skeleton = SkinnedModel->Skeleton; + + // Copy pose from the master + if (_masterPose && _masterPose->SkinnedModel->Skeleton.Nodes.Count() == skeleton.Nodes.Count()) + { + ANIM_GRAPH_PROFILE_EVENT("Copy Master Pose"); + const auto& masterInstance = _masterPose->GraphInstance; + GraphInstance.NodesPose = masterInstance.NodesPose; + GraphInstance.RootTransform = masterInstance.RootTransform; + GraphInstance.RootMotion = masterInstance.RootMotion; + } + + // Calculate the final bones transformations and update skinning + { + ANIM_GRAPH_PROFILE_EVENT("Final Pose"); + UpdateBones.Resize(skeleton.Bones.Count(), false); + for (int32 boneIndex = 0; boneIndex < skeleton.Bones.Count(); boneIndex++) + { + auto& bone = skeleton.Bones[boneIndex]; + UpdateBones[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex]; + } + } + _skinningData.SetData(UpdateBones.Get(), !PerBoneMotionBlur); + UpdateBounds(); UpdateSockets(); ApplyRootMotion(GraphInstance.RootMotion); _blendShapes.Update(SkinnedModel.Get()); + AnimationUpdated(); } void AnimatedModel::OnSkinnedModelChanged() diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index d5d9b4424..df218b897 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -6,6 +6,8 @@ #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Content/Assets/AnimationGraph.h" #include "Engine/Graphics/Models/SkinnedMeshDrawData.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Core/Delegate.h" /// /// Performs an animation and renders a skinned model. @@ -63,6 +65,7 @@ private: float _lastMinDstSqr; uint64 _lastUpdateFrame; BlendShapesInstance _blendShapes; + ScriptingObjectReference _masterPose; public: @@ -165,9 +168,14 @@ public: API_FUNCTION() void ResetAnimation(); /// - /// Performs the full animation update. + /// Performs the full animation update. The actual update will be performed during gameplay tick. /// API_FUNCTION() void UpdateAnimation(); + + /// + /// Called after animation gets updated (new skeleton pose). + /// + API_EVENT() Action AnimationUpdated; /// /// Validates and creates a proper skinning data. @@ -180,17 +188,19 @@ public: API_FUNCTION() void PreInitSkinningData(); /// - /// Updates the child bone socket actors. - /// - API_FUNCTION() void UpdateSockets(); - - /// - /// Gets the per-node final transformations. + /// Gets the per-node final transformations (skeleton pose). /// /// The output per-node final transformation matrices. /// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor. API_FUNCTION() void GetCurrentPose(API_PARAM(Out) Array& nodesTransformation, bool worldSpace = false) const; + /// + /// Sets the per-node final transformations (skeleton pose). + /// + /// The per-node final transformation matrices. + /// True if convert matrices from world-space, otherwise values are in local-space of the actor. + API_FUNCTION() void SetCurrentPose(const Array& nodesTransformation, bool worldSpace = false); + /// /// Gets the node final transformation. /// @@ -207,6 +217,20 @@ public: /// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor. API_FUNCTION() void GetNodeTransformation(const StringView& nodeName, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const; + /// + /// Finds the closest node to a given location. + /// + /// The text location (in local-space of the actor or world-space depending on ). + /// True if convert input location is in world-space, otherwise it's in local-space of the actor. + /// The zero-based index of the found node. Returns -1 if skeleton is missing. + API_FUNCTION() int32 FindClosestNode(const Vector3& location, bool worldSpace = false) const; + + /// + /// Sets the master pose model that will be used to copy the skeleton nodes animation. Useful for modular characters. + /// + /// The master pose actor to use. + API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose); + public: /// @@ -275,27 +299,14 @@ public: private: - /// - /// Applies the root motion delta to the target actor. - /// void ApplyRootMotion(const RootMotionData& rootMotionDelta); - - /// - /// Synchronizes the parameters collection (may lost existing params data on collection change detected). - /// void SyncParameters(); - /// - /// Updates the local bounds of the actor. - /// + void Update(); void UpdateLocalBounds(); - void UpdateBounds(); - - /// - /// Called after animation graph update. - /// - void OnAnimUpdate(); + void UpdateSockets(); + void OnAnimationUpdated(); void OnSkinnedModelChanged(); void OnSkinnedModelLoaded(); @@ -303,8 +314,6 @@ private: void OnGraphChanged(); void OnGraphLoaded(); - void Update(); - public: // [ModelInstanceActor] diff --git a/Source/Engine/Level/Actors/BoneSocket.cpp b/Source/Engine/Level/Actors/BoneSocket.cpp index 028b1c507..5efe8deed 100644 --- a/Source/Engine/Level/Actors/BoneSocket.cpp +++ b/Source/Engine/Level/Actors/BoneSocket.cpp @@ -96,7 +96,7 @@ void BoneSocket::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/BoxBrush.cpp b/Source/Engine/Level/Actors/BoxBrush.cpp index f02dd5bd3..4330424c7 100644 --- a/Source/Engine/Level/Actors/BoxBrush.cpp +++ b/Source/Engine/Level/Actors/BoxBrush.cpp @@ -128,6 +128,13 @@ void BoxBrush::GetSurfaces(CSG::Surface surfaces[6]) } } +void BoxBrush::SetMaterial(int32 surfaceIndex, MaterialBase* material) +{ + CHECK(Math::IsInRange(surfaceIndex, 0, 5)); + Surfaces[surfaceIndex].Material = material; + OnBrushModified(); +} + bool BoxBrush::Intersects(int32 surfaceIndex, const Ray& ray, float& distance, Vector3& normal) const { distance = MAX_float; @@ -232,6 +239,26 @@ void BoxBrush::OnDebugDrawSelected() #endif +Scene* BoxBrush::GetBrushScene() const +{ + return GetScene(); +} + +Guid BoxBrush::GetBrushID() const +{ + return GetID(); +} + +bool BoxBrush::CanUseCSG() const +{ + return IsActiveInHierarchy(); +} + +CSG::Mode BoxBrush::GetBrushMode() const +{ + return _mode; +} + void BoxBrush::GetSurfaces(Array& surfaces) { surfaces.Clear(); @@ -240,6 +267,11 @@ void BoxBrush::GetSurfaces(Array& surfaces) GetSurfaces(surfaces.Get()); } +int32 BoxBrush::GetSurfacesCount() +{ + return 6; +} + void BoxBrush::OnTransformChanged() { // Base @@ -272,7 +304,7 @@ void BoxBrush::OnParentChanged() { // Base Actor::OnParentChanged(); - + if (!IsDuringPlay()) return; diff --git a/Source/Engine/Level/Actors/BoxBrush.h b/Source/Engine/Level/Actors/BoxBrush.h index e8fc63e3f..ae9be5b83 100644 --- a/Source/Engine/Level/Actors/BoxBrush.h +++ b/Source/Engine/Level/Actors/BoxBrush.h @@ -153,6 +153,13 @@ public: /// Surfaces void GetSurfaces(CSG::Surface surfaces[6]); + /// + /// Sets the brush surface material. + /// + /// The brush surface index. + /// The material. + API_FUNCTION() void SetMaterial(int32 surfaceIndex, MaterialBase* material); + public: /// @@ -169,7 +176,7 @@ public: /// Otherwise performs simple vs test. /// For more efficient collisions detection and ray casting use physics. /// - /// The brush surface index.. + /// The brush surface index. /// The ray to test. /// When the method completes and returns true, contains the distance of the intersection (if any valid). /// When the method completes, contains the intersection surface normal vector (if any valid). @@ -179,7 +186,7 @@ public: /// /// Gets the brush surface triangles array (group by 3 vertices). /// - /// The brush surface index.. + /// The brush surface index. /// The output vertices buffer with triangles or empty if no data loaded. API_FUNCTION() void GetVertices(int32 surfaceIndex, API_PARAM(Out) Array& outputData) const; @@ -204,32 +211,12 @@ public: #endif // [CSG::Brush] - Scene* GetBrushScene() const override - { - return GetScene(); - } - - Guid GetBrushID() const override - { - return GetID(); - } - - bool CanUseCSG() const override - { - return IsActiveInHierarchy(); - } - - CSG::Mode GetBrushMode() const override - { - return _mode; - } - + Scene* GetBrushScene() const override; + Guid GetBrushID() const override; + bool CanUseCSG() const override; + CSG::Mode GetBrushMode() const override; void GetSurfaces(Array& surfaces) override; - - int32 GetSurfacesCount() override - { - return 6; - } + int32 GetSurfacesCount() override; protected: diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index 95eebefc1..1cc7ba53b 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -5,24 +5,27 @@ #include "Engine/Core/Math/Viewport.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Content.h" -#include "Engine/Platform/Window.h" #include "Engine/Serialization/Serialization.h" -#include "Engine/Level/Scene/SceneRendering.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/Managed/ManagedEditor.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Level/Scene/SceneRendering.h" #else #include "Engine/Engine/Engine.h" +#include "Engine/Platform/Window.h" #endif Array Camera::Cameras; Camera* Camera::CutSceneCamera = nullptr; -Camera* Camera::OverrideMainCamera = nullptr; +ScriptingObjectReference Camera::OverrideMainCamera; Camera* Camera::GetMainCamera() { - if (OverrideMainCamera) - return OverrideMainCamera; + Camera* overrideMainCamera = OverrideMainCamera.Get(); + if (overrideMainCamera) + return overrideMainCamera; if (CutSceneCamera) return CutSceneCamera; return Cameras.HasItems() ? Cameras.First() : nullptr; @@ -101,12 +104,12 @@ void Camera::SetOrthographicScale(float value) } } -void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpaceLocation) const +void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& gameWindowSpaceLocation) const { - ProjectPoint(worldSpaceLocation, screenSpaceLocation, GetViewport()); + ProjectPoint(worldSpaceLocation, gameWindowSpaceLocation, GetViewport()); } -void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpaceLocation, const Viewport& viewport) const +void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& cameraViewportSpaceLocation, const Viewport& viewport) const { Matrix v, p, vp; GetMatrices(v, p, viewport); @@ -114,7 +117,7 @@ void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpac Vector3 clipSpaceLocation; Vector3::Transform(worldSpaceLocation, vp, clipSpaceLocation); viewport.Project(worldSpaceLocation, vp, clipSpaceLocation); - screenSpaceLocation = Vector2(clipSpaceLocation); + cameraViewportSpaceLocation = Vector2(clipSpaceLocation); } Ray Camera::ConvertMouseToRay(const Vector2& mousePosition) const diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index 7f40617dd..505479e98 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -8,6 +8,7 @@ #include "Engine/Core/Math/Viewport.h" #include "Engine/Core/Math/Ray.h" #include "Engine/Core/Types/LayersMask.h" +#include "Engine/Scripting/ScriptingObjectReference.h" #if USE_EDITOR #include "Engine/Content/AssetReference.h" #include "Engine/Graphics/Models/ModelInstanceEntry.h" @@ -28,7 +29,7 @@ DECLARE_SCENE_OBJECT(Camera); static Camera* CutSceneCamera; // The overriden main camera. - API_FIELD() static Camera* OverrideMainCamera; + API_FIELD() static ScriptingObjectReference OverrideMainCamera; // Gets the main camera. API_PROPERTY() static Camera* GetMainCamera(); @@ -174,19 +175,19 @@ public: public: /// - /// Projects the point from 3D world-space to the camera screen-space (in screen pixels for default viewport calculated from ). + /// Projects the point from 3D world-space to game window coordinates (in screen pixels for default viewport calculated from ). /// /// The input world-space location (XYZ in world). - /// The output screen-space location (XY in screen pixels). - API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& screenSpaceLocation) const; + /// The output game window coordinates (XY in screen pixels). + API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& gameWindowSpaceLocation) const; /// - /// Projects the point from 3D world-space to the camera screen-space (in screen pixels for given viewport). + /// Projects the point from 3D world-space to the camera viewport-space (in screen pixels for given viewport). /// /// The input world-space location (XYZ in world). - /// The output screen-space location (XY in screen pixels). + /// The output camera viewport-space location (XY in screen pixels). /// The viewport. - API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& screenSpaceLocation, API_PARAM(Ref) const Viewport& viewport) const; + API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& cameraViewportSpaceLocation, API_PARAM(Ref) const Viewport& viewport) const; /// /// Converts the mouse position to 3D ray. diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index 240344fc6..8810b0de6 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -92,6 +92,6 @@ void DirectionalLight::OnTransformChanged() // Base LightWithShadow::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/EmptyActor.cpp b/Source/Engine/Level/Actors/EmptyActor.cpp index 309f63652..dfb64824d 100644 --- a/Source/Engine/Level/Actors/EmptyActor.cpp +++ b/Source/Engine/Level/Actors/EmptyActor.cpp @@ -22,6 +22,6 @@ void EmptyActor::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp index 9a2bb08a8..d5939d722 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp +++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp @@ -3,6 +3,7 @@ #include "EnvironmentProbe.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/ProbesRenderer.h" diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 77f8e16e7..366ab7de4 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -228,6 +228,6 @@ void ExponentialHeightFog::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/Light.cpp b/Source/Engine/Level/Actors/Light.cpp index 56e3368b0..9f80216c5 100644 --- a/Source/Engine/Level/Actors/Light.cpp +++ b/Source/Engine/Level/Actors/Light.cpp @@ -2,6 +2,9 @@ #include "Light.h" #include "../Scene/Scene.h" +#if USE_EDITOR +#include "Engine/Graphics/RenderView.h" +#endif #include "Engine/Serialization/Serialization.h" Light::Light(const SpawnParams& params) diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 8aaf5fdd3..1e35c27f0 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -4,14 +4,17 @@ #include "DirectionalLight.h" #include "Engine/Core/Math/Color.h" #include "Engine/Content/Content.h" -#include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/AtmospherePreCompute.h" #include "Engine/Renderer/GBufferPass.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/GPUContext.h" -#include "Engine/Serialization/Serialization.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Serialization/Serialization.h" #include "Engine/Level/Scene/SceneRendering.h" PACK_STRUCT(struct Data { @@ -259,6 +262,6 @@ void Sky::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index c5c81edc0..eafd8d9e6 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -9,6 +9,8 @@ #include "Engine/Renderer/Config.h" #include "Engine/Renderer/DrawCall.h" +class GPUPipelineState; + /// /// Sky actor renders atmosphere around the scene with fog and sky. /// diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index 03e1d3ca5..da7efbf31 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -3,6 +3,7 @@ #include "SkyLight.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/ProbesRenderer.h" diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index e8ea08c89..4edd596b7 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -146,6 +146,6 @@ void Skybox::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 76ecb9653..4c0d21cd7 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -3,10 +3,12 @@ #include "Spline.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Animations/CurveSerialization.h" +#include "Engine/Core/Math/Matrix.h" #include Spline::Spline(const SpawnParams& params) : Actor(params) + , _localBounds(Vector3::Zero, Vector3::Zero) { } @@ -411,17 +413,27 @@ void Spline::SetTangentsSmooth() void Spline::UpdateSpline() { + auto& keyframes = Curve.GetKeyframes(); + const int32 count = keyframes.Count(); + // Always keep last point in the loop - const int32 count = Curve.GetKeyframes().Count(); if (_loop && count > 1) { - auto& first = Curve[0]; - auto& last = Curve[count - 1]; + auto& first = keyframes[0]; + auto& last = keyframes[count - 1]; last.Value = first.Value; last.TangentIn = first.TangentIn; last.TangentOut = first.TangentOut; } + // Update bounds + _localBounds = BoundingBox(count != 0 ? keyframes[0].Value.Translation : Vector3::Zero); + for (int32 i = 1; i < count; i++) + _localBounds.Merge(keyframes[i].Value.Translation); + Matrix world; + _transform.GetWorld(world); + BoundingBox::Transform(_localBounds, world, _box); + SplineUpdated(); } @@ -485,6 +497,34 @@ void Spline::OnDebugDrawSelected() #endif +void Spline::OnTransformChanged() +{ + // Base + Actor::OnTransformChanged(); + + Matrix world; + _transform.GetWorld(world); + BoundingBox::Transform(_localBounds, world, _box); + BoundingSphere::FromBox(_box, _sphere); +} + +void Spline::PostLoad() +{ + // Base + Actor::PostLoad(); + + auto& keyframes = Curve.GetKeyframes(); + const int32 count = keyframes.Count(); + + // Update bounds + _localBounds = BoundingBox(count != 0 ? keyframes[0].Value.Translation : Vector3::Zero); + for (int32 i = 1; i < count; i++) + _localBounds.Merge(keyframes[i].Value.Translation); + Matrix world; + _transform.GetWorld(world); + BoundingBox::Transform(_localBounds, world, _box); +} + void Spline::Serialize(SerializeStream& stream, const void* otherObj) { // Base diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index f19f9a443..79fce685d 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -15,6 +15,7 @@ DECLARE_SCENE_OBJECT(Spline); private: bool _loop = false; + BoundingBox _localBounds; public: @@ -377,6 +378,8 @@ public: void OnDebugDraw() override; void OnDebugDrawSelected() override; #endif + void OnTransformChanged() override; + void PostLoad() override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 84306716b..3e874f07f 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -5,9 +5,14 @@ #include "Engine/Engine/Engine.h" #include "Engine/Core/Math/Matrix3x4.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Graphics/GPUBufferDescription.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" #if USE_EDITOR #include "Editor/Editor.h" #endif @@ -110,7 +115,7 @@ void SplineModel::OnSplineUpdated() // Skip updates when actor is disabled or something is missing if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2) { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); return; } @@ -138,11 +143,10 @@ void SplineModel::OnSplineUpdated() Vector3 tmp = corners[i] * _preTransform.Scale; double rotation[4] = { (double)_preTransform.Orientation.X, (double)_preTransform.Orientation.Y, (double)_preTransform.Orientation.Z, (double)_preTransform.Orientation.W }; const double length = sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]); - const double inv = 1.0 / length; - rotation[0] *= inv; - rotation[1] *= inv; - rotation[2] *= inv; - rotation[3] *= inv; + rotation[0] /= length; + rotation[1] /= length; + rotation[2] /= length; + rotation[3] /= length; double pos[3] = { (double)tmp.X, (double)tmp.Y, (double)tmp.Z }; const double x = rotation[0] + rotation[0]; const double y = rotation[1] + rotation[1]; @@ -251,7 +255,7 @@ void SplineModel::UpdateDeformationBuffer() AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) { - const float alpha = (float)chunk * chunksPerSegmentInv; + const float alpha = (chunk == chunksPerSegment - 1)? 1.0f : ((float)chunk * chunksPerSegmentInv); // Evaluate transformation at the curve AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index a884c9707..0005e74f9 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -2,7 +2,10 @@ #include "StaticModel.h" #include "Engine/Engine/Engine.h" +#include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/GPUBufferDescription.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scene/Scene.h" @@ -187,7 +190,7 @@ void StaticModel::UpdateBounds() } else { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); } BoundingSphere::FromBox(_box, _sphere); } diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index a255ba4ab..b3276cc83 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -4,6 +4,7 @@ #include "ModelInstanceActor.h" #include "Engine/Content/Assets/Model.h" +#include "Engine/Renderer/DrawCall.h" #include "Engine/Renderer/Lightmaps.h" /// diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 049e9ebf3..efc73dc63 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -10,6 +10,7 @@ #include "Engine/Core/Collections/CollectionPoolCache.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Core/Config/LayersTagsSettings.h" +#include "Engine/Core/Types/LayersMask.h" #include "Engine/Debug/Exceptions/ArgumentException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" @@ -202,7 +203,7 @@ void LayersAndTagsSettings::Apply() void LevelService::Update() { - PROFILE_CPU(); + PROFILE_CPU_NAMED("Level::Update"); ScopeLock lock(Level::ScenesLock); auto& scenes = Level::Scenes; @@ -231,7 +232,7 @@ void LevelService::Update() void LevelService::LateUpdate() { - PROFILE_CPU(); + PROFILE_CPU_NAMED("Level::LateUpdate"); ScopeLock lock(Level::ScenesLock); auto& scenes = Level::Scenes; @@ -263,7 +264,7 @@ void LevelService::LateUpdate() void LevelService::FixedUpdate() { - PROFILE_CPU(); + PROFILE_CPU_NAMED("Level::FixedUpdate"); ScopeLock lock(Level::ScenesLock); auto& scenes = Level::Scenes; @@ -496,14 +497,8 @@ public: // - load scenes (from temporary files) // Note: we don't want to override original scene files - // If no scene loaded just reload scripting - if (!Level::IsAnySceneLoaded()) - { - // Reload scripting - LOG(Info, "No scenes loaded, performing fast scripts reload"); - Scripting::Reload(false); - return false; - } + LOG(Info, "Scripts reloading start"); + const auto startTime = DateTime::NowUTC(); // Cache data struct SceneData @@ -538,8 +533,6 @@ public: scenes[i].Init(Level::Scenes[i]); // Fire event - LOG(Info, "Scripts reloading start"); - const auto startTime = DateTime::NowUTC(); Level::ScriptsReloadStart(); // Save scenes (to memory) @@ -566,9 +559,10 @@ public: Scripting::Reload(); // Restore scenes (from memory) - LOG(Info, "Loading temporary scenes"); for (int32 i = 0; i < scenesCount; i++) { + LOG(Info, "Restoring scene {0}", scenes[i].Name); + // Parse json const auto& sceneData = scenes[i].Data; ISerializable::SerializeDocument document; @@ -590,8 +584,9 @@ public: scenes.Resize(0); // Initialize scenes (will link references and create managed objects using new assembly) - LOG(Info, "Prepare scene objects"); + if (Level::Scenes.HasItems()) { + LOG(Info, "Prepare scene objects"); SceneBeginData beginData; for (int32 i = 0; i < Level::Scenes.Count(); i++) { @@ -601,8 +596,7 @@ public: } // Fire event - const auto endTime = DateTime::NowUTC(); - LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast((endTime - startTime).GetTotalMilliseconds())); + LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast((DateTime::NowUTC() - startTime).GetTotalMilliseconds())); Level::ScriptsReloadEnd(); return false; @@ -649,7 +643,7 @@ public: void LevelImpl::CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId) { - PROFILE_CPU(); + PROFILE_CPU_NAMED("Level::CallSceneEvent"); // Call event const auto scriptsDomain = Scripting::GetScriptsDomain(); @@ -1037,9 +1031,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoI // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) // TODO: resave and force sync scenes during game cooking so this step could be skipped in game - Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); - Scripting::ObjectsLookupIdMapping.Set(nullptr); // Delete objects without parent for (int32 i = 1; i < objectsCount; i++) diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 9fe9cb39b..1f7f0bb98 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -5,6 +5,7 @@ #include "Engine/Core/Delegate.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/DataContainer.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Scripting/ScriptingType.h" #include "Engine/Serialization/JsonFwd.h" diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index c0029aa93..abad0c275 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Core/Cache.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Script.h" #include "Engine/Serialization/Json.h" @@ -358,27 +359,47 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array& p { obj->Deserialize(instance.Data[dataIndex], modifier.Value); - // Send events because some properties may be modified during prefab changes apply - // TODO: maybe send only valid events (need to track changes for before-after state) - Actor* actor = dynamic_cast(obj); - if (actor && actor->IsDuringPlay()) + // Preserve order in parent (values from prefab are used) + if (i != 0) { - Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, actor, nullptr); - Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, actor, nullptr); - Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, actor, nullptr); + auto prefab = Content::Load(prefabId); + const auto defaultInstance = prefab ? prefab->GetDefaultInstance(obj->GetPrefabObjectID()) : nullptr; + if (defaultInstance) + { + obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + } } } } Scripting::ObjectsLookupIdMapping.Set(nullptr); - // Setup objects after deserialization + // Setup new objects after deserialization for (int32 i = existingObjectsCount; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects.Value->At(i); obj->PostLoad(); } + // Synchronize existing objects logic with deserialized state (fire events) + for (int32 i = 0; i < existingObjectsCount; i++) + { + SceneObject* obj = sceneObjects->At(i); + Actor* actor = dynamic_cast(obj); + if (actor) + { + const bool shouldBeActiveInHierarchy = actor->GetIsActive() && (!actor->GetParent() || actor->GetParent()->IsActiveInHierarchy()); + if (shouldBeActiveInHierarchy != actor->IsActiveInHierarchy()) + { + actor->_isActiveInHierarchy = shouldBeActiveInHierarchy; + actor->OnActiveInTreeChanged(); + Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, actor, nullptr); + } + Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, actor, nullptr); + Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, actor, nullptr); + } + } + // Restore order in parent instance.TargetActor->SetOrderInParent(instance.OrderInParent); @@ -784,6 +805,20 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr obj->Deserialize(diffDataDocument[dataIndex], modifier.Value); sceneObjects->Add(obj); + + // Synchronize order of the scene objects with the serialized data (eg. user reordered actors in prefab editor and applied changes) + if (i != 0) + { + for (int32 j = 0; j < targetObjects->Count(); j++) + { + SceneObject* targetObject = targetObjects->At(j); + if (targetObject->GetPrefabObjectID() == obj->GetID()) + { + obj->SetOrderInParent(targetObject->GetOrderInParent()); + break; + } + } + } } else { @@ -800,7 +835,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr sceneObjects->RemoveAtKeepOrder(i); } - // Deserialize new prefab objects (add new objects) + // Deserialize new prefab objects int32 newPrefabInstanceIdToDataIndexCounter = 0; int32 newPrefabInstanceIdToDataIndexStart = sceneObjects->Count(); sceneObjects->Resize(sceneObjects->Count() + newPrefabInstanceIdToDataIndex.Count()); @@ -822,9 +857,27 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr { const int32 dataIndex = i->Value; SceneObject* obj = sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++); - if (obj) + if (!obj) + continue; + SceneObjectsFactory::Deserialize(obj, diffDataDocument[dataIndex], modifier.Value); + } + for (int32 j = 0; j < targetObjects->Count(); j++) + { + auto obj = targetObjects->At(j); + Guid prefabObjectId; + if (newPrefabInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId)) { - SceneObjectsFactory::Deserialize(obj, diffDataDocument[dataIndex], modifier.Value); + newPrefabInstanceIdToDataIndexCounter = 0; + for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i) + { + SceneObject* e = sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++); + if (e->GetID() == prefabObjectId) + { + // Synchronize order of new objects with the order in target instance + e->SetOrderInParent(obj->GetOrderInParent()); + break; + } + } } } Scripting::ObjectsLookupIdMapping.Set(nullptr); @@ -861,7 +914,10 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr for (int32 i = 0; i < sceneObjects->Count(); i++) { auto obj = sceneObjects.Value->At(i); - obj->PostLoad(); + if (obj) + { + obj->PostLoad(); + } } // Update transformations diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index ed4ed2a2b..53bbb9d14 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -11,7 +11,7 @@ #include "Engine/Scripting/Scripting.h" #endif -REGISTER_JSON_ASSET(Prefab, "FlaxEngine.Prefab"); +REGISTER_JSON_ASSET(Prefab, "FlaxEngine.Prefab", false); Prefab::Prefab(const SpawnParams& params, const AssetInfo* info) : JsonAssetBase(params, info) diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h index f5c25d273..694b50cdd 100644 --- a/Source/Engine/Level/Prefabs/Prefab.h +++ b/Source/Engine/Level/Prefabs/Prefab.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Content/JsonAsset.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" class Actor; @@ -15,7 +16,6 @@ class SceneObject; API_CLASS(NoSpawn) class FLAXENGINE_API Prefab : public JsonAssetBase { DECLARE_ASSET_HEADER(Prefab); - private: bool _isCreatingDefaultInstance; diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index 512d6e9b7..77698e739 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -16,6 +16,7 @@ #include "Engine/Core/Cache.h" #include "Engine/Debug/Exceptions/ArgumentException.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Scripting/Script.h" #include "Engine/Scripting/Scripting.h" #if USE_EDITOR @@ -216,21 +217,57 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryIdsMapping); SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); - Scripting::ObjectsLookupIdMapping.Set(nullptr); } - // Delete objects without parent + // Delete objects without parent or with invalid linkage to the prefab for (int32 i = 1; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects->At(i); - if (obj && obj->GetParent() == nullptr) + if (!obj) + continue; + + // Check for missing parent (eg. parent object has been deleted) + if (obj->GetParent() == nullptr) { sceneObjects->At(i) = nullptr; LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); obj->DeleteObject(); + continue; } + +#if USE_EDITOR && !BUILD_RELEASE + // Check for not being added to the parent (eg. invalid setup events fault on registration) + auto actor = dynamic_cast(obj); + auto script = dynamic_cast(obj); + if (obj->GetParent() == obj || (actor && !actor->GetParent()->Children.Contains(actor)) || (script && !script->GetParent()->Scripts.Contains(script))) + { + sceneObjects->At(i) = nullptr; + LOG(Warning, "Scene object {0} {1} has invalid parent object linkage after load. Removing it.", obj->GetID(), obj->ToString()); + obj->DeleteObject(); + continue; + } +#endif + +#if USE_EDITOR && BUILD_DEBUG + // Check for being added to parent not from spawned prefab (eg. invalid parentId linkage fault) + bool hasParentInInstance = false; + for (int32 j = 0; j < sceneObjects->Count(); j++) + { + if (sceneObjects->At(j) == obj->GetParent()) + { + hasParentInInstance = true; + break; + } + } + if (!hasParentInInstance) + { + sceneObjects->At(i) = nullptr; + LOG(Warning, "Scene object {0} {1} has invalid parent object after load. Removing it.", obj->GetID(), obj->ToString()); + obj->DeleteObject(); + continue; + } +#endif } // Link objects to prefab (only deserialized from prefab data) diff --git a/Source/Engine/Level/Scene/Lightmap.cpp b/Source/Engine/Level/Scene/Lightmap.cpp index 4a2eec796..88fa88b5a 100644 --- a/Source/Engine/Level/Scene/Lightmap.cpp +++ b/Source/Engine/Level/Scene/Lightmap.cpp @@ -6,6 +6,7 @@ #include "Engine/Content/Content.h" #include "Engine/Level/Level.h" #include "Engine/Level/Scene/SceneLightmapsData.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #if USE_EDITOR #include "Engine/ContentImporters/ImportTexture.h" #include "Engine/ContentImporters/AssetsImportingManager.h" @@ -136,6 +137,14 @@ void Lightmap::EnsureSize(int32 size) } } +bool Lightmap::IsReady() const +{ + // TODO: link for events and cache this to be a boolean value + return _textures[0] && _textures[0]->GetTexture()->ResidentMipLevels() > 0 + && _textures[1] && _textures[1]->GetTexture()->ResidentMipLevels() > 0 + && _textures[2] && _textures[2]->GetTexture()->ResidentMipLevels() > 0; +} + #if USE_EDITOR bool Lightmap::OnInitLightmap(TextureData& image) diff --git a/Source/Engine/Level/Scene/Lightmap.h b/Source/Engine/Level/Scene/Lightmap.h index c4550145d..6941f4323 100644 --- a/Source/Engine/Level/Scene/Lightmap.h +++ b/Source/Engine/Level/Scene/Lightmap.h @@ -96,14 +96,7 @@ public: /// /// Determines whether this lightmap is ready (textures can be used by the renderer). /// - /// True if lightmap textures are ready to use by renderer, otherwise false. - FORCE_INLINE bool IsReady() const - { - // TODO: link for events and cache this to be a boolean value - return _textures[0] && _textures[0]->GetTexture()->ResidentMipLevels() > 0 - && _textures[1] && _textures[1]->GetTexture()->ResidentMipLevels() > 0 - && _textures[2] && _textures[2]->GetTexture()->ResidentMipLevels() > 0; - } + bool IsReady() const; private: diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index d38bd4225..fb9b4ce50 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -13,8 +13,11 @@ #include "Engine/Navigation/NavMesh.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/Serialization.h" +#if USE_EDITOR +#include "Engine/Engine/Globals.h" +#endif -REGISTER_JSON_ASSET(SceneAsset, "FlaxEngine.SceneAsset"); +REGISTER_JSON_ASSET(SceneAsset, "FlaxEngine.SceneAsset", false); SceneAsset::SceneAsset(const SpawnParams& params, const AssetInfo* info) : JsonAsset(params, info) @@ -362,6 +365,6 @@ void Scene::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 0fb96b735..ce93b3e1c 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -2,6 +2,7 @@ #include "SceneRendering.h" #include "Scene.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Level/Actors/PostFxVolume.h" diff --git a/Source/Engine/Level/Scene/SceneTicking.h b/Source/Engine/Level/Scene/SceneTicking.h index 8962f1810..e8fbe8236 100644 --- a/Source/Engine/Level/Scene/SceneTicking.h +++ b/Source/Engine/Level/Scene/SceneTicking.h @@ -8,7 +8,7 @@ /// /// Scene gameplay updating helper subsystem that boosts the level ticking by providing efficient objects cache. /// -class SceneTicking +class FLAXENGINE_API SceneTicking { friend Scene; @@ -17,7 +17,7 @@ public: /// /// Tick function type. /// - struct Tick + struct FLAXENGINE_API Tick { typedef void (*Signature)(); typedef void (*SignatureObj)(void*); @@ -51,7 +51,7 @@ public: } }; - class TickData + class FLAXENGINE_API TickData { public: @@ -144,7 +144,7 @@ public: } }; - class FixedUpdateTickData : public TickData + class FLAXENGINE_API FixedUpdateTickData : public TickData { public: @@ -156,7 +156,7 @@ public: void TickScripts(const Array& scripts) override; }; - class UpdateTickData : public TickData + class FLAXENGINE_API UpdateTickData : public TickData { public: @@ -168,7 +168,7 @@ public: void TickScripts(const Array& scripts) override; }; - class LateUpdateTickData : public TickData + class FLAXENGINE_API LateUpdateTickData : public TickData { public: diff --git a/Source/Engine/Level/SceneObject.h b/Source/Engine/Level/SceneObject.h index a3ebdc71d..e3fcb8863 100644 --- a/Source/Engine/Level/SceneObject.h +++ b/Source/Engine/Level/SceneObject.h @@ -2,9 +2,9 @@ #pragma once -#include "Engine/Core/Common.h" -#include "Engine/Serialization/ISerializable.h" #include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Serialization/ISerializable.h" +#include "Engine/Core/Collections/Array.h" class SceneTicking; class ScriptsFactory; diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index a5cb27712..85cbc930f 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -10,6 +10,7 @@ #include "Engine/Serialization/ISerializeModifier.h" #include "Engine/Serialization/JsonWriters.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Threading/ThreadLocal.h" SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream, ISerializeModifier* modifier) { @@ -204,6 +205,8 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO { PROFILE_CPU_NAMED("SynchronizePrefabInstances"); + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); + // Check all objects with prefab linkage for moving to a proper parent const int32 objectsToCheckCount = sceneObjects.Count(); for (int32 i = 0; i < objectsToCheckCount; i++) @@ -257,6 +260,16 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO // Reparent obj->SetParent(actualParent, false); } + + // Preserve order in parent (values from prefab are used) + if (i != 0) + { + const auto defaultInstance = prefab->GetDefaultInstance(obj->GetPrefabObjectID()); + if (defaultInstance) + { + obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + } + } } // Check all actors with prefab linkage for adding missing objects @@ -305,6 +318,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO continue; // Create instance (including all children) + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); SynchronizeNewPrefabInstance(prefab, actor, prefabObjectId, sceneObjects, modifier); } } @@ -313,8 +327,19 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO for (int32 i = objectsToCheckCount; i < sceneObjects.Count(); i++) { SceneObject* obj = sceneObjects[i]; + + // Preserve order in parent (values from prefab are used) + auto prefab = Content::LoadAsync(obj->GetPrefabID()); + const auto defaultInstance = prefab && prefab->IsLoaded() ? prefab->GetDefaultInstance(obj->GetPrefabObjectID()) : nullptr; + if (defaultInstance) + { + obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + } + obj->PostLoad(); } + + Scripting::ObjectsLookupIdMapping.Set(nullptr); } void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value) @@ -414,6 +439,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Prefab* prefab, Actor* ac // Map prefab object ID to the new prefab object instance modifier->IdsMapping[prefabObjectId] = Guid::New(); + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); // Create prefab instance (recursive prefab loading to support nested prefabs) auto child = Spawn(*(ISerializable::DeserializeStream*)prefabData, modifier); diff --git a/Source/Engine/Localization/CultureInfo.cpp b/Source/Engine/Localization/CultureInfo.cpp new file mode 100644 index 000000000..d46bacfbe --- /dev/null +++ b/Source/Engine/Localization/CultureInfo.cpp @@ -0,0 +1,190 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "CultureInfo.h" +#include "Engine/Core/Log.h" +#include "Engine/Scripting/ManagedCLR/MType.h" +#include "Engine/Utilities/StringConverter.h" +#if USE_MONO +#include +#include +#include + +typedef struct +{ + MonoObject obj; + MonoBoolean is_read_only; + gint32 lcid; + //... +} MonoCultureInfo; +#endif + +CultureInfo::CultureInfo(int32 lcid) +{ + _lcid = lcid; + _lcidParent = 0; + _data = nullptr; + if (lcid == 0) + return; + if (lcid == 127) + { + _englishName = TEXT("Invariant Culture"); + return; + } +#if USE_MONO + for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++) + { + auto& e = culture_entries[i]; + if (e.lcid == lcid) + { + _data = (void*)&e; + _lcidParent = (int32)e.parent_lcid; + const char* name = idx2string(e.name); + _name.SetUTF8(name, StringUtils::Length(name)); + const char* nativename = idx2string(e.nativename); + _nativeName.SetUTF8(nativename, StringUtils::Length(nativename)); + const char* englishname = idx2string(e.englishname); + _englishName.SetUTF8(englishname, StringUtils::Length(englishname)); + break; + } + } +#else +#error "Missing CultureInfo implementation." +#endif + if (!_data) + { + _englishName = TEXT("Invariant Culture"); + LOG(Error, "Unknown LCID {0} for CultureInfo", lcid); + } +} + +CultureInfo::CultureInfo(const StringView& name) + : CultureInfo(StringAnsiView(StringAsANSI<16>(name.Get(), name.Length()).Get(), name.Length())) +{ +} + +CultureInfo::CultureInfo(const StringAnsiView& name) +{ + _data = nullptr; + if (name.IsEmpty()) + { + _lcid = 127; + _lcidParent = 0; + _englishName = TEXT("Invariant Culture"); + return; + } +#if USE_MONO + for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++) + { + auto& e = culture_entries[i]; + if (name == idx2string(e.name)) + { + _data = (void*)&e; + _lcid = (int32)e.lcid; + _lcidParent = (int32)e.parent_lcid; + _name.SetUTF8(name.Get(), name.Length()); + const char* nativename = idx2string(e.nativename); + _nativeName.SetUTF8(nativename, StringUtils::Length(nativename)); + const char* englishname = idx2string(e.englishname); + _englishName.SetUTF8(englishname, StringUtils::Length(englishname)); + break; + } + } +#else +#error "Missing CultureInfo implementation." +#endif + if (!_data) + { + _lcid = 127; + _lcidParent = 0; + _englishName = TEXT("Invariant Culture"); + LOG(Error, "Unknown name {0} for CultureInfo", String(name)); + } +} + +int32 CultureInfo::GetLCID() const +{ + return _lcid; +} + +int32 CultureInfo::GetParentLCID() const +{ + return _lcidParent; +} + +const String& CultureInfo::GetName() const +{ + return _name; +} + +const String& CultureInfo::GetNativeName() const +{ + return _nativeName; +} + +const String& CultureInfo::GetEnglishName() const +{ + return _englishName; +} + +bool CultureInfo::IsRightToLeft() const +{ +#if USE_MONO + const auto data = static_cast(_data); + if (data) + return data->text_info.is_right_to_left ? true : false; +#else +#error "Missing CultureInfo implementation." +#endif + return false; +} + +String CultureInfo::ToString() const +{ + return _name; +} + +bool CultureInfo::operator==(const CultureInfo& other) const +{ + return _lcid == other._lcid; +} + +void* MUtils::ToManaged(const CultureInfo& value) +{ +#if USE_MONO + const auto klass = mono_class_from_name(mono_get_corlib(), "System.Globalization", "CultureInfo"); + if (klass) + { + void* iter = nullptr; + MonoMethod* method; + while ((method = mono_class_get_methods(klass, &iter))) + { + if (StringUtils::Compare(mono_method_get_name(method), ".ctor") == 0) + { + const auto sig = mono_method_signature(method); + void* sigParams = nullptr; + mono_signature_get_params(sig, &sigParams); + if (mono_signature_get_param_count(sig) == 1 && mono_type_get_class(((MonoType**)sigParams)[0]) != mono_get_string_class()) + { + MonoObject* obj = mono_object_new(mono_domain_get(), klass); + int32 lcid = value.GetLCID(); + void* params[1]; + params[0] = &lcid; + mono_runtime_invoke(method, obj, params, nullptr); + return obj; + } + } + } + } +#endif + return nullptr; +} + +CultureInfo MUtils::ToNative(void* value) +{ + int32 lcid = 127; +#if USE_MONO + if (value) + lcid = static_cast(value)->lcid; +#endif + return CultureInfo(lcid); +} diff --git a/Source/Engine/Localization/CultureInfo.h b/Source/Engine/Localization/CultureInfo.h new file mode 100644 index 000000000..7a156e2dd --- /dev/null +++ b/Source/Engine/Localization/CultureInfo.h @@ -0,0 +1,82 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Formatting.h" + +/// +/// The information about a specific culture (aka locale). The information includes the names for the culture, the writing system, the calendar used, the sort order of strings, and formatting for dates and numbers. +/// +API_CLASS(InBuild, Namespace="System.Globalization") class FLAXENGINE_API CultureInfo +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(CultureInfo); +private: + void* _data; + int32 _lcid; + int32 _lcidParent; + String _name; + String _nativeName; + String _englishName; + +public: + /// + /// Initializes a new instance of the class. + /// + /// The culture identifier. + CultureInfo(int32 lcid); + + /// + /// Initializes a new instance of the class. + /// + /// The culture name (eg. pl-PL). + CultureInfo(const StringView& name); + + /// + /// Initializes a new instance of the class. + /// + /// The culture name (eg. pl-PL). + CultureInfo(const StringAnsiView& name); + +public: + /// + /// Gets the culture identifier. + /// + int32 GetLCID() const; + + /// + /// Gets the parent culture identifier. + /// + int32 GetParentLCID() const; + + /// + /// Gets the culture name (eg. pl-PL). + /// + const String& GetName() const; + + /// + /// Gets the full localized culture name (eg. Polish (Poland)). + /// + const String& GetNativeName() const; + + /// + /// Gets the culture name in English (eg. polski (Polska)). + /// + const String& GetEnglishName() const; + + /// + /// Returns true if culture uses right-to-left (RTL) text layout. Otherwise it's more common left-to-right. + /// + bool IsRightToLeft() const; + + String ToString() const; + bool operator==(const CultureInfo& other) const; +}; + +DEFINE_DEFAULT_FORMATTING_VIA_TO_STRING(CultureInfo); + +namespace MUtils +{ + extern void* ToManaged(const CultureInfo& value); + extern CultureInfo ToNative(void* value); +} diff --git a/Source/Engine/Localization/Localization.Build.cs b/Source/Engine/Localization/Localization.Build.cs new file mode 100644 index 000000000..7f730dcab --- /dev/null +++ b/Source/Engine/Localization/Localization.Build.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using Flax.Build; +using Flax.Build.NativeCpp; + +/// +/// Localization and internalization module. +/// +public class Localization : EngineModule +{ + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.PublicDependencies.Add("Scripting"); + + if (options.Target.IsEditor) + { + options.PrivateDependencies.Add("ContentImporters"); + } + } +} diff --git a/Source/Engine/Localization/Localization.cpp b/Source/Engine/Localization/Localization.cpp new file mode 100644 index 000000000..1de886590 --- /dev/null +++ b/Source/Engine/Localization/Localization.cpp @@ -0,0 +1,286 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Localization.h" +#include "CultureInfo.h" +#include "LocalizedString.h" +#include "LocalizationSettings.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Config/GameSettings.h" +#include "Engine/Engine/EngineService.h" +#include "Engine/Content/Content.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Serialization/Serialization.h" +#include + +class LocalizationService : public EngineService +{ +public: + CultureInfo CurrentCulture; + CultureInfo CurrentLanguage; + Array> LocalizedStringTables; + + LocalizationService() + : EngineService(TEXT("Localization"), -500) + , CurrentCulture(0) + , CurrentLanguage(0) + { + } + + void OnLocalizationChanged(); + + bool Init() override; +}; + +namespace +{ + LocalizationService Instance; +} + +IMPLEMENT_SETTINGS_GETTER(LocalizationSettings, Localization); + +void LocalizationSettings::Apply() +{ + Instance.OnLocalizationChanged(); +} + +void LocalizationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(LocalizedStringTables); +} + +LocalizedString::LocalizedString(const LocalizedString& other) + : Id(other.Id) + , Value(other.Value) +{ +} + +LocalizedString::LocalizedString(LocalizedString&& other) noexcept + : Id(MoveTemp(other.Id)) + , Value(MoveTemp(other.Value)) +{ +} + +LocalizedString::LocalizedString(const StringView& value) + : Value(value) +{ +} + +LocalizedString::LocalizedString(String&& value) noexcept + : Value(MoveTemp(value)) +{ +} + +LocalizedString& LocalizedString::operator=(const LocalizedString& other) +{ + if (this != &other) + { + Id = other.Id; + Value = other.Value; + } + return *this; +} + +LocalizedString& LocalizedString::operator=(LocalizedString&& other) noexcept +{ + if (this != &other) + { + Id = MoveTemp(other.Id); + Value = MoveTemp(other.Value); + } + return *this; +} + +LocalizedString& LocalizedString::operator=(const StringView& value) +{ + Id.Clear(); + Value = value; + return *this; +} + +LocalizedString& LocalizedString::operator=(String&& value) noexcept +{ + Id.Clear(); + Value = MoveTemp(value); + return *this; +} + +String LocalizedString::ToString() const +{ + return Localization::GetString(Id, Value); +} + +String LocalizedString::ToStringPlural(int32 n) const +{ + return Localization::GetPluralString(Id, n, Value); +} + +void LocalizationService::OnLocalizationChanged() +{ + PROFILE_CPU(); + + Instance.LocalizedStringTables.Clear(); + + // Collect all localization tables into mapping locale -> tables + auto& settings = *LocalizationSettings::Get(); + Dictionary, InlinedAllocation<8>>> tables; + for (auto& e : settings.LocalizedStringTables) + { + auto table = e.Get(); + if (table && !table->WaitForLoaded()) + { + tables[table->Locale].Add(table); + } + } + + // Pick localization tables for a current language + auto* table = tables.TryGet(Instance.CurrentLanguage.GetName()); + if (!table) + { + // Try using parent culture (eg. en if en-GB is missing) + const CultureInfo parentLanguage(Instance.CurrentLanguage.GetParentLCID()); + if (parentLanguage.GetName().HasChars()) + table = tables.TryGet(parentLanguage.GetName()); + if (!table) + { + // Fallback to English + const CultureInfo english("en"); + table = tables.TryGet(english.GetName()); + } + } + + // Apply localization table + if (table) + { + String locale; + for (auto& e : tables) + { + if (&e.Value == table) + { + locale = e.Key; + break; + } + } + LOG(Info, "Using localization for {0}", locale); + Instance.LocalizedStringTables.Add(table->Get(), table->Count()); + } + + // Change C++ locale (eg. used by fmt lib for values formatting) + { + char localeName[100]; + const auto& currentCulture = Instance.CurrentCulture.GetName(); + if (currentCulture.IsEmpty()) + { + localeName[0] = 0; + } + else + { + StringUtils::ConvertUTF162ANSI(currentCulture.GetText(), localeName, currentCulture.Length()); + for (int32 i = 0; i < currentCulture.Length(); i++) + if (localeName[i] == '-') + localeName[i] = '_'; + localeName[currentCulture.Length() + 0] = '.'; + localeName[currentCulture.Length() + 1] = 'U'; + localeName[currentCulture.Length() + 2] = 'T'; + localeName[currentCulture.Length() + 3] = 'F'; + localeName[currentCulture.Length() + 4] = '-'; + localeName[currentCulture.Length() + 5] = '8'; + localeName[currentCulture.Length() + 6] = 0; + } + std::locale::global(std::locale(localeName)); + } + + // Send event + Localization::LocalizationChanged(); +} + +bool LocalizationService::Init() +{ + // Use system language as default + CurrentLanguage = CurrentCulture = CultureInfo(Platform::GetUserLocaleName()); + + // Setup localization + Instance.OnLocalizationChanged(); + + return false; +} + +Delegate<> Localization::LocalizationChanged; + +const CultureInfo& Localization::GetCurrentCulture() +{ + return Instance.CurrentCulture; +} + +void Localization::SetCurrentCulture(const CultureInfo& value) +{ + if (Instance.CurrentCulture == value) + return; + + LOG(Info, "Changing current culture to: {0} ({1})", value.GetName(), value.GetLCID()); + Instance.CurrentCulture = value; + Instance.OnLocalizationChanged(); +} + +const CultureInfo& Localization::GetCurrentLanguage() +{ + return Instance.CurrentLanguage; +} + +void Localization::SetCurrentLanguage(const CultureInfo& value) +{ + if (Instance.CurrentLanguage == value) + return; + + LOG(Info, "Changing current language to: {0} ({1})", value.GetName(), value.GetLCID()); + Instance.CurrentLanguage = value; + Instance.OnLocalizationChanged(); +} + +void Localization::SetCurrentLanguageCulture(const CultureInfo& value) +{ + if (Instance.CurrentCulture == value && Instance.CurrentLanguage == value) + return; + + LOG(Info, "Changing current language and culture to: {0} ({1})", value.GetName(), value.GetLCID()); + Instance.CurrentCulture = value; + Instance.CurrentLanguage = value; + Instance.OnLocalizationChanged(); +} + +String Localization::GetString(const String& id, const String& fallback) +{ + String result; + for (auto& e : Instance.LocalizedStringTables) + { + const auto table = e.Get(); + const auto messages = table ? table->Entries.TryGet(id) : nullptr; + if (messages && messages->Count() != 0) + { + result = messages->At(0); + break; + } + } + if (result.IsEmpty()) + result = fallback; + return result; +} + +String Localization::GetPluralString(const String& id, int32 n, const String& fallback) +{ + CHECK_RETURN(n >= 1, fallback); + n--; + StringView result; + 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); + break; + } + } + if (result.IsEmpty()) + result = fallback; + return String::Format(result.GetText(), n); +} diff --git a/Source/Engine/Localization/Localization.h b/Source/Engine/Localization/Localization.h new file mode 100644 index 000000000..6502c5523 --- /dev/null +++ b/Source/Engine/Localization/Localization.h @@ -0,0 +1,62 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "CultureInfo.h" +#include "Engine/Core/Types/BaseTypes.h" + +/// +/// The language and culture localization manager. +/// +API_CLASS(Static) class FLAXENGINE_API Localization +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(Localization); +public: + /// + /// Gets the current culture (date, time, currency and values formatting locale). + /// + API_PROPERTY() static const CultureInfo& GetCurrentCulture(); + + /// + /// Sets the current culture (date, time, currency and values formatting locale). + /// + API_PROPERTY() static void SetCurrentCulture(const CultureInfo& value); + + /// + /// Gets the current language (text display locale). + /// + API_PROPERTY() static const CultureInfo& GetCurrentLanguage(); + + /// + /// Sets the current language (text display locale). + /// + API_PROPERTY() static void SetCurrentLanguage(const CultureInfo& value); + + /// + /// Sets both the current language (text display locale) and the current culture (date, time, currency and values formatting locale) at once. + /// + API_FUNCTION() static void SetCurrentLanguageCulture(const CultureInfo& value); + + /// + /// Occurs when current culture or language gets changed. Can be used to refresh UI to reflect language changes. + /// + API_EVENT() static Delegate<> LocalizationChanged; + +public: + /// + /// Gets the localized string for the current language by using string id lookup. + /// + /// The message identifier. + /// The optional fallback string value to use if localized string is missing. + /// The localized text. + API_FUNCTION() static String GetString(const String& id, const String& fallback = String::Empty); + + /// + /// Gets the localized plural string for the current language by using string id lookup. + /// + /// The message identifier. + /// The value count for plural message selection. + /// The optional fallback string value to use if localized string is missing. + /// The localized text. + API_FUNCTION() static String GetPluralString(const String& id, int32 n, const String& fallback = String::Empty); +}; diff --git a/Source/Engine/Localization/LocalizationSettings.h b/Source/Engine/Localization/LocalizationSettings.h new file mode 100644 index 000000000..26ba06c96 --- /dev/null +++ b/Source/Engine/Localization/LocalizationSettings.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Config/Settings.h" +#include "Engine/Content/AssetReference.h" +#include "LocalizedStringTable.h" + +/// +/// Game localization and internalization settings container. +/// +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LocalizationSettings : public SettingsBase +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(LocalizationSettings); +public: + /// + /// The list of the string localization tables used by the game. + /// + API_FIELD() + Array> LocalizedStringTables; + +public: + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static LocalizationSettings* Get(); + + // [SettingsBase] + void Apply() override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; +}; diff --git a/Source/Engine/Localization/LocalizedString.cs b/Source/Engine/Localization/LocalizedString.cs new file mode 100644 index 000000000..2d6535336 --- /dev/null +++ b/Source/Engine/Localization/LocalizedString.cs @@ -0,0 +1,150 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Globalization; + +namespace FlaxEngine +{ + partial class Localization + { + /// + /// Creates new culture. + /// + /// The name (eg. en, pl-PL). + /// The culture. + public static CultureInfo NewCulture(string name) + { + return new CultureInfo(name); + } + } + + partial class LocalizedString : IEquatable, IEquatable, IComparable, IComparable, IComparable + { + /// + /// Empty string without localization. + /// + public static readonly LocalizedString Empty = new LocalizedString(null); + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public LocalizedString(string value) + { + Value = value; + } + + /// + /// Gets the localized plural string for the current language by using string id lookup. + /// + /// The value count for plural message selection. + /// The localized text. + public string ToStringPlural(int n) + { + return string.IsNullOrEmpty(Value) ? Localization.GetPluralString(Id, n) : Value; + } + + /// + /// Implicit converter of into . + /// + /// The localized string. + /// The string. + public static implicit operator string(LocalizedString str) + { + if ((object)str == null) + return null; + return string.IsNullOrEmpty(str.Value) ? Localization.GetString(str.Id) : str.Value; + } + + /// + /// Implicit converter of into . + /// + /// The string. + /// The localized string. + public static implicit operator LocalizedString(string str) + { + if (str == null) + return null; + return new LocalizedString(str); + } + + /// + /// Compares two localized strings. + /// + /// The lft string. + /// The right string. + /// True if both values are equal, otherwise false. + public static bool operator ==(LocalizedString left, LocalizedString right) + { + return left?.Equals(right) ?? (object)right == null; + } + + /// + /// Compares two localized strings. + /// + /// The lft string. + /// The right string. + /// True if both values are not equal, otherwise false. + public static bool operator !=(LocalizedString left, LocalizedString right) + { + if ((object)left == null) + return (object)right != null; + return !left.Equals(right); + } + + /// + public int CompareTo(object obj) + { + if (obj is string asString) + return CompareTo(asString); + if (obj is LocalizedString asLocalizedString) + return CompareTo(asLocalizedString); + return 0; + } + + /// + public bool Equals(LocalizedString other) + { + return (object)other != null && Id == other.Id && Value == other.Value; + } + + /// + public bool Equals(string other) + { + return Value == other || Localization.GetString(Id) == other; + } + + /// + public int CompareTo(LocalizedString other) + { + return string.Compare(ToString(), ToString(), StringComparison.Ordinal); + } + + /// + public int CompareTo(string other) + { + return string.Compare(ToString(), other, StringComparison.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return (object)this == (object)obj || obj is LocalizedString other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((Id != null ? Id.GetHashCode() : 0) * 397) ^ (Value != null ? Value.GetHashCode() : 0); + } + } + + /// + public override string ToString() + { + return string.IsNullOrEmpty(Value) ? Localization.GetString(Id) : Value; + } + } +} diff --git a/Source/Engine/Localization/LocalizedString.h b/Source/Engine/Localization/LocalizedString.h new file mode 100644 index 000000000..8137808b1 --- /dev/null +++ b/Source/Engine/Localization/LocalizedString.h @@ -0,0 +1,119 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/String.h" +#include "Engine/Serialization/SerializationFwd.h" + +/// +/// The string container that supports using localized text. +/// +API_CLASS(Sealed) class FLAXENGINE_API LocalizedString +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(LocalizedString); +public: + /// + /// The localized string identifier. Used to lookup text value for a current language (via ). + /// + API_FIELD() String Id; + + /// + /// The overriden string value to use. If empty, the localized string will be used. + /// + API_FIELD() String Value; + +public: + LocalizedString() = default; + LocalizedString(const LocalizedString& other); + LocalizedString(LocalizedString&& other) noexcept; + LocalizedString(const StringView& value); + LocalizedString(String&& value) noexcept; + + LocalizedString& operator=(const LocalizedString& other); + LocalizedString& operator=(LocalizedString&& other) noexcept; + LocalizedString& operator=(const StringView& value); + LocalizedString& operator=(String&& value) noexcept; + + friend bool operator==(const LocalizedString& a, const LocalizedString& b) + { + return a.Id == b.Id && a.Value == b.Value; + } + + friend bool operator!=(const LocalizedString& a, const LocalizedString& b) + { + return !(a == b); + } + + friend bool operator==(const LocalizedString& a, const StringView& b) + { + return a.Value == b || a.ToString() == b; + } + + friend bool operator!=(const LocalizedString& a, const StringView& b) + { + return !(a == b); + } + +public: + String ToString() const; + String ToStringPlural(int32 n) const; +}; + +inline uint32 GetHash(const LocalizedString& key) +{ + return GetHash(key.ToString()); +} + +namespace Serialization +{ + inline bool ShouldSerialize(const LocalizedString& v, const void* otherObj) + { + return !otherObj || v != *(LocalizedString*)otherObj; + } + + inline void Serialize(ISerializable::SerializeStream& stream, const LocalizedString& v, const void* otherObj) + { + if (v.Id.IsEmpty()) + { + stream.String(v.Value); + } + else + { + stream.StartObject(); + stream.JKEY("Id"); + stream.String(v.Id); + if (v.Value.HasChars()) + { + stream.JKEY("Value"); + stream.String(v.Value); + } + stream.EndObject(); + } + } + + inline void Deserialize(ISerializable::DeserializeStream& stream, LocalizedString& v, ISerializeModifier* modifier) + { + if (stream.IsString()) + { + v.Id = String::Empty; + v.Value = stream.GetText(); + } + else if (stream.IsObject()) + { + auto e = SERIALIZE_FIND_MEMBER(stream, "Id"); + if (e != stream.MemberEnd()) + v.Id.SetUTF8(e->value.GetString(), e->value.GetStringLength()); + e = SERIALIZE_FIND_MEMBER(stream, "Value"); + if (e != stream.MemberEnd()) + v.Value.SetUTF8(e->value.GetString(), e->value.GetStringLength()); + else if (v.Id.HasChars()) + v.Value.Clear(); + } + else + { + v = LocalizedString(); + } + } +} + +DEFINE_DEFAULT_FORMATTING_VIA_TO_STRING(LocalizedString); diff --git a/Source/Engine/Localization/LocalizedStringTable.cpp b/Source/Engine/Localization/LocalizedStringTable.cpp new file mode 100644 index 000000000..c705856f4 --- /dev/null +++ b/Source/Engine/Localization/LocalizedStringTable.cpp @@ -0,0 +1,138 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "LocalizedStringTable.h" +#include "Engine/Serialization/JsonTools.h" +#include "Engine/Serialization/SerializationFwd.h" +#include "Engine/Content/Factories/JsonAssetFactory.h" +#if USE_EDITOR +#include "Engine/ContentImporters/CreateJson.h" +#include "Engine/Serialization/JsonWriters.h" +#include "Engine/Threading/Threading.h" +#include "Engine/Core/Log.h" +#endif + +REGISTER_JSON_ASSET(LocalizedStringTable, "FlaxEngine.LocalizedStringTable", true); + +LocalizedStringTable::LocalizedStringTable(const SpawnParams& params, const AssetInfo* info) + : JsonAssetBase(params, info) +{ +} + +void LocalizedStringTable::AddString(const StringView& id, const StringView& value) +{ + auto& values = Entries[id]; + values.Resize(1); + values[0] = value; +} + +void LocalizedStringTable::AddPluralString(const StringView& id, const StringView& value, int32 n) +{ + CHECK(n >= 0 && n < 1024); + auto& values = Entries[id]; + values.Resize(Math::Max(values.Count(), n + 1)); + values[n] = value; +} + +#if USE_EDITOR + +bool LocalizedStringTable::Save(const StringView& path) +{ + // Validate state + if (WaitForLoaded()) + { + LOG(Error, "Asset loading failed. Cannot save it."); + return true; + } + if (IsVirtual() && path.IsEmpty()) + { + LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); + return true; + } + + ScopeLock lock(Locker); + + // Serialize data + rapidjson_flax::StringBuffer outputData; + PrettyJsonWriter writerObj(outputData); + JsonWriter& writer = writerObj; + writer.StartObject(); + { + writer.JKEY("Locale"); + writer.String(Locale); + + writer.JKEY("Entries"); + writer.StartObject(); + for (auto& e : Entries) + { + writer.Key(e.Key); + if (e.Value.Count() == 1) + { + writer.String(e.Value[0]); + } + else + { + writer.StartArray(); + for (auto& q : e.Value) + writer.String(q); + writer.EndArray(); + } + } + writer.EndObject(); + } + writer.EndObject(); + + // Save asset + const bool saveResult = CreateJson::Create(path.HasChars() ? path : StringView(GetPath()), outputData, TypeName); + if (saveResult) + { + LOG(Error, "Cannot save \'{0}\'", ToString()); + return true; + } + + return false; +} + +#endif + +Asset::LoadResult LocalizedStringTable::loadAsset() +{ + // Base + auto result = JsonAssetBase::loadAsset(); + if (result != LoadResult::Ok || IsInternalType()) + return result; + + JsonTools::GetString(Locale, *Data, "Locale"); + const auto entriesMember = SERIALIZE_FIND_MEMBER((*Data), "Entries"); + if (entriesMember != Data->MemberEnd() && entriesMember->value.IsObject()) + { + Entries.EnsureCapacity(entriesMember->value.MemberCount()); + for (auto i = entriesMember->value.MemberBegin(); i != entriesMember->value.MemberEnd(); ++i) + { + const String key(i->name.GetText()); + auto& e = Entries[key]; + auto& value = i->value; + if (value.IsString()) + { + e.Resize(1); + e[0] = value.GetText(); + } + else if (value.IsArray()) + { + e.Resize(value.Size()); + for (int32 q = 0; q < e.Count(); q++) + e[q] = value[q].GetText(); + } + } + } + + return result; +} + +void LocalizedStringTable::unload(bool isReloading) +{ + // Base + JsonAssetBase::unload(isReloading); + + Locale.Clear(); + Entries.Clear(); +} diff --git a/Source/Engine/Localization/LocalizedStringTable.h b/Source/Engine/Localization/LocalizedStringTable.h new file mode 100644 index 000000000..1c1b8d795 --- /dev/null +++ b/Source/Engine/Localization/LocalizedStringTable.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Content/JsonAsset.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" + +/// +/// Contains localized strings table for a given culture. +/// +/// +API_CLASS(NoSpawn) class FLAXENGINE_API LocalizedStringTable : public JsonAssetBase +{ +DECLARE_ASSET_HEADER(LocalizedStringTable); +public: + /// + /// The locale of the localized string table (eg. pl-PL). + /// + API_FIELD() String Locale; + + /// + /// The string table. Maps the message id into the localized text. For plural messages the list contains separate items for value numbers. + /// + API_FIELD() Dictionary> Entries; + +public: + /// + /// Adds the localized string to the table. + /// + /// The message id. Used for lookups. + /// The localized text. + API_FUNCTION() void AddString(const StringView& id, const StringView& value); + + /// + /// Adds the localized plural string to the table. + /// + /// The message id. Used for lookups. + /// The localized text. + /// The plural value (0, 1, 2..). + API_FUNCTION() void AddPluralString(const StringView& id, const StringView& value, int32 n); + +#if USE_EDITOR + + /// + /// Saves this asset to the file. Supported only in Editor. + /// + /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. + /// True if cannot save data, otherwise false. + API_FUNCTION() bool Save(const StringView& path = StringView::Empty); + +#endif + +protected: + // [JsonAssetBase] + LoadResult loadAsset() override; + void unload(bool isReloading) override; +}; diff --git a/Source/Engine/Main/UWP/main.cpp b/Source/Engine/Main/UWP/main.cpp index 08d9cc422..e615a2b38 100644 --- a/Source/Engine/Main/UWP/main.cpp +++ b/Source/Engine/Main/UWP/main.cpp @@ -65,22 +65,6 @@ void Game::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs void Game::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args) { - /* - // Save app state asynchronously after requesting a deferral. Holding a deferral - // indicates that the application is busy performing suspending operations. Be - // aware that a deferral may not be held indefinitely. After about five seconds, - // the app will be forced to exit. - SuspendingDeferral^ deferral = args->SuspendingOperation->GetDeferral(); - - create_task([this, deferral]() - { - m_deviceResources->Trim(); - - m_main->Suspend(); - - deferral->Complete(); - }); - */ } void Game::OnResuming(Platform::Object^ sender, Platform::Object^ args) @@ -396,9 +380,10 @@ int PlatformImpl::GetSpecialFolderPath(const SpecialFolder type, wchar_t* buffer path = Windows::Storage::ApplicationData::Current->LocalFolder->Path; break; case SpecialFolder::ProgramData: + path = Windows::Storage::ApplicationData::Current->RoamingFolder->Path; break; - //case SpecialFolder::Temporary: path = Windows::Storage::ApplicationData::Current->TemporaryFolder->Path; break; case SpecialFolder::Temporary: + //path = Windows::Storage::ApplicationData::Current->TemporaryFolder->Path; path = Windows::Storage::ApplicationData::Current->LocalFolder->Path; break; } @@ -409,7 +394,7 @@ int PlatformImpl::GetSpecialFolderPath(const SpecialFolder type, wchar_t* buffer if (length >= bufferLength) length = bufferLength - 1; const wchar_t* data = path->Data(); - for (int i = 0; iLogicalDpi; + *dpi = (int)currentDisplayInformation->LogicalDpi; } void GetTitle(wchar_t* buffer, int bufferLength) override diff --git a/Source/Engine/Main/Windows/main.cpp b/Source/Engine/Main/Windows/main.cpp index b60f6d2c5..7f8aa872a 100644 --- a/Source/Engine/Main/Windows/main.cpp +++ b/Source/Engine/Main/Windows/main.cpp @@ -27,7 +27,7 @@ __declspec(dllexport) int32 AmdPowerXpressRequestHighPerformance = 1; extern LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep); -int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow) +int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { #ifdef USE_VS_MEM_LEAKS_CHECK // Memory leaks detect inside VS diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 55319bb9e..755f4179b 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -1016,6 +1016,8 @@ void NavMeshBuilder::Update() { NavBuildQueue.RemoveAt(i--); const auto scene = req.Scene.Get(); + if (!scene) + continue; // Early out if scene has no bounds volumes to define nav mesh area if (scene->NavigationVolumes.IsEmpty()) diff --git a/Source/Engine/Navigation/NavMeshData.cpp b/Source/Engine/Navigation/NavMeshData.cpp index fdf1f7aeb..3ea39e268 100644 --- a/Source/Engine/Navigation/NavMeshData.cpp +++ b/Source/Engine/Navigation/NavMeshData.cpp @@ -2,6 +2,7 @@ #include "NavMeshData.h" #include "Engine/Core/Log.h" +#include "Engine/Serialization/WriteStream.h" #include "Engine/Serialization/MemoryReadStream.h" void NavMeshData::Save(WriteStream& stream) diff --git a/Source/Engine/Navigation/NavMeshData.h b/Source/Engine/Navigation/NavMeshData.h index 806862c69..b2763abf5 100644 --- a/Source/Engine/Navigation/NavMeshData.h +++ b/Source/Engine/Navigation/NavMeshData.h @@ -3,6 +3,9 @@ #pragma once #include "Engine/Core/Types/DataContainer.h" +#include "Engine/Core/Collections/Array.h" + +class WriteStream; struct NavMeshTileDataHeader { diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index e6c255782..b35ade92d 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -16,9 +16,6 @@ #define USE_NAV_MESH_ALLOC 1 // TODO: try not using USE_NAV_MESH_ALLOC -#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f -#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f - namespace { FORCE_INLINE void InitFilter(dtQueryFilter& filter) @@ -58,7 +55,7 @@ bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit dtQueryFilter filter; InitFilter(filter); - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 extent = Properties.DefaultQueryExtent; Vector3 startPositionNavMesh; Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); @@ -96,7 +93,7 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo dtQueryFilter filter; InitFilter(filter); - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 extent = Properties.DefaultQueryExtent; Vector3 startPositionNavMesh, endPositionNavMesh; Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); @@ -126,10 +123,9 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo Quaternion invRotation; Quaternion::Invert(Properties.Rotation, invRotation); - // Check for special case, where path has not been found, and starting polygon was the one closest to the target if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) { - // In this case we find a point on starting polygon, that's closest to destination and store it as path end + // TODO: skip adding 2nd end point if it's not reachable (use navmesh raycast check? or physics check? or local Z distance check?) resultPath.Resize(2); resultPath[0] = startPosition; resultPath[1] = startPositionNavMesh; @@ -165,7 +161,7 @@ bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPo dtQueryFilter filter; InitFilter(filter); - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 extent = Properties.DefaultQueryExtent; Vector3 startPositionNavMesh, endPositionNavMesh; Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); @@ -212,7 +208,7 @@ bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const dtQueryFilter filter; InitFilter(filter); - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 extent = Properties.DefaultQueryExtent; Vector3 pointNavMesh; Vector3::Transform(point, Properties.Rotation, pointNavMesh); @@ -270,7 +266,7 @@ bool NavMeshRuntime::FindRandomPointAroundCircle(const Vector3& center, float ra dtQueryFilter filter; InitFilter(filter); - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 extent = Properties.DefaultQueryExtent; Vector3 centerNavMesh; Vector3::Transform(center, Properties.Rotation, centerNavMesh); @@ -308,7 +304,7 @@ bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPos dtQueryFilter filter; InitFilter(filter); - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 extent = Properties.DefaultQueryExtent; Vector3 startPositionNavMesh, endPositionNavMesh; Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); @@ -371,18 +367,9 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) PROFILE_CPU_NAMED("NavMeshRuntime.EnsureCapacity"); // Navmesh tiles capacity growing rule - int32 newCapacity = 0; - if (capacity) - { - while (newCapacity < newTilesCount) - { - newCapacity = Math::RoundUpToPowerOf2(newCapacity); - } - } - else - { - newCapacity = 32; - } + int32 newCapacity = capacity ? capacity : 32; + while (newCapacity < newTilesCount) + newCapacity = Math::RoundUpToPowerOf2(newCapacity); LOG(Info, "Resizing navmesh {2} from {0} to {1} tiles capacity", capacity, newCapacity, Properties.Name); diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index db9c95089..d7fdcfc33 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -146,7 +146,7 @@ bool NavAreaProperties::operator==(const NavAreaProperties& other) const bool NavMeshProperties::operator==(const NavMeshProperties& other) const { - return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent; + return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent && Vector3::NearEqual(DefaultQueryExtent, other.DefaultQueryExtent); } class NavigationService : public EngineService diff --git a/Source/Engine/Navigation/NavigationSettings.cs b/Source/Engine/Navigation/NavigationSettings.cs index 8a97e4adc..bfd2b5381 100644 --- a/Source/Engine/Navigation/NavigationSettings.cs +++ b/Source/Engine/Navigation/NavigationSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. using System; +using System.Runtime.Serialization; using FlaxEngine; namespace FlaxEditor.Content.Settings @@ -22,6 +23,7 @@ namespace FlaxEditor.Content.Settings navMesh.Agent.Height = 144.0f; navMesh.Agent.StepHeight = 35.0f; navMesh.Agent.MaxSlopeAngle = 60.0f; + navMesh.DefaultQueryExtent = new Vector3(50.0f, 250.0f, 50.0f); // Init nav areas NavAreas = new NavAreaProperties[2]; @@ -51,6 +53,7 @@ namespace FlaxEditor.Content.Settings navMesh.Agent.Height = 144.0f; navMesh.Agent.StepHeight = 35.0f; navMesh.Agent.MaxSlopeAngle = 60.0f; + navMesh.DefaultQueryExtent = new Vector3(50.0f, 250.0f, 50.0f); } // [Deprecated on 12.01.2021, expires on 12.01.2022] @@ -105,6 +108,23 @@ namespace FlaxEditor.Content.Settings namespace FlaxEngine { + partial struct NavMeshProperties + { + [OnDeserialized] + internal void OnDeserialized(StreamingContext context) + { + // [Deprecated on 07.04.2021, expires on 07.04.2022] + if (DefaultQueryExtent.IsZero) + DefaultQueryExtent = new Vector3(50.0f, 250.0f, 50.0f); + } + + /// + public override string ToString() + { + return Name; + } + } + partial struct NavAgentProperties { /// diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h index 11ebaeb1a..732ce4a0f 100644 --- a/Source/Engine/Navigation/NavigationTypes.h +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -83,6 +83,12 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshProperties); API_FIELD(Attributes="EditorOrder(30)") NavAgentProperties Agent; + /// + /// The default extents for the nav queries that defines the search distance along each axis (x, y, z). Smaller values prevent queries from snapping to too far locations. + /// + API_FIELD(Attributes="EditorOrder(40)") + Vector3 DefaultQueryExtent = Vector3(50.0f, 250.0f, 50.0f); + bool operator==(const NavMeshProperties& other) const; bool operator!=(const NavMeshProperties& other) const diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index 9a7f92080..019dc41f3 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -72,7 +72,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod case 3: value = matrix.GetRow4(); break; - default: CRASH; + default: break; } break; diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index 2415350ed..52b159947 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -1,8 +1,9 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ParticleEmitterGraph.CPU.h" -#include "Engine/Content/Assets/Model.h" #include "Engine/Core/Collections/Sorting.h" +#include "Engine/Content/Assets/Model.h" +#include "Engine/Renderer/RenderList.h" #include "Engine/Particles/ParticleEffect.h" #include "Engine/Engine/Time.h" diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h index 33423e9d7..34d7cec2e 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h @@ -6,7 +6,9 @@ #include "Engine/Particles/ParticlesSimulation.h" #include "Engine/Particles/ParticlesData.h" #include "Engine/Visject/VisjectGraph.h" +#include "Engine/Core/Collections/Dictionary.h" +struct RenderContext; class ParticleEffect; class ParticleEmitterGraphCPU; class ParticleEmitterGraphCPUBase; diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp index 3d49efec0..a285c5718 100644 --- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp +++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp @@ -6,6 +6,8 @@ #include "Engine/Particles/ParticleEmitter.h" #include "Engine/Particles/ParticleEffect.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" @@ -37,7 +39,12 @@ bool GPUParticles::Init(ParticleEmitter* owner, MemoryReadStream& shaderCacheStr // Load shader ASSERT(GPUDevice::Instance); - _shader = GPUDevice::Instance->CreateShader(owner->GetPath()); +#if GPU_ENABLE_RESOURCE_NAMING + const StringView name(owner->GetPath()); +#else + const StringView name; +#endif + _shader = GPUDevice::Instance->CreateShader(name); if (_shader->Create(shaderCacheStream)) { LOG(Warning, "Failed to load shader."); diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index 3ac8ad9a0..12793c198 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ParticleEmitterGraph.GPU.h" +#include "Engine/Graphics/Materials/MaterialInfo.h" #define SET_ATTRIBUTE(attribute, value) _writer.Write(TEXT("\t{0} = {1};\n"), attribute.Value, value) diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp index e1d864b4e..35c30ce55 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp @@ -3,6 +3,7 @@ #if COMPILE_WITH_PARTICLE_GPU_GRAPH #include "ParticleEmitterGraph.GPU.h" +#include "Engine/Graphics/Materials/MaterialInfo.h" bool ParticleEmitterGPUGenerator::loadTexture(Node* caller, Box* box, const SerializedMaterialParam& texture, Value& result) { diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp index 785e53ae3..72a98009c 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp @@ -5,6 +5,7 @@ #include "ParticleEmitterGraph.GPU.h" #include "Engine/Serialization/FileReadStream.h" #include "Engine/Visject/ShaderGraphUtilities.h" +#include "Engine/Engine/Globals.h" /// /// GPU particles shader source code template has special marks for generated code. @@ -456,7 +457,7 @@ void ParticleEmitterGPUGenerator::PrepareGraph(ParticleEmitterGraphGPU* graph) break; case VariantType::Vector4: mp.Type = MaterialParameterType::Vector4; - mp.AsVector4 = param->Value.AsVector4(); + *(Vector4*)&mp.AsData = param->Value.AsVector4(); break; case VariantType::Color: mp.Type = MaterialParameterType::Color; @@ -490,7 +491,7 @@ void ParticleEmitterGPUGenerator::PrepareGraph(ParticleEmitterGraphGPU* graph) break; case VariantType::Matrix: mp.Type = MaterialParameterType::Matrix; - mp.AsMatrix = (Matrix)param->Value; + *(Matrix*)&mp.AsData = (Matrix)param->Value; break; default: OnError(nullptr, nullptr, String::Format(TEXT("Invalid or unsupported particle parameter type {0}."), param->Type)); diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 40594904d..f23aff28c 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -306,7 +306,7 @@ void ParticleEffect::UpdateBounds() // Empty bounds if there is no particle system to play or it has been never played if (bounds == BoundingBox::Empty) { - bounds = BoundingBox(_transform.Translation, _transform.Translation); + bounds = BoundingBox(_transform.Translation); } _box = bounds; diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index 777c2f0f5..0b1d3fbf4 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -8,22 +8,28 @@ #include "Engine/Content/Upgraders/ShaderAssetUpgrader.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/DataContainer.h" -#include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/Shaders/Cache/ShaderCacheManager.h" -#include "Engine/Graphics/Shaders/Cache/ShaderStorage.h" #include "Engine/Level/Level.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR #include "ParticleEmitterFunction.h" #include "Engine/ShadersCompilation/Config.h" +#if BUILD_DEBUG +#include "Engine/Engine/Globals.h" +#endif +#endif +#if COMPILE_WITH_GPU_PARTICLES +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/Shaders/Cache/ShaderStorage.h" #endif #if COMPILE_WITH_PARTICLE_GPU_GRAPH && COMPILE_WITH_SHADER_COMPILER #include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h" #include "Engine/Utilities/Encryption.h" #endif -REGISTER_BINARY_ASSET(ParticleEmitter, "FlaxEngine.ParticleEmitter", ::New(), false); +REGISTER_BINARY_ASSET_WITH_UPGRADER(ParticleEmitter, "FlaxEngine.ParticleEmitter", ShaderAssetUpgrader, false); ParticleEmitter::ParticleEmitter(const SpawnParams& params, const AssetInfo* info) : ShaderAssetTypeBase(params, info) @@ -128,10 +134,10 @@ Asset::LoadResult ParticleEmitter::load() #if USE_EDITOR || !HasChunk(SHADER_FILE_CHUNK_SOURCE) #endif -#if COMPILE_WITH_DEV_ENV - // Set to true to enable force GPU particle simulation shaders regeneration - || false || HasDependenciesModified() +#if COMPILE_WITH_DEV_ENV + // Set to true to enable force GPU particle simulation shaders regeneration (don't commit it) + || false #endif )) { diff --git a/Source/Engine/Particles/ParticleEmitterFunction.cpp b/Source/Engine/Particles/ParticleEmitterFunction.cpp index 3dc48321b..24282f083 100644 --- a/Source/Engine/Particles/ParticleEmitterFunction.cpp +++ b/Source/Engine/Particles/ParticleEmitterFunction.cpp @@ -8,7 +8,7 @@ #endif #include "Engine/Content/Factories/BinaryAssetFactory.h" -REGISTER_BINARY_ASSET(ParticleEmitterFunction, "FlaxEngine.ParticleEmitterFunction", nullptr, false); +REGISTER_BINARY_ASSET(ParticleEmitterFunction, "FlaxEngine.ParticleEmitterFunction", false); ParticleEmitterFunction::ParticleEmitterFunction(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Particles/ParticleManager.cpp b/Source/Engine/Particles/ParticleManager.cpp index 99ce64b73..75d39cd13 100644 --- a/Source/Engine/Particles/ParticleManager.cpp +++ b/Source/Engine/Particles/ParticleManager.cpp @@ -7,13 +7,15 @@ #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Time.h" #include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/GPUPipelineStatePermutations.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" #include "ParticleEffect.h" #if COMPILE_WITH_GPU_PARTICLES #include "Engine/Content/Assets/Shader.h" -#include "Engine/Graphics/GPUPipelineStatePermutations.h" #include "Engine/Profiler/ProfilerGPU.h" #include "Engine/Renderer/Utils/BitonicSort.h" #endif diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 63b69b132..cb6bd4061 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -7,7 +7,7 @@ #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" -REGISTER_BINARY_ASSET(ParticleSystem, "FlaxEngine.ParticleSystem", nullptr, true); +REGISTER_BINARY_ASSET(ParticleSystem, "FlaxEngine.ParticleSystem", true); ParticleSystem::ParticleSystem(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Particles/ParticlesData.cpp b/Source/Engine/Particles/ParticlesData.cpp index 13840eb66..ee490d216 100644 --- a/Source/Engine/Particles/ParticlesData.cpp +++ b/Source/Engine/Particles/ParticlesData.cpp @@ -3,6 +3,8 @@ #include "ParticlesData.h" #include "ParticleEmitter.h" #include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/DynamicBuffer.h" ParticleBuffer::ParticleBuffer() { diff --git a/Source/Engine/Particles/ParticlesData.h b/Source/Engine/Particles/ParticlesData.h index 418792cfa..edda3c507 100644 --- a/Source/Engine/Particles/ParticlesData.h +++ b/Source/Engine/Particles/ParticlesData.h @@ -4,7 +4,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" -#include "Engine/Renderer/RenderList.h" +#include "Engine/Core/Collections/Array.h" #include "Types.h" // The maximum amount of particle attributes to use diff --git a/Source/Engine/Particles/ParticlesSimulation.cpp b/Source/Engine/Particles/ParticlesSimulation.cpp index 0b6e3691d..fb0e9bf74 100644 --- a/Source/Engine/Particles/ParticlesSimulation.cpp +++ b/Source/Engine/Particles/ParticlesSimulation.cpp @@ -4,6 +4,8 @@ #include "ParticleManager.h" #include "ParticleSystem.h" #include "ParticleEmitter.h" +#include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/GPUDevice.h" ParticleEmitterInstance::ParticleEmitterInstance() { diff --git a/Source/Engine/Physics/Actors/PhysicsActor.cpp b/Source/Engine/Physics/Actors/PhysicsActor.cpp index 79c4ccf7f..8e92ea902 100644 --- a/Source/Engine/Physics/Actors/PhysicsActor.cpp +++ b/Source/Engine/Physics/Actors/PhysicsActor.cpp @@ -61,7 +61,7 @@ void PhysicsActor::UpdateBounds() } else { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); } } else @@ -71,7 +71,7 @@ void PhysicsActor::UpdateBounds() } else { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); } BoundingSphere::FromBox(_box, _sphere); } diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index 12f4ed0d9..1f213f8f3 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -276,7 +276,8 @@ void RigidBody::UpdateMass() float raiseMassToPower = 0.75f; // TODO: link physical material or expose density parameter - PxRigidBodyExt::updateMassAndInertia(*_actor, densityKGPerCubicUU); + PxVec3 centerOfMassOffset = C2P(_centerOfMassOffset); + PxRigidBodyExt::updateMassAndInertia(*_actor, densityKGPerCubicUU, ¢erOfMassOffset); // Grab old mass so we can apply new mass while maintaining inertia tensor const float oldMass = _actor->getMass(); @@ -315,6 +316,14 @@ void RigidBody::AddForce(const Vector3& force, ForceMode mode) const } } +void RigidBody::AddForceAtPosition(const Vector3& force, const Vector3& position, ForceMode mode) const +{ + if (_actor && GetEnableSimulation()) + { + PxRigidBodyExt::addForceAtPos(*_actor, C2P(force), C2P(position), static_cast(mode)); + } +} + void RigidBody::AddRelativeForce(const Vector3& force, ForceMode mode) const { AddForce(Vector3::Transform(force, _transform.Orientation), mode); @@ -382,6 +391,15 @@ void RigidBody::OnTriggerExit(PhysicsColliderActor* c) TriggerExit(c); } +void RigidBody::OnColliderChanged(Collider* c) +{ + UpdateMass(); + + // TODO: maybe wake up only if one ore more shapes attached is active? + //if (GetStartAwake()) + // WakeUp(); +} + void RigidBody::CreateActor() { ASSERT(_actor == nullptr); @@ -393,7 +411,7 @@ void RigidBody::CreateActor() // Setup flags #if WITH_PVD - PxActorFlags actorFlags = PxActorFlag::eVISUALIZATION; + PxActorFlags actorFlags = PxActorFlag::eVISUALIZATION; #else PxActorFlags actorFlags = static_cast(0); #endif diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h index 84ab304aa..bfd6348df 100644 --- a/Source/Engine/Physics/Actors/RigidBody.h +++ b/Source/Engine/Physics/Actors/RigidBody.h @@ -14,7 +14,7 @@ class PhysicsColliderActor; API_CLASS() class FLAXENGINE_API RigidBody : public PhysicsActor { DECLARE_SCENE_OBJECT(RigidBody); -private: +protected: PxRigidDynamic* _actor; Vector3 _cachedScale; @@ -428,7 +428,7 @@ public: /// Applies a force (or impulse) defined in the world space to the rigidbody at its center of mass. /// /// - /// This will not induce a torque + /// This will not induce any torque /// /// ForceMode determines if the force is to be conventional or impulsive. /// @@ -443,11 +443,31 @@ public: /// The mode to use when applying the force/impulse. API_FUNCTION() void AddForce(const Vector3& force, ForceMode mode = ForceMode::Force) const; + /// + /// Applies a force (or impulse) defined in the world space to the rigidbody at given position in the world space. + /// + /// + /// Also applies appropriate amount of torque + /// + /// ForceMode determines if the force is to be conventional or impulsive. + /// + /// + /// Each actor has an acceleration and a velocity change accumulator which are directly modified using the modes ForceMode::Acceleration + /// and ForceMode::VelocityChange respectively. The modes ForceMode::Force and ForceMode::Impulse also modify these same + /// accumulators and are just short hand for multiplying the vector parameter by inverse mass and then using ForceMode::Acceleration and + /// ForceMode::VelocityChange respectively. + /// + /// + /// The force/impulse to apply defined in the world space. + /// The position of the force/impulse in the world space. + /// The mode to use when applying the force/impulse. + API_FUNCTION() void AddForceAtPosition(const Vector3& force, const Vector3& position, ForceMode mode = ForceMode::Force) const; + /// /// Applies a force (or impulse) defined in the local space of the rigidbody (relative to its coordinate system) at its center of mass. /// /// - /// This will not induce a torque + /// This will not induce any torque /// /// ForceMode determines if the force is to be conventional or impulsive. /// @@ -551,6 +571,9 @@ public: void OnTriggerEnter(PhysicsColliderActor* c); void OnTriggerExit(PhysicsColliderActor* c); + // Called when collider gets detached from this rigidbody or activated/deactivated. Used to update rigidbody mass. + virtual void OnColliderChanged(Collider* c); + protected: /// diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp index a610e76ce..b341f7008 100644 --- a/Source/Engine/Physics/Actors/SplineRopeBody.cpp +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -178,6 +178,6 @@ void SplineRopeBody::OnTransformChanged() { Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp new file mode 100644 index 000000000..558e4f98c --- /dev/null +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -0,0 +1,603 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "WheeledVehicle.h" +#include "Engine/Physics/Physics.h" +#include "Engine/Physics/Utilities.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Serialization/Serialization.h" +#if USE_EDITOR +#include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Debug/DebugDraw.h" +#endif +#if WITH_VEHICLE +#include +#include +#include +#include +#include +#include +#endif + +#if WITH_VEHICLE +extern void InitVehicleSDK(); +extern Array WheelVehicles; +#endif + +namespace +{ + void FreeDrive(WheeledVehicle::DriveTypes driveType, PxVehicleWheels* drive) + { + switch (driveType) + { + case WheeledVehicle::DriveTypes::Drive4W: + ((PxVehicleDrive4W*)drive)->free(); + break; + case WheeledVehicle::DriveTypes::DriveNW: + ((PxVehicleDriveNW*)drive)->free(); + break; + case WheeledVehicle::DriveTypes::NoDrive: + ((PxVehicleNoDrive*)drive)->free(); + break; + } + } +} + +WheeledVehicle::WheeledVehicle(const SpawnParams& params) + : RigidBody(params) +{ + _useCCD = 1; +} + +WheeledVehicle::DriveTypes WheeledVehicle::GetDriveType() const +{ + return _driveType; +} + +void WheeledVehicle::SetDriveType(DriveTypes value) +{ + if (_driveType == value) + return; + _driveType = value; + Setup(); +} + +const Array& WheeledVehicle::GetWheels() const +{ + return _wheels; +} + +void WheeledVehicle::SetWheels(const Array& value) +{ + _wheels = value; + Setup(); +} + +WheeledVehicle::EngineSettings WheeledVehicle::GetEngine() const +{ + return _engine; +} + +void WheeledVehicle::SetEngine(const EngineSettings& value) +{ + _engine = value; +} + +WheeledVehicle::DifferentialSettings WheeledVehicle::GetDifferential() const +{ + return _differential; +} + +void WheeledVehicle::SetDifferential(const DifferentialSettings& value) +{ + _differential = value; +} + +WheeledVehicle::GearboxSettings WheeledVehicle::GetGearbox() const +{ + return _gearbox; +} + +void WheeledVehicle::SetGearbox(const GearboxSettings& value) +{ +#if WITH_VEHICLE + auto& drive = (PxVehicleDrive*&)_drive; + if (drive) + { + drive->mDriveDynData.setUseAutoGears(value.AutoGear); + drive->mDriveDynData.setAutoBoxSwitchTime(Math::Max(value.SwitchTime, 0.0f)); + } +#endif + _gearbox = value; +} + +void WheeledVehicle::SetThrottle(float value) +{ + _throttle = Math::Clamp(value, -1.0f, 1.0f); +} + +void WheeledVehicle::SetSteering(float value) +{ + _steering = Math::Clamp(value, -1.0f, 1.0f); +} + +void WheeledVehicle::SetBrake(float value) +{ + _brake = Math::Saturate(value); +} + +void WheeledVehicle::SetHandbrake(float value) +{ + _handBrake = Math::Saturate(value); +} + +void WheeledVehicle::ClearInput() +{ + _throttle = 0; + _steering = 0; + _brake = 0; + _handBrake = 0; +} + +float WheeledVehicle::GetForwardSpeed() const +{ +#if WITH_VEHICLE + auto& drive = (const PxVehicleWheels*&)_drive; + return drive ? drive->computeForwardSpeed() : 0.0f; +#else + return 0.0f; +#endif +} + +float WheeledVehicle::GetSidewaysSpeed() const +{ +#if WITH_VEHICLE + auto& drive = (const PxVehicleWheels*&)_drive; + return drive ? drive->computeSidewaysSpeed() : 0.0f; +#else + return 0.0f; +#endif +} + +float WheeledVehicle::GetEngineRotationSpeed() const +{ +#if WITH_VEHICLE + auto& drive = (const PxVehicleDrive*&)_drive; + return drive && _driveType != DriveTypes::NoDrive ? RadPerSToRpm(drive->mDriveDynData.getEngineRotationSpeed()) : 0.0f; +#else + return 0.0f; +#endif +} + +int32 WheeledVehicle::GetCurrentGear() const +{ +#if WITH_VEHICLE + auto& drive = (const PxVehicleDrive*&)_drive; + return drive && _driveType != DriveTypes::NoDrive ? (int32)drive->mDriveDynData.getCurrentGear() - 1 : 0; +#else + return 0; +#endif +} + +void WheeledVehicle::SetCurrentGear(int32 value) +{ +#if WITH_VEHICLE + auto& drive = (PxVehicleDrive*&)_drive; + if (drive && _driveType != DriveTypes::NoDrive) + { + drive->mDriveDynData.forceGearChange((PxU32)(value + 1)); + } +#endif +} + +int32 WheeledVehicle::GetTargetGear() const +{ +#if WITH_VEHICLE + auto& drive = (const PxVehicleDrive*&)_drive; + return drive && _driveType != DriveTypes::NoDrive ? (int32)drive->mDriveDynData.getTargetGear() - 1 : 0; +#else + return 0; +#endif +} + +void WheeledVehicle::SetTargetGear(int32 value) +{ +#if WITH_VEHICLE + auto& drive = (PxVehicleDrive*&)_drive; + if (drive && _driveType != DriveTypes::NoDrive) + { + drive->mDriveDynData.startGearChange((PxU32)(value + 1)); + } +#endif +} + +void WheeledVehicle::GetWheelState(int32 index, WheelState& result) +{ + if (index >= 0 && index < _wheels.Count()) + { + const auto collider = _wheels[index].Collider.Get(); + for (auto& wheelData : _wheelsData) + { + if (wheelData.Collider == collider) + { + result = wheelData.State; + return; + } + } + } +} + +void WheeledVehicle::Setup() +{ +#if WITH_VEHICLE + if (!_actor || !IsDuringPlay()) + return; + auto& drive = (PxVehicleWheels*&)_drive; + + // Release previous + if (drive) + { + WheelVehicles.Remove(this); + FreeDrive(_driveTypeCurrent, drive); + drive = nullptr; + } + + // Get wheels + Array> wheels; + _wheelsData.Clear(); + for (auto& wheel : _wheels) + { + if (!wheel.Collider) + { + LOG(Warning, "Missing wheel collider in vehicle {0}", ToString()); + continue; + } + if (wheel.Collider->GetParent() != this) + { + LOG(Warning, "Invalid wheel collider {1} in vehicle {0} attached to {2} (wheels needs to be added as children to vehicle)", ToString(), wheel.Collider->ToString(), wheel.Collider->GetParent() ? wheel.Collider->GetParent()->ToString() : String::Empty); + continue; + } + if (wheel.Collider->GetIsTrigger()) + { + LOG(Warning, "Invalid wheel collider {1} in vehicle {0} cannot be a trigger", ToString(), wheel.Collider->ToString()); + continue; + } + if (wheel.Collider->IsDuringPlay()) + { + wheels.Add(&wheel); + } + } + if (wheels.IsEmpty()) + { + // No wheel, no car + // No woman, no cry + return; + } + _wheelsData.Resize(wheels.Count()); + + InitVehicleSDK(); + + // Get linked shapes for the wheels mapping + Array> shapes; + shapes.Resize(_actor->getNbShapes()); + _actor->getShapes(shapes.Get(), shapes.Count(), 0); + const PxTransform centerOfMassOffset = _actor->getCMassLocalPose(); + + // Initialize wheels simulation data + PxVec3 offsets[PX_MAX_NB_WHEELS]; + for (int32 i = 0; i < wheels.Count(); i++) + { + Wheel& wheel = *wheels[i]; + offsets[i] = C2P(wheel.Collider->GetLocalPosition()); + } + PxF32 sprungMasses[PX_MAX_NB_WHEELS]; + PxVehicleComputeSprungMasses(wheels.Count(), offsets, centerOfMassOffset.p, _actor->getMass(), 1, sprungMasses); + PxVehicleWheelsSimData* wheelsSimData = PxVehicleWheelsSimData::allocate(wheels.Count()); + for (int32 i = 0; i < wheels.Count(); i++) + { + Wheel& wheel = *wheels[i]; + + auto& data = _wheelsData[i]; + data.Collider = wheel.Collider; + data.LocalOrientation = wheel.Collider->GetLocalOrientation(); + + PxVehicleSuspensionData suspensionData; + const float suspensionFrequency = 7.0f; + const float suspensionDampingRatio = 1.0f; + suspensionData.mMaxCompression = 10.0f; + suspensionData.mMaxDroop = 10.0f; + suspensionData.mSprungMass = sprungMasses[i]; + suspensionData.mSpringStrength = Math::Square(suspensionFrequency) * suspensionData.mSprungMass; + suspensionData.mSpringDamperRate = suspensionDampingRatio * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass); + + PxVehicleTireData tire; + tire.mType = 0; + + PxVehicleWheelData wheelData; + wheelData.mMass = wheel.Mass; + wheelData.mRadius = wheel.Radius; + wheelData.mWidth = wheel.Width; + wheelData.mMOI = 0.5f * wheelData.mMass * Math::Square(wheelData.mRadius); + wheelData.mDampingRate = M2ToCm2(wheel.DampingRate); + wheelData.mMaxSteer = wheel.MaxSteerAngle * DegreesToRadians; + wheelData.mMaxBrakeTorque = M2ToCm2(wheel.MaxBrakeTorque); + wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque); + + PxVec3 centreOffset = centerOfMassOffset.transformInv(offsets[i]); + float suspensionForceOffset = 0.0f; + PxVec3 forceAppPointOffset(centreOffset.x, centreOffset.y, centreOffset.z + suspensionForceOffset); + + wheelsSimData->setTireData(i, tire); + wheelsSimData->setWheelData(i, wheelData); + wheelsSimData->setSuspensionData(i, suspensionData); + wheelsSimData->setSuspTravelDirection(i, PxVec3(0, -1, 0)); + wheelsSimData->setWheelCentreOffset(i, centreOffset); + wheelsSimData->setSuspForceAppPointOffset(i, forceAppPointOffset); + wheelsSimData->setTireForceAppPointOffset(i, forceAppPointOffset); + + PxShape* wheelShape = wheel.Collider->GetPxShape(); + if (wheel.Collider->IsActiveInHierarchy()) + { + wheelsSimData->setWheelShapeMapping(i, shapes.Find(wheelShape)); + + // Setup Vehicle ID inside word3 for suspension raycasts to ignore self + PxFilterData filter = wheelShape->getQueryFilterData(); + filter.word3 = _id.D + 1; + wheelShape->setQueryFilterData(filter); + wheelShape->setSimulationFilterData(filter); + wheelsSimData->setSceneQueryFilterData(i, filter); + } + else + { + wheelsSimData->setWheelShapeMapping(i, -1); + wheelsSimData->disableWheel(i); + } + } + for (auto child : Children) + { + auto collider = Cast(child); + if (collider && collider->GetAttachedRigidBody() == this) + { + bool isWheel = false; + for (auto& w : wheels) + { + if (w->Collider == collider) + { + isWheel = true; + break; + } + } + if (!isWheel) + { + // Setup Vehicle ID inside word3 for suspension raycasts to ignore self + PxShape* shape = collider->GetPxShape(); + PxFilterData filter = shape->getQueryFilterData(); + filter.word3 = _id.D + 1; + shape->setQueryFilterData(filter); + shape->setSimulationFilterData(filter); + } + } + } + + // Initialize vehicle drive + _driveTypeCurrent = _driveType; + switch (_driveType) + { + case DriveTypes::Drive4W: + { + PxVehicleDriveSimData4W driveSimData; + + // Differential + PxVehicleDifferential4WData diff; + diff.mType = (PxVehicleDifferential4WData::Enum)_differential.Type; + diff.mFrontRearSplit = _differential.FrontRearSplit; + diff.mFrontLeftRightSplit = _differential.FrontLeftRightSplit; + diff.mRearLeftRightSplit = _differential.RearLeftRightSplit; + diff.mCentreBias = _differential.CentreBias; + diff.mFrontBias = _differential.FrontBias; + diff.mRearBias = _differential.RearBias; + driveSimData.setDiffData(diff); + + // Engine + PxVehicleEngineData engine; + engine.mMOI = M2ToCm2(_engine.MOI); + engine.mPeakTorque = M2ToCm2(_engine.MaxTorque); + engine.mMaxOmega = RpmToRadPerS(_engine.MaxRotationSpeed); + engine.mDampingRateFullThrottle = M2ToCm2(0.15f); + engine.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f); + engine.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f); + driveSimData.setEngineData(engine); + + // Gears + PxVehicleGearsData gears; + gears.mSwitchTime = Math::Max(_gearbox.SwitchTime, 0.0f); + driveSimData.setGearsData(gears); + + // Auto Box + PxVehicleAutoBoxData autoBox; + driveSimData.setAutoBoxData(autoBox); + + // Clutch + PxVehicleClutchData clutch; + clutch.mStrength = M2ToCm2(_gearbox.ClutchStrength); + driveSimData.setClutchData(clutch); + + // Ackermann steer accuracy + PxVehicleAckermannGeometryData ackermann; + ackermann.mAxleSeparation = Math::Abs(wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_LEFT).x - wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_LEFT).x); + ackermann.mFrontWidth = Math::Abs(wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_RIGHT).z - wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_LEFT).z); + ackermann.mRearWidth = Math::Abs(wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_RIGHT).z - wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_LEFT).z); + driveSimData.setAckermannGeometryData(ackermann); + + // Create vehicle drive + auto drive4W = PxVehicleDrive4W::allocate(wheels.Count()); + drive4W->setup(CPhysX, _actor, *wheelsSimData, driveSimData, Math::Max(wheels.Count() - 4, 0)); + drive4W->setToRestState(); + drive4W->mDriveDynData.forceGearChange(PxVehicleGearsData::eFIRST); + drive4W->mDriveDynData.setUseAutoGears(_gearbox.AutoGear); + drive = drive4W; + break; + } + case DriveTypes::DriveNW: + { + PxVehicleDriveSimDataNW driveSimData; + + // Differential + PxVehicleDifferentialNWData diff; + for (int32 i = 0; i < wheels.Count(); i++) + diff.setDrivenWheel(i, true); + driveSimData.setDiffData(diff); + + // Engine + PxVehicleEngineData engine; + engine.mMOI = M2ToCm2(_engine.MOI); + engine.mPeakTorque = M2ToCm2(_engine.MaxTorque); + engine.mMaxOmega = RpmToRadPerS(_engine.MaxRotationSpeed); + engine.mDampingRateFullThrottle = M2ToCm2(0.15f); + engine.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f); + engine.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f); + driveSimData.setEngineData(engine); + + // Gears + PxVehicleGearsData gears; + gears.mSwitchTime = Math::Max(_gearbox.SwitchTime, 0.0f); + driveSimData.setGearsData(gears); + + // Auto Box + PxVehicleAutoBoxData autoBox; + driveSimData.setAutoBoxData(autoBox); + + // Clutch + PxVehicleClutchData clutch; + clutch.mStrength = M2ToCm2(_gearbox.ClutchStrength); + driveSimData.setClutchData(clutch); + + // Create vehicle drive + auto driveNW = PxVehicleDriveNW::allocate(wheels.Count()); + driveNW->setup(CPhysX, _actor, *wheelsSimData, driveSimData, wheels.Count()); + driveNW->setToRestState(); + driveNW->mDriveDynData.forceGearChange(PxVehicleGearsData::eFIRST); + driveNW->mDriveDynData.setUseAutoGears(_gearbox.AutoGear); + drive = driveNW; + break; + } + case DriveTypes::NoDrive: + { + // Create vehicle drive + auto driveNo = PxVehicleNoDrive::allocate(wheels.Count()); + driveNo->setup(CPhysX, _actor, *wheelsSimData); + driveNo->setToRestState(); + drive = driveNo; + break; + } + default: + CRASH; + } + + WheelVehicles.Add(this); + wheelsSimData->free(); + _actor->setSolverIterationCounts(12, 4); + +#else + LOG(Fatal, "PhysX Vehicle SDK is not supported."); +#endif +} + +#if USE_EDITOR + +void WheeledVehicle::DrawPhysicsDebug(RenderView& view) +{ + // Wheels shapes + for (auto& wheel : _wheels) + { + 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); + } + } +} + +void WheeledVehicle::OnDebugDrawSelected() +{ + // Wheels shapes + for (auto& wheel : _wheels) + { + 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); + } + } + + // Center of mass + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(_transform.LocalToWorld(_centerOfMassOffset), 10.0f), Color::Blue, 0, false); + + RigidBody::OnDebugDrawSelected(); +} + +#endif + +void WheeledVehicle::Serialize(SerializeStream& stream, const void* otherObj) +{ + RigidBody::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(WheeledVehicle); + + SERIALIZE_MEMBER(DriveType, _driveType); + SERIALIZE_MEMBER(Wheels, _wheels); + SERIALIZE(UseReverseAsBrake); + SERIALIZE_MEMBER(Engine, _engine); + SERIALIZE_MEMBER(Differential, _differential); + SERIALIZE_MEMBER(Gearbox, _gearbox); +} + +void WheeledVehicle::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + RigidBody::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(DriveType, _driveType); + DESERIALIZE_MEMBER(Wheels, _wheels); + DESERIALIZE(UseReverseAsBrake); + DESERIALIZE_MEMBER(Engine, _engine); + DESERIALIZE_MEMBER(Differential, _differential); + DESERIALIZE_MEMBER(Gearbox, _gearbox); +} + +void WheeledVehicle::OnColliderChanged(Collider* c) +{ + RigidBody::OnColliderChanged(c); + + // Rebuild vehicle when someone adds/removed wheels + Setup(); +} + +void WheeledVehicle::BeginPlay(SceneBeginData* data) +{ + RigidBody::BeginPlay(data); + +#if WITH_VEHICLE + Setup(); +#endif + +#if USE_EDITOR + GetSceneRendering()->AddPhysicsDebug(this); +#endif +} + +void WheeledVehicle::EndPlay() +{ +#if USE_EDITOR + GetSceneRendering()->RemovePhysicsDebug(this); +#endif + +#if WITH_VEHICLE + auto& drive = (PxVehicleWheels*&)_drive; + if (drive) + { + // Parkway Drive + WheelVehicles.Remove(this); + FreeDrive(_driveTypeCurrent, drive); + drive = nullptr; + } +#endif + + RigidBody::EndPlay(); +} diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h new file mode 100644 index 000000000..bb947d68d --- /dev/null +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -0,0 +1,428 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Physics/Actors/RigidBody.h" +#include "Engine/Physics/Colliders/Collider.h" +#include "Engine/Scripting/ScriptingObjectReference.h" + +class Physics; + +/// +/// Representation of the car vehicle that uses wheels. Built on top of the RigidBody with collider representing its chassis shape and wheels. +/// +/// +API_CLASS() class FLAXENGINE_API WheeledVehicle : public RigidBody +{ + friend Physics; +DECLARE_SCENE_OBJECT(WheeledVehicle); +public: + + /// + /// Vehicle driving mode types. + /// + API_ENUM() enum class DriveTypes + { + // Four-wheel drive. Any additional wheels are non-drivable. Optimized for 4-wheel cars. + Drive4W, + // N-wheel drive. Up to 20 drivable wheels. Suits generic wheels configurations. + DriveNW, + // Non-drivable vehicle. + NoDrive, + }; + + /// + /// Vehicle engine settings. + /// + API_STRUCT() struct EngineSettings : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(EngineSettings); + API_AUTO_SERIALIZATION(); + + /// + /// Moment of inertia of the engine around the axis of rotation. Specified in kilograms metres-squared (kg m^2). + /// + API_FIELD() float MOI = 1.0f; + + /// + /// Maximum torque available to apply to the engine when the accelerator pedal is at maximum. Specified in kilograms metres-squared per second-squared (kg m^2 s^-2). + /// + API_FIELD() float MaxTorque = 500.0f; + + /// + /// Maximum rotation speed of the engine (Revolutions Per Minute is the number of turns in one minute). + /// + API_FIELD() float MaxRotationSpeed = 6000.0f; + }; + + /// + /// Vehicle differential types. + /// + API_ENUM() enum class DifferentialTypes + { + // Limited slip differential for car with 4 driven wheels. + LimitedSlip4W, + // Limited slip differential for car with front-wheel drive. + LimitedSlipFrontDrive, + // Limited slip differential for car with rear-wheel drive. + LimitedSlipRearDrive, + // Open differential for car with 4 driven wheels. + Open4W, + // Open differential for car with front-wheel drive. + OpenFrontDrive, + // Open differential for car with rear-wheel drive. + OpenRearDrive, + }; + + /// + /// Vehicle differential settings. + /// + API_STRUCT() struct DifferentialSettings : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(DifferentialSettings); + API_AUTO_SERIALIZATION(); + + /// + /// Type of differential. + /// + API_FIELD() DifferentialTypes Type = DifferentialTypes::LimitedSlip4W; + + /// + /// Ratio of torque split between front and rear (higher then 0.5 means more to front, smaller than 0.5 means more to rear). Only applied to LimitedSlip4W and Open4W. + /// + API_FIELD(Attributes="Limit(0, 1)") float FrontRearSplit = 0.45f; + + /// + /// Ratio of torque split between front-left and front-right (higher then 0.5 means more to front-left, smaller than 0.5 means more to front-right). Only applied to LimitedSlip4W and Open4W and LimitedSlipFrontDrive. + /// + API_FIELD(Attributes="Limit(0, 1)") float FrontLeftRightSplit = 0.5f; + + /// + /// Ratio of torque split between rear-left and rear-right (higher then 0.5 means more to rear-left, smaller than 0.5 means more to rear-right). Only applied to LimitedSlip4W and Open4W and LimitedSlipRearDrive. + /// + API_FIELD(Attributes="Limit(0, 1)") float RearLeftRightSplit = 0.5f; + + /// + /// Maximum allowed ratio of average front wheel rotation speed and rear wheel rotation speeds. The differential will divert more torque to the slower wheels when the bias is exceeded. Only applied to LimitedSlip4W. + /// + API_FIELD(Attributes="Limit(1)") float CentreBias = 1.3f; + + /// + /// Maximum allowed ratio of front-left and front-right wheel rotation speeds. The differential will divert more torque to the slower wheel when the bias is exceeded. Only applied to LimitedSlip4W and LimitedSlipFrontDrive. + /// + API_FIELD(Attributes="Limit(1)") float FrontBias = 1.3f; + + /// + /// Maximum allowed ratio of rear-left and rear-right wheel rotation speeds. The differential will divert more torque to the slower wheel when the bias is exceeded. Only applied to LimitedSlip4W and LimitedSlipRearDrive. + /// + API_FIELD(Attributes="Limit(1)") float RearBias = 1.3f; + }; + + /// + /// Vehicle gearbox settings. + /// + API_STRUCT() struct GearboxSettings : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(GearboxSettings); + API_AUTO_SERIALIZATION(); + + /// + /// If enabled the vehicle gears will be changes automatically, otherwise it's fully manual. + /// + API_FIELD() bool AutoGear = true; + + /// + /// Time it takes to switch gear. Specified in seconds (s). + /// + API_FIELD(Attributes="Limit(0)") float SwitchTime = 0.5f; + + /// + /// Strength of clutch. A stronger clutch more strongly couples the engine to the wheels, while a clutch of strength zero completely decouples the engine from the wheels. + /// Stronger clutches more quickly bring the wheels and engine into equilibrium, while weaker clutches take longer, resulting in periods of clutch slip and delays in power transmission from the engine to the wheels. + /// Specified in kilograms metres-squared per second (kg m^2 s^-1). + /// + API_FIELD(Attributes="Limit(0)") float ClutchStrength = 10.0f; + }; + + /// + /// Vehicle wheel types. + /// + API_ENUM() enum class WheelTypes + { + // Left wheel of the front axle. + FrontLeft, + // Right wheel of the front axle. + FrontRight, + // Left wheel of the rear axle. + RearLeft, + // Right wheel of the rear axle. + ReadRight, + // Non-drivable wheel. + NoDrive, + }; + + /// + /// Vehicle wheel settings. + /// + API_STRUCT() struct Wheel : ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(Wheel); + API_AUTO_SERIALIZATION(); + + /// + /// Wheel placement type. + /// + API_FIELD() WheelTypes Type = WheelTypes::FrontLeft; + + /// + /// Combined mass of the wheel and the tire in kg. Typically, a wheel has mass between 20Kg and 80Kg but can be lower and higher depending on the vehicle. + /// + API_FIELD() float Mass = 20.0f; + + /// + /// Distance in metres between the center of the wheel and the outside rim of the tire. It is important that the value of the radius closely matches the radius of the render mesh of the wheel. Any mismatch will result in the wheels either hovering above the ground or intersecting the ground. + /// + API_FIELD() float Radius = 50.0f; + + /// + /// Full width of the wheel in metres. This parameter has no bearing on the handling but is a very useful parameter to have when trying to render debug data relating to the wheel/tire/suspension. + /// + API_FIELD() float Width = 20.0f; + + /// + /// Max steer angle that can be achieved by the wheel (in degrees). + /// + API_FIELD(Attributes="Limit(0)") float MaxSteerAngle = 0.0f; + + /// + /// Damping rate applied to wheel. Specified in kilograms metres-squared per second (kg m^2 s^-1). + /// + API_FIELD(Attributes="Limit(0)") float DampingRate = 0.25f; + + /// + /// Max brake torque that can be applied to wheel. Specified in kilograms metres-squared per second-squared (kg m^2 s^-2) + /// + API_FIELD(Attributes="Limit(0)") float MaxBrakeTorque = 1500.0f; + + /// + /// Max handbrake torque that can be applied to wheel. Specified in kilograms metres-squared per second-squared (kg m^2 s^-2) + /// + API_FIELD(Attributes="Limit(0)") float MaxHandBrakeTorque = 2000.0f; + + /// + /// 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; + }; + + /// + /// Vehicle wheel dynamic simulation state container. + /// + API_STRUCT() struct WheelState + { + DECLARE_SCRIPTING_TYPE_MINIMAL(WheelState); + + /// + /// True if suspension travel limits forbid the wheel from touching the drivable surface. + /// + API_FIELD() bool IsInAir = false; + + /// + /// The wheel is not in the air then it's set to the collider of the driving surface under the corresponding vehicle wheel. + /// + API_FIELD() PhysicsColliderActor* TireContactCollider = nullptr; + + /// + /// The wheel is not in the air then it's set to the point on the drivable surface hit by the tire. + /// + API_FIELD() Vector3 TireContactPoint = Vector3::Zero; + + /// + /// The wheel is not in the air then it's set to the normal on the drivable surface hit by the tire. + /// + API_FIELD() Vector3 TireContactNormal = Vector3::Zero; + + /// + /// The friction experienced by the tire for the combination of tire type and surface type after accounting. + /// + API_FIELD() float TireFriction = 0.0f; + }; + +private: + + struct WheelData + { + Collider* Collider; + Quaternion LocalOrientation; + WheelState State; + }; + + void* _drive = nullptr; + DriveTypes _driveType = DriveTypes::Drive4W, _driveTypeCurrent; + Array> _wheelsData; + float _throttle = 0.0f, _steering = 0.0f, _brake = 0.0f, _handBrake = 0.0f; + Array _wheels; + EngineSettings _engine; + DifferentialSettings _differential; + GearboxSettings _gearbox; + +public: + + /// + /// If checked, the negative throttle value will be used as brake and reverse to behave in a more arcade style where holding reverse also functions as brake. Disable it for more realistic driving controls. + /// + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Vehicle\")") + bool UseReverseAsBrake = true; + + /// + /// Gets the vehicle driving model type. + /// + API_PROPERTY(Attributes="EditorOrder(1), EditorDisplay(\"Vehicle\")") DriveTypes GetDriveType() const; + + /// + /// Sets the vehicle driving model type. + /// + API_PROPERTY() void SetDriveType(DriveTypes value); + + /// + /// Gets the vehicle wheels settings. + /// + API_PROPERTY(Attributes="EditorOrder(2), EditorDisplay(\"Vehicle\")") const Array& GetWheels() const; + + /// + /// Sets the vehicle wheels settings. + /// + API_PROPERTY() void SetWheels(const Array& value); + + /// + /// Gets the vehicle engine settings. + /// + API_PROPERTY(Attributes="EditorOrder(3), EditorDisplay(\"Vehicle\")") EngineSettings GetEngine() const; + + /// + /// Sets the vehicle engine settings. + /// + API_PROPERTY() void SetEngine(const EngineSettings& value); + + /// + /// Gets the vehicle differential settings. + /// + API_PROPERTY(Attributes="EditorOrder(4), EditorDisplay(\"Vehicle\")") DifferentialSettings GetDifferential() const; + + /// + /// Sets the vehicle differential settings. + /// + API_PROPERTY() void SetDifferential(const DifferentialSettings& value); + + /// + /// Gets the vehicle gearbox settings. + /// + API_PROPERTY(Attributes="EditorOrder(5), EditorDisplay(\"Vehicle\")") GearboxSettings GetGearbox() const; + + /// + /// Sets the vehicle gearbox settings. + /// + API_PROPERTY() void SetGearbox(const GearboxSettings& value); + +public: + + /// + /// Sets the input for vehicle throttle. It is the analog accelerator pedal value in range (0,1) where 1 represents the pedal fully pressed and 0 represents the pedal in its rest state. + /// + /// The value (-1,1 range). When using UseReverseAsBrake it can be negative and will be used as brake and backward driving. + API_FUNCTION() void SetThrottle(float value); + + /// + /// Sets the input for vehicle steering. Steer is the analog steer value in range (-1,1) where -1 represents the steering wheel at left lock and +1 represents the steering wheel at right lock. + /// + /// The value (-1,1 range). + API_FUNCTION() void SetSteering(float value); + + /// + /// Sets the input for vehicle brakes. Brake is the analog brake pedal value in range (0,1) where 1 represents the pedal fully pressed and 0 represents the pedal in its rest state. + /// + /// The value (0,1 range). + API_FUNCTION() void SetBrake(float value); + + /// + /// Sets the input for vehicle handbrake. Handbrake is the analog handbrake value in range (0,1) where 1 represents the handbrake fully engaged and 0 represents the handbrake in its rest state. + /// + /// The value (0,1 range). + API_FUNCTION() void SetHandbrake(float value); + + /// + /// Clears all the vehicle control inputs to the default values (throttle, steering, breaks). + /// + API_FUNCTION() void ClearInput(); + +public: + + /// + /// Gets the current forward vehicle movement speed (along forward vector of the actor transform). + /// + API_PROPERTY() float GetForwardSpeed() const; + + /// + /// Gets the current sideways vehicle movement speed (along right vector of the actor transform). + /// + API_PROPERTY() float GetSidewaysSpeed() const; + + /// + /// Gets the current engine rotation speed (Revolutions Per Minute is the number of turns in one minute). + /// + API_PROPERTY() float GetEngineRotationSpeed() const; + + /// + /// Gets the current gear number. Neutral gears is 0, reverse gears is -1, forward gears are 1 and higher. + /// + API_PROPERTY(Attributes="HideInEditor") int32 GetCurrentGear() const; + + /// + /// Sets the current gear number. The gear change is instant. Neutral gears is 0, reverse gears is -1, forward gears are 1 and higher. + /// + API_PROPERTY() void SetCurrentGear(int32 value); + + /// + /// Gets the target gear number. Neutral gears is 0, reverse gears is -1, forward gears are 1 and higher. + /// + API_PROPERTY(Attributes="HideInEditor") int32 GetTargetGear() const; + + /// + /// Sets the target gear number. Gearbox will change the current gear to the target. Neutral gears is 0, reverse gears is -1, forward gears are 1 and higher. + /// + API_PROPERTY() void SetTargetGear(int32 value); + + /// + /// Gets the current state of the wheel. + /// + /// The index of the wheel. + /// The current state. + API_FUNCTION() void GetWheelState(int32 index, API_PARAM(Out) WheelState& result); + + /// + /// Rebuilds the vehicle. Call it after modifying vehicle settings (eg. engine options). + /// + API_FUNCTION() void Setup(); + +private: + +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view); +#endif + +public: + + // [Vehicle] +#if USE_EDITOR + void OnDebugDrawSelected() override; +#endif + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnColliderChanged(Collider* c) override; + +protected: + + // [Vehicle] + void BeginPlay(SceneBeginData* data) override; + void EndPlay() override; +}; diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 7657c7594..ef856bf79 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -23,7 +23,8 @@ CharacterController::CharacterController(const SpawnParams& params) , _height(150.0f) , _minMoveDistance(0.0f) , _isUpdatingTransform(false) - , _nonWalkableMode(CharacterController::NonWalkableModes::PreventClimbing) + , _upDirection(Vector3::Up) + , _nonWalkableMode(NonWalkableModes::PreventClimbing) , _lastFlags(CollisionFlags::None) { static_assert(sizeof(_filterData) == sizeof(PxFilterData), "Invalid filter data size."); @@ -60,7 +61,12 @@ void CharacterController::SetSlopeLimit(float value) _slopeLimit = value; if (_controller) - _controller->setSlopeLimit(cosf(value * DegreesToRadians)); + _controller->setSlopeLimit(Math::Cos(value * DegreesToRadians)); +} + +CharacterController::NonWalkableModes CharacterController::GetNonWalkableMode() const +{ + return _nonWalkableMode; } void CharacterController::SetNonWalkableMode(NonWalkableModes value) @@ -71,6 +77,11 @@ void CharacterController::SetNonWalkableMode(NonWalkableModes value) _controller->setNonWalkableMode(static_cast(value)); } +float CharacterController::GetStepOffset() const +{ + return _stepOffset; +} + void CharacterController::SetStepOffset(float value) { if (Math::NearEqual(value, _stepOffset)) @@ -82,6 +93,18 @@ void CharacterController::SetStepOffset(float value) _controller->setStepOffset(value); } +void CharacterController::SetUpDirection(const Vector3& up) +{ + if (_controller) + _controller->setUpDirection(C2P(up)); + _upDirection = up; +} + +Vector3 CharacterController::GetUpDirection() const +{ + return _controller ? P2C(_controller->getUpDirection()) : _upDirection; +} + void CharacterController::SetMinMoveDistance(float value) { _minMoveDistance = Math::Max(value, 0.0f); @@ -180,6 +203,7 @@ void CharacterController::CreateActor() // Create controller _controller = (PxCapsuleController*)Physics::GetControllerManager()->createController(desc); ASSERT(_controller); + _controller->setUpDirection(C2P(_upDirection)); const auto actor = _controller->getActor(); ASSERT(actor && actor->getNbShapes() == 1); actor->getShapes(&_shape, 1); @@ -220,7 +244,7 @@ void CharacterController::UpdateBounds() if (actor) _box = P2C(actor->getWorldBounds(boundsScale)); else - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); } @@ -345,7 +369,7 @@ void CharacterController::OnTransformChanged() } else if (!_controller) { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); } } @@ -363,6 +387,7 @@ void CharacterController::Serialize(SerializeStream& stream, const void* otherOb SERIALIZE_MEMBER(Radius, _radius); SERIALIZE_MEMBER(Height, _height); SERIALIZE_MEMBER(MinMoveDistance, _minMoveDistance); + SERIALIZE_MEMBER(UpDirection, _upDirection); } void CharacterController::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -376,4 +401,5 @@ void CharacterController::Deserialize(DeserializeStream& stream, ISerializeModif DESERIALIZE_MEMBER(Radius, _radius); DESERIALIZE_MEMBER(Height, _height); DESERIALIZE_MEMBER(MinMoveDistance, _minMoveDistance); + DESERIALIZE_MEMBER(UpDirection, _upDirection); } diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index f850b9124..5b4dd3f49 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -65,6 +65,7 @@ private: float _height; float _minMoveDistance; bool _isUpdatingTransform; + Vector3 _upDirection; NonWalkableModes _nonWalkableMode; CollisionFlags _lastFlags; uint32 _filterData[4]; @@ -117,10 +118,7 @@ public: /// Gets the non-walkable mode for the character controller. /// API_PROPERTY(Attributes="EditorOrder(215), DefaultValue(NonWalkableModes.PreventClimbing), EditorDisplay(\"Character Controller\")") - FORCE_INLINE NonWalkableModes GetNonWalkableMode() const - { - return _nonWalkableMode; - } + NonWalkableModes GetNonWalkableMode() const; /// /// Sets the non-walkable mode for the character controller. @@ -131,16 +129,24 @@ public: /// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error. /// API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\")") - FORCE_INLINE float GetStepOffset() const - { - return _stepOffset; - } + float GetStepOffset() const; /// /// Sets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error. /// API_PROPERTY() void SetStepOffset(float value); + /// + /// Gets the character up vector. + /// + API_PROPERTY(Attributes="EditorOrder(240), DefaultValue(true), EditorDisplay(\"Character Controller\")") + Vector3 GetUpDirection() const; + + /// + /// Sets the character up vector. + /// + API_PROPERTY() void SetUpDirection(const Vector3& up); + /// /// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index a1a84a523..acf6af5d1 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -170,19 +170,7 @@ bool Collider::ComputePenetration(const Collider* colliderA, const Collider* col const PxTransform poseA(C2P(colliderA->GetPosition()), C2P(colliderA->GetOrientation())); const PxTransform poseB(C2P(colliderB->GetPosition()), C2P(colliderB->GetOrientation())); - return PxGeometryQuery::computePenetration( - C2P(direction), - distance, - shapeA->getGeometry().any(), - poseA, - shapeB->getGeometry().any(), - poseB - ); -} - -bool Collider::IsAttached() const -{ - return _shape && _shape->getActor() != nullptr; + return PxGeometryQuery::computePenetration(C2P(direction), distance, shapeA->getGeometry().any(), poseA, shapeB->getGeometry().any(), poseB); } bool Collider::CanAttach(RigidBody* rigidBody) const @@ -350,6 +338,9 @@ void Collider::CreateStaticActor() _staticActor = CPhysX->createRigidStatic(trans); ASSERT(_staticActor); _staticActor->userData = this; +#if WITH_PVD + _staticActor->setActorFlag(PxActorFlag::eVISUALIZATION, true); +#endif // Reset local pos of the shape and link it to the actor _shape->setLocalPose(PxTransform(C2P(_center))); @@ -439,15 +430,18 @@ void Collider::BeginPlay(SceneBeginData* data) void Collider::EndPlay() { + // Base + PhysicsColliderActor::EndPlay(); + if (_shape) { // Detach from the actor auto actor = _shape->getActor(); if (actor) actor->detachShape(*_shape); - - // Check if was using a static actor and cleanup it - if (_staticActor) + if (actor && actor->is()) + static_cast(actor->userData)->OnColliderChanged(this); + else if (_staticActor) { RemoveStaticActor(); } @@ -457,9 +451,6 @@ void Collider::EndPlay() _shape->release(); _shape = nullptr; } - - // Base - PhysicsColliderActor::EndPlay(); } void Collider::OnActiveInTreeChanged() @@ -476,11 +467,7 @@ void Collider::OnActiveInTreeChanged() auto rigidBody = GetAttachedRigidBody(); if (rigidBody) { - rigidBody->UpdateMass(); - - // TODO: maybe wake up only if one ore more shapes attached is active? - //if (rigidBody->GetStartAwake()) - // rigidBody->WakeUp(); + rigidBody->OnColliderChanged(this); } } } @@ -497,6 +484,8 @@ void Collider::OnParentChanged() auto actor = _shape->getActor(); if (actor) actor->detachShape(*_shape); + if (actor && actor->is()) + static_cast(actor->userData)->OnColliderChanged(this); // Check if the new parent is a rigidbody const auto rigidBody = dynamic_cast(GetParent()); diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 3e7b5ecfd..ca88ea66c 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -149,12 +149,6 @@ public: public: - /// - /// Determines whether this collider is attached to the body. - /// - /// true if this instance is attached; otherwise, false. - API_PROPERTY() bool IsAttached() const; - /// /// Determines whether this collider can be attached the specified rigid body. /// diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 7ba76f963..5ed8da92c 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -2,9 +2,13 @@ #include "MeshCollider.h" #include "Engine/Core/Math/Matrix.h" +#include "Engine/Core/Math/Ray.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/Physics.h" +#if USE_EDITOR +#include "Engine/Debug/DebugLog.h" +#endif MeshCollider::MeshCollider(const SpawnParams& params) : Collider(params) @@ -39,6 +43,12 @@ bool MeshCollider::CanAttach(RigidBody* rigidBody) const CollisionDataType type = CollisionDataType::None; if (CollisionData && CollisionData->IsLoaded()) type = CollisionData->GetOptions().Type; +#if USE_EDITOR + if (type == CollisionDataType::TriangleMesh) + { + LOG(Warning, "Cannot attach {0} using Triangle Mesh collider {1} to RigidBody (not supported)", GetNamePath(), CollisionData->ToString()); + } +#endif return type != CollisionDataType::TriangleMesh; } diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index e44d986cc..98665aa05 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -3,6 +3,7 @@ #include "SplineCollider.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Matrix.h" +#include "Engine/Core/Math/Ray.h" #include "Engine/Level/Actors/Spline.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" @@ -64,7 +65,7 @@ void SplineCollider::OnSplineUpdated() { if (!_spline || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2 || !CollisionData || !CollisionData->IsLoaded()) { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); return; } @@ -178,7 +179,7 @@ void SplineCollider::UpdateBounds() void SplineCollider::GetGeometry(PxGeometryHolder& geometry) { // Reset bounds - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); // Skip if sth is missing diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index 9076d979e..5af787c5e 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -5,6 +5,7 @@ #include "CollisionCooking.h" #include "Engine/Threading/Task.h" #include "Engine/Graphics/Async/GPUTask.h" +#include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Core/Log.h" #include "Physics.h" #include @@ -117,15 +118,14 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali for (int32 i = 0; i < meshesCount; i++) { const auto mesh = lod->Meshes[i]; + if ((arg.MaterialSlotsMask & (1 << mesh->MaterialSlotIndex)) == 0) + continue; vCount += mesh->Positions.Count(); - if (needIndexBuffer) - { iCount += mesh->Indices.Count() * 3; - } } - if (meshesCount == 1) + if (meshesCount == 1 && vCount != 0) { // Link a single mesh const auto mesh = lod->Meshes[0]; @@ -144,6 +144,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali for (int32 i = 0; i < meshesCount; i++) { const auto mesh = lod->Meshes[i]; + if ((arg.MaterialSlotsMask & (1 << mesh->MaterialSlotIndex)) == 0) + continue; const int32 firstVertexIndex = vertexCounter; const int32 vertexCount = mesh->Positions.Count(); @@ -186,14 +188,15 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali } // Pick a proper model LOD - const int32 lodIndex = Math::Clamp(arg.ModelLodIndex, 0, arg.Model->LODs.Count()); - auto lod = &arg.Model->LODs[lodIndex]; + const int32 lodIndex = Math::Clamp(arg.ModelLodIndex, 0, arg.Model->GetLODsCount()); + Array meshes; + arg.Model->GetMeshes(meshes, lodIndex); // Download model LOD data from the GPU. // It's easier than reading internal, versioned mesh storage format. // Also it works with virtual assets that have no dedicated storage. // Note: request all meshes data at once and wait for the tasks to be done. - const int32 meshesCount = lod->Meshes.Count(); + const int32 meshesCount = meshes.Count(); Array vertexBuffers; Array indexBuffers; vertexBuffers.Resize(meshesCount); @@ -205,9 +208,11 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali Array tasks(meshesCount + meshesCount); for (int32 i = 0; i < meshesCount; i++) { - const auto& mesh = lod->Meshes[i]; + const auto& mesh = *meshes[i]; + if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0) + continue; - auto task = mesh.ExtractDataAsync(MeshBufferType::Vertex0, vertexBuffers[i]); + auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, vertexBuffers[i]); if (task == nullptr) return true; task->Start(); @@ -216,7 +221,7 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali if (needIndexBuffer) { - task = mesh.ExtractDataAsync(MeshBufferType::Index, indexBuffers[i]); + task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, indexBuffers[i]); if (task == nullptr) return true; task->Start(); @@ -236,7 +241,9 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali int32 vertexCounter = 0, indexCounter = 0; for (int32 i = 0; i < meshesCount; i++) { - const auto& mesh = lod->Meshes[i]; + const auto& mesh = *meshes[i]; + if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0) + continue; const auto& vData = vertexBuffers[i]; const int32 firstVertexIndex = vertexCounter; @@ -251,7 +258,7 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali if (mesh.Use16BitIndexBuffer()) { auto dst = finalIndexData.Get() + indexCounter; - auto src = (uint16*)iData.Get(); + auto src = iData.Get(); for (int32 j = 0; j < indexCount; j++) { *dst++ = firstVertexIndex + *src++; @@ -261,7 +268,7 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali else { auto dst = finalIndexData.Get() + indexCounter; - auto src = (uint32*)iData.Get(); + auto src = iData.Get(); for (int32 j = 0; j < indexCount; j++) { *dst++ = firstVertexIndex + *src++; diff --git a/Source/Engine/Physics/CollisionCooking.h b/Source/Engine/Physics/CollisionCooking.h index 658f88428..992038224 100644 --- a/Source/Engine/Physics/CollisionCooking.h +++ b/Source/Engine/Physics/CollisionCooking.h @@ -7,7 +7,7 @@ #include "Engine/Core/Types/DataContainer.h" #include "Engine/Physics/CollisionData.h" #include "Engine/Graphics/Models/ModelData.h" -#include "Engine/Content/Assets/Model.h" +#include "Engine/Content/Assets/ModelBase.h" namespace physx { @@ -40,8 +40,9 @@ public: { CollisionDataType Type = CollisionDataType::None; ModelData* OverrideModelData = nullptr; - AssetReference Model; + AssetReference Model; int32 ModelLodIndex = 0; + uint32 MaterialSlotsMask = MAX_uint32; ConvexMeshGenerationFlags ConvexFlags = ConvexMeshGenerationFlags::None; int32 ConvexVertexLimit = 255; }; diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp index 8de5bfcaa..a8621da09 100644 --- a/Source/Engine/Physics/CollisionData.cpp +++ b/Source/Engine/Physics/CollisionData.cpp @@ -12,7 +12,7 @@ #include #include -REGISTER_BINARY_ASSET(CollisionData, "FlaxEngine.CollisionData", nullptr, true); +REGISTER_BINARY_ASSET(CollisionData, "FlaxEngine.CollisionData", true); CollisionData::CollisionData(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) @@ -23,7 +23,7 @@ CollisionData::CollisionData(const SpawnParams& params, const AssetInfo* info) #if COMPILE_WITH_PHYSICS_COOKING -bool CollisionData::CookCollision(CollisionDataType type, Model* modelObj, int32 modelLodIndex, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) +bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { // Validate state if (!IsVirtual()) @@ -37,6 +37,7 @@ bool CollisionData::CookCollision(CollisionDataType type, Model* modelObj, int32 arg.Type = type; arg.Model = modelObj; arg.ModelLodIndex = modelLodIndex; + arg.MaterialSlotsMask = materialSlotsMask; arg.ConvexFlags = convexFlags; arg.ConvexVertexLimit = convexVertexLimit; diff --git a/Source/Engine/Physics/CollisionData.h b/Source/Engine/Physics/CollisionData.h index d3621f8d9..c2ff38815 100644 --- a/Source/Engine/Physics/CollisionData.h +++ b/Source/Engine/Physics/CollisionData.h @@ -4,11 +4,17 @@ #include "Engine/Content/BinaryAsset.h" #include "Engine/Core/Math/BoundingBox.h" -#include "Engine/Physics/Types.h" class Model; +class ModelBase; class ModelData; +namespace physx +{ + class PxConvexMesh; + class PxTriangleMesh; +} + /// /// A storage data type. /// @@ -156,8 +162,8 @@ public: private: CollisionDataOptions _options; - PxConvexMesh* _convexMesh; - PxTriangleMesh* _triangleMesh; + physx::PxConvexMesh* _convexMesh; + physx::PxTriangleMesh* _triangleMesh; public: @@ -173,8 +179,7 @@ public: /// /// Gets the convex mesh object (valid only if asset is loaded and has cooked convex data). /// - /// The convex mesh - FORCE_INLINE PxConvexMesh* GetConvex() const + FORCE_INLINE physx::PxConvexMesh* GetConvex() const { return _convexMesh; } @@ -182,8 +187,7 @@ public: /// /// Gets the triangle mesh object (valid only if asset is loaded and has cooked triangle data). /// - /// The triangle mesh - FORCE_INLINE PxTriangleMesh* GetTriangle() const + FORCE_INLINE physx::PxTriangleMesh* GetTriangle() const { return _triangleMesh; } @@ -201,9 +205,10 @@ public: /// The collision data type. /// The source model. /// The source model LOD index. + /// The source model material slots mask. One bit per-slot. Can be sued to exclude particular material slots from collision cooking. /// The convex mesh generation flags. /// The convex mesh vertex limit. Use values in range [8;255] - API_FUNCTION() bool CookCollision(CollisionDataType type, Model* model, int32 modelLodIndex = 0, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags::None, int32 convexVertexLimit = 255); + API_FUNCTION() bool CookCollision(CollisionDataType type, ModelBase* model, int32 modelLodIndex = 0, uint32 materialSlotsMask = MAX_uint32, 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. diff --git a/Source/Engine/Physics/Joints/Joint.cpp b/Source/Engine/Physics/Joints/Joint.cpp index 56c5c1b32..2a8cc57ca 100644 --- a/Source/Engine/Physics/Joints/Joint.cpp +++ b/Source/Engine/Physics/Joints/Joint.cpp @@ -327,7 +327,7 @@ void Joint::OnTransformChanged() // TODO: this could track only local transform changed - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (_joint) diff --git a/Source/Engine/Physics/Physics.Queries.cpp b/Source/Engine/Physics/Physics.Queries.cpp index 15630e7ff..d32a4affc 100644 --- a/Source/Engine/Physics/Physics.Queries.cpp +++ b/Source/Engine/Physics/Physics.Queries.cpp @@ -2,6 +2,7 @@ #include "Physics.h" #include "Utilities.h" +#include "CollisionData.h" #include "Actors/PhysicsColliderActor.h" #include #include @@ -358,6 +359,102 @@ bool Physics::SphereCastAll(const Vector3& center, const float radius, const Vec return true; } +bool Physics::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) +{ + // Prepare data + SCENE_QUERY_SETUP_SWEEP_1(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxCapsuleGeometry geometry(radius, height * 0.5f); + + // Perform sweep test + return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()); +} + +bool Physics::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) +{ + // Prepare data + SCENE_QUERY_SETUP_SWEEP_1(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxCapsuleGeometry geometry(radius, height * 0.5f); + + // Perform sweep test + if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback())) + return false; + + // Collect results + SCENE_QUERY_COLLECT_SINGLE(); + + return true; +} + +bool Physics::CapsuleCastAll(const Vector3& center, const float radius, const float height, const Vector3& direction, Array& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) +{ + // Prepare data + SCENE_QUERY_SETUP_SWEEP(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxCapsuleGeometry geometry(radius, height * 0.5f); + + // Perform sweep test + if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback())) + return false; + + // Collect results + SCENE_QUERY_COLLECT_ALL(); + + return true; +} + +bool Physics::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) +{ + CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false) + + // Prepare data + SCENE_QUERY_SETUP_SWEEP_1(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale))); + + // Perform sweep test + return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()); +} + +bool Physics::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) +{ + CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false) + + // Prepare data + SCENE_QUERY_SETUP_SWEEP_1(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale))); + + // Perform sweep test + if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback())) + return false; + + // Collect results + SCENE_QUERY_COLLECT_SINGLE(); + + return true; +} + +bool Physics::ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, Array& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) +{ + CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false) + + // Prepare data + SCENE_QUERY_SETUP_SWEEP(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale))); + + // Perform sweep test + if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback())) + return false; + + // Collect results + SCENE_QUERY_COLLECT_ALL(); + + return true; +} + bool Physics::CheckBox(const Vector3& center, const Vector3& halfExtents, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) { // Prepare data @@ -380,6 +477,30 @@ bool Physics::CheckSphere(const Vector3& center, const float radius, uint32 laye return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()); } +bool Physics::CheckCapsule(const Vector3& center, const float radius, const float height, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) +{ + // Prepare data + SCENE_QUERY_SETUP_OVERLAP_1(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxCapsuleGeometry geometry(radius, height * 0.5f); + + // Perform overlap test + return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()); +} + +bool Physics::CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) +{ + CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false) + + // Prepare data + SCENE_QUERY_SETUP_OVERLAP_1(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale))); + + // Perform overlap test + return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()); +} + bool Physics::OverlapBox(const Vector3& center, const Vector3& halfExtents, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) { // Prepare data @@ -414,6 +535,42 @@ bool Physics::OverlapSphere(const Vector3& center, const float radius, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) +{ + // Prepare data + SCENE_QUERY_SETUP_OVERLAP(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxCapsuleGeometry geometry(radius, height * 0.5f); + + // Perform overlap test + if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback())) + return false; + + // Collect results + SCENE_QUERY_COLLECT_OVERLAP_COLLIDER(); + + return true; +} + +bool Physics::OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) +{ + CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false) + + // Prepare data + SCENE_QUERY_SETUP_OVERLAP(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale))); + + // Perform overlap test + if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback())) + return false; + + // Collect results + SCENE_QUERY_COLLECT_OVERLAP_COLLIDER(); + + return true; +} + bool Physics::OverlapBox(const Vector3& center, const Vector3& halfExtents, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) { // Prepare data @@ -447,3 +604,39 @@ bool Physics::OverlapSphere(const Vector3& center, const float radius, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) +{ + // Prepare data + SCENE_QUERY_SETUP_OVERLAP(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxCapsuleGeometry geometry(radius, height * 0.5f); + + // Perform overlap test + if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback())) + return false; + + // Collect results + SCENE_QUERY_COLLECT_OVERLAP(); + + return true; +} + +bool Physics::OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) +{ + CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false) + + // Prepare data + SCENE_QUERY_SETUP_OVERLAP(); + const PxTransform pose(C2P(center), C2P(rotation)); + const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale))); + + // Perform overlap test + if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback())) + return false; + + // Collect results + SCENE_QUERY_COLLECT_OVERLAP(); + + return true; +} diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 98237948f..bdee2fdc2 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -9,6 +9,9 @@ #include "Utilities.h" #include "PhysicsStepper.h" #include "SimulationEventCallback.h" +#if WITH_VEHICLE +#include "Actors/WheeledVehicle.h" +#endif #include "Engine/Level/Level.h" #include "Actors/PhysicsActor.h" #include "Engine/Profiler/ProfilerCPU.h" @@ -18,6 +21,9 @@ #include "Engine/Engine/Time.h" #include #include +#if WITH_VEHICLE +#include +#endif #if WITH_PVD #include #endif @@ -61,7 +67,7 @@ public: } }; -PxFilterFlags PhysiXFilterShader( +PxFilterFlags FilterShader( PxFilterObjectAttributes attributes0, PxFilterData filterData0, PxFilterObjectAttributes attributes1, PxFilterData filterData1, PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize) @@ -146,8 +152,22 @@ namespace PxMaterial* DefaultMaterial = nullptr; PxControllerManager* ControllerManager = nullptr; PxCpuDispatcher* CpuDispatcher = nullptr; + float LastDeltaTime = 0.0f; +#if WITH_VEHICLE + bool VehicleSDKInitialized = false; + Array WheelVehiclesCache; + Array WheelQueryResults; + Array WheelHitResults; + Array WheelVehiclesResultsPerWheel; + Array WheelVehiclesResultsPerVehicle; + PxBatchQuery* WheelRaycastBatchQuery = nullptr; + PxVehicleDrivableSurfaceToTireFrictionPairs* WheelTireFrictions = nullptr; +#endif } +#if WITH_VEHICLE +Array WheelVehicles; +#endif bool Physics::AutoSimulation = true; uint32 Physics::LayerMasks[32]; @@ -172,7 +192,7 @@ PhysicsService PhysicsServiceInstance; PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) { #if WITH_PVD - PxShapeFlags flags = PxShapeFlag::eVISUALIZATION; + PxShapeFlags flags = PxShapeFlag::eVISUALIZATION; #else PxShapeFlags flags = static_cast(0); #endif @@ -194,6 +214,38 @@ PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) return flags; } +#if WITH_VEHICLE + +void InitVehicleSDK() +{ + if (!VehicleSDKInitialized) + { + VehicleSDKInitialized = true; + PxInitVehicleSDK(*CPhysX); + PxVehicleSetBasisVectors(PxVec3(0, 1, 0), PxVec3(1, 0, 0)); + PxVehicleSetUpdateMode(PxVehicleUpdateMode::eVELOCITY_CHANGE); + } +} + +static PxQueryHitType::Enum WheelRaycastPreFilter(PxFilterData filterData0, PxFilterData filterData1, const void* constantBlock, PxU32 constantBlockSize, PxHitFlags& queryFlags) +{ + // Hardcoded id for vehicle shapes masking + if (filterData0.word3 == filterData1.word3) + { + return PxQueryHitType::eNONE; + } + + // Collide for pairs (A,B) where the filtermask of A contains the ID of B and vice versa + if ((filterData0.word0 & filterData1.word1) && (filterData1.word0 & filterData0.word1)) + { + return PxQueryHitType::eBLOCK; + } + + return PxQueryHitType::eNONE; +} + +#endif + void PhysicsSettings::Apply() { Time::_physicsMaxDeltaTime = MaxDeltaTime; @@ -317,13 +369,6 @@ bool PhysicsService::Init() _foundation = PxCreateFoundation(PX_PHYSICS_VERSION, PhysXAllocatorCallback, PhysXErrorCallback); CHECK_INIT(_foundation, "PxCreateFoundation failed!"); - // Recording memory allocations is necessary if you want to - // use the memory facilities in PVD effectively. Since PVD isn't necessarily connected - // right away, we add a mechanism that records all outstanding memory allocations and - // forwards them to PVD when it does connect. - // This certainly has a performance and memory profile effect and thus should be used - // only in non-production builds. - #if PHYSX_MEMORY_STATS _foundation->setReportAllocationNames(true); #endif @@ -334,64 +379,56 @@ bool PhysicsService::Init() PxPvd* pvd = nullptr; #if WITH_PVD - { - // Connection parameters - const char* pvd_host_ip = "127.0.0.1"; // IP of the PC which is running PVD - int port = 5425; // TCP port to connect to, where PVD is listening - unsigned int timeout = 100; // Timeout in milliseconds to wait for PVD to respond, consoles and remote PCs need a higher timeout. - - // Init PVD - pvd = PxCreatePvd(*_foundation); - PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate(pvd_host_ip, port, timeout); - const bool isConnected = pvd->connect(*transport, PxPvdInstrumentationFlag::eALL); - if (isConnected) - { - LOG(Info, "Connected to PhysX Visual Debugger (PVD)")); - } - - CPVD = pvd; - } + { + // Init PVD + pvd = PxCreatePvd(*_foundation); + PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate("127.0.0.1", 5425, 100); + //PxPvdTransport* transport = PxDefaultPvdFileTransportCreate("D:\\physx_sample.pxd2"); + if (transport) + { + const bool isConnected = pvd->connect(*transport, PxPvdInstrumentationFlag::eALL); + if (isConnected) + { + LOG(Info, "Connected to PhysX Visual Debugger (PVD)"); + } + } + CPVD = pvd; + } #endif - // Init top-level PhysX objects + // Init PhysX CPhysX = PxCreatePhysics(PX_PHYSICS_VERSION, *_foundation, ToleranceScale, false, pvd); CHECK_INIT(CPhysX, "PxCreatePhysics failed!"); - // Init Extensions + // Init extensions const bool extensionsInit = PxInitExtensions(*CPhysX, pvd); CHECK_INIT(extensionsInit, "PxInitExtensions failed!"); -#if WITH_VEHICLE - PxInitVehicleSDK(*Physics); -#endif + // Init collision cooking #if COMPILE_WITH_PHYSICS_COOKING - #if !USE_EDITOR if (settings.SupportCookingAtRuntime) #endif { - // Init cooking PxCookingParams cookingParams(ToleranceScale); cookingParams.meshWeldTolerance = 0.1f; // Weld to 1mm precision cookingParams.meshPreprocessParams = PxMeshPreprocessingFlags(PxMeshPreprocessingFlag::eWELD_VERTICES); Cooking = PxCreateCooking(PX_PHYSICS_VERSION, *_foundation, cookingParams); CHECK_INIT(Cooking, "PxCreateCooking failed!"); } - #endif // Create scene description PxSceneDesc sceneDesc(CPhysX->getTolerancesScale()); sceneDesc.gravity = C2P(settings.DefaultGravity); sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS; - //sceneDesc.flags |= PxSceneFlag::eEXCLUDE_KINEMATICS_FROM_ACTIVE_ACTORS; // TODO: set it? if (!settings.DisableCCD) sceneDesc.flags |= PxSceneFlag::eENABLE_CCD; if (settings.EnableAdaptiveForce) sceneDesc.flags |= PxSceneFlag::eADAPTIVE_FORCE; sceneDesc.simulationEventCallback = &EventsCallback; - sceneDesc.filterShader = PhysiXFilterShader; + sceneDesc.filterShader = FilterShader; sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity; if (sceneDesc.cpuDispatcher == nullptr) { @@ -407,6 +444,17 @@ bool PhysicsService::Init() // Create scene PhysicsScene = CPhysX->createScene(sceneDesc); CHECK_INIT(PhysicsScene, "createScene failed!"); +#if WITH_PVD + auto pvdClient = PhysicsScene->getScenePvdClient(); + if (pvdClient) + { + pvdClient->setScenePvdFlags(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS | PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES | PxPvdSceneFlag::eTRANSMIT_CONTACTS); + } + else + { + LOG(Info, "Missing PVD client scene."); + } +#endif // Init characters controller ControllerManager = PxCreateControllerManager(*PhysicsScene); @@ -428,10 +476,24 @@ void PhysicsService::Dispose() { // Ensure to finish (wait for simulation end) Physics::CollectResults(); - - // Cleanup if (CPhysX) Physics::FlushRequests(); + +#if WITH_VEHICLE + if (VehicleSDKInitialized) + { + VehicleSDKInitialized = false; + PxCloseVehicleSDK(); + } + RELEASE_PHYSX(WheelRaycastBatchQuery); + RELEASE_PHYSX(WheelTireFrictions); + WheelQueryResults.Resize(0); + WheelHitResults.Resize(0); + WheelVehiclesResultsPerWheel.Resize(0); + WheelVehiclesResultsPerVehicle.Resize(0); +#endif + + // Cleanup RELEASE_PHYSX(DefaultMaterial); SAFE_DELETE(Stepper); Allocator::Free(ScratchMemory); @@ -463,15 +525,12 @@ void PhysicsService::Dispose() if (CPhysX) { -#if WITH_VEHICLE - PxCloseVehicleSDK(); -#endif PxCloseExtensions(); } RELEASE_PHYSX(CPhysX); #if WITH_PVD - RELEASE_PHYSX(CPVD); + RELEASE_PHYSX(CPVD); #endif RELEASE_PHYSX(_foundation); } @@ -571,6 +630,7 @@ void Physics::Simulate(float dt) if (Stepper->advance(PhysicsScene, dt, ScratchMemory, SCRATCH_BLOCK_SIZE) == false) return; EventsCallback.Clear(); + LastDeltaTime = dt; // TODO: move this call after rendering done Stepper->renderDone(); @@ -578,9 +638,9 @@ void Physics::Simulate(float dt) void Physics::CollectResults() { - ASSERT(IsInMainThread()); if (!_isDuringSimulation) return; + ASSERT(IsInMainThread()); ASSERT(CPhysX && Stepper); { @@ -590,6 +650,210 @@ void Physics::CollectResults() Stepper->wait(PhysicsScene); } +#if WITH_VEHICLE + if (WheelVehicles.HasItems()) + { + PROFILE_CPU_NAMED("Physics.Vehicles"); + + // Update vehicles steering + WheelVehiclesCache.Clear(); + WheelVehiclesCache.EnsureCapacity(WheelVehicles.Count()); + int32 wheelsCount = 0; + for (auto wheelVehicle : WheelVehicles) + { + auto drive = (PxVehicleWheels*)wheelVehicle->_drive; + ASSERT(drive); + WheelVehiclesCache.Add(drive); + wheelsCount += drive->mWheelsSimData.getNbWheels(); + + float throttle = wheelVehicle->_throttle; + float brake = wheelVehicle->_brake; + if (wheelVehicle->UseReverseAsBrake) + { + const float invalidDirectionThreshold = 80.0f; + const float breakThreshold = 8.0f; + const float forwardSpeed = wheelVehicle->GetForwardSpeed(); + + // Automatic gear change when changing driving direction + if (Math::Abs(forwardSpeed) < invalidDirectionThreshold) + { + if (throttle < -ZeroTolerance && wheelVehicle->GetCurrentGear() >= 0 && wheelVehicle->GetTargetGear() >= 0) + { + wheelVehicle->SetCurrentGear(-1); + } + else if (throttle > ZeroTolerance && wheelVehicle->GetCurrentGear() <= 0 && wheelVehicle->GetTargetGear() <= 0) + { + wheelVehicle->SetCurrentGear(1); + } + } + + // Automatic break when changing driving direction + if (throttle > 0.0f) + { + if (forwardSpeed < -invalidDirectionThreshold) + { + brake = 1.0f; + } + } + else if (throttle < 0.0f) + { + if (forwardSpeed > invalidDirectionThreshold) + { + brake = 1.0f; + } + } + else + { + if (forwardSpeed < breakThreshold && forwardSpeed > -breakThreshold) + { + brake = 1.0f; + } + } + + // Block throttle if user is changing driving direction + if ((throttle > 0.0f && wheelVehicle->GetTargetGear() < 0) || (throttle < 0.0f && wheelVehicle->GetTargetGear() > 0)) + { + throttle = 0.0f; + } + + throttle = Math::Abs(throttle); + } + else + { + throttle = Math::Max(throttle, 0.0f); + } + // @formatter:off + // Reference: PhysX SDK docs + // TODO: expose input control smoothing data + static constexpr PxVehiclePadSmoothingData padSmoothing = + { + { + 6.0f, // rise rate eANALOG_INPUT_ACCEL + 6.0f, // rise rate eANALOG_INPUT_BRAKE + 12.0f, // rise rate eANALOG_INPUT_HANDBRAKE + 2.5f, // rise rate eANALOG_INPUT_STEER_LEFT + 2.5f, // rise rate eANALOG_INPUT_STEER_RIGHT + }, + { + 10.0f, // fall rate eANALOG_INPUT_ACCEL + 10.0f, // fall rate eANALOG_INPUT_BRAKE + 12.0f, // fall rate eANALOG_INPUT_HANDBRAKE + 5.0f, // fall rate eANALOG_INPUT_STEER_LEFT + 5.0f, // fall rate eANALOG_INPUT_STEER_RIGHT + } + }; + // Reference: PhysX SDK docs + // TODO: expose steer vs forward curve into per-vehicle (up to 8 points, values clamped into 0/1 range) + static constexpr PxF32 steerVsForwardSpeedData[] = + { + 0.0f, 1.0f, + 20.0f, 0.9f, + 65.0f, 0.8f, + 120.0f, 0.7f, + PX_MAX_F32, PX_MAX_F32, + PX_MAX_F32, PX_MAX_F32, + PX_MAX_F32, PX_MAX_F32, + PX_MAX_F32, PX_MAX_F32, + }; + const PxFixedSizeLookupTable<8> steerVsForwardSpeed(steerVsForwardSpeedData, 4); + // @formatter:on + switch (wheelVehicle->_driveTypeCurrent) + { + case WheeledVehicle::DriveTypes::Drive4W: + { + PxVehicleDrive4WRawInputData rawInputData; + rawInputData.setAnalogAccel(throttle); + rawInputData.setAnalogBrake(brake); + rawInputData.setAnalogSteer(wheelVehicle->_steering); + rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); + PxVehicleDrive4WSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive); + break; + } + case WheeledVehicle::DriveTypes::DriveNW: + { + PxVehicleDriveNWRawInputData rawInputData; + rawInputData.setAnalogAccel(throttle); + rawInputData.setAnalogBrake(brake); + rawInputData.setAnalogSteer(wheelVehicle->_steering); + rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); + PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive); + break; + } + } + } + + // Update batches queries cache + if (wheelsCount > WheelQueryResults.Count()) + { + if (WheelRaycastBatchQuery) + WheelRaycastBatchQuery->release(); + WheelQueryResults.Resize(wheelsCount); + WheelQueryResults.Resize(WheelQueryResults.Capacity()); + PxBatchQueryDesc desc(wheelsCount, 0, 0); + desc.queryMemory.userRaycastResultBuffer = WheelQueryResults.Get(); + desc.queryMemory.userRaycastTouchBuffer = WheelHitResults.Get(); + desc.queryMemory.raycastTouchBufferSize = wheelsCount; + desc.preFilterShader = WheelRaycastPreFilter; + WheelRaycastBatchQuery = PhysicsScene->createBatchQuery(desc); + } + + // TODO: expose vehicle tires configuration + if (!WheelTireFrictions) + { + PxVehicleDrivableSurfaceType surfaceTypes[1]; + surfaceTypes[0].mType = 0; + const PxMaterial* surfaceMaterials[1]; + surfaceMaterials[0] = DefaultMaterial; + WheelTireFrictions = PxVehicleDrivableSurfaceToTireFrictionPairs::allocate(1, 1); + WheelTireFrictions->setup(1, 1, surfaceMaterials, surfaceTypes); + WheelTireFrictions->setTypePairFriction(0, 0, 5.0f); + } + + // Setup cache for wheel states + WheelVehiclesResultsPerVehicle.Resize(WheelVehicles.Count(), false); + WheelVehiclesResultsPerWheel.Resize(wheelsCount, false); + wheelsCount = 0; + for (int32 i = 0; i < WheelVehicles.Count(); i++) + { + auto drive = (PxVehicleWheels*)WheelVehicles[i]->_drive; + auto& perVehicle = WheelVehiclesResultsPerVehicle[i]; + perVehicle.nbWheelQueryResults = drive->mWheelsSimData.getNbWheels(); + perVehicle.wheelQueryResults = WheelVehiclesResultsPerWheel.Get() + wheelsCount; + wheelsCount += perVehicle.nbWheelQueryResults; + } + + // Update vehicles + PxVehicleSuspensionRaycasts(WheelRaycastBatchQuery, WheelVehiclesCache.Count(), WheelVehiclesCache.Get(), WheelQueryResults.Count(), WheelQueryResults.Get()); + PxVehicleUpdates(LastDeltaTime, PhysicsScene->getGravity(), *WheelTireFrictions, WheelVehiclesCache.Count(), WheelVehiclesCache.Get(), WheelVehiclesResultsPerVehicle.Get()); + + // Synchronize state + for (int32 i = 0; i < WheelVehicles.Count(); i++) + { + auto wheelVehicle = WheelVehicles[i]; + auto drive = WheelVehiclesCache[i]; + auto& perVehicle = WheelVehiclesResultsPerVehicle[i]; + + // Update wheels + for (int32 j = 0; j < wheelVehicle->_wheelsData.Count(); j++) + { + auto& wheelData = wheelVehicle->_wheelsData[j]; + auto& perWheel = perVehicle.wheelQueryResults[j]; + + auto& state = wheelData.State; + state.IsInAir = perWheel.isInAir; + state.TireContactCollider = perWheel.tireContactShape ? static_cast(perWheel.tireContactShape->userData) : nullptr; + state.TireContactPoint = P2C(perWheel.tireContactPoint); + state.TireContactNormal = P2C(perWheel.tireContactNormal); + state.TireFriction = perWheel.tireFriction; + + const float wheelRotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); + const float wheelSteerAngle = RadiansToDegrees * perWheel.steerAngle; + wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, wheelSteerAngle, wheelRotationAngle) * wheelData.LocalOrientation); + } + } + } +#endif + { PROFILE_CPU_NAMED("Physics.FlushActiveTransforms"); diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index 05b64cb0f..55ff70f31 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -11,6 +11,7 @@ class PhysicsColliderActor; class Joint; class Collider; +class CollisionData; /// /// Raycast hit result data. @@ -67,9 +68,8 @@ API_CLASS(Static) class FLAXENGINE_API Physics DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics); /// - /// Gets master physics object + /// Gets the master physics object. /// - /// Physics object static PxPhysics* GetPhysics(); #if COMPILE_WITH_PHYSICS_COOKING @@ -77,7 +77,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics); /// /// Gets physics cooking object /// - /// Physics cooking object static PxCooking* GetCooking(); #endif @@ -85,37 +84,31 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics); /// /// Gets PhysX scene object /// - /// Scene object static PxScene* GetScene(); /// /// Gets PhysX characters controller manager object /// - /// Controller manager object static PxControllerManager* GetControllerManager(); /// /// Gets the physics tolerances scale. /// - /// The tolerances scale. static PxTolerancesScale* GetTolerancesScale(); /// /// Gets the default query filter callback used for the scene queries. /// - /// The query filter callback. static PxQueryFilterCallback* GetQueryFilterCallback(); /// /// Gets the default query filter callback used for the character controller collisions detection. /// - /// The query filter callback. static PxQueryFilterCallback* GetCharacterQueryFilterCallback(); /// /// Gets the default physical material. /// - /// The native material resource. static PxMaterial* GetDefaultMaterial(); public: @@ -276,6 +269,94 @@ public: /// True if sphere hits an matching object, otherwise false. API_FUNCTION() static bool SphereCastAll(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) Array& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + /// + /// Performs a sweep test against objects in the scene using a capsule geometry. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The normalized direction in which cast a capsule. + /// The capsule rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule hits an matching object, otherwise false. + API_FUNCTION() static bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a capsule geometry. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The normalized direction in which cast a capsule. + /// The result hit information. Valid only when method returns true. + /// The capsule rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule hits an matching object, otherwise false. + API_FUNCTION() static bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a capsule geometry. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The normalized direction in which cast a capsule. + /// The result hits. Valid only when method returns true. + /// The capsule rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule hits an matching object, otherwise false. + API_FUNCTION() static bool CapsuleCastAll(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The normalized direction in which cast a convex mesh. + /// The convex mesh rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh hits an matching object, otherwise false. + API_FUNCTION() static bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The normalized direction in which cast a convex mesh. + /// The result hit information. Valid only when method returns true. + /// The convex mesh rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh hits an matching object, otherwise false. + API_FUNCTION() static bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The normalized direction in which cast a convex mesh. + /// The result hits. Valid only when method returns true. + /// The convex mesh rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh hits an matching object, otherwise false. + API_FUNCTION() static bool ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + /// /// Checks whether the given box overlaps with other colliders or not. /// @@ -297,6 +378,30 @@ public: /// True if sphere overlaps any matching object, otherwise false. API_FUNCTION() static bool CheckSphere(const Vector3& center, float radius, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + /// + /// Checks whether the given capsule overlaps with other colliders or not. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The capsule rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule overlaps any matching object, otherwise false. + API_FUNCTION() static bool CheckCapsule(const Vector3& center, float radius, float height, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Checks whether the given convex mesh overlaps with other colliders or not. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The convex mesh rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh overlaps any matching object, otherwise false. + API_FUNCTION() static bool CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + /// /// Finds all colliders touching or inside of the given box. /// @@ -320,6 +425,32 @@ public: /// True if sphere overlaps any matching object, otherwise false. API_FUNCTION() static bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + /// + /// Finds all colliders touching or inside of the given capsule. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The result colliders that overlap with the given capsule. Valid only when method returns true. + /// The capsule rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule overlaps any matching object, otherwise false. + API_FUNCTION() static bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The result colliders that overlap with the given convex mesh. Valid only when method returns true. + /// The convex mesh rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh overlaps any matching object, otherwise false. + API_FUNCTION() static bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + /// /// Finds all colliders touching or inside of the given box. /// @@ -343,6 +474,32 @@ public: /// True if sphere overlaps any matching object, otherwise false. API_FUNCTION() static bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + /// + /// Finds all colliders touching or inside of the given capsule. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The result colliders that overlap with the given capsule. Valid only when method returns true. + /// The capsule rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule overlaps any matching object, otherwise false. + API_FUNCTION() static bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The result colliders that overlap with the given convex mesh. Valid only when method returns true. + /// The convex mesh rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh overlaps any matching object, otherwise false. + API_FUNCTION() static bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + public: /// diff --git a/Source/Engine/Physics/SimulationEventCallback.h b/Source/Engine/Physics/SimulationEventCallback.h index 9e0e3393e..00f610717 100644 --- a/Source/Engine/Physics/SimulationEventCallback.h +++ b/Source/Engine/Physics/SimulationEventCallback.h @@ -4,8 +4,9 @@ #include "Types.h" #include "Collisions.h" -#include "Engine/Core/Types/Pair.h" #include "Colliders/Collider.h" +#include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Collections/Dictionary.h" #include /// diff --git a/Source/Engine/Physics/Utilities.h b/Source/Engine/Physics/Utilities.h index 34ee7f66d..b2c3eb0e3 100644 --- a/Source/Engine/Physics/Utilities.h +++ b/Source/Engine/Physics/Utilities.h @@ -74,4 +74,24 @@ inline Vector3 P2C(const PxExtendedVec3& v) #endif } +inline float M2ToCm2(float v) +{ + return v * (100.0f * 100.0f); +} + +inline float Cm2ToM2(float v) +{ + return v / (100.0f * 100.0f); +} + +inline float RpmToRadPerS(float v) +{ + return v * (PI / 30.0f); +} + +inline float RadPerSToRpm(float v) +{ + return v * (30.0f / PI); +} + extern PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled); diff --git a/Source/Engine/Platform/Android/AndroidFileSystem.cpp b/Source/Engine/Platform/Android/AndroidFileSystem.cpp index b6186d7df..0d76e34af 100644 --- a/Source/Engine/Platform/Android/AndroidFileSystem.cpp +++ b/Source/Engine/Platform/Android/AndroidFileSystem.cpp @@ -6,6 +6,7 @@ #include "Engine/Platform/File.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Math/Math.h" #include "Engine/Engine/Globals.h" #include "Engine/Utilities/StringConverter.h" diff --git a/Source/Engine/Platform/Android/AndroidWindow.cpp b/Source/Engine/Platform/Android/AndroidWindow.cpp index abfee9d13..4d3c4ca67 100644 --- a/Source/Engine/Platform/Android/AndroidWindow.cpp +++ b/Source/Engine/Platform/Android/AndroidWindow.cpp @@ -6,10 +6,14 @@ #include "Engine/Graphics/RenderTask.h" #include +#define DefaultDPI 96 + AndroidWindow::AndroidWindow(const CreateWindowSettings& settings) : WindowBase(settings) { _clientSize = settings.Size; + _dpi = DefaultDPI; + _dpiScale = (float)_dpi / (float)DefaultDPI; } AndroidWindow::~AndroidWindow() diff --git a/Source/Engine/Platform/Base/FileBase.cpp b/Source/Engine/Platform/Base/FileBase.cpp index d7308b0fd..9ff9149db 100644 --- a/Source/Engine/Platform/Base/FileBase.cpp +++ b/Source/Engine/Platform/Base/FileBase.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Types/DataContainer.h" +#include "Engine/Core/Log.h" bool FileBase::ReadAllBytes(const StringView& path, byte* data, int32 length) { @@ -36,16 +37,23 @@ bool FileBase::ReadAllBytes(const StringView& path, Array& data) auto file = File::Open(path, FileMode::OpenExisting, FileAccess::Read, FileShare::All); if (file) { - uint32 size = file->GetSize(); - data.Resize(size, false); - if (size > 0) + const uint32 size = file->GetSize(); + if (size < MAX_int32) { - result = file->Read(data.Get(), size) != 0; + data.Resize(size, false); + if (size > 0) + { + result = file->Read(data.Get(), size) != 0; + } + else + { + data.Resize(0, false); + result = false; + } } else { - data.Resize(0, false); - result = false; + LOG(Error, "Failed to load file {0}. It's too big. Size: {1} MB", path, size / (1024 * 1024)); } Delete(file); @@ -61,16 +69,23 @@ bool FileBase::ReadAllBytes(const StringView& path, DataContainer& data) auto file = File::Open(path, FileMode::OpenExisting, FileAccess::Read, FileShare::All); if (file) { - uint32 size = file->GetSize(); - data.Allocate(size); - if (size > 0) + const uint32 size = file->GetSize(); + if (size < MAX_int32) { - result = file->Read(data.Get(), size) != 0; + data.Allocate(size); + if (size > 0) + { + result = file->Read(data.Get(), size) != 0; + } + else + { + data.Release(); + result = false; + } } else { - data.Release(); - result = false; + LOG(Error, "Failed to load file {0}. It's too big. Size: {1} MB", path, size / (1024 * 1024)); } Delete(file); diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 4060bfee6..55218b657 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -12,22 +12,12 @@ bool NetworkBase::DestroySocket(NetworkSocket& socket) return true; } -bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) -{ - return true; -} - bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) { return true; } -bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) -{ - return true; -} - -bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32& value) { return true; } @@ -57,7 +47,7 @@ bool NetworkBase::IsReadable(NetworkSocket& socket) return true; } -bool NetworkBase::IsWriteable(NetworkSocket& socket) +bool NetworkBase::IsWritable(NetworkSocket& socket) { return true; } @@ -94,7 +84,6 @@ bool NetworkBase::GetSocketFromGroup(NetworkSocketGroup& group, uint32 index, Ne void NetworkBase::RemoveSocketFromGroup(NetworkSocketGroup& group, uint32 index) { - } bool NetworkBase::RemoveSocketFromGroup(NetworkSocketGroup& group, NetworkSocket& socket) @@ -104,7 +93,6 @@ bool NetworkBase::RemoveSocketFromGroup(NetworkSocketGroup& group, NetworkSocket void NetworkBase::ClearGroup(NetworkSocketGroup& group) { - group.Count = 0; } int32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) @@ -117,7 +105,7 @@ int32 NetworkBase::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferS return -1; } -bool NetworkBase::CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +bool NetworkBase::CreateEndPoint(const String& address, const String& port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) { return true; } diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 5d382ab02..8615f31ac 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -4,50 +4,65 @@ #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Core/Types/String.h" + API_INJECT_CPP_CODE("#include \"Engine/Platform/Network.h\""); -#define SOCKGROUP_ITEMSIZE 16 - -enum class FLAXENGINE_API NetworkProtocol +/// +/// Network connection protocol type. +/// +API_ENUM() enum class NetworkProtocol { /// Not specified. Undefined, /// User Datagram Protocol. Udp, /// Transmission Control Protocol. - Tcp + Tcp, }; -enum class FLAXENGINE_API NetworkIPVersion +/// +/// IP version type. +/// +API_ENUM() enum class NetworkIPVersion { /// Not specified. Undefined, /// Internet Protocol version 4. IPv4, /// Internet Protocol version 6. - IPv6 + IPv6, }; -struct FLAXENGINE_API NetworkSocket +/// +/// Network socket. +/// +API_STRUCT() struct FLAXENGINE_API NetworkSocket { - NetworkProtocol Protocol = NetworkProtocol::Undefined; - NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; - byte Data[8] = {}; +DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkSocketGroup) + + /// Socket protocol type. + API_FIELD() NetworkProtocol Protocol = NetworkProtocol::Undefined; + /// Socket address IP version. + API_FIELD() NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; + API_FIELD(Private, NoArray) byte Data[8] = {}; }; -struct FLAXENGINE_API NetworkAddress +/// +/// Network end-point. +/// +API_STRUCT() struct FLAXENGINE_API NetworkEndPoint { - String Address; - String Port; +DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkSocketGroup) + + /// End-point IP version. + API_FIELD(ReadOnly) NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; + API_FIELD(Private, NoArray) byte Data[28] = {}; }; -struct FLAXENGINE_API NetworkEndPoint -{ - NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; - byte Data[28] = {}; -}; - -enum class FLAXENGINE_API NetworkSocketOption +/// +/// Network socket options. +/// +API_ENUM() enum class NetworkSocketOption { /// Enables debugging info recording. Debug, @@ -81,29 +96,53 @@ enum class FLAXENGINE_API NetworkSocketOption IPv6Only, /// Retrieve the current path MTU, the socket must be connected UDP/TCP. Mtu, - // Socket type, DGRAM, STREAM .. - Type + /// Socket type, DGRAM, STREAM .. + Type, }; -struct FLAXENGINE_API NetworkSocketState +/// +/// Network socket state. +/// +API_ENUM() enum class NetworkSocketState { - bool Error = false; - bool Invalid = false; - bool Disconnected = false; - bool Readable = false; - bool Writeable = false; + /// Nothing. + None = 0, + /// Socket error. + Error = 1 << 0, + /// Invalid request. + Invalid = 1 << 1, + /// Socket disconnected. + Disconnected = 1 << 2, + /// Socket is readable. + Readable = 1 << 3, + /// Socket is writable. + Writeable = 1 << 4, }; -struct FLAXENGINE_API NetworkSocketGroup +DECLARE_ENUM_OPERATORS(NetworkSocketState); + +/// +/// Network sockets group. +/// +API_STRUCT() struct FLAXENGINE_API NetworkSocketGroup { - uint32 Count = 0; - uint32 Capacity = 0; - byte *Data; +DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkSocketGroup) + + /// Group size. + API_FIELD(ReadOnly) uint32 Count = 0; + /// Group capacity. + API_FIELD(ReadOnly) uint32 Capacity = 0; + API_FIELD(Private) byte* Data = nullptr; }; -class FLAXENGINE_API NetworkBase +/// +/// Low-level networking implementation interface with Berkeley sockets. +/// +API_CLASS(Static, Name="Network") class FLAXENGINE_API NetworkBase { public: + static struct FLAXENGINE_API ScriptingTypeInitializer TypeInitializer; + /// /// Creates a new native socket. /// @@ -111,14 +150,14 @@ public: /// The protocol. /// The ip version. /// Returns true on error, otherwise false. - static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); + API_FUNCTION() static bool CreateSocket(API_PARAM(Ref) NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); /// /// Closes native socket. /// /// The socket. /// Returns true on error, otherwise false. - static bool DestroySocket(NetworkSocket& socket); + API_FUNCTION() static bool DestroySocket(API_PARAM(Ref) NetworkSocket& socket); /// /// Sets the specified socket option. @@ -127,16 +166,7 @@ public: /// The option. /// The value. /// Returns true on error, otherwise false. - static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); - - /// - /// Sets the specified socket option. - /// - /// The socket. - /// The option. - /// The value. - /// Returns true on error, otherwise false. - static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + API_FUNCTION() static bool SetSocketOption(API_PARAM(Ref) NetworkSocket& socket, NetworkSocketOption option, int32 value); /// /// Gets the specified socket option. @@ -145,16 +175,7 @@ public: /// The option. /// The returned value. /// Returns true on error, otherwise false. - static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); - - /// - /// Gets the specified socket option. - /// - /// The socket. - /// The option. - /// The returned value. - /// Returns true on error, otherwise false. - static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + API_FUNCTION() static bool GetSocketOption(API_PARAM(Ref) NetworkSocket& socket, NetworkSocketOption option, API_PARAM(Out) int32& value); /// /// Connects a socket to the specified end point. @@ -162,7 +183,7 @@ public: /// The socket. /// The end point. /// Returns true on error, otherwise false. - static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + API_FUNCTION() static bool ConnectSocket(API_PARAM(Ref) NetworkSocket& socket, API_PARAM(Ref) NetworkEndPoint& endPoint); /// /// Binds a socket to the specified end point. @@ -170,7 +191,7 @@ public: /// The socket. /// The end point. /// Returns true on error, otherwise false. - static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + API_FUNCTION() static bool BindSocket(API_PARAM(Ref) NetworkSocket& socket, API_PARAM(Ref) NetworkEndPoint& endPoint); /// /// Listens for incoming connection. @@ -178,30 +199,30 @@ public: /// The socket. /// Pending connection queue size. /// Returns true on error, otherwise false. - static bool Listen(NetworkSocket& socket, uint16 queueSize); + API_FUNCTION() static bool Listen(API_PARAM(Ref) NetworkSocket& socket, uint16 queueSize); /// /// Accepts a pending connection. /// - /// The socket. - /// The newly connected socket. + /// The socket. + /// The newly connected socket. /// The end point of the new socket. /// Returns true on error, otherwise false. - static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + API_FUNCTION() static bool Accept(API_PARAM(Ref) NetworkSocket& serverSocket, API_PARAM(Ref) NetworkSocket& newSocket, API_PARAM(Out) NetworkEndPoint& newEndPoint); /// /// Checks for socket readability. /// /// The socket. /// Returns true when data is available. Otherwise false. - static bool IsReadable(NetworkSocket& socket); + API_FUNCTION() static bool IsReadable(API_PARAM(Ref) NetworkSocket& socket); /// /// Checks for socket writeability. /// /// The socket. /// Returns true when data can be written. Otherwise false. - static bool IsWriteable(NetworkSocket& socket); + API_FUNCTION() static bool IsWritable(API_PARAM(Ref) NetworkSocket& socket); /// /// Creates a socket group. It allocate memory based on the desired capacity. @@ -209,21 +230,21 @@ public: /// The group capacity (fixed). /// The group. /// Returns true on error, otherwise false. - static bool CreateSocketGroup(uint32 capacity, NetworkSocketGroup& group); + API_FUNCTION() static bool CreateSocketGroup(uint32 capacity, API_PARAM(Out) NetworkSocketGroup& group); /// /// Destroy the socket group, and free the allocated memory. /// /// The group. /// Returns true if the group is already destroyed, otherwise false. - static bool DestroySocketGroup(NetworkSocketGroup& group); - + API_FUNCTION() static bool DestroySocketGroup(API_PARAM(Ref) NetworkSocketGroup& group); + /// /// Updates sockets states. /// /// The sockets group. /// Returns -1 on error, The number of elements where states are nonzero, otherwise 0. - static int32 Poll(NetworkSocketGroup& group); + API_FUNCTION() static int32 Poll(API_PARAM(Ref) NetworkSocketGroup& group); /// /// Retrieves socket state. @@ -232,7 +253,7 @@ public: /// The socket index in group. /// The returned state. /// Returns true on error, otherwise false. - static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + API_FUNCTION() static bool GetSocketState(API_PARAM(Ref) NetworkSocketGroup& group, uint32 index, API_PARAM(Out) NetworkSocketState& state); /// /// Adds a socket to a group. @@ -240,24 +261,23 @@ public: /// The group. /// The socket. /// Returns the socket index in group or -1 on error. - static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + API_FUNCTION() static int32 AddSocketToGroup(API_PARAM(Ref) NetworkSocketGroup& group, API_PARAM(Ref) NetworkSocket& socket); /// - /// Gets a socket by index. - /// Some data like socket IPVersion might be undefined. + /// Gets a socket by index. Some data like socket IPVersion might be undefined. /// /// The group. /// The index. /// The returned socket. /// Returns true on error, otherwise false. - static bool GetSocketFromGroup(NetworkSocketGroup& group, uint32 index, NetworkSocket* socket); + API_FUNCTION() static bool GetSocketFromGroup(API_PARAM(Ref) NetworkSocketGroup& group, uint32 index, API_PARAM(Out) NetworkSocket* socket); /// /// Removes the socket at the specified index. /// /// The group. /// The index. - static void RemoveSocketFromGroup(NetworkSocketGroup& group, uint32 index); + API_FUNCTION() static void RemoveSocketFromGroup(API_PARAM(Ref) NetworkSocketGroup& group, uint32 index); /// /// Removes the socket if present. @@ -265,13 +285,13 @@ public: /// The group. /// The socket. /// Returns true if the socket is not found, otherwise false. - static bool RemoveSocketFromGroup(NetworkSocketGroup& group, NetworkSocket& socket); - + API_FUNCTION() static bool RemoveSocketFromGroup(API_PARAM(Ref) NetworkSocketGroup& group, API_PARAM(Ref) NetworkSocket& socket); + /// /// Clears the socket group. /// /// The group. - static void ClearGroup(NetworkSocketGroup& group); + API_FUNCTION() static void ClearGroup(API_PARAM(Ref) NetworkSocketGroup& group); /// /// Writes data to the socket. @@ -279,9 +299,9 @@ public: /// The socket. /// The data to write. /// The length of data. - /// If protocol is UDP , the destination end point. Otherwise nullptr. + /// If protocol is UDP, the destination end point. Otherwise nullptr. /// Returns -1 on error, otherwise bytes written. - static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + API_FUNCTION() static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); /// /// Reads data on the socket. @@ -291,22 +311,23 @@ public: /// Size of the buffer. /// If UDP, the end point from where data is coming. Otherwise nullptr. /// Returns -1 on error, otherwise bytes read. - static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + API_FUNCTION() static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); /// /// Creates an end point. /// - /// The address. + /// The network address. + /// The network port. /// The ip version. /// The created end point. /// True if the end point will be connected or binded. /// Returns true on error, otherwise false. - static bool CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = true); + API_FUNCTION() static bool CreateEndPoint(const String& address, const String& port, NetworkIPVersion ipv, API_PARAM(Out) NetworkEndPoint& endPoint, bool bindable = true); /// /// Remaps an ipv4 end point to an ipv6 one. /// /// The ipv4 end point. /// The ipv6 end point. - static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); + API_FUNCTION() static NetworkEndPoint RemapEndPointToIPv6(API_PARAM(Ref) NetworkEndPoint& endPoint); }; diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index e33ae1499..f6aa0e2d6 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -7,17 +7,19 @@ #include "Engine/Platform/FileSystem.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Math/Rectangle.h" #include "Engine/Core/Utilities.h" #if COMPILE_WITH_PROFILER -#include "Engine/Profiler/ProfilerMemory.h" +#include "Engine/Profiler/ProfilerCPU.h" #endif #include "Engine/Threading/Threading.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Engine/Engine.h" +#include "Engine/Engine/Globals.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Platform/BatteryInfo.h" #include @@ -163,6 +165,44 @@ void PlatformBase::Exit() { } +#if COMPILE_WITH_PROFILER + +void PlatformBase::OnMemoryAlloc(void* ptr, uint64 size) +{ + if (!ptr) + return; + +#if TRACY_ENABLE + // Track memory allocation in Tracy + //tracy::Profiler::MemAlloc(ptr, size, false); + tracy::Profiler::MemAllocCallstack(ptr, size, 12, false); +#endif + + // Register allocation during the current CPU event + auto thread = ProfilerCPU::GetCurrentThread(); + if (thread != nullptr && thread->Buffer.GetCount() != 0) + { + auto& activeEvent = thread->Buffer.Last().Event(); + if (activeEvent.End < ZeroTolerance) + { + activeEvent.NativeMemoryAllocation += (int32)size; + } + } +} + +void PlatformBase::OnMemoryFree(void* ptr) +{ + if (!ptr) + return; + +#if TRACY_ENABLE + // Track memory allocation in Tracy + tracy::Profiler::MemFree(ptr, false); +#endif +} + +#endif + void* PlatformBase::AllocatePages(uint64 numPages, uint64 pageSize) { // Fallback to the default memory allocation @@ -259,7 +299,7 @@ void PlatformBase::Fatal(const Char* msg, void* context) } // Create separate folder with crash info - const String crashDataFolder = StringUtils::GetDirectoryName(Log::Logger::LogFilePath) / TEXT("Crash_") + StringUtils::GetFileNameWithoutExtension(Log::Logger::LogFilePath).Substring(4); + const String crashDataFolder = String(StringUtils::GetDirectoryName(Log::Logger::LogFilePath)) / TEXT("Crash_") + StringUtils::GetFileNameWithoutExtension(Log::Logger::LogFilePath).Substring(4); FileSystem::CreateDirectory(crashDataFolder); // Capture the platform-dependant crash info (eg. memory dump) @@ -458,15 +498,6 @@ Vector2 PlatformBase::GetVirtualDesktopSize() return Platform::GetVirtualDesktopBounds().Size; } -#if COMPILE_WITH_PROFILER - -void PlatformBase::TrackAllocation(uint64 size) -{ - ProfilerMemory::OnAllocation((uint32)size, false); -} - -#endif - void PlatformBase::GetEnvironmentVariables(Dictionary& result) { // Not supported diff --git a/Source/Engine/Platform/Base/PlatformBase.cs b/Source/Engine/Platform/Base/PlatformBase.cs deleted file mode 100644 index ee0ca5724..000000000 --- a/Source/Engine/Platform/Base/PlatformBase.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -namespace FlaxEngine -{ - partial class Platform - { - /// - /// Checks if current execution in on the main thread. - /// - /// True if running on the main thread, otherwise false. - public static bool IsInMainThread => CurrentThreadID == Globals.MainThreadID; - } -} diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index c6c99a2c6..a1a857451 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -299,7 +299,8 @@ public: static void Prefetch(void const* ptr) = delete; #if COMPILE_WITH_PROFILER - static void TrackAllocation(uint64 size); + static void OnMemoryAlloc(void* ptr, uint64 size); + static void OnMemoryFree(void* ptr); #endif /// @@ -569,17 +570,17 @@ public: API_PROPERTY() static BatteryInfo GetBatteryInfo(); /// - /// Gets the screen DPI setting. + /// Gets the primary monitor's DPI setting. /// API_PROPERTY() static int32 GetDpi(); /// - /// Gets the screen DPI setting scale factor (1 is default). Includes custom DPI scale. + /// Gets the primary monitor's DPI setting scale factor (1 is default). Includes custom DPI scale. /// API_PROPERTY() static float GetDpiScale(); /// - /// The custom screen DPI scale factor to apply globally. Can be used to adjust the User Interface scale (resolution). + /// The custom DPI scale factor to apply globally. Can be used to adjust the User Interface scale (resolution). /// API_FIELD() static float CustomDpiScale; diff --git a/Source/Engine/Platform/Base/StringUtilsBase.cpp b/Source/Engine/Platform/Base/StringUtilsBase.cpp index b492abc9b..bf90282e8 100644 --- a/Source/Engine/Platform/Base/StringUtilsBase.cpp +++ b/Source/Engine/Platform/Base/StringUtilsBase.cpp @@ -289,20 +289,19 @@ void RemoveLongPathPrefix(const String& path, String& result) result.Remove(2, 6); } -String StringUtils::GetDirectoryName(const String& path) +StringView StringUtils::GetDirectoryName(const StringView& path) { const int32 lastFrontSlash = path.FindLast('\\'); const int32 lastBackSlash = path.FindLast('/'); const int32 splitIndex = Math::Max(lastBackSlash, lastFrontSlash); - return splitIndex != INVALID_INDEX ? path.Left(splitIndex) : String::Empty; + return splitIndex != INVALID_INDEX ? path.Left(splitIndex) : StringView::Empty; } -String StringUtils::GetFileName(const String& path) +StringView StringUtils::GetFileName(const StringView& path) { Char chr; const int32 length = path.Length(); int32 num = length; - do { num--; @@ -310,28 +309,23 @@ String StringUtils::GetFileName(const String& path) return path; chr = path[num]; } while (chr != DirectorySeparatorChar && chr != AltDirectorySeparatorChar && chr != VolumeSeparatorChar); - return path.Substring(num + 1, length - num - 1); } -String StringUtils::GetFileNameWithoutExtension(const String& path) +StringView StringUtils::GetFileNameWithoutExtension(const StringView& path) { - String filename = GetFileName(path); + StringView filename = GetFileName(path); const int32 num = filename.FindLast('.'); if (num != -1) - { return filename.Substring(0, num); - } return filename; } -String StringUtils::GetPathWithoutExtension(const String& path) +StringView StringUtils::GetPathWithoutExtension(const StringView& path) { const int32 num = path.FindLast('.'); if (num != -1) - { return path.Substring(0, num); - } return path; } @@ -371,7 +365,7 @@ void StringUtils::PathRemoveRelativeParts(String& path) } } - bool isRooted = path.StartsWith(TEXT('/')); + const bool isRooted = path.StartsWith(TEXT('/')); path.Clear(); for (auto& e : stack) path /= e; @@ -379,7 +373,7 @@ void StringUtils::PathRemoveRelativeParts(String& path) path.Insert(0, TEXT("/")); } -const char digit_pairs[201] = { +const char DigitPairs[201] = { "00010203040506070809" "10111213141516171819" "20212223242526272829" @@ -416,21 +410,17 @@ String StringUtils::ToString(int32 value) { char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE]; char* it = &buf[STRING_UTILS_ITOSTR_BUFFER_SIZE - 2]; - int32 div = value / 100; - if (value >= 0) { while (div) { - Platform::MemoryCopy(it, &digit_pairs[2 * (value - div * 100)], 2); + Platform::MemoryCopy(it, &DigitPairs[2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } - - Platform::MemoryCopy(it, &digit_pairs[2 * value], 2); - + Platform::MemoryCopy(it, &DigitPairs[2 * value], 2); if (value < 10) it++; } @@ -438,20 +428,16 @@ String StringUtils::ToString(int32 value) { while (div) { - Platform::MemoryCopy(it, &digit_pairs[-2 * (value - div * 100)], 2); + Platform::MemoryCopy(it, &DigitPairs[-2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } - - Platform::MemoryCopy(it, &digit_pairs[-2 * value], 2); - + Platform::MemoryCopy(it, &DigitPairs[-2 * value], 2); if (value <= -10) it--; - *it = '-'; } - return String(it, (int32)(&buf[STRING_UTILS_ITOSTR_BUFFER_SIZE] - it)); } @@ -459,21 +445,17 @@ String StringUtils::ToString(int64 value) { char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE]; char* it = &buf[STRING_UTILS_ITOSTR_BUFFER_SIZE - 2]; - int64 div = value / 100; - if (value >= 0) { while (div) { - Platform::MemoryCopy(it, &digit_pairs[2 * (value - div * 100)], 2); + Platform::MemoryCopy(it, &DigitPairs[2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } - - Platform::MemoryCopy(it, &digit_pairs[2 * value], 2); - + Platform::MemoryCopy(it, &DigitPairs[2 * value], 2); if (value < 10) it++; } @@ -481,20 +463,16 @@ String StringUtils::ToString(int64 value) { while (div) { - Platform::MemoryCopy(it, &digit_pairs[-2 * (value - div * 100)], 2); + Platform::MemoryCopy(it, &DigitPairs[-2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } - - Platform::MemoryCopy(it, &digit_pairs[-2 * value], 2); - + Platform::MemoryCopy(it, &DigitPairs[-2 * value], 2); if (value <= -10) it--; - *it = '-'; } - return String(it, (int32)(&buf[STRING_UTILS_ITOSTR_BUFFER_SIZE] - it)); } @@ -502,21 +480,17 @@ String StringUtils::ToString(uint32 value) { char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE]; char* it = &buf[STRING_UTILS_ITOSTR_BUFFER_SIZE - 2]; - int32 div = value / 100; while (div) { - Platform::MemoryCopy(it, &digit_pairs[2 * (value - div * 100)], 2); + Platform::MemoryCopy(it, &DigitPairs[2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } - - Platform::MemoryCopy(it, &digit_pairs[2 * value], 2); - + Platform::MemoryCopy(it, &DigitPairs[2 * value], 2); if (value < 10) it++; - return String((char*)it, (int32)((char*)&buf[STRING_UTILS_ITOSTR_BUFFER_SIZE] - (char*)it)); } @@ -524,21 +498,17 @@ String StringUtils::ToString(uint64 value) { char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE]; char* it = &buf[STRING_UTILS_ITOSTR_BUFFER_SIZE - 2]; - int64 div = value / 100; while (div) { - Platform::MemoryCopy(it, &digit_pairs[2 * (value - div * 100)], 2); + Platform::MemoryCopy(it, &DigitPairs[2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } - - Platform::MemoryCopy(it, &digit_pairs[2 * value], 2); - + Platform::MemoryCopy(it, &DigitPairs[2 * value], 2); if (value < 10) it++; - return String((char*)it, (int32)((char*)&buf[STRING_UTILS_ITOSTR_BUFFER_SIZE] - (char*)it)); } diff --git a/Source/Engine/Platform/Base/ThreadBase.cpp b/Source/Engine/Platform/Base/ThreadBase.cpp index 41f5f0a57..ddcba4f7a 100644 --- a/Source/Engine/Platform/Base/ThreadBase.cpp +++ b/Source/Engine/Platform/Base/ThreadBase.cpp @@ -4,6 +4,10 @@ #include "Engine/Threading/IRunnable.h" #include "Engine/Threading/ThreadRegistry.h" #include "Engine/Core/Log.h" +#if TRACY_ENABLE +#include "Engine/Core/Math/Math.h" +#include +#endif Delegate ThreadBase::ThreadStarting; Delegate ThreadBase::ThreadExiting; @@ -70,6 +74,13 @@ int32 ThreadBase::Run() ASSERT(_runnable); const auto thread = static_cast(this); _id = Platform::GetCurrentThreadID(); +#if TRACY_ENABLE + char threadName[100]; + const int32 threadNameLength = Math::Min(ARRAY_COUNT(threadName) - 1, _name.Length()); + StringUtils::ConvertUTF162ANSI(*_name, threadName, threadNameLength); + threadName[threadNameLength] = 0; + tracy::SetThreadName(threadName); +#endif ThreadRegistry::Add(thread); ThreadStarting(thread); int32 exitCode = 1; diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index a8215bc8b..b432a8a4d 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -281,6 +281,8 @@ protected: String _title; CursorType _cursor; Vector2 _clientSize; + int _dpi; + float _dpiScale; Vector2 _trackingMouseOffset; bool _isUsingMouseOffset; @@ -542,6 +544,21 @@ public: /// The screen space position. API_FUNCTION() virtual Vector2 ClientToScreen(const Vector2& clientPos) const = 0; + /// + /// Gets the window DPI setting. + /// + API_PROPERTY() int GetDpi() const + { + return _dpi; + } + + /// + /// Gets the window DPI scale factor (1 is default). Includes custom DPI scale + /// + API_PROPERTY() float GetDpiScale() const + { + return Platform::CustomDpiScale * _dpiScale; + } public: /// diff --git a/Source/Engine/Platform/Base/WindowsManager.cpp b/Source/Engine/Platform/Base/WindowsManager.cpp index 36b88e0ca..ef2d50b71 100644 --- a/Source/Engine/Platform/Base/WindowsManager.cpp +++ b/Source/Engine/Platform/Base/WindowsManager.cpp @@ -3,7 +3,6 @@ #include "../WindowsManager.h" #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" -#include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Engine/EngineService.h" class WindowsManagerService : public EngineService diff --git a/Source/Engine/Platform/IGuiData.h b/Source/Engine/Platform/IGuiData.h index a285d62cb..05cc3c6f8 100644 --- a/Source/Engine/Platform/IGuiData.h +++ b/Source/Engine/Platform/IGuiData.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Types/String.h" +#include "Engine/Core/Collections/Array.h" /// /// Interface for GUI data object container. diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index ffe91a700..08119e338 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -6,6 +6,7 @@ #include "Engine/Platform/File.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Math/Math.h" #include "Engine/Utilities/StringConverter.h" #include diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index a49d655dd..90bd1784f 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -20,6 +20,7 @@ #include "Engine/Platform/MessageBox.h" #include "Engine/Platform/WindowsManager.h" #include "Engine/Platform/Clipboard.h" +#include "Engine/Platform/IGuiData.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Threading/Threading.h" #include "Engine/Engine/Engine.h" @@ -58,6 +59,15 @@ X11::Display* xDisplay = nullptr; X11::XIM IM = nullptr; X11::XIC IC = nullptr; X11::Atom xAtomDeleteWindow; +X11::Atom xAtomXdndEnter; +X11::Atom xAtomXdndPosition; +X11::Atom xAtomXdndLeave; +X11::Atom xAtomXdndDrop; +X11::Atom xAtomXdndActionCopy; +X11::Atom xAtomXdndStatus; +X11::Atom xAtomXdndSelection; +X11::Atom xAtomXdndFinished; +X11::Atom xAtomXdndAware; X11::Atom xAtomWmState; X11::Atom xAtomWmStateHidden; X11::Atom xAtomWmStateMaxVert; @@ -65,6 +75,11 @@ X11::Atom xAtomWmStateMaxHorz; X11::Atom xAtomWmWindowOpacity; X11::Atom xAtomWmName; X11::Atom xAtomClipboard; +X11::Atom xDnDRequested = 0; +X11::Window xDndSourceWindow = 0; +DragDropEffect xDndResult; +Vector2 xDndPos; +int32 xDnDVersion = 0; int32 SystemDpi = 96; X11::Cursor Cursors[(int32)CursorType::MAX]; X11::XcursorImage* CursorsImg[(int32)CursorType::MAX]; @@ -1171,6 +1186,13 @@ public: } }; +struct Property +{ + unsigned char* data; + int format, nitems; + X11::Atom type; +}; + namespace Impl { LinuxKeyboard Keyboard; @@ -1222,6 +1244,407 @@ namespace Impl } } } + + Property ReadProperty(X11::Display* display, X11::Window window, X11::Atom property) + { + X11::Atom readType = 0; + int readFormat = 0; + unsigned long nitems = 0; + unsigned long readBytes = 0; + unsigned char* result = nullptr; + int bytesCount = 1024; + if (property != 0) + { + do + { + if (result != nullptr) + X11::XFree(result); + XGetWindowProperty(display, window, property, 0, bytesCount, 0, AnyPropertyType, &readType, &readFormat, &nitems, &readBytes, &result); + bytesCount *= 2; + } while (readBytes != 0); + } + Property p = { result, readFormat, (int)nitems, readType }; + return p; + } + + static X11::Atom SelectTargetFromList(X11::Display* display, const char* targetType, X11::Atom* list, int count) + { + for (int i = 0; i < count; i++) + { + X11::Atom atom = list[i]; + if (atom != 0 && StringAnsi(XGetAtomName(display, atom)) == targetType) + return atom; + } + return 0; + } + + static X11::Atom SelectTargetFromAtoms(X11::Display* display, const char* targetType, X11::Atom t1, X11::Atom t2, X11::Atom t3) + { + if (t1 != 0 && StringAnsi(XGetAtomName(display, t1)) == targetType) + return t1; + if (t2 != 0 && StringAnsi(XGetAtomName(display, t2)) == targetType) + return t2; + if (t3 != 0 && StringAnsi(XGetAtomName(display, t3)) == targetType) + return t3; + return 0; + } + + static X11::Window FindAppWindow(X11::Display* display, X11::Window w) + { + int nprops, i = 0; + X11::Atom* a; + if (w == 0) + return 0; + a = X11::XListProperties(display, w, &nprops); + for (i = 0; i < nprops; i++) + { + if (a[i] == xAtomXdndAware) + break; + } + if (nprops) + X11::XFree(a); + if (i != nprops) + return w; + X11::Window child, wtmp; + int tmp; + unsigned int utmp; + X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp); + return FindAppWindow(display, child); + } +} + +class LinuxDropFilesData : public IGuiData +{ +public: + Array Files; + + Type GetType() const override + { + return Type::Files; + } + String GetAsText() const override + { + return String::Empty; + } + void GetAsFiles(Array* files) const override + { + files->Add(Files); + } +}; + +class LinuxDropTextData : public IGuiData +{ +public: + StringView Text; + + Type GetType() const override + { + return Type::Text; + } + String GetAsText() const override + { + return String(Text); + } + void GetAsFiles(Array* files) const override + { + } +}; + +DragDropEffect LinuxWindow::DoDragDrop(const StringView& data) +{ + auto cursorWrong = X11::XCreateFontCursor(xDisplay, 54); + auto cursorTransient = X11::XCreateFontCursor(xDisplay, 24); + auto cursorGood = X11::XCreateFontCursor(xDisplay, 4); + Array> formats; + formats.Add(X11::XInternAtom(xDisplay, "text/plain", 0)); + formats.Add(X11::XInternAtom(xDisplay, "TEXT", 0)); + formats.Add((X11::Atom)31); + StringAnsi dataAnsi(data); + LinuxDropTextData dropData; + dropData.Text = data; + + // Begin dragging + auto screen = X11::XDefaultScreen(xDisplay); + auto rootWindow = X11::XRootWindow(xDisplay, screen); + if (X11::XGrabPointer(xDisplay, _window, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess) + return DragDropEffect::None; + X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, _window, CurrentTime); + + // Process events + X11::XEvent event; + enum Status + { + Unaware, + Unreceptive, + CanDrop, + }; + int status = Unaware, previousVersion = -1; + X11::Window previousWindow = 0; + DragDropEffect result = DragDropEffect::None; + float lastDraw = Platform::GetTimeSeconds(); + float startTime = lastDraw; + while (true) + { + X11::XNextEvent(xDisplay, &event); + + if (event.type == SelectionClear) + break; + if (event.type == SelectionRequest) + { + // Extract the relavent data + X11::Window owner = event.xselectionrequest.owner; + X11::Atom selection = event.xselectionrequest.selection; + X11::Atom target = event.xselectionrequest.target; + X11::Atom property = event.xselectionrequest.property; + X11::Window requestor = event.xselectionrequest.requestor; + X11::Time timestamp = event.xselectionrequest.time; + X11::Display* disp = event.xselection.display; + X11::XEvent s; + s.xselection.type = SelectionNotify; + s.xselection.requestor = requestor; + s.xselection.selection = selection; + s.xselection.target = target; + s.xselection.property = 0; + s.xselection.time = timestamp; + if (target == X11::XInternAtom(disp, "TARGETS", 0)) + { + Array targets; + targets.Add(target); + targets.Add(X11::XInternAtom(disp, "MULTIPLE", 0)); + targets.Add(formats.Get(), formats.Count()); + X11::XChangeProperty(disp, requestor, property, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)targets.Get(), targets.Count()); + s.xselection.property = property; + } + else if (formats.Contains(target)) + { + s.xselection.property = property; + X11::XChangeProperty(disp, requestor, property, target, 8, PropModeReplace, reinterpret_cast(dataAnsi.Get()), dataAnsi.Length()); + } + X11::XSendEvent(event.xselection.display, event.xselectionrequest.requestor, 1, 0, &s); + } + else if (event.type == MotionNotify) + { + // Find window under mouse + auto window = Impl::FindAppWindow(xDisplay, rootWindow); + int fmt, version = -1; + X11::Atom atmp; + unsigned long nitems, bytesLeft; + unsigned char* data = nullptr; + if (window == previousWindow) + version = previousVersion; + else if(window == 0) + ; + else if (X11::XGetWindowProperty(xDisplay, window, xAtomXdndAware, 0, 2, 0, AnyPropertyType, &atmp, &fmt, &nitems, &bytesLeft, &data) != Success) + continue; + else if (data == 0) + continue; + else if (fmt != 32) + continue; + else if (nitems != 1) + continue; + else + version = data[0]; + if (status == Unaware && version != -1) + status = Unreceptive; + else if(version == -1) + status = Unaware; + xDndPos = Vector2((float)event.xmotion.x_root, (float)event.xmotion.y_root); + + // Update mouse grab + if (status == Unaware) + X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorWrong, CurrentTime); + else if(status == Unreceptive) + X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorTransient, CurrentTime); + else + X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorGood, CurrentTime); + + if (window != previousWindow && previousVersion != -1) + { + // Send drag left event + auto ww = WindowsManager::GetByNativePtr((void*)previousWindow); + if (ww) + { + ww->_dragOver = false; + ww->OnDragLeave(); + } + else + { + X11::XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = previousWindow; + m.message_type = xAtomXdndLeave; + m.format = 32; + m.data.l[0] = _window; + m.data.l[1] = 0; + m.data.l[2] = 0; + m.data.l[3] = 0; + m.data.l[4] = 0; + X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m); + X11::XFlush(xDisplay); + } + } + + if (window != previousWindow && version != -1) + { + // Send drag enter event + auto ww = WindowsManager::GetByNativePtr((void*)window); + if (ww) + { + xDndPos = ww->ScreenToClient(Platform::GetMousePosition()); + xDndResult = DragDropEffect::None; + ww->OnDragEnter(&dropData, xDndPos, xDndResult); + } + else + { + X11::XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = window; + m.message_type = xAtomXdndEnter; + m.format = 32; + m.data.l[0] = _window; + m.data.l[1] = Math::Min(5, version) << 24 | (formats.Count() > 3); + m.data.l[2] = formats.Count() > 0 ? formats[0] : 0; + m.data.l[3] = formats.Count() > 1 ? formats[1] : 0; + m.data.l[4] = formats.Count() > 2 ? formats[2] : 0; + X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m); + X11::XFlush(xDisplay); + } + } + + if (version != -1) + { + // Send position event + auto ww = WindowsManager::GetByNativePtr((void*)window); + if (ww) + { + xDndPos = ww->ScreenToClient(Platform::GetMousePosition()); + ww->_dragOver = true; + xDndResult = DragDropEffect::None; + ww->OnDragOver(&dropData, xDndPos, xDndResult); + status = CanDrop; + } + else + { + int x, y, tmp; + unsigned int utmp; + X11::Window wtmp; + X11::XQueryPointer(xDisplay, window, &wtmp, &wtmp, &tmp, &tmp, &x, &y, &utmp); + X11::XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = window; + m.message_type = xAtomXdndPosition; + m.format = 32; + m.data.l[0] = _window; + m.data.l[1] = 0; + m.data.l[2] = (x << 16) | y; + m.data.l[3] = CurrentTime; + m.data.l[4] = xAtomXdndActionCopy; + X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m); + X11::XFlush(xDisplay); + } + } + + previousWindow = window; + previousVersion = version; + } + else if (event.type == ClientMessage && event.xclient.message_type == xAtomXdndStatus) + { + if ((event.xclient.data.l[1]&1) && status != Unaware) + status = CanDrop; + if (!(event.xclient.data.l[1]&1) && status != Unaware) + status = Unreceptive; + } + else if (event.type == ButtonRelease && event.xbutton.button == Button1) + { + if (status == CanDrop) + { + // Send drop event + auto ww = WindowsManager::GetByNativePtr((void*)previousWindow); + if (ww) + { + xDndPos = ww->ScreenToClient(Platform::GetMousePosition()); + xDndResult = DragDropEffect::None; + ww->OnDragDrop(&dropData, xDndPos, xDndResult); + ww->Focus(); + result = xDndResult; + } + else + { + X11::XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = previousWindow; + m.message_type = xAtomXdndDrop; + m.format = 32; + m.data.l[0] = _window; + m.data.l[1] = 0; + m.data.l[2] = CurrentTime; + m.data.l[3] = 0; + m.data.l[4] = 0; + X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m); + X11::XFlush(xDisplay); + result = DragDropEffect::Copy; + } + } + break; + } + + // Redraw + const float time = Platform::GetTimeSeconds(); + if (time - lastDraw >= 1.0f / 20.0f) + { + lastDraw = time; + Engine::OnDraw(); + } + + // Prevent dead-loop + if (time - startTime >= 10.0f) + { + break; + } + } + + // Drag end + if (previousWindow != 0 && previousVersion != -1) + { + // Send drag left event + auto ww = WindowsManager::GetByNativePtr((void*)previousWindow); + if (ww) + { + ww->_dragOver = false; + ww->OnDragLeave(); + } + else + { + X11::XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = previousWindow; + m.message_type = xAtomXdndLeave; + m.format = 32; + m.data.l[0] = _window; + m.data.l[1] = 0; + m.data.l[2] = 0; + m.data.l[3] = 0; + m.data.l[4] = 0; + X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m); + X11::XFlush(xDisplay); + } + } + + // End grabbing + X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, 0, CurrentTime); + XUngrabPointer(xDisplay, CurrentTime); + + return result; } void LinuxClipboard::Clear() @@ -1608,7 +2031,9 @@ bool LinuxPlatform::Init() char buffer[UNIX_APP_BUFF_SIZE]; // Get user locale string - char* locale = setlocale(LC_ALL, NULL); + const char* locale = setlocale(LC_ALL, NULL); + if (strcmp(locale, "C") == 0) + locale = ""; UserLocale = String(locale); // Get computer name string @@ -1651,7 +2076,15 @@ bool LinuxPlatform::Init() } xAtomDeleteWindow = X11::XInternAtom(xDisplay, "WM_DELETE_WINDOW", 0); - xAtomWmState = X11::XInternAtom(xDisplay, "_NET_WM_STATE", 0); + xAtomXdndEnter = X11::XInternAtom(xDisplay, "XdndEnter", 0); + xAtomXdndPosition = X11::XInternAtom(xDisplay, "XdndPosition", 0); + xAtomXdndLeave = X11::XInternAtom(xDisplay, "XdndLeave", 0); + xAtomXdndDrop = X11::XInternAtom(xDisplay, "XdndDrop", 0); + xAtomXdndActionCopy = X11::XInternAtom(xDisplay, "XdndActionCopy", 0); + xAtomXdndStatus = X11::XInternAtom(xDisplay, "XdndStatus", 0); + xAtomXdndSelection = X11::XInternAtom(xDisplay, "XdndSelection", 0); + xAtomXdndFinished = X11::XInternAtom(xDisplay, "XdndFinished", 0); + xAtomXdndAware = X11::XInternAtom(xDisplay, "XdndAware", 0); xAtomWmStateHidden = X11::XInternAtom(xDisplay, "_NET_WM_STATE_HIDDEN", 0); xAtomWmStateMaxHorz = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", 0); xAtomWmStateMaxVert = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", 0); @@ -1799,6 +2232,93 @@ void LinuxPlatform::Tick() window->Close(ClosingReason::User); } } + else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndEnter) + { + // Drag&drop enter + X11::Window source = event.xclient.data.l[0]; + xDnDVersion = (int32)(event.xclient.data.l[1] >> 24); + const char* targetTypeFiles = "text/uri-list"; + if (event.xclient.data.l[1] & 1) + { + Property p = Impl::ReadProperty(xDisplay, source, XInternAtom(xDisplay, "XdndTypeList", 0)); + xDnDRequested = Impl::SelectTargetFromList(xDisplay, targetTypeFiles, (X11::Atom*)p.data, p.nitems); + X11::XFree(p.data); + } + else + { + xDnDRequested = Impl::SelectTargetFromAtoms(xDisplay, targetTypeFiles, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]); + } + } + else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndPosition) + { + // Drag&drop move + X11::XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = event.xclient.data.l[0]; + m.message_type = xAtomXdndStatus; + m.format = 32; + m.data.l[0] = event.xany.window; + m.data.l[1] = (xDnDRequested != 0); + m.data.l[2] = 0; + m.data.l[3] = 0; + m.data.l[4] = xAtomXdndActionCopy; + X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m); + X11::XFlush(xDisplay); + xDndPos = Vector2((float)(event.xclient.data.l[2] >> 16), (float)(event.xclient.data.l[2] & 0xffff)); + window = WindowsManager::GetByNativePtr((void*)event.xany.window); + if (window) + { + LinuxDropFilesData dropData; + xDndResult = DragDropEffect::None; + if (window->_dragOver) + { + window->OnDragEnter(&dropData, xDndPos, xDndResult); + } + else + { + window->_dragOver = true; + window->OnDragOver(&dropData, xDndPos, xDndResult); + } + } + } + else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndLeave) + { + window = WindowsManager::GetByNativePtr((void*)event.xany.window); + if (window && window->_dragOver) + { + window->_dragOver = false; + window->OnDragLeave(); + } + } + else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndDrop) + { + auto w = event.xany.window; + if (xDnDRequested != 0) + { + xDndSourceWindow = event.xclient.data.l[0]; + auto primary = XInternAtom(xDisplay, "PRIMARY", 0); + if (xDnDVersion >= 1) + XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, primary, w, event.xclient.data.l[2]); + else + XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, primary, w, CurrentTime); + } + else + { + X11::XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event.xclient.display; + m.window = event.xclient.data.l[0]; + m.message_type = xAtomXdndFinished; + m.format = 32; + m.data.l[0] = w; + m.data.l[1] = 0; + m.data.l[2] = 0; + X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m); + } + } break; case MapNotify: // Auto-focus shown windows @@ -1980,6 +2500,41 @@ void LinuxPlatform::Tick() X11::XSendEvent(xDisplay, ev.requestor, 0, 0, (X11::XEvent*)&ev); break; } + case SelectionNotify: + if (event.xselection.target == xDnDRequested) + { + // Drag&drop + window = WindowsManager::GetByNativePtr((void*)event.xany.window); + if (window) + { + Property p = Impl::ReadProperty(xDisplay, event.xany.window, X11::XInternAtom(xDisplay, "PRIMARY", 0)); + if (xDndResult != DragDropEffect::None) + { + LinuxDropFilesData dropData; + const String filesList((const char*)p.data); + filesList.Split('\n', dropData.Files); + for (auto& e : dropData.Files) + { + e.Replace(TEXT("file://"), TEXT("")); + e = e.TrimTrailing(); + } + xDndResult = DragDropEffect::None; + window->OnDragDrop(&dropData, xDndPos, xDndResult); + } + } + X11::XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = xDisplay; + m.window = xDndSourceWindow; + m.message_type = xAtomXdndFinished; + m.format = 32; + m.data.l[0] = event.xany.window; + m.data.l[1] = 1; + m.data.l[2] = xAtomXdndActionCopy; + XSendEvent(xDisplay, xDndSourceWindow, 0, NoEventMask, (X11::XEvent*)&m); + } + break; default: break; } diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 32a511834..04389eb29 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -31,6 +31,7 @@ #define _NET_WM_STATE_REMOVE 0L // remove/unset property #define _NET_WM_STATE_ADD 1L // add/set property #define _NET_WM_STATE_TOGGLE 2L // toggle property +#define DefaultDPI 96 // Window routines function prolog #define LINUX_WINDOW_PROLOG X11::Display* display = (X11::Display*)LinuxPlatform::GetXDisplay(); X11::Window window = (X11::Window)_window @@ -102,7 +103,6 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) bool Fullscreen; bool AllowMinimize; bool AllowMaximize; - bool AllowDragAndDrop; */ const X11::Window window = X11::XCreateWindow( @@ -150,7 +150,10 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) X11::XSetTransientForHint(display, window, (X11::Window)((LinuxWindow*)settings.Parent)->GetNativePtr()); } - // Set events mask + _dpi = Platform::GetDpi(); + _dpiScale = (float)_dpi / (float)DefaultDPI; + + // Set input mask long eventMask = ExposureMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | @@ -219,6 +222,15 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) } X11::XChangeProperty(display, window, wmState, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)states, statesCount); + // Drag&drop support + if (settings.AllowDragAndDrop) + { + auto xdndVersion = 5; + auto xdndAware = XInternAtom(display, "XdndAware", 0); + if (xdndAware != 0) + X11::XChangeProperty(display, window, xdndAware, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)&xdndVersion, 1); + } + // Sync X11::XFlush(display); X11::XSync(display, 0); @@ -721,12 +733,6 @@ void LinuxWindow::SetTitle(const StringView& title) _title = title; } -DragDropEffect LinuxWindow::DoDragDrop(const StringView& data) -{ - // TODO: impl drag and drop on Linux - return DragDropEffect::None; -} - void LinuxWindow::StartTrackingMouse(bool useMouseScreenOffset) { // TODO: impl this diff --git a/Source/Engine/Platform/Linux/LinuxWindow.h b/Source/Engine/Platform/Linux/LinuxWindow.h index 845fd587e..24a3d94a2 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.h +++ b/Source/Engine/Platform/Linux/LinuxWindow.h @@ -13,14 +13,13 @@ class LinuxWindow : public WindowBase { friend LinuxPlatform; - public: typedef unsigned long HandleType; private: - bool _resizeDisabled, _focusOnMapped = false; + bool _resizeDisabled, _focusOnMapped = false, _dragOver = false; float _opacity = 1.0f; HandleType _window; diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h index ec91013de..b1f5746ac 100644 --- a/Source/Engine/Platform/Network.h +++ b/Source/Engine/Platform/Network.h @@ -7,13 +7,13 @@ #elif PLATFORM_UWP #include "Win32/Win32Network.h" #elif PLATFORM_LINUX -#include "Base/NetworkBase.h" +#include "Unix/UnixNetwork.h" #elif PLATFORM_PS4 #include "Base/NetworkBase.h" #elif PLATFORM_XBOX_SCARLETT #include "Win32/Win32Network.h" #elif PLATFORM_ANDROID -#include "Base/NetworkBase.h" +#include "Unix/UnixNetwork.h" #elif PLATFORM_SWITCH #include "Base/NetworkBase.h" #else diff --git a/Source/Engine/Platform/Platform.cs b/Source/Engine/Platform/Platform.cs new file mode 100644 index 000000000..118e326fc --- /dev/null +++ b/Source/Engine/Platform/Platform.cs @@ -0,0 +1,79 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + partial class Platform + { + /// + /// Checks if current execution in on the main thread. + /// + public static bool IsInMainThread => CurrentThreadID == Globals.MainThreadID; + } + + partial class Network + { + /// + /// Writes data to the socket. + /// + /// The socket. + /// The data to write. + /// Returns -1 on error, otherwise bytes written. + [Unmanaged] + public static unsafe int WriteSocket(NetworkSocket socket, byte[] data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + fixed (byte* ptr = data) + return Internal_WriteSocket(ref socket, ptr, (uint)data.Length, null); + } + + /// + /// Writes data to the socket. + /// + /// The socket. + /// The data to write. + /// If protocol is UDP, the destination end point. + /// Returns -1 on error, otherwise bytes written. + [Unmanaged] + public static unsafe int WriteSocket(NetworkSocket socket, byte[] data, NetworkEndPoint endPoint) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + fixed (byte* ptr = data) + return Internal_WriteSocket(ref socket, ptr, (uint)data.Length, &endPoint); + } + + /// + /// Reads data on the socket. + /// + /// The socket. + /// The buffer. + /// Returns -1 on error, otherwise bytes read. + [Unmanaged] + public static unsafe int ReadSocket(NetworkSocket socket, byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + fixed (byte* ptr = buffer) + return Internal_ReadSocket(ref socket, ptr, (uint)buffer.Length, null); + } + + /// + /// Reads data on the socket. + /// + /// The socket. + /// The buffer. + /// If UDP, the end point from where data is coming. Otherwise nullptr. + /// Returns -1 on error, otherwise bytes read. + [Unmanaged] + public static unsafe int ReadSocket(NetworkSocket socket, byte[] buffer, NetworkEndPoint endPoint) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + fixed (byte* ptr = buffer) + return Internal_ReadSocket(ref socket, ptr, (uint)buffer.Length, &endPoint); + } + } +} diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h index 7e0b7b422..101049b8e 100644 --- a/Source/Engine/Platform/StringUtils.h +++ b/Source/Engine/Platform/StringUtils.h @@ -207,19 +207,19 @@ public: // Returns the directory name of the specified path string // @param path The path string from which to obtain the directory name // @returns Directory name - static String GetDirectoryName(const String& path); + static StringView GetDirectoryName(const StringView& path); // Returns the file name and extension of the specified path string // @param path The path string from which to obtain the file name and extension // @returns File name with extension - static String GetFileName(const String& path); + static StringView GetFileName(const StringView& path); // Returns the file name without extension of the specified path string // @param path The path string from which to obtain the file name // @returns File name without extension - static String GetFileNameWithoutExtension(const String& path); + static StringView GetFileNameWithoutExtension(const StringView& path); - static String GetPathWithoutExtension(const String& path); + static StringView GetPathWithoutExtension(const StringView& path); static void PathRemoveRelativeParts(String& path); diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index 2eadf565b..2fff3089d 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -68,8 +68,8 @@ class LinuxThread; typedef LinuxThread Thread; class LinuxWindow; typedef LinuxWindow Window; -class NetworkBase; -typedef NetworkBase Network; +class UnixNetwork; +typedef UnixNetwork Network; #elif PLATFORM_PS4 @@ -137,8 +137,8 @@ class AndroidThread; typedef AndroidThread Thread; class AndroidWindow; typedef AndroidWindow Window; -class NetworkBase; -typedef NetworkBase Network; +class UnixNetwork; +typedef UnixNetwork Network; #elif PLATFORM_SWITCH diff --git a/Source/Engine/Platform/UWP/UWPPlatformImpl.h b/Source/Engine/Platform/UWP/UWPPlatformImpl.h index f636f9983..99d18d67f 100644 --- a/Source/Engine/Platform/UWP/UWPPlatformImpl.h +++ b/Source/Engine/Platform/UWP/UWPPlatformImpl.h @@ -121,7 +121,7 @@ public: virtual void SetMousePosition(float x, float y) = 0; virtual void GetMousePosition(float* x, float* y) = 0; virtual void GetBounds(float* x, float* y, float* width, float* height) = 0; - virtual void GetDpi(float* dpi) = 0; + virtual void GetDpi(int* dpi) = 0; virtual void GetTitle(wchar_t* buffer, int bufferLength) = 0; virtual void SetTitle(const wchar_t* title) = 0; virtual int GetGamepadsCount() = 0; diff --git a/Source/Engine/Platform/UWP/UWPWindow.cpp b/Source/Engine/Platform/UWP/UWPWindow.cpp index dfd03b297..ba509b089 100644 --- a/Source/Engine/Platform/UWP/UWPWindow.cpp +++ b/Source/Engine/Platform/UWP/UWPWindow.cpp @@ -474,7 +474,7 @@ void UWPWindow::onDpiChanged(float dpi) { if (_dpi != dpi) { - _dpi = dpi; + _dpi = (int)dpi; _dpiScale = _dpi / 96.0f; // When the display DPI changes, the logical size of the window (measured in Dips) also changes and needs to be updated @@ -584,8 +584,8 @@ void UWPWindow::onPointerExited(UWPWindowImpl::PointerData* pointer) void UWPWindow::OnSizeChange() { // Update the actual rendering output resolution - _clientSize.X = ConvertDipsToPixels(_logicalSize.X, _dpi); - _clientSize.Y = ConvertDipsToPixels(_logicalSize.Y, _dpi); + _clientSize.X = ConvertDipsToPixels(_logicalSize.X, (float)_dpi); + _clientSize.Y = ConvertDipsToPixels(_logicalSize.Y, (float)_dpi); // Check if output has been created if (_swapChain != nullptr) diff --git a/Source/Engine/Platform/UWP/UWPWindow.h b/Source/Engine/Platform/UWP/UWPWindow.h index e687ba32a..f73a046dc 100644 --- a/Source/Engine/Platform/UWP/UWPWindow.h +++ b/Source/Engine/Platform/UWP/UWPWindow.h @@ -100,7 +100,6 @@ private: UWPWindowImpl* _impl; - float _dpi, _dpiScale; Vector2 _logicalSize; public: diff --git a/Source/Engine/Platform/Unix/UnixFile.cpp b/Source/Engine/Platform/Unix/UnixFile.cpp index 1b6516582..cbbd51b57 100644 --- a/Source/Engine/Platform/Unix/UnixFile.cpp +++ b/Source/Engine/Platform/Unix/UnixFile.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Core/Log.h" #if USE_LINUX diff --git a/Source/Engine/Platform/Unix/UnixNetwork.cpp b/Source/Engine/Platform/Unix/UnixNetwork.cpp new file mode 100644 index 000000000..d37b07952 --- /dev/null +++ b/Source/Engine/Platform/Unix/UnixNetwork.cpp @@ -0,0 +1,322 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#if PLATFORM_UNIX + +#include "UnixNetwork.h" +#include "Engine/Core/Log.h" +#include "Engine/Utilities/StringConverter.h" +#include +#include +#include +#include +#include +#include +#include +#include + +struct UnixSocketData +{ + int sockfd; +}; + +static_assert(sizeof(NetworkSocket::Data) >= sizeof(UnixSocketData), "NetworkSocket::Data is not big enough to contains UnixSocketData !"); +static_assert(sizeof(NetworkEndPoint::Data) >= sizeof(sockaddr_in6), "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); + +static int GetAddrSize(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); +} + +static int GetAddrSizeFromEP(NetworkEndPoint& endPoint) +{ + return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); +} + +static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, int32* name) +{ + switch (option) + { +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) +#ifdef SO_USELOOPBACK + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) +#endif + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) +#ifdef TCP_NODELAY + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) +#endif + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + SOCKOPT(NetworkSocketOption::Mtu, IPPROTO_IP, IP_MTU) + SOCKOPT(NetworkSocketOption::Type, SOL_SOCKET, SO_TYPE) +#undef SOCKOPT + default: + *level = 0; + *name = 0; + break; + } +} +static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) +{ + uint32 size = GetAddrSize(*addr); + uint16 port; + void* paddr; + if (addr->sa_family == AF_INET6) + { + paddr = &((sockaddr_in6*)addr)->sin6_addr; + port = ntohs(((sockaddr_in6*)addr)->sin6_port); + } + else if (addr->sa_family == AF_INET) + { + paddr = &((sockaddr_in*)addr)->sin_addr; + port = ntohs(((sockaddr_in*)addr)->sin_port); + } + else + { + LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6! Family : {0}", addr->sa_family); + return true; + } + + char ip[INET6_ADDRSTRLEN]; + if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) + { + LOG(Error, "Unable to extract address from sockaddr!"); + LOG_UNIX_LAST_ERROR; + return true; + } + char strPort[6]; + sprintf(strPort, "%d", port); + endPoint.IPVersion = addr->sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4; + memcpy(endPoint.Data, addr, size); + return false; +} + +bool UnixNetwork::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) +{ + socket.Protocol = proto; + socket.IPVersion = ipv; + const int domain = socket.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; + const int type = socket.Protocol == NetworkProtocol::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const int protocol = socket.Protocol == NetworkProtocol::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + auto& sock = *(UnixSocketData*)&socket.Data; + sock.sockfd = ::socket(domain, type, protocol); + if (sock.sockfd < 0) + { + LOG(Error, "Can't create native socket"); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::DestroySocket(NetworkSocket& socket) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + ::close(sock.sockfd); + return false; +} + +bool UnixNetwork::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) +{ + int32 optlvl = 0; + int32 optnme = 0; + TranslateSockOptToNative(option, &optlvl, &optnme); + auto& sock = *(UnixSocketData*)&socket.Data; + if (setsockopt(sock.sockfd, optlvl, optnme, (char*)&value, sizeof(value)) == -1) + { + LOG(Warning, "Unable to set socket option ! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32& value) +{ + int32 optlvl = 0; + int32 optnme = 0; + TranslateSockOptToNative(option, &optlvl, &optnme); + socklen_t size; + auto& sock = *(UnixSocketData*)&socket.Data; + if (getsockopt(sock.sockfd, optlvl, optnme, (char*)&value, &size) == -1) + { + LOG(Warning, "Unable to get socket option ! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + const int size = GetAddrSizeFromEP(endPoint); + auto& sock = *(UnixSocketData*)&socket.Data; + if (connect(sock.sockfd, (const sockaddr*)endPoint.Data, size) == -1) + { + LOG(Error, "Unable to connect socket to address! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (socket.IPVersion != endPoint.IPVersion) + { + LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", sock.sockfd); + return true; + } + const int size = GetAddrSizeFromEP(endPoint); + if (bind(sock.sockfd, (const sockaddr*)endPoint.Data, size) == -1) + { + LOG(Error, "Unable to bind socket! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::Listen(NetworkSocket& socket, uint16 queueSize) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (listen(sock.sockfd, (int32)queueSize) == -1) + { + LOG(Error, "Unable to listen ! Socket : {0}", sock.sockfd); + return true; + } + return false; +} + +bool UnixNetwork::Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint) +{ + auto& serverSock = *(UnixSocketData*)&serverSocket.Data; + if (serverSocket.Protocol != NetworkProtocol::Tcp) + { + LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", serverSock.sockfd); + return true; + } + sockaddr_in6 addr; + socklen_t size = sizeof(sockaddr_in6); + int sock = accept(serverSock.sockfd, (sockaddr*)&addr, &size); + if (sock < 0) + { + LOG(Warning, "Unable to accept incoming connection! Socket : {0}", serverSock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + auto& newSock = *(UnixSocketData*)&newSocket.Data; + newSock.sockfd = sock; + memcpy(newEndPoint.Data, &addr, size); + newSocket.Protocol = serverSocket.Protocol; + newSocket.IPVersion = serverSocket.IPVersion; + if (CreateEndPointFromAddr((sockaddr*)&addr, newEndPoint)) + return true; + return false; +} + +int32 UnixNetwork::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (endPoint != nullptr && socket.IPVersion != endPoint->IPVersion) + { + LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", sock.sockfd); + return -1; + } + uint32 size; + if (endPoint == nullptr && socket.Protocol == NetworkProtocol::Tcp) + { + if ((size = send(sock.sockfd, (const char*)data, length, 0)) == -1) + { + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + } + else if (endPoint != nullptr && socket.Protocol == NetworkProtocol::Udp) + { + if ((size = sendto(sock.sockfd, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == -1) + { + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + } + else + { + // TODO: better explanation + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + return size; +} + +int32 UnixNetwork::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + uint32 size; + if (endPoint == nullptr) + { + if ((size = recv(sock.sockfd, (char*)buffer, bufferSize, 0)) == -1) + { + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1}", sock.sockfd, bufferSize); + LOG_UNIX_LAST_ERROR; + return -1; + } + } + else + { + socklen_t addrsize = sizeof(sockaddr_in6); + sockaddr_in6 addr; + if ((size = recvfrom(sock.sockfd, (void*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == -1) + { + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1}", sock.sockfd, bufferSize); + return -1; + } + if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) + return true; + } + return size; +} + +bool UnixNetwork::CreateEndPoint(const String& address, const String& port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +{ + int status; + addrinfo hints; + addrinfo* info; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; + hints.ai_flags |= AI_ADDRCONFIG; + hints.ai_flags |= AI_V4MAPPED; + if (bindable) + hints.ai_flags = AI_PASSIVE; + const StringAsANSI<60> addressAnsi(*address, address.Length()); + const StringAsANSI<10> portAnsi(*port, port.Length()); + if ((status = getaddrinfo(address.IsEmpty() ? nullptr : addressAnsi.Get(), port.IsEmpty() ? nullptr : portAnsi.Get(), &hints, &info)) != 0) + { + LOG(Error, "Unable to query info for address : {0}::{1} Error : {2}", address, port, String(gai_strerror(status))); + return true; + } + if (info == nullptr) + { + LOG(Error, "Unable to resolve address! Address : {0}::{1}", address, port); + return true; + } + if (CreateEndPointFromAddr(info->ai_addr, endPoint)) + { + freeaddrinfo(info); + return true; + } + freeaddrinfo(info); + return false; +} + +#endif diff --git a/Source/Engine/Platform/Unix/UnixNetwork.h b/Source/Engine/Platform/Unix/UnixNetwork.h new file mode 100644 index 000000000..4557303a5 --- /dev/null +++ b/Source/Engine/Platform/Unix/UnixNetwork.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_UNIX + +#include "Engine/Platform/Base/NetworkBase.h" + +class FLAXENGINE_API UnixNetwork : public NetworkBase +{ +public: + + // [NetworkBase] + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); + static bool DestroySocket(NetworkSocket& socket); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32& value); + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Listen(NetworkSocket& socket, uint16 queueSize); + static bool Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint); + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static bool CreateEndPoint(const String& address, const String& port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = true); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); +}; + +#endif diff --git a/Source/Engine/Platform/Win32/Win32ConditionVariable.h b/Source/Engine/Platform/Win32/Win32ConditionVariable.h index ad3bdcff0..8273d914d 100644 --- a/Source/Engine/Platform/Win32/Win32ConditionVariable.h +++ b/Source/Engine/Platform/Win32/Win32ConditionVariable.h @@ -5,6 +5,7 @@ #if PLATFORM_WIN32 #include "Win32CriticalSection.h" +#include "Engine/Core/Types/BaseTypes.h" /// /// Win32 implementation of a condition variables. Condition variables are synchronization primitives that enable threads to wait until a particular condition occurs. Condition variables enable threads to atomically release a lock and enter the sleeping state. diff --git a/Source/Engine/Platform/Win32/Win32FileSystem.cpp b/Source/Engine/Platform/Win32/Win32FileSystem.cpp index 5eeb25064..d1f28c6ca 100644 --- a/Source/Engine/Platform/Win32/Win32FileSystem.cpp +++ b/Source/Engine/Platform/Win32/Win32FileSystem.cpp @@ -12,10 +12,19 @@ const DateTime WindowsEpoch(1970, 1, 1); +#define WIN32_INIT_BUFFER(path, buffer) \ + Char buffer[MAX_PATH]; \ + if (path.Length() > MAX_PATH) \ + return true; \ + Platform::MemoryCopy(buffer, path.Get(), path.Length() * sizeof(Char)); \ + buffer[path.Length()] = 0 + bool Win32FileSystem::CreateDirectory(const StringView& path) { + WIN32_INIT_BUFFER(path, buffer); + // If the specified directory name doesn't exist, do our thing - const DWORD fileAttributes = GetFileAttributesW(*path); + const DWORD fileAttributes = GetFileAttributesW(buffer); if (fileAttributes == INVALID_FILE_ATTRIBUTES) { const auto error = GetLastError(); @@ -33,7 +42,7 @@ bool Win32FileSystem::CreateDirectory(const StringView& path) } // Create the last directory on the path (the recursive calls will have taken care of the parent directories by now) - const BOOL result = ::CreateDirectoryW(*path, nullptr); + const BOOL result = ::CreateDirectoryW(buffer, nullptr); if (result == FALSE) { return true; @@ -106,13 +115,14 @@ bool Win32FileSystem::DeleteDirectory(const String& path, bool deleteContents) RemoveDirectoryW(*path); // Check if still exists - const int32 result = GetFileAttributesW(*path); + const DWORD result = GetFileAttributesW(*path); return result != 0xFFFFFFFF && result & FILE_ATTRIBUTE_DIRECTORY; } bool Win32FileSystem::DirectoryExists(const StringView& path) { - const int32 result = GetFileAttributesW(*path); + WIN32_INIT_BUFFER(path, buffer); + const DWORD result = GetFileAttributesW(buffer); return result != 0xFFFFFFFF && result & FILE_ATTRIBUTE_DIRECTORY; } @@ -128,8 +138,8 @@ bool Win32FileSystem::GetChildDirectories(Array& results, const String& // Try to find first file WIN32_FIND_DATA info; String pattern = directory / TEXT('*'); - const HANDLE hp = FindFirstFileW(*pattern, &info); - if (INVALID_HANDLE_VALUE == hp) + const HANDLE handle = FindFirstFileW(*pattern, &info); + if (INVALID_HANDLE_VALUE == handle) { // Check if no files at all return GetLastError() != ERROR_FILE_NOT_FOUND; @@ -147,15 +157,16 @@ bool Win32FileSystem::GetChildDirectories(Array& results, const String& // Add directory results.Add(directory / info.cFileName); } - } while (FindNextFileW(hp, &info) != 0); - FindClose(hp); + } while (FindNextFileW(handle, &info) != 0); + FindClose(handle); return GetLastError() != ERROR_NO_MORE_FILES; } bool Win32FileSystem::FileExists(const StringView& path) { - const uint32 result = GetFileAttributesW(*path); + WIN32_INIT_BUFFER(path, buffer); + const DWORD result = GetFileAttributesW(buffer); return result != 0xFFFFFFFF && !(result & FILE_ATTRIBUTE_DIRECTORY); } @@ -166,8 +177,9 @@ bool Win32FileSystem::DeleteFile(const StringView& path) uint64 Win32FileSystem::GetFileSize(const StringView& path) { + WIN32_INIT_BUFFER(path, buffer); WIN32_FILE_ATTRIBUTE_DATA info; - if (!!GetFileAttributesExW(*path, GetFileExInfoStandard, &info)) + if (!!GetFileAttributesExW(buffer, GetFileExInfoStandard, &info)) { if ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { @@ -182,44 +194,46 @@ uint64 Win32FileSystem::GetFileSize(const StringView& path) bool Win32FileSystem::IsReadOnly(const StringView& path) { - const uint32 result = GetFileAttributesW(*path); - if (result != 0xFFFFFFFF) - { - return !!(result & FILE_ATTRIBUTE_READONLY); - } - return false; + WIN32_INIT_BUFFER(path, buffer); + const DWORD result = GetFileAttributesW(buffer); + return result != 0xFFFFFFFF ? !!(result & FILE_ATTRIBUTE_READONLY) : false; } bool Win32FileSystem::SetReadOnly(const StringView& path, bool isReadOnly) { - return SetFileAttributesW(*path, isReadOnly ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL) == 0; + WIN32_INIT_BUFFER(path, buffer); + return SetFileAttributesW(buffer, isReadOnly ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL) == 0; } bool Win32FileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite) { const DWORD flags = MOVEFILE_COPY_ALLOWED | (overwrite ? MOVEFILE_REPLACE_EXISTING : 0); + WIN32_INIT_BUFFER(dst, bufferDst); + WIN32_INIT_BUFFER(src, bufferSrc); // If paths are almost the same but some characters have different case we need to use a proxy file - if (StringUtils::CompareIgnoreCase(*dst, *src) == 0) + if (dst.Length() == src.Length() && StringUtils::CompareIgnoreCase(*dst, *src) == 0) { String tmp; GetTempFilePath(tmp); - return MoveFileExW(*src, *tmp, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) == 0 || MoveFileExW(*tmp, *dst, flags) == 0; + return MoveFileExW(bufferSrc, *tmp, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) == 0 || MoveFileExW(*tmp, bufferDst, flags) == 0; } - return MoveFileExW(*src, *dst, flags) == 0; + return MoveFileExW(bufferSrc, bufferDst, flags) == 0; } bool Win32FileSystem::CopyFile(const StringView& dst, const StringView& src) { + WIN32_INIT_BUFFER(dst, bufferDst); + WIN32_INIT_BUFFER(src, bufferSrc); #if PLATFORM_UWP const bool overwrite = true; COPYFILE2_EXTENDED_PARAMETERS param = { 0 }; param.dwSize = sizeof(COPYFILE2_EXTENDED_PARAMETERS); param.dwCopyFlags = (!overwrite) ? COPY_FILE_FAIL_IF_EXISTS : 0; - return FAILED(CopyFile2(*src, *dst, ¶m)); + return FAILED(CopyFile2(bufferSrc, bufferDst, ¶m)); #else - return CopyFileW(*src, *dst, FALSE) == 0; + return CopyFileW(bufferSrc, bufferDst, FALSE) == 0; #endif } @@ -227,7 +241,7 @@ void Win32FileSystem::ConvertLineEndingsToDos(const StringView& text, Array #include #include -#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; +#define SOCKGROUP_ITEMSIZE 16 -static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); -static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); +static_assert(sizeof(NetworkSocket::Data) >= sizeof(SOCKET), "NetworkSocket::Data is not big enough to contains SOCKET !"); +static_assert(sizeof(NetworkEndPoint::Data) >= sizeof(sockaddr_in6), "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); static_assert(SOCKGROUP_ITEMSIZE >= sizeof(pollfd), "SOCKGROUP_ITEMSIZE macro is not big enough to contains pollfd !"); // @formatter:off -static const IN6_ADDR v4MappedPrefix = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }; +static const IN6_ADDR v4MappedPrefix = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }; // @formatter:on -/* - * Todo : - * Return precise errors so user can understand what's happening ( disconnected, ect ... ) - * Known issues : - * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation - */ - static String GetErrorMessage(int error) { wchar_t* s = nullptr; - FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&s), 0, nullptr); + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&s), 0, nullptr); String str(s); LocalFree(s); return str; @@ -44,17 +34,12 @@ static String GetLastErrorMessage() static int GetAddrSize(const sockaddr& addr) { - return addr.sa_family == AF_INET6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + return addr.sa_family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); } static int GetAddrSizeFromEP(NetworkEndPoint& endPoint) { - return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; -} - -static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) -{ - return addr.sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;; + return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); } static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) @@ -86,38 +71,16 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) } char strPort[6]; _itoa(port, strPort, 10); - endPoint.IPVersion = GetIPVersionFromAddr(*addr); + endPoint.IPVersion = addr->sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4; memcpy(endPoint.Data, addr, size); return false; } -static void PrintAddrFromInfo(addrinfoW& info) -{ - addrinfoW* curr; - for (curr = &info; curr != nullptr; curr = curr->ai_next) - { - void* addr; - if (curr->ai_family == AF_INET) - { - sockaddr_in* ipv4 = (struct sockaddr_in*)curr->ai_addr; - addr = &(ipv4->sin_addr); - } - else - { - sockaddr_in6* ipv6 = (struct sockaddr_in6*)curr->ai_addr; - addr = &(ipv6->sin6_addr); - } - - char str[INET6_ADDRSTRLEN]; - inet_ntop(curr->ai_family, addr, str, INET6_ADDRSTRLEN); - LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString()); - } -} - static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, int32* name) { switch (option) { +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) @@ -135,6 +98,11 @@ static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, i SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) SOCKOPT(NetworkSocketOption::Mtu, IPPROTO_IP, IP_MTU) SOCKOPT(NetworkSocketOption::Type, SOL_SOCKET, SO_TYPE) +#undef SOCKOPT + default: + *level = 0; + *name = 0; + break; } } @@ -146,18 +114,17 @@ bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, Ne const uint8 stype = socket.Protocol == NetworkProtocol::Tcp ? SOCK_STREAM : SOCK_DGRAM; const uint8 prot = socket.Protocol == NetworkProtocol::Tcp ? IPPROTO_TCP : IPPROTO_UDP; SOCKET sock; - if ((sock = ::socket(family, stype, prot)) == INVALID_SOCKET) { LOG(Error, "Can't create native socket! Error : {0}", GetLastErrorMessage()); return true; } - memcpy(socket.Data, &sock, sizeof sock); + memcpy(socket.Data, &sock, sizeof(sock)); unsigned long value = 1; if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) { LOG(Error, "Can't set socket to NON-BLOCKING type! Error : {0}", GetLastErrorMessage()); - return true; // Support using blocking socket , need to test it + return true; } return false; } @@ -174,20 +141,12 @@ bool Win32Network::DestroySocket(NetworkSocket& socket) return true; } -bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) -{ - const int32 v = value; - return SetSocketOption(socket, option, v); -} - bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) { int32 optlvl = 0; int32 optnme = 0; - TranslateSockOptToNative(option, &optlvl, &optnme); - - if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof value) == SOCKET_ERROR) + if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof(value)) == SOCKET_ERROR) { LOG(Warning, "Unable to set socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); return true; @@ -195,23 +154,13 @@ bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption op return false; } -bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) -{ - int32 v; - const bool status = GetSocketOption(socket, option, &v); - *value = v == 1 ? true : false; - return status; -} - -bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32& value) { int32 optlvl = 0; int32 optnme = 0; - TranslateSockOptToNative(option, &optlvl, &optnme); - int32 size; - if (getsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, &size) == SOCKET_ERROR) + if (getsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, &size) == SOCKET_ERROR) { LOG(Warning, "Unable to get socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); return true; @@ -221,10 +170,10 @@ bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption op bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) { - const uint16 size = GetAddrSizeFromEP(endPoint); + const int size = GetAddrSizeFromEP(endPoint); if (connect(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { - int error = WSAGetLastError(); + const int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; LOG(Error, "Unable to connect socket to address! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); @@ -240,8 +189,7 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); return true; } - - const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + const int size = GetAddrSizeFromEP(endPoint); if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { LOG(Error, "Unable to bind socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); @@ -260,28 +208,28 @@ bool Win32Network::Listen(NetworkSocket& socket, uint16 queueSize) return false; } -bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +bool Win32Network::Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint) { - if (serverSock.Protocol != NetworkProtocol::Tcp) + if (serverSocket.Protocol != NetworkProtocol::Tcp) { - LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", *(SOCKET*)serverSock.Data); + LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", *(SOCKET*)serverSocket.Data); return true; } SOCKET sock; sockaddr_in6 addr; - int32 size = sizeof sockaddr_in6; - if ((sock = accept(*(SOCKET*)serverSock.Data, (sockaddr*)&addr, &size)) == INVALID_SOCKET) + int32 size = sizeof(sockaddr_in6); + if ((sock = accept(*(SOCKET*)serverSocket.Data, (sockaddr*)&addr, &size)) == INVALID_SOCKET) { - int32 error = WSAGetLastError(); + const int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; - LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetErrorMessage(error)); + LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSocket.Data, GetErrorMessage(error)); return true; } - memcpy(newSock.Data, &sock, sizeof sock); + memcpy(newSocket.Data, &sock, sizeof(sock)); memcpy(newEndPoint.Data, &addr, size); - newSock.Protocol = serverSock.Protocol; - newSock.IPVersion = serverSock.IPVersion; + newSocket.Protocol = serverSocket.Protocol; + newSocket.IPVersion = serverSocket.IPVersion; if (CreateEndPointFromAddr((sockaddr*)&addr, newEndPoint)) return true; return false; @@ -294,7 +242,7 @@ bool Win32Network::IsReadable(NetworkSocket& socket) entry.events = POLLRDNORM; if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) { - int32 error = WSAGetLastError(); + const int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); @@ -305,14 +253,14 @@ bool Win32Network::IsReadable(NetworkSocket& socket) return false; } -bool Win32Network::IsWriteable(NetworkSocket& socket) +bool Win32Network::IsWritable(NetworkSocket& socket) { pollfd entry; entry.fd = *(SOCKET*)socket.Data; entry.events = POLLWRNORM; if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) { - int32 error = WSAGetLastError(); + const int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); @@ -325,15 +273,15 @@ bool Win32Network::IsWriteable(NetworkSocket& socket) bool Win32Network::CreateSocketGroup(uint32 capacity, NetworkSocketGroup& group) { - if (!(group.Data = (byte*)Platform::Allocate(capacity * SOCKGROUP_ITEMSIZE, 16))) + group.Data = (byte*)Platform::Allocate(capacity * SOCKGROUP_ITEMSIZE, 16); + if (!group.Data) { LOG(Error, "Unable to malloc NetworkSocketGroup::Data ! Size : {0}", capacity * SOCKGROUP_ITEMSIZE); return true; } group.Capacity = capacity; - for (int i = 0; i < (int)group.Capacity; i++) + for (uint32 i = 0; i < group.Capacity; i++) ((pollfd*)&group.Data[i * SOCKGROUP_ITEMSIZE])->fd = -1; - return false; } @@ -347,7 +295,7 @@ bool Win32Network::DestroySocketGroup(NetworkSocketGroup& group) int32 Win32Network::Poll(NetworkSocketGroup& group) { - int32 pollret = WSAPoll((pollfd*)group.Data, group.Count, 0); + const int pollret = WSAPoll((pollfd*)group.Data, group.Count, 0); if (pollret == SOCKET_ERROR) LOG(Error, "Unable to poll socket group! Error : {0}", GetLastErrorMessage()); return pollret; @@ -358,17 +306,17 @@ bool Win32Network::GetSocketState(NetworkSocketGroup& group, uint32 index, Netwo if (index >= group.Capacity) return true; pollfd* pollptr = (pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE]; - memset(&state, 0, sizeof state); + state = NetworkSocketState::None; if (pollptr->revents & POLLERR) - state.Error = true; + state |= NetworkSocketState::Error; if (pollptr->revents & POLLHUP) - state.Disconnected = true; + state |= NetworkSocketState::Disconnected; if (pollptr->revents & POLLNVAL) - state.Invalid = true; + state |= NetworkSocketState::Invalid; if (pollptr->revents & POLLRDNORM) - state.Readable = true; + state |= NetworkSocketState::Readable; if (pollptr->revents & POLLWRNORM) - state.Writeable = true; + state |= NetworkSocketState::Writeable; return false; } @@ -376,12 +324,10 @@ int32 Win32Network::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& s { if (group.Count >= group.Capacity) return -1; - pollfd pollinfo; pollinfo.fd = *(SOCKET*)socket.Data; pollinfo.events = POLLRDNORM | POLLWRNORM; - - for (int i = 0; i < (int)group.Capacity; i++) + for (uint32 i = 0; i < group.Capacity; i++) { if (((pollfd*)&group.Data[i * SOCKGROUP_ITEMSIZE])->fd == -1) { @@ -398,9 +344,9 @@ bool Win32Network::GetSocketFromGroup(NetworkSocketGroup& group, uint32 index, N if (index >= group.Capacity) return true; SOCKET s = ((pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE])->fd; - memcpy(socket->Data, &s, sizeof s); + memcpy(socket->Data, &s, sizeof(s)); int32 value; - if (GetSocketOption(*socket, NetworkSocketOption::Type, &value)) + if (GetSocketOption(*socket, NetworkSocketOption::Type, value)) return true; if (value == SOCK_DGRAM) socket->Protocol = NetworkProtocol::Udp; @@ -436,7 +382,7 @@ bool Win32Network::RemoveSocketFromGroup(NetworkSocketGroup& group, NetworkSocke void Win32Network::ClearGroup(NetworkSocketGroup& group) { - for (int i = 0; i < (int)group.Capacity; i++) + for (uint32 i = 0; i < group.Capacity; i++) ((pollfd*)&group.Data[i * SOCKGROUP_ITEMSIZE])->fd = -1; group.Count = 0; } @@ -481,7 +427,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer { if ((size = recv(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0)) == SOCKET_ERROR) { - const int32 error = WSAGetLastError(); + const int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return 0; LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetErrorMessage(error)); @@ -490,7 +436,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer } else { - int32 addrsize = sizeof sockaddr_in6; + int32 addrsize = sizeof(sockaddr_in6); sockaddr_in6 addr; if ((size = recvfrom(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) { @@ -503,42 +449,38 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer return size; } -bool Win32Network::CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +bool Win32Network::CreateEndPoint(const String& address, const String& port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) { - int status; + INT status; addrinfoW hints; addrinfoW* info; - memset(&hints, 0, sizeof hints); + memset(&hints, 0, sizeof(hints)); hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; hints.ai_flags |= AI_ADDRCONFIG; hints.ai_flags |= AI_V4MAPPED; if (bindable) hints.ai_flags = AI_PASSIVE; - - // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) - if ((status = GetAddrInfoW(address.Address == String::Empty ? nullptr : address.Address.Get(), address.Port == String::Empty ? nullptr : address.Port.Get(), &hints, &info)) != 0) + // Consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) + if ((status = GetAddrInfoW(address.IsEmpty() ? nullptr : *address, port.IsEmpty() ? nullptr : *port, &hints, &info)) != 0) { - LOG(Error, "Unable to query info for address : {0} Error : {1}", address.Address != String::Empty ? address.Address : String("ANY"), gai_strerror(status)); + LOG(Error, "Unable to query info for address : {0}::{1} Error : {2}", address, port, gai_strerror(status)); return true; } - if (info == nullptr) { - LOG(Error, "Unable to resolve address! Address : {0}", address.Address != String::Empty ? address.Address : String("ANY")); + LOG(Error, "Unable to resolve address! Address : {0}::{1}", address, port); return true; } - if (CreateEndPointFromAddr(info->ai_addr, endPoint)) { FreeAddrInfoW(info); return true; } FreeAddrInfoW(info); - return false; } -NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) +NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint& endPoint) { if (endPoint.IPVersion == NetworkIPVersion::IPv6) { @@ -552,7 +494,7 @@ NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) const SCOPE_ID scope = SCOPEID_UNSPECIFIED_INIT; // Can be replaced by windows built-in macro IN6ADDR_SETV4MAPPED() - memset(addr6, 0, sizeof sockaddr_in6); + memset(addr6, 0, sizeof(sockaddr_in6)); addr6->sin6_family = AF_INET6; addr6->sin6_scope_struct = scope; addr6->sin6_addr = v4MappedPrefix; @@ -562,3 +504,5 @@ NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) return pv6; } + +#endif diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index ce4e59718..c8c281e2a 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -9,19 +9,18 @@ class FLAXENGINE_API Win32Network : public NetworkBase { public: + // [NetworkBase] static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); static bool DestroySocket(NetworkSocket& socket); - static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); - static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); - static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32& value); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Listen(NetworkSocket& socket, uint16 queueSize); - static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + static bool Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket); - static bool IsWriteable(NetworkSocket& socket); + static bool IsWritable(NetworkSocket& socket); static bool CreateSocketGroup(uint32 capacity, NetworkSocketGroup& group); static bool DestroySocketGroup(NetworkSocketGroup& group); static int32 Poll(NetworkSocketGroup& group); @@ -33,8 +32,8 @@ public: static void ClearGroup(NetworkSocketGroup& group); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); - static bool CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = true); - static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); + static bool CreateEndPoint(const String& address, const String& port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = true); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); }; #endif diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index e01a8a8e1..31c0f4a36 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -10,6 +10,7 @@ #include "Engine/Core/Math/Math.h" #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Log.h" +#include "Engine/Engine/CommandLine.h" #include "IncludeWindowsHeaders.h" #include #include @@ -17,6 +18,8 @@ #include #include #include +#include +#include #pragma comment(lib, "Iphlpapi.lib") namespace @@ -62,6 +65,28 @@ bool Win32Platform::Init() if (PlatformBase::Init()) return true; + // Init console output (engine is linked with /SUBSYSTEM:WINDOWS so it lacks of proper console output on Windows) + if (CommandLine::Options.Std) + { + // Attaches output of application to parent console, returns true if running in console-mode + // [Reference: https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application] + if (AttachConsole(ATTACH_PARENT_PROCESS)) + { + const HANDLE consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (consoleHandleOut != INVALID_HANDLE_VALUE) + { + freopen("CONOUT$", "w", stdout); + setvbuf(stdout, NULL, _IONBF, 0); + } + const HANDLE consoleHandleError = GetStdHandle(STD_ERROR_HANDLE); + if (consoleHandleError != INVALID_HANDLE_VALUE) + { + freopen("CONOUT$", "w", stderr); + setvbuf(stderr, NULL, _IONBF, 0); + } + } + } + // Init timing LARGE_INTEGER frequency; const auto freqResult = QueryPerformanceFrequency(&frequency); @@ -304,28 +329,33 @@ void Win32Platform::Prefetch(void const* ptr) void* Win32Platform::Allocate(uint64 size, uint64 alignment) { + void* ptr = _aligned_malloc((size_t)size, (size_t)alignment); #if COMPILE_WITH_PROFILER - TrackAllocation(size); + OnMemoryAlloc(ptr, size); #endif - return _aligned_malloc((size_t)size, (size_t)alignment); + return ptr; } void Win32Platform::Free(void* ptr) { +#if COMPILE_WITH_PROFILER + OnMemoryFree(ptr); +#endif _aligned_free(ptr); } void* Win32Platform::AllocatePages(uint64 numPages, uint64 pageSize) { const uint64 numBytes = numPages * pageSize; - - // Use VirtualAlloc to allocate page-aligned memory - return VirtualAlloc(nullptr, numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#if PLATFORM_UWP + return VirtualAllocFromApp(nullptr, (SIZE_T)numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#else + return VirtualAlloc(nullptr, (SIZE_T)numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#endif } void Win32Platform::FreePages(void* ptr) { - // Free page-aligned memory VirtualFree(ptr, 0, MEM_RELEASE); } diff --git a/Source/Engine/Platform/Window.cs b/Source/Engine/Platform/Window.cs index 8a8a19c01..d000c6f90 100644 --- a/Source/Engine/Platform/Window.cs +++ b/Source/Engine/Platform/Window.cs @@ -7,8 +7,6 @@ namespace FlaxEngine { partial class Window { - internal float _dpiScale; - /// /// Window closing delegate. /// @@ -176,7 +174,6 @@ namespace FlaxEngine private Window() { GUI = new WindowRootControl(this); - _dpiScale = Platform.DpiScale; } internal void Internal_OnShow() @@ -192,7 +189,7 @@ namespace FlaxEngine internal void Internal_OnDraw() { - Matrix3x3.Scaling(_dpiScale, out var scale); + Matrix3x3.Scaling(DpiScale, out var scale); Render2D.PushTransform(ref scale); GUI.Draw(); Render2D.PopTransform(); @@ -200,7 +197,7 @@ namespace FlaxEngine internal void Internal_OnResize(int width, int height) { - GUI.Size = new Vector2(width / _dpiScale, height / _dpiScale); + GUI.Size = new Vector2(width / DpiScale, height / DpiScale); } internal void Internal_OnCharInput(char c) @@ -223,7 +220,7 @@ namespace FlaxEngine internal void Internal_OnMouseDown(ref Vector2 mousePos, MouseButton button) { - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; bool handled = false; MouseDown?.Invoke(ref pos, button, ref handled); @@ -235,7 +232,7 @@ namespace FlaxEngine internal void Internal_OnMouseUp(ref Vector2 mousePos, MouseButton button) { - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; bool handled = false; MouseUp?.Invoke(ref pos, button, ref handled); @@ -247,7 +244,7 @@ namespace FlaxEngine internal void Internal_OnMouseDoubleClick(ref Vector2 mousePos, MouseButton button) { - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; bool handled = false; MouseDoubleClick?.Invoke(ref pos, button, ref handled); @@ -259,7 +256,7 @@ namespace FlaxEngine internal void Internal_OnMouseWheel(ref Vector2 mousePos, float delta) { - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; bool handled = false; MouseWheel?.Invoke(ref pos, delta, ref handled); @@ -271,7 +268,7 @@ namespace FlaxEngine internal void Internal_OnMouseMove(ref Vector2 mousePos) { - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; MouseMove?.Invoke(ref pos); GUI.OnMouseMove(pos); @@ -285,7 +282,7 @@ namespace FlaxEngine internal void Internal_OnTouchDown(ref Vector2 pointerPosition, int pointerId) { - Vector2 pos = pointerPosition / _dpiScale; + Vector2 pos = pointerPosition / DpiScale; bool handled = false; TouchDown?.Invoke(ref pos, pointerId, ref handled); @@ -297,7 +294,7 @@ namespace FlaxEngine internal void Internal_OnTouchMove(ref Vector2 pointerPosition, int pointerId) { - Vector2 pos = pointerPosition / _dpiScale; + Vector2 pos = pointerPosition / DpiScale; bool handled = false; TouchMove?.Invoke(ref pos, pointerId, ref handled); @@ -309,7 +306,7 @@ namespace FlaxEngine internal void Internal_OnTouchUp(ref Vector2 pointerPosition, int pointerId) { - Vector2 pos = pointerPosition / _dpiScale; + Vector2 pos = pointerPosition / DpiScale; bool handled = false; TouchUp?.Invoke(ref pos, pointerId, ref handled); @@ -335,7 +332,7 @@ namespace FlaxEngine { if (HitTest != null) { - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; result = HitTest(ref pos); handled = true; } @@ -356,7 +353,7 @@ namespace FlaxEngine dragData = new DragDataText(data[0]); else dragData = new DragDataFiles(data); - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; return GUI.OnDragEnter(ref pos, dragData); } @@ -367,7 +364,7 @@ namespace FlaxEngine dragData = new DragDataText(data[0]); else dragData = new DragDataFiles(data); - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; return GUI.OnDragMove(ref pos, dragData); } @@ -378,7 +375,7 @@ namespace FlaxEngine dragData = new DragDataText(data[0]); else dragData = new DragDataFiles(data); - Vector2 pos = mousePos / _dpiScale; + Vector2 pos = mousePos / DpiScale; return GUI.OnDragDrop(ref pos, dragData); } diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp index 1816c9552..6cf467976 100644 --- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp +++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp @@ -260,7 +260,7 @@ bool WindowsFileSystem::ShowSaveFileDialog(Window* parentWindow, const StringVie of.lpstrFilter = filter.HasChars() ? filter.Get() : nullptr; of.lpstrFile = fileNamesBuffer.Get(); of.nMaxFile = maxFilenamesSize; - of.Flags = OFN_EXPLORER | OFN_ENABLESIZING; + of.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_OVERWRITEPROMPT; of.lpstrTitle = title.HasChars() ? title.Get() : nullptr; of.lpstrInitialDir = initialDirectory.HasChars() ? initialDirectory.Get() : nullptr; if (parentWindow) diff --git a/Source/Engine/Platform/Windows/WindowsFileSystemWatcher.cpp b/Source/Engine/Platform/Windows/WindowsFileSystemWatcher.cpp index 63e610cd1..832fd9bb2 100644 --- a/Source/Engine/Platform/Windows/WindowsFileSystemWatcher.cpp +++ b/Source/Engine/Platform/Windows/WindowsFileSystemWatcher.cpp @@ -182,6 +182,7 @@ WindowsFileSystemWatcher::~WindowsFileSystemWatcher() FileSystemWatchers::ThreadActive = false; QueueUserAPC(FileSystemWatchers::StopProc, FileSystemWatchers::Thread->GetHandle(), 0); FileSystemWatchers::Thread->Join(); + Delete(FileSystemWatchers::Thread); FileSystemWatchers::Thread = nullptr; } FileSystemWatchers::Locker.Unlock(); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 4dd2164ae..00a5c0d0f 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -11,6 +11,7 @@ #include "Engine/Engine/Globals.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Platform/MessageBox.h" #include "Engine/Engine/Engine.h" #include "../Win32/IncludeWindowsHeaders.h" @@ -36,9 +37,30 @@ namespace int32 SystemDpi = 96; #if CRASH_LOG_ENABLE CriticalSection SymLocker; +#if TRACY_ENABLE + bool SymInitialized = true; +#else bool SymInitialized = false; - bool SymModulesDirty = true; - char* SymPath = nullptr; +#endif + Array SymbolsPath; + + void OnSymbolsPathModified() + { + if (!SymInitialized) + return; + HANDLE process = GetCurrentProcess(); + SymCleanup(process); + String symbolSearchPath; + for (auto& path : SymbolsPath) + { + symbolSearchPath += path; + symbolSearchPath += ";"; + } + symbolSearchPath += Platform::GetWorkingDirectory(); + SymInitializeW(process, *symbolSearchPath, TRUE); + //SymSetSearchPathW(process, *symbolSearchPath); + //SymRefreshModuleList(process); + } #endif } @@ -76,16 +98,6 @@ int32 CalculateDpi(HMODULE shCoreDll) return (dpiX + dpiY) / 2; } -int32 CalculateDpi() -{ - if (const HMODULE shCoreDll = LoadLibraryW(L"Shcore.dll")) - { - return CalculateDpi(shCoreDll); - } - - return 96; -} - LONG GetStringRegKey(HKEY hKey, const Char* strValueName, String& strValue, const String& strDefaultValue) { strValue = strDefaultValue; @@ -387,6 +399,20 @@ void WindowsPlatform::PreInit(void* hInstance) Error(TEXT("OLE initalization failed!")); exit(-1); } + +#if CRASH_LOG_ENABLE + TCHAR buffer[MAX_PATH] = { 0 }; + SymLocker.Lock(); + if (::GetModuleFileNameW(::GetModuleHandleW(nullptr), buffer, MAX_PATH)) + SymbolsPath.Add(StringUtils::GetDirectoryName(buffer)); + if (::GetEnvironmentVariableW(TEXT("_NT_SYMBOL_PATH"), buffer, MAX_PATH)) + SymbolsPath.Add(StringUtils::GetDirectoryName(buffer)); + DWORD options = SymGetOptions(); + options |= SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS; + SymSetOptions(options); + OnSymbolsPathModified(); + SymLocker.Unlock(); +#endif } bool WindowsPlatform::IsWindows10() @@ -613,13 +639,14 @@ void WindowsPlatform::Exit() { #if CRASH_LOG_ENABLE SymLocker.Lock(); +#if !TRACY_ENABLE if (SymInitialized) { SymInitialized = false; SymCleanup(GetCurrentProcess()); - free(SymPath); - SymPath = nullptr; } +#endif + SymbolsPath.Resize(0); SymLocker.Unlock(); #endif @@ -660,25 +687,20 @@ void WindowsPlatform::SetHighDpiAwarenessEnabled(bool enable) const HMODULE shCoreDll = LoadLibraryW(L"Shcore.dll"); if (!shCoreDll) return; - typedef enum _PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; - typedef HRESULT (STDAPICALLTYPE *SetProcessDpiAwarenessProc)(PROCESS_DPI_AWARENESS Value); const SetProcessDpiAwarenessProc setProcessDpiAwareness = (SetProcessDpiAwarenessProc)GetProcAddress(shCoreDll, "SetProcessDpiAwareness"); - if (setProcessDpiAwareness) { - setProcessDpiAwareness(enable ? PROCESS_SYSTEM_DPI_AWARE : PROCESS_DPI_UNAWARE); + setProcessDpiAwareness(enable ? PROCESS_PER_MONITOR_DPI_AWARE : PROCESS_DPI_UNAWARE); } - SystemDpi = CalculateDpi(shCoreDll); - - FreeLibrary(shCoreDll); + ::FreeLibrary(shCoreDll); } BatteryInfo WindowsPlatform::GetBatteryInfo() @@ -1116,7 +1138,12 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) #if CRASH_LOG_ENABLE // Refresh modules info during next stack trace collecting to have valid debug symbols information SymLocker.Lock(); - SymModulesDirty = true; + const auto folder = StringUtils::GetDirectoryName(filename); + if (!SymbolsPath.Contains(folder)) + { + SymbolsPath.Add(folder); + OnSymbolsPathModified(); + } SymLocker.Unlock(); #endif @@ -1135,83 +1162,16 @@ Array WindowsPlatform::GetStackFrames(int32 skipCount, if (!SymInitialized) { SymInitialized = true; - - // Build search path - const size_t nSymPathLen = 4096; - SymPath = (char*)malloc(nSymPathLen); - SymPath[0] = 0; - strcat_s(SymPath, nSymPathLen, ".;"); - const size_t nTempLen = 1024; - char szTemp[nTempLen]; - - // Current directory path - if (GetCurrentDirectoryA(nTempLen, szTemp) > 0) + String symbolSearchPath; + for (auto& path : SymbolsPath) { - szTemp[nTempLen - 1] = 0; - strcat_s(SymPath, nSymPathLen, szTemp); - strcat_s(SymPath, nSymPathLen, ";"); + symbolSearchPath += path; + symbolSearchPath += ";"; } - - // Main module path - if (GetModuleFileNameA(nullptr, szTemp, nTempLen) > 0) - { - szTemp[nTempLen - 1] = 0; - for (char* p = (szTemp + strlen(szTemp) - 1); p >= szTemp; --p) - { - // Locate the rightmost path separator - if ((*p == '\\') || (*p == '/') || (*p == ':')) - { - *p = 0; - break; - } - } - if (strlen(szTemp) > 0) - { - strcat_s(SymPath, nSymPathLen, szTemp); - strcat_s(SymPath, nSymPathLen, ";"); - } - } - - // System symbols paths - if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0) - { - szTemp[nTempLen - 1] = 0; - strcat_s(SymPath, nSymPathLen, szTemp); - strcat_s(SymPath, nSymPathLen, ";"); - } - if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0) - { - szTemp[nTempLen - 1] = 0; - strcat_s(SymPath, nSymPathLen, szTemp); - strcat_s(SymPath, nSymPathLen, ";"); - } - if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0) - { - szTemp[nTempLen - 1] = 0; - strcat_s(SymPath, nSymPathLen, szTemp); - strcat_s(SymPath, nSymPathLen, ";"); - - strcat_s(szTemp, nTempLen, "\\system32"); - strcat_s(SymPath, nSymPathLen, szTemp); - strcat_s(SymPath, nSymPathLen, ";"); - } - - SymInitialize(process, SymPath, FALSE); - - DWORD options = SymGetOptions(); - options |= SYMOPT_LOAD_LINES; - options |= SYMOPT_FAIL_CRITICAL_ERRORS; - SymSetOptions(options); + symbolSearchPath += Platform::GetWorkingDirectory(); + SymInitializeW(process, *symbolSearchPath, TRUE); } - // Load modules - if (SymModulesDirty) - { - SymModulesDirty = false; - GetModuleListPSAPI(process); - } - SymRefreshModuleList(process); - // Capture the context if missing /*EXCEPTION_POINTERS exceptionPointers; CONTEXT contextData; diff --git a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp index 1bec8868a..a6dc90a1f 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp @@ -10,6 +10,8 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Engine/Engine.h" #include "Engine/Platform/IGuiData.h" +#include "Engine/Input/Input.h" +#include "Engine/Input/Mouse.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/ThreadPool.h" #include "Engine/Scripting/Scripting.h" @@ -630,6 +632,10 @@ DragDropEffect WindowsWindow::DoDragDrop(const StringView& data) dropSource->Release(); ReleaseStgMedium(&stgmed); + // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop) + if (Input::GetMouseButton(MouseButton::Left)) + Input::Mouse->OnMouseUp(Input::Mouse->GetPosition(), MouseButton::Left, this); + return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None; } diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 3a35d24e9..b3687cf78 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -13,6 +13,8 @@ #include "../Win32/IncludeWindowsHeaders.h" #include +#define DefaultDPI 96 + // Use improved borderless window support for Editor #define WINDOWS_USE_NEW_BORDER_LESS USE_EDITOR && 0 #if WINDOWS_USE_NEW_BORDER_LESS @@ -123,6 +125,21 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings) (HINSTANCE)Platform::Instance, nullptr); + // Query DPI + _dpi = Platform::GetDpi(); + const HMODULE user32Dll = LoadLibraryW(L"user32.dll"); + if (user32Dll) + { + typedef UINT (STDAPICALLTYPE* GetDpiForWindowProc)(HWND hwnd); + const GetDpiForWindowProc getDpiForWindowProc = (GetDpiForWindowProc)GetProcAddress(user32Dll, "GetDpiForWindow"); + if (getDpiForWindowProc) + { + _dpi = getDpiForWindowProc(_handle); + } + FreeLibrary(user32Dll); + } + _dpiScale = (float)_dpi / (float)DefaultDPI; + // Validate result if (!HasHWND()) { @@ -1021,6 +1038,22 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) } break; } + case WM_DPICHANGED: + { + // Maybe https://stackoverflow.com/a/45110656 + _dpi = HIWORD(wParam); + _dpiScale = (float)_dpi / (float)DefaultDPI; + RECT* windowRect = (RECT*)lParam; + SetWindowPos(_handle, + nullptr, + windowRect->left, + windowRect->top, + windowRect->right - windowRect->left, + windowRect->bottom - windowRect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + // TODO: Recalculate fonts + return 0; + } case WM_ENTERSIZEMOVE: { _isResizing = true; diff --git a/Source/Engine/Profiler/Profiler.Build.cs b/Source/Engine/Profiler/Profiler.Build.cs index aeda888e5..660be038c 100644 --- a/Source/Engine/Profiler/Profiler.Build.cs +++ b/Source/Engine/Profiler/Profiler.Build.cs @@ -27,5 +27,14 @@ public class Profiler : EngineModule options.PrivateDependencies.Clear(); options.PublicDefinitions.Add("COMPILE_WITH_PROFILER"); + + // Tracy profiling tools + switch (options.Platform.Target) + { + case TargetPlatform.Windows: + case TargetPlatform.Android: + options.PublicDependencies.Add("tracy"); + break; + } } } diff --git a/Source/Engine/Profiler/ProfilerCPU.cpp b/Source/Engine/Profiler/ProfilerCPU.cpp index 8693f8ddf..2119a25d7 100644 --- a/Source/Engine/Profiler/ProfilerCPU.cpp +++ b/Source/Engine/Profiler/ProfilerCPU.cpp @@ -7,9 +7,23 @@ #include "Engine/Threading/ThreadRegistry.h" THREADLOCAL ProfilerCPU::Thread* ProfilerCPU::Thread::Current = nullptr; -Array ProfilerCPU::Threads(64); +Array> ProfilerCPU::Threads; bool ProfilerCPU::Enabled = false; +ProfilerCPU::EventBuffer::EventBuffer() +{ + _capacity = Math::RoundUpToPowerOf2(10 * 1000); + _capacityMask = _capacity - 1; + _data = NewArray(_capacity); + _head = 0; + _count = 0; +} + +ProfilerCPU::EventBuffer::~EventBuffer() +{ + DeleteArray(_data, _capacity); +} + void ProfilerCPU::EventBuffer::Extract(Array& data, bool withRemove) { data.Clear(); @@ -87,28 +101,23 @@ void ProfilerCPU::EventBuffer::Extract(Array& data, bool withRemove) Platform::MemoryCopy(data.Get() + spaceLeftCount, &_data[0], overflow * sizeof(Event)); } -int32 ProfilerCPU::Thread::BeginEvent(const Char* name) +int32 ProfilerCPU::Thread::BeginEvent() { const double time = Platform::GetTimeSeconds() * 1000.0; const auto index = Buffer.Add(); - Event& e = Buffer.Get(index); e.Start = time; e.End = 0; e.Depth = _depth++; e.NativeMemoryAllocation = 0; e.ManagedMemoryAllocation = 0; - e.Name = name; - return index; } void ProfilerCPU::Thread::EndEvent(int32 index) { const double time = Platform::GetTimeSeconds() * 1000.0; - _depth--; - Event& e = Buffer.Get(index); e.End = time; } @@ -123,7 +132,7 @@ ProfilerCPU::Thread* ProfilerCPU::GetCurrentThread() return Enabled ? Thread::Current : nullptr; } -int32 ProfilerCPU::BeginEvent(const Char* name) +int32 ProfilerCPU::BeginEvent() { if (!Enabled) return -1; @@ -142,8 +151,45 @@ int32 ProfilerCPU::BeginEvent(const Char* name) Thread::Current = thread; Threads.Add(thread); } + return thread->BeginEvent(); +} - return thread->BeginEvent(name); +int32 ProfilerCPU::BeginEvent(const Char* name) +{ + if (!Enabled) + return -1; + const auto index = BeginEvent(); + const auto thread = Thread::Current; + auto& e = thread->Buffer.Get(index); + auto dst = e.Name; + auto src = name; + if (src) + { + const auto end = dst + ARRAY_COUNT(e.Name) - 1; + while (*src && dst != end) + *dst++ = *src++; + } + *dst = 0; + return index; +} + +int32 ProfilerCPU::BeginEvent(const char* name) +{ + if (!Enabled) + return -1; + const auto index = BeginEvent(); + const auto thread = Thread::Current; + auto& e = thread->Buffer.Get(index); + auto dst = e.Name; + auto src = name; + if (src) + { + const auto end = dst + ARRAY_COUNT(e.Name) - 1; + while (*src && dst != end) + *dst++ = *src++; + } + *dst = 0; + return index; } void ProfilerCPU::EndEvent(int32 index) diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index 72859d8cf..feccdd79b 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -5,16 +5,13 @@ #include "Engine/Platform/Platform.h" #include "Engine/Core/NonCopyable.h" #include "Engine/Core/Types/String.h" -#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingType.h" +#include #if COMPILE_WITH_PROFILER -// Profiler events buffers capacity (tweaked manually) -#define PROFILER_CPU_EVENTS_FRAMES 10 -#define PROFILER_CPU_EVENTS_PER_FRAME 1000 - /// /// Provides CPU performance measuring methods. /// @@ -55,10 +52,7 @@ public: /// API_FIELD() int32 ManagedMemoryAllocation; - /// - /// The name of the event. - /// - API_FIELD() const Char* Name; + API_FIELD(Private, NoArray) Char Name[100]; }; /// @@ -76,26 +70,14 @@ public: public: - EventBuffer() - { - _capacity = Math::RoundUpToPowerOf2(PROFILER_CPU_EVENTS_FRAMES * PROFILER_CPU_EVENTS_PER_FRAME); - _capacityMask = _capacity - 1; - _data = NewArray(_capacity); - _head = 0; - _count = 0; - } - - ~EventBuffer() - { - DeleteArray(_data, _capacity); - } + EventBuffer(); + ~EventBuffer(); public: /// /// Gets the amount of the events in the buffer. /// - /// The events count. FORCE_INLINE int32 GetCount() const { return _count; @@ -169,9 +151,8 @@ public: public: /// - /// Checks if iterator is in the end of the collection + /// Checks if iterator is in the end of the collection. /// - /// True if is in the end, otherwise false bool IsEnd() const { ASSERT(_buffer); @@ -179,9 +160,8 @@ public: } /// - /// Checks if iterator is not in the end of the collection + /// Checks if iterator is not in the end of the collection. /// - /// True if is not in the end, otherwise false bool IsNotEnd() const { ASSERT(_buffer); @@ -286,7 +266,6 @@ public: /// /// Gets the name. /// - /// The name. FORCE_INLINE const String& GetName() const { return _name; @@ -302,9 +281,8 @@ public: /// /// Begins the event running on a this thread. Call EndEvent with index parameter equal to the returned value by BeginEvent function. /// - /// The event name. /// The event token. - int32 BeginEvent(const Char* name); + int32 BeginEvent(); /// /// Ends the event running on a this thread. @@ -318,7 +296,7 @@ public: /// /// The registered threads. /// - static Array Threads; + static Array> Threads; /// /// The profiling tools usage flag. Can be used to disable profiler. Engine turns it down before the exit and before platform startup. @@ -330,15 +308,19 @@ public: /// /// Determines whether the current (calling) thread is being profiled by the service (it may has no active profile block but is registered). /// - /// true if service is profiling the current thread; otherwise, false. static bool IsProfilingCurrentThread(); /// /// Gets the current thread (profiler service shadow object). /// - /// The current thread object or null if not profiled yet. static Thread* GetCurrentThread(); + /// + /// Begins the event. Call EndEvent with index parameter equal to the returned value by BeginEvent function. + /// + /// The event token. + static int32 BeginEvent(); + /// /// Begins the event. Call EndEvent with index parameter equal to the returned value by BeginEvent function. /// @@ -346,6 +328,13 @@ public: /// The event token. static int32 BeginEvent(const Char* name); + /// + /// Begins the event. Call EndEvent with index parameter equal to the returned value by BeginEvent function. + /// + /// The event name. + /// The event token. + static int32 BeginEvent(const char* name); + /// /// Ends the event. /// @@ -353,7 +342,7 @@ public: static void EndEvent(int32 index); /// - /// Releases resources. Calls to the profiling API after Dispose are not valid + /// Releases resources. Calls to the profiling API after Dispose are not valid. /// static void Dispose(); }; @@ -363,24 +352,19 @@ public: /// struct ScopeProfileBlockCPU { - /// - /// The event token index. - /// int32 Index; - /// - /// Initializes a new instance of the struct. - /// - /// The event name. - ScopeProfileBlockCPU(const Char* name) + FORCE_INLINE ScopeProfileBlockCPU(const Char* name) { Index = ProfilerCPU::BeginEvent(name); } - /// - /// Finalizes an instance of the class. - /// - ~ScopeProfileBlockCPU() + FORCE_INLINE ScopeProfileBlockCPU(const char* name) + { + Index = ProfilerCPU::BeginEvent(name); + } + + FORCE_INLINE ~ScopeProfileBlockCPU() { ProfilerCPU::EndEvent(Index); } @@ -393,18 +377,26 @@ struct TIsPODType }; // Shortcut macros for profiling a single code block execution on CPU -#define PROFILE_CPU_NAMED(name) ScopeProfileBlockCPU ProfileBlockCPU(TEXT(name)) +// Use ZoneTransient for Tracy for code that can be hot-reloaded (eg. in Editor) or if name can be a variable + +#define PROFILE_CPU_NAMED(name) ZoneTransientN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name) #if defined(_MSC_VER) -#define PROFILE_CPU() ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__)) + +#if USE_EDITOR +#define PROFILE_CPU() ZoneTransient(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__)) #else -#define PROFILE_CPU() \ - const char* _functionName = __FUNCTION__; \ - const int32 _functionNameLength = ARRAY_COUNT(__FUNCTION__); \ - Char _functionNameBuffer[_functionNameLength + 1]; \ - StringUtils::ConvertANSI2UTF16(_functionName, _functionNameBuffer, _functionNameLength); \ - _functionNameBuffer[_functionNameLength] = 0; \ - ScopeProfileBlockCPU ProfileBlockCPU(_functionNameBuffer) +#define PROFILE_CPU() ZoneNamed(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__)) +#endif + +#else + +#if USE_EDITOR +#define PROFILE_CPU() ZoneTransient(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(__FUNCTION__) +#else +#define PROFILE_CPU() ZoneNamed(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(__FUNCTION__) +#endif + #endif #else diff --git a/Source/Engine/Profiler/ProfilerGPU.cpp b/Source/Engine/Profiler/ProfilerGPU.cpp index b1902d7e8..48c8444a0 100644 --- a/Source/Engine/Profiler/ProfilerGPU.cpp +++ b/Source/Engine/Profiler/ProfilerGPU.cpp @@ -16,6 +16,11 @@ bool ProfilerGPU::Enabled = true; int32 ProfilerGPU::CurrentBuffer = 0; ProfilerGPU::EventBuffer ProfilerGPU::Buffers[PROFILER_GPU_EVENTS_FRAMES]; +bool ProfilerGPU::EventBuffer::HasData() const +{ + return _isResolved && _data.HasItems(); +} + void ProfilerGPU::EventBuffer::EndAll() { for (int32 i = 0; i < _data.Count(); i++) @@ -41,7 +46,7 @@ void ProfilerGPU::EventBuffer::TryResolve() { auto& e = _data[i]; e.Time = e.Timer->GetResult(); - FreeTimerQuery(e.Timer); + _timerQueriesFree.Add(e.Timer); e.Timer = nullptr; } @@ -55,6 +60,20 @@ int32 ProfilerGPU::EventBuffer::Add(const Event& e) return index; } +void ProfilerGPU::EventBuffer::Extract(Array& data) const +{ + // Don't use unresolved data + ASSERT(_isResolved); + data = _data; +} + +void ProfilerGPU::EventBuffer::Clear() +{ + _data.Clear(); + _isResolved = false; + FrameIndex = 0; +} + GPUTimerQuery* ProfilerGPU::GetTimerQuery() { GPUTimerQuery* result; diff --git a/Source/Engine/Profiler/ProfilerGPU.h b/Source/Engine/Profiler/ProfilerGPU.h index 9f5e3ea19..981f1d92b 100644 --- a/Source/Engine/Profiler/ProfilerGPU.h +++ b/Source/Engine/Profiler/ProfilerGPU.h @@ -62,21 +62,9 @@ public: { private: - bool _isResolved; + bool _isResolved = true; Array _data; - public: - - EventBuffer() - : _data(256) - { - _isResolved = true; - } - - ~EventBuffer() - { - } - public: /// @@ -87,11 +75,7 @@ public: /// /// Determines whether this buffer has ready data (resolved and not empty). /// - /// true if this buffer has data; otherwise, false. - FORCE_INLINE bool HasData() const - { - return _isResolved && _data.HasItems(); - } + bool HasData() const; /// /// Ends all used timer queries. @@ -124,22 +108,12 @@ public: /// Extracts the buffer data. /// /// The output data. - void Extract(Array& data) const - { - // Don't use unresolved data - ASSERT(_isResolved); - data = _data; - } + void Extract(Array& data) const; /// /// Clears this buffer. /// - void Clear() - { - _data.Clear(); - _isResolved = false; - FrameIndex = 0; - } + void Clear(); }; private: @@ -151,11 +125,6 @@ private: static GPUTimerQuery* GetTimerQuery(); - FORCE_INLINE static void FreeTimerQuery(GPUTimerQuery* q) - { - _timerQueriesFree.Add(q); - } - public: /// @@ -222,24 +191,14 @@ public: /// struct ScopeProfileBlockGPU { - /// - /// The event token index. - /// int32 Index; - /// - /// Initializes a new instance of the struct. - /// - /// The event name. - ScopeProfileBlockGPU(const Char* name) + FORCE_INLINE ScopeProfileBlockGPU(const Char* name) { Index = ProfilerGPU::BeginEvent(name); } - - /// - /// Finalizes an instance of the class. - /// - ~ScopeProfileBlockGPU() + + FORCE_INLINE ~ScopeProfileBlockGPU() { ProfilerGPU::EndEvent(Index); } @@ -252,8 +211,7 @@ struct TIsPODType }; // Shortcut macro for profiling rendering on GPU -#define PROFILE_GPU(name) \ - ScopeProfileBlockGPU ProfileBlockGPU(TEXT(name)) +#define PROFILE_GPU(name) ScopeProfileBlockGPU ProfileBlockGPU(TEXT(name)) #else diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp deleted file mode 100644 index ae8bff067..000000000 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#if COMPILE_WITH_PROFILER - -#include "ProfilerMemory.h" -#include "ProfilerCPU.h" - -void ProfilerMemory::OnAllocation(int32 bytes, bool isGC) -{ - // Register allocation during the current CPU event - auto thread = ProfilerCPU::GetCurrentThread(); - if (thread != nullptr && thread->Buffer.GetCount() != 0) - { - auto& activeEvent = thread->Buffer.Last().Event(); - if (activeEvent.End < ZeroTolerance) - { - if (isGC) - activeEvent.ManagedMemoryAllocation += bytes; - else - activeEvent.NativeMemoryAllocation += bytes; - } - } -} - -#endif diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h deleted file mode 100644 index a3d0098f7..000000000 --- a/Source/Engine/Profiler/ProfilerMemory.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Core/Types/BaseTypes.h" - -#if COMPILE_WITH_PROFILER - -/// -/// Provides memory allocations measuring methods. -/// -class FLAXENGINE_API ProfilerMemory -{ -public: - - /// - /// Called on memory allocation. - /// - /// The allocated bytes count. - /// True if allocation comes from the Garbage Collector, otherwise false. - static void OnAllocation(int32 bytes, bool isGC); -}; - -#endif diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp index d5d88568a..4afab6c04 100644 --- a/Source/Engine/Profiler/ProfilingTools.cpp +++ b/Source/Engine/Profiler/ProfilingTools.cpp @@ -9,7 +9,7 @@ #include "Engine/Graphics/GPUDevice.h" ProfilingTools::MainStats ProfilingTools::Stats; -Array ProfilingTools::EventsCPU(128); +Array> ProfilingTools::EventsCPU; Array ProfilingTools::EventsGPU; class ProfilingToolsService : public EngineService diff --git a/Source/Engine/Profiler/ProfilingTools.h b/Source/Engine/Profiler/ProfilingTools.h index 135041a33..b35caf9a8 100644 --- a/Source/Engine/Profiler/ProfilingTools.h +++ b/Source/Engine/Profiler/ProfilingTools.h @@ -116,7 +116,7 @@ public: /// /// The CPU threads profiler events. /// - API_FIELD(ReadOnly) static Array EventsCPU; + API_FIELD(ReadOnly) static Array> EventsCPU; /// /// The GPU rendering profiler events. diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index fd2e64763..537c604d9 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -166,7 +166,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines xAdvance = (kerning + entry.AdvanceX) * scale; // Check if character fits the line or skip wrapping - if (cursorX + xAdvance < boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) + if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) { // Move character cursorX += xAdvance; diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index 8b14b1cfc..5210bc9e2 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -12,7 +12,7 @@ #include "Engine/Platform/FileSystem.h" #endif -REGISTER_BINARY_ASSET(FontAsset, "FlaxEngine.FontAsset", ::New(), false); +REGISTER_BINARY_ASSET_WITH_UPGRADER(FontAsset, "FlaxEngine.FontAsset", FontAssetUpgrader, false); FontAsset::FontAsset(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index ec7a61257..13b46991f 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -67,7 +67,7 @@ bool FontManagerService::Init() ASSERT(Library == nullptr); // Scale UI fonts to match the monitor DPI - FontManager::FontScale = (float)Platform::GetDpi() / (float)DefaultDPI; + FontManager::FontScale = (float)Platform::GetDpi() / (float)DefaultDPI; // TODO: Adjust this at runtime // Init Free Type FreeTypeMemory.user = nullptr; @@ -109,7 +109,7 @@ void FontManagerService::Dispose() FontTextureAtlas* FontManager::GetAtlas(int32 index) { - return Atlases[index]; + return index >= 0 && index < Atlases.Count() ? Atlases.Get()[index] : nullptr; } bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) @@ -206,6 +206,7 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) // End for empty glyphs if (GlyphImageData.IsEmpty()) { + entry.TextureIndex = MAX_uint8; if (bitmap == &tmpBitmap) { FT_Bitmap_Done(Library, bitmap); diff --git a/Source/Engine/Render2D/FontTextureAtlas.cpp b/Source/Engine/Render2D/FontTextureAtlas.cpp index c394937fd..63c547750 100644 --- a/Source/Engine/Render2D/FontTextureAtlas.cpp +++ b/Source/Engine/Render2D/FontTextureAtlas.cpp @@ -7,7 +7,7 @@ #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/GPUDevice.h" -REGISTER_BINARY_ASSET(FontTextureAtlas, "FlaxEngine.FontTextureAtlas", nullptr, true); +REGISTER_BINARY_ASSET(FontTextureAtlas, "FlaxEngine.FontTextureAtlas", true); FontTextureAtlas::FontTextureAtlas(const SpawnParams& params, const AssetInfo* info) : Texture(params, info) diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index c79a59d95..de58f6ea7 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -11,16 +11,18 @@ #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/Content.h" #include "Engine/Profiler/Profiler.h" -#include "Engine/Graphics/RenderTask.h" -#include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/GPUPipelineState.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/DynamicBuffer.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Animations/AnimationUtils.h" #include "Engine/Core/Math/Half.h" #include "Engine/Core/Math/Math.h" #include "Engine/Engine/EngineService.h" -#include "Engine/Graphics/GPUPipelineState.h" -#include "Engine/Graphics/RenderTargetPool.h" #if USE_EDITOR @@ -60,7 +62,7 @@ PACK_STRUCT(struct Data { PACK_STRUCT(struct BlurData { Vector2 InvBufferSize; - int32 SampleCount; + uint32 SampleCount; float Dummy0; Vector4 Bounds; Vector4 WeightAndOffsets[RENDER2D_BLUR_MAX_SAMPLES / 2]; @@ -875,20 +877,16 @@ static Vector2 GetWeightAndOffset(float dist, float sigma) return Vector2(totalWeight, offset); } -static int32 ComputeBlurWeights(int32 kernelSize, float sigma, Vector4* outWeightsAndOffsets) +static uint32 ComputeBlurWeights(int32 kernelSize, float sigma, Vector4* outWeightsAndOffsets) { - const int32 numSamples = Math::DivideAndRoundUp(kernelSize, 2); - + const uint32 numSamples = Math::DivideAndRoundUp((uint32)kernelSize, 2u); outWeightsAndOffsets[0] = Vector4(Vector2(GetWeight(0, sigma), 0), GetWeightAndOffset(1, sigma)); - - int32 sampleIndex = 1; - for (int32 x = 3; x < kernelSize; x += 4) + uint32 sampleIndex = 1; + for (uint32 x = 3; x < kernelSize; x += 4) { outWeightsAndOffsets[sampleIndex] = Vector4(GetWeightAndOffset((float)x, sigma), GetWeightAndOffset((float)(x + 2), sigma)); - sampleIndex++; } - return numSamples; } @@ -1137,11 +1135,17 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, // Get texture atlas that contains current character fontAtlasIndex = entry.TextureIndex; fontAtlas = FontManager::GetAtlas(fontAtlasIndex); - fontAtlas->EnsureTextureCreated(); - drawCall.AsChar.Tex = fontAtlas->GetTexture(); - - // Cache atlas inverted size (inverted to improve performance) - invAtlasSize = 1.0f / fontAtlas->GetSize(); + if (fontAtlas) + { + fontAtlas->EnsureTextureCreated(); + drawCall.AsChar.Tex = fontAtlas->GetTexture(); + invAtlasSize = 1.0f / fontAtlas->GetSize(); + } + else + { + drawCall.AsChar.Tex = nullptr; + invAtlasSize = 1.0f; + } } // Check if character is a whitespace @@ -1250,9 +1254,17 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, // Get texture atlas that contains current character fontAtlasIndex = entry.TextureIndex; fontAtlas = FontManager::GetAtlas(fontAtlasIndex); - fontAtlas->EnsureTextureCreated(); - invAtlasSize = 1.0f / fontAtlas->GetSize(); - drawCall.AsChar.Tex = fontAtlas->GetTexture(); + if (fontAtlas) + { + fontAtlas->EnsureTextureCreated(); + invAtlasSize = 1.0f / fontAtlas->GetSize(); + drawCall.AsChar.Tex = fontAtlas->GetTexture(); + } + else + { + invAtlasSize = 1.0f; + drawCall.AsChar.Tex = nullptr; + } } // Get kerning @@ -1528,7 +1540,7 @@ void Render2D::DrawTexture(TextureBase* t, const Rectangle& rect, const Color& c void Render2D::DrawSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; - if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) + if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) return; Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index); @@ -1555,7 +1567,7 @@ void Render2D::DrawTexturePoint(GPUTexture* t, const Rectangle& rect, const Colo void Render2D::DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; - if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) + if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) return; Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index); diff --git a/Source/Engine/Render2D/SpriteAtlas.cpp b/Source/Engine/Render2D/SpriteAtlas.cpp index fba2b8a06..c43f9f26f 100644 --- a/Source/Engine/Render2D/SpriteAtlas.cpp +++ b/Source/Engine/Render2D/SpriteAtlas.cpp @@ -12,7 +12,7 @@ const SpriteHandle SpriteHandle::Invalid = { nullptr, INVALID_INDEX }; -REGISTER_BINARY_ASSET(SpriteAtlas, "FlaxEngine.SpriteAtlas", ::New(), true); +REGISTER_BINARY_ASSET_WITH_UPGRADER(SpriteAtlas, "FlaxEngine.SpriteAtlas", TextureAssetUpgrader, true); bool SpriteHandle::GetSprite(Sprite* result) const { @@ -40,10 +40,26 @@ SpriteAtlas::SpriteAtlas(const SpawnParams& params, const AssetInfo* info) { } +int32 SpriteAtlas::GetSpritesCount() const +{ + return Sprites.Count(); +} + +Sprite SpriteAtlas::GetSprite(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < Sprites.Count(), Sprite()) + return Sprites.Get()[index]; +} + +void SpriteAtlas::SetSprite(int32 index, const Sprite& value) +{ + CHECK(index >= 0 && index < Sprites.Count()); + Sprites.Get()[index] = value; +} + SpriteHandle SpriteAtlas::FindSprite(const StringView& name) const { SpriteHandle result(const_cast(this), -1); - for (int32 i = 0; i < Sprites.Count(); i++) { if (name == Sprites[i].Name) @@ -52,16 +68,13 @@ SpriteHandle SpriteAtlas::FindSprite(const StringView& name) const break; } } - return result; } SpriteHandle SpriteAtlas::AddSprite(const Sprite& sprite) { const int32 index = Sprites.Count(); - Sprites.Add(sprite); - return SpriteHandle(this, index); } @@ -91,10 +104,8 @@ bool SpriteAtlas::SaveSprites() MemoryWriteStream stream(1024); stream.WriteInt32(1); // Version stream.WriteInt32(Sprites.Count()); // Sprites Count - for (int32 i = 0; i < Sprites.Count(); i++) + for (Sprite& t : Sprites) { - // Save sprite - Sprite t = Sprites[i]; stream.Write(&t.Area); stream.WriteString(t.Name, 49); } @@ -105,7 +116,7 @@ bool SpriteAtlas::SaveSprites() // Save (use silent mode to prevent asset reloading) bool saveResult = SaveAsset(data, true); - dataChunk->Data.Release(); + dataChunk->Data.Unlink(); if (saveResult) { LOG(Warning, "Failed to save sprite atlas \'{0}\'.", GetPath()); @@ -119,53 +130,50 @@ bool SpriteAtlas::SaveSprites() bool SpriteAtlas::LoadSprites(ReadStream& stream) { -#if USE_EDITOR + ScopeLock lock(Locker); + // Sprites may be used on rendering thread so lock drawing for a while - if (GPUDevice::Instance) - GPUDevice::Instance->Locker.Lock(); +#if USE_EDITOR + GPUDevice* gpuDevice = GPUDevice::Instance; + if (gpuDevice) + gpuDevice->Locker.Lock(); #endif - // Cleanup first Sprites.Clear(); - // Load tiles data int32 tilesVersion, tilesCount; stream.ReadInt32(&tilesVersion); if (tilesVersion != 1) { #if USE_EDITOR - if (GPUDevice::Instance) - GPUDevice::Instance->Locker.Unlock(); + if (gpuDevice) + gpuDevice->Locker.Unlock(); #endif LOG(Warning, "Invalid tiles version."); return true; } stream.ReadInt32(&tilesCount); Sprites.Resize(tilesCount); - for (int32 i = 0; i < tilesCount; i++) + for (Sprite& t : Sprites) { - // Load sprite - Sprite& t = Sprites[i]; stream.Read(&t.Area); stream.ReadString(&t.Name, 49); } #if USE_EDITOR - if (GPUDevice::Instance) - GPUDevice::Instance->Locker.Unlock(); + if (gpuDevice) + gpuDevice->Locker.Unlock(); #endif return false; } Asset::LoadResult SpriteAtlas::load() { - // Get sprites data auto spritesDataChunk = GetChunk(15); if (spritesDataChunk == nullptr || spritesDataChunk->IsMissing()) return LoadResult::MissingDataChunk; MemoryReadStream spritesData(spritesDataChunk->Get(), spritesDataChunk->Size()); - // Load sprites if (LoadSprites(spritesData)) { LOG(Warning, "Cannot load sprites atlas data."); @@ -177,7 +185,6 @@ Asset::LoadResult SpriteAtlas::load() void SpriteAtlas::unload(bool isReloading) { - // Release sprites Sprites.Resize(0); // Base diff --git a/Source/Engine/Render2D/SpriteAtlas.h b/Source/Engine/Render2D/SpriteAtlas.h index 9631b9ac9..6845de610 100644 --- a/Source/Engine/Render2D/SpriteAtlas.h +++ b/Source/Engine/Render2D/SpriteAtlas.h @@ -111,32 +111,21 @@ public: /// /// Gets the sprites count. /// - /// The sprites count. - API_PROPERTY() int32 GetSpritesCount() const - { - return Sprites.Count(); - } + API_PROPERTY() int32 GetSpritesCount() const; /// /// Gets the sprite data. /// /// The index. /// The sprite data. - API_FUNCTION() Sprite GetSprite(const int32 index) const - { - return Sprites[index]; - } + API_FUNCTION() Sprite GetSprite(int32 index) const; /// /// Sets the sprite data. /// /// The index. /// The sprite data. - /// The sprite handle. - API_FUNCTION() void SetSprite(const int32 index, API_PARAM(Ref) const Sprite& value) - { - Sprites[index] = value; - } + API_FUNCTION() void SetSprite(int32 index, API_PARAM(Ref) const Sprite& value); /// /// Finds the sprite by the name. diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.cpp b/Source/Engine/Renderer/AmbientOcclusionPass.cpp index 46027e118..7e4ac0d33 100644 --- a/Source/Engine/Renderer/AmbientOcclusionPass.cpp +++ b/Source/Engine/Renderer/AmbientOcclusionPass.cpp @@ -1,16 +1,18 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "AmbientOcclusionPass.h" +#include "RenderList.h" +#include "GBufferPass.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Utilities/StringConverter.h" -#include "RenderList.h" -#include "GBufferPass.h" AmbientOcclusionPass::ASSAO_Settings::ASSAO_Settings() { diff --git a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp index 3dcb6ae71..1675f6193 100644 --- a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp @@ -5,6 +5,7 @@ #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/Graphics.h" +#include "Engine/Graphics/RenderTask.h" bool FXAA::Init() { diff --git a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp index 7c1ee0c16..515d2a883 100644 --- a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp @@ -6,6 +6,7 @@ #include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/RenderTask.h" bool SMAA::setupResources() { diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.cpp b/Source/Engine/Renderer/AntiAliasing/TAA.cpp index a1fee93ad..e61132b42 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/TAA.cpp @@ -7,6 +7,8 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Renderer/RenderList.h" #include "Engine/Engine/Engine.h" bool TAA::Init() diff --git a/Source/Engine/Renderer/AtmospherePreCompute.cpp b/Source/Engine/Renderer/AtmospherePreCompute.cpp index cef9964ca..d0083bfc6 100644 --- a/Source/Engine/Renderer/AtmospherePreCompute.cpp +++ b/Source/Engine/Renderer/AtmospherePreCompute.cpp @@ -6,13 +6,15 @@ #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/GPUPipelineState.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Platform/Window.h" #include "RendererPass.h" #include "Engine/Threading/ThreadPoolTask.h" -#include "Engine/Graphics/GPUPipelineState.h" -#include "Engine/Graphics/RenderTask.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/AssetReference.h" diff --git a/Source/Engine/Renderer/ColorGradingPass.cpp b/Source/Engine/Renderer/ColorGradingPass.cpp index c40248caa..536caf91a 100644 --- a/Source/Engine/Renderer/ColorGradingPass.cpp +++ b/Source/Engine/Renderer/ColorGradingPass.cpp @@ -1,10 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ColorGradingPass.h" +#include "RenderList.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/RenderTask.h" PACK_STRUCT(struct Data { Vector4 ColorSaturationShadows; diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index 4e4ee7ed1..3e5cdda92 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -1,15 +1,19 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "DepthOfFieldPass.h" +#include "RenderList.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/GPUBuffer.h" -#include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/GPUContext.h" -#include "Engine/Graphics/PostProcessBase.h" -#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/PostProcessBase.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Shaders/GPUShader.h" // This must match hlsl defines #define DOF_MAX_SAMPLE_RADIUS 10 @@ -34,6 +38,8 @@ bool DepthOfFieldPass::Init() _platformSupportsDoF = limits.HasCompute; _platformSupportsBokeh = _platformSupportsDoF && limits.HasGeometryShaders && limits.HasDrawIndirect && limits.HasAppendConsumeBuffers; + _platformSupportsBokeh &= GPUDevice::Instance->GetRendererType() != RendererType::DirectX12; // TODO: fix bokeh crash on d3d12 (driver issue probably - started to happen recently) + // Create pipeline states if (_platformSupportsDoF) { diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp index 9ba9f1a0d..4f1533870 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp @@ -5,7 +5,9 @@ #include "LightmapUVsDensity.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUPipelineState.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Renderer/DrawCall.h" diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.h b/Source/Engine/Renderer/Editor/LightmapUVsDensity.h index 70a7b1b0e..8431fb168 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.h +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.h @@ -9,6 +9,8 @@ #include "Engine/Content/Assets/Texture.h" #include "Engine/Graphics/Materials/IMaterial.h" +class GPUPipelineState; + /// /// Lightmap UVs Density rendering for profiling and debugging in editor. /// diff --git a/Source/Engine/Renderer/Editor/VertexColors.cpp b/Source/Engine/Renderer/Editor/VertexColors.cpp index 07bdea813..a74a70baf 100644 --- a/Source/Engine/Renderer/Editor/VertexColors.cpp +++ b/Source/Engine/Renderer/Editor/VertexColors.cpp @@ -4,7 +4,9 @@ #include "VertexColors.h" #include "Engine/Content/Content.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUPipelineState.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Renderer/DrawCall.h" diff --git a/Source/Engine/Renderer/Editor/VertexColors.h b/Source/Engine/Renderer/Editor/VertexColors.h index 5ee726789..49e2b7c9d 100644 --- a/Source/Engine/Renderer/Editor/VertexColors.h +++ b/Source/Engine/Renderer/Editor/VertexColors.h @@ -8,6 +8,8 @@ #include "Engine/Content/Assets/Shader.h" #include "Engine/Graphics/Materials/IMaterial.h" +class GPUPipelineState; + /// /// Vertex Colors rendering for profiling and debugging in editor. /// diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp index 3bfbb9daa..42f973c23 100644 --- a/Source/Engine/Renderer/EyeAdaptationPass.cpp +++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp @@ -1,18 +1,21 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "EyeAdaptationPass.h" +#include "HistogramPass.h" #include "RenderList.h" #include "Engine/Core/Math/Int2.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" -#include "Engine/Graphics/PostProcessBase.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/PostProcessBase.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderBuffers.h" -#include "Engine/Engine/Time.h" -#include "Engine/Graphics/GPULimits.h" #include "Engine/Engine/Engine.h" -#include "HistogramPass.h" +#include "Engine/Engine/Time.h" PACK_STRUCT(struct EyeAdaptationData { float MinBrightness; diff --git a/Source/Engine/Renderer/ForwardPass.cpp b/Source/Engine/Renderer/ForwardPass.cpp index 93ccf0750..83d0429e5 100644 --- a/Source/Engine/Renderer/ForwardPass.cpp +++ b/Source/Engine/Renderer/ForwardPass.cpp @@ -1,12 +1,16 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ForwardPass.h" +#include "RenderList.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Shaders/GPUShader.h" ForwardPass::ForwardPass() : _shader(nullptr) diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 2165e25dc..0791f1dc2 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -1,11 +1,15 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "GBufferPass.h" +#include "RenderList.h" #include "Engine/Renderer/Editor/VertexColors.h" #include "Engine/Renderer/Editor/LightmapUVsDensity.h" #include "Engine/Core/Collections/Sorting.h" -#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" diff --git a/Source/Engine/Renderer/HistogramPass.cpp b/Source/Engine/Renderer/HistogramPass.cpp index e8bfc9fad..928f0c6b3 100644 --- a/Source/Engine/Renderer/HistogramPass.cpp +++ b/Source/Engine/Renderer/HistogramPass.cpp @@ -9,6 +9,8 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/GPULimits.h" #include "Engine/Engine/Engine.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Shaders/GPUShader.h" // Those defines must match the HLSL #define THREADGROUP_SIZE_X 16 diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index c79033c37..64219ef9c 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -9,6 +9,7 @@ #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Content/Assets/CubeTexture.h" #include "Engine/Content/Content.h" +#include "Engine/Graphics/RenderTask.h" PACK_STRUCT(struct PerLight{ LightData Light; diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index 689916708..8555676a4 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -3,6 +3,7 @@ #include "MotionBlurPass.h" #include "GBufferPass.h" #include "Renderer.h" +#include "RenderList.h" #include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" @@ -10,6 +11,10 @@ #include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/PostProcessSettings.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Engine/Time.h" PACK_STRUCT(struct Data { diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 4e5b4d4fb..660e76ac5 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -6,6 +6,7 @@ #include "Engine/Content/Content.h" #include "Engine/Graphics/PostProcessBase.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Engine/Time.h" diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 3f48f8c6e..0a078b4b7 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -14,10 +14,11 @@ #include "Engine/Level/SceneQuery.h" #include "Engine/ContentExporters/AssetExporters.h" #include "Engine/Serialization/FileWriteStream.h" -#include "Engine/Graphics/GPUContext.h" #include "Engine/Engine/Time.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/AssetReference.h" +#include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Engine/Engine.h" diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index c3108cf0d..90214371f 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -2,11 +2,13 @@ #include "ReflectionsPass.h" #include "GBufferPass.h" +#include "RenderList.h" #include "ScreenSpaceReflectionsPass.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Level/Actors/EnvironmentProbe.h" diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 89efef74c..f2abe867a 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -4,6 +4,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Engine/EngineService.h" #include "GBufferPass.h" #include "ForwardPass.h" diff --git a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp index 5e81db45c..bd1662773 100644 --- a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp +++ b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp @@ -3,6 +3,7 @@ #include "ScreenSpaceReflectionsPass.h" #include "ReflectionsPass.h" #include "GBufferPass.h" +#include "RenderList.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderTools.h" @@ -12,6 +13,7 @@ #include "Engine/Platform/Window.h" #include "Utils/MultiScaler.h" #include "Engine/Engine/Engine.h" +#include "Engine/Graphics/RenderTask.h" #define REFLECTIONS_FORMAT PixelFormat::R11G11B10_Float #define RESOLVE_PASS_OUTPUT_FORMAT PixelFormat::R16G16B16A16_Float diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 4c3260045..3f724dfaa 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -4,9 +4,10 @@ #include "GBufferPass.h" #include "VolumetricFogPass.h" #include "Engine/Graphics/Graphics.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" -#include "Engine/Content/Content.h" #include "Engine/Graphics/PixelFormatExtensions.h" +#include "Engine/Content/Content.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" #endif @@ -87,8 +88,8 @@ bool ShadowsPass::Init() if (!_supportsShadows) { LOG(Warning, "GPU doesn't support shadows rendering"); - LOG(Warning, "Format: {0} features support: {1}", (int32)SHADOW_MAPS_FORMAT, (uint32)formatFeaturesDepth.Support); - LOG(Warning, "Format: {0} features support: {1}", (int32)formatTexture, (uint32)formatFeaturesTexture.Support); + LOG(Warning, "Format: {0}, features support: {1}", (int32)SHADOW_MAPS_FORMAT, (uint32)formatFeaturesDepth.Support); + LOG(Warning, "Format: {0}, features support: {1}", (int32)formatTexture, (uint32)formatFeaturesTexture.Support); } return false; @@ -251,6 +252,7 @@ void ShadowsPass::Prepare(RenderContext& renderContext, GPUContext* context) auto& shadowView = _shadowContext.View; shadowView.Flags = view.Flags; shadowView.StaticFlagsMask = view.StaticFlagsMask; + shadowView.RenderLayersMask = view.RenderLayersMask; shadowView.IsOfflinePass = view.IsOfflinePass; shadowView.ModelLODBias = view.ModelLODBias + view.ShadowModelLODBias; shadowView.ModelLODDistanceFactor = view.ModelLODDistanceFactor * view.ShadowModelLODDistanceFactor; diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h index 3d3190670..6a72913f8 100644 --- a/Source/Engine/Renderer/ShadowsPass.h +++ b/Source/Engine/Renderer/ShadowsPass.h @@ -7,6 +7,7 @@ #include "RendererPass.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Assets/Model.h" +#include "Engine/Graphics/RenderTask.h" /// /// Pixel format for fullscreen render target used for shadows calculations diff --git a/Source/Engine/Renderer/Utils/BitonicSort.cpp b/Source/Engine/Renderer/Utils/BitonicSort.cpp index 89313a95d..3858a7fae 100644 --- a/Source/Engine/Renderer/Utils/BitonicSort.cpp +++ b/Source/Engine/Renderer/Utils/BitonicSort.cpp @@ -2,14 +2,27 @@ #include "BitonicSort.h" #include "Engine/Content/Content.h" +#include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPULimits.h" #define INDIRECT_ARGS_STRIDE 12 -BitonicSort::BitonicSort() - : _dispatchArgsBuffer(nullptr) +// The sorting keys buffer item structure template. Matches the shader type. +struct Item { -} + float Key; + uint32 Value; +}; + +PACK_STRUCT(struct Data { + Item NullItem; + uint32 CounterOffset; + uint32 MaxIterations; + uint32 LoopK; + float KeySign; + uint32 LoopJ; + float Dummy0; + }); String BitonicSort::ToString() const { diff --git a/Source/Engine/Renderer/Utils/BitonicSort.h b/Source/Engine/Renderer/Utils/BitonicSort.h index 053cee6ba..caef88078 100644 --- a/Source/Engine/Renderer/Utils/BitonicSort.h +++ b/Source/Engine/Renderer/Utils/BitonicSort.h @@ -7,36 +7,15 @@ /// /// Bitonic Sort implementation using GPU compute shaders. -/// It has a complexity of O(n*(log n)^2), which is inferior to most -/// traditional sorting algorithms, but because GPUs have so many threads, -/// and because each thread can be utilized, the algorithm can fully load -/// the GPU, taking advantage of its high ALU and bandwidth capabilities. +/// It has a complexity of O(n*(log n)^2), which is inferior to most traditional sorting algorithms, but because GPUs have so many threads, +/// and because each thread can be utilized, the algorithm can fully load the GPU, taking advantage of its high ALU and bandwidth capabilities. /// class BitonicSort : public RendererPass { -public: - - // The sorting keys buffer item structure template. Matches the shader type. - struct Item - { - float Key; - uint32 Value; - }; - private: - PACK_STRUCT(struct Data { - Item NullItem; - uint32 CounterOffset; - uint32 MaxIterations; - uint32 LoopK; - float KeySign; - uint32 LoopJ; - float Dummy0; - }); - AssetReference _shader; - GPUBuffer* _dispatchArgsBuffer; + GPUBuffer* _dispatchArgsBuffer = nullptr; GPUConstantBuffer* _cb; GPUShaderProgramCS* _indirectArgsCS; GPUShaderProgramCS* _preSortCS; @@ -44,13 +23,6 @@ private: GPUShaderProgramCS* _outerSortCS; GPUShaderProgramCS* _copyIndicesCS; -public: - - /// - /// Initializes a new instance of the class. - /// - BitonicSort(); - public: /// diff --git a/Source/Engine/Renderer/Utils/MultiScaler.cpp b/Source/Engine/Renderer/Utils/MultiScaler.cpp index 2c6e8ba14..f38edab35 100644 --- a/Source/Engine/Renderer/Utils/MultiScaler.cpp +++ b/Source/Engine/Renderer/Utils/MultiScaler.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "MultiScaler.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Content/Content.h" MultiScaler::MultiScaler() diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 92a8eb092..c460533d6 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -4,6 +4,7 @@ #include "ShadowsPass.h" #include "GBufferPass.h" #include "Engine/Graphics/Graphics.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/RenderTargetPool.h" @@ -193,9 +194,9 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, _cache.Data.GlobalExtinctionScale = options.ExtinctionScale; _cache.Data.GlobalEmissive = options.Emissive.ToVector3() * options.Emissive.A; _cache.Data.GridSize = _cache.GridSize; - _cache.Data.GridSizeIntX = (int32)_cache.GridSize.X; - _cache.Data.GridSizeIntY = (int32)_cache.GridSize.Y; - _cache.Data.GridSizeIntZ = (int32)_cache.GridSize.Z; + _cache.Data.GridSizeIntX = (uint32)_cache.GridSize.X; + _cache.Data.GridSizeIntY = (uint32)_cache.GridSize.Y; + _cache.Data.GridSizeIntZ = (uint32)_cache.GridSize.Z; _cache.Data.HistoryWeight = _cache.HistoryWeight; _cache.Data.FogParameters = options.FogParameters; _cache.Data.InverseSquaredLightDistanceBiasScale = _cache.InverseSquaredLightDistanceBiasScale; @@ -298,6 +299,8 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte context->SetViewportAndScissors(_cache.Data.GridSize.X, _cache.Data.GridSize.Y); // Setup data + perLight.SliceToDepth.X = _cache.Data.GridSize.Z; + perLight.SliceToDepth.Y = _cache.Data.VolumetricFogMaxDistance; perLight.MinZ = volumeZBoundsMin; perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius); @@ -355,6 +358,8 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte bool withShadow = false; // Setup data + perLight.SliceToDepth.X = cache.Data.GridSize.Z; + perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance; perLight.MinZ = volumeZBoundsMin; perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius); diff --git a/Source/Engine/Renderer/VolumetricFogPass.h b/Source/Engine/Renderer/VolumetricFogPass.h index 52fc84a77..94ada2034 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.h +++ b/Source/Engine/Renderer/VolumetricFogPass.h @@ -6,6 +6,10 @@ #include "Engine/Graphics/GPUPipelineStatePermutations.h" #include "RendererPass.h" +struct VolumetricFogOptions; +struct RendererSpotLightData; +struct RendererPointLightData; + /// /// Volumetric fog rendering service. /// @@ -42,9 +46,9 @@ private: Vector3 GridSize; uint32 MissedHistorySamplesCount; - int32 GridSizeIntX; - int32 GridSizeIntY; - int32 GridSizeIntZ; + uint32 GridSizeIntX; + uint32 GridSizeIntY; + uint32 GridSizeIntZ; float PhaseG; Vector2 Dummy0; diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index a348b04cd..be5a356d2 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -13,7 +13,6 @@ #include "FlaxEngine.Gen.h" #include "MException.h" #include "Scripting.h" -#include "StdTypesContainer.h" #include "Events.h" Dictionary, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable; @@ -332,14 +331,13 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S // Script module->Types.AddUninitialized(); new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); - const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + if (module->TypeNameToTypeIndex.ContainsKey(fullname)) { LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); } #endif - module->TypeNameToTypeIndex[typeName] = TypeIndex; + module->TypeNameToTypeIndex[fullname] = TypeIndex; } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) @@ -348,14 +346,13 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S // Class module->Types.AddUninitialized(); new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces); - const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + if (module->TypeNameToTypeIndex.ContainsKey(fullname)) { LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); } #endif - module->TypeNameToTypeIndex[typeName] = TypeIndex; + module->TypeNameToTypeIndex[fullname] = TypeIndex; } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) @@ -364,14 +361,13 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S // Structure module->Types.AddUninitialized(); new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); - const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + if (module->TypeNameToTypeIndex.ContainsKey(fullname)) { LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); } #endif - module->TypeNameToTypeIndex[typeName] = TypeIndex; + module->TypeNameToTypeIndex[fullname] = TypeIndex; } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) @@ -380,14 +376,13 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S // Interface module->Types.AddUninitialized(); new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, baseType, interfaces); - const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + if (module->TypeNameToTypeIndex.ContainsKey(fullname)) { LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); } #endif - module->TypeNameToTypeIndex[typeName] = TypeIndex; + module->TypeNameToTypeIndex[fullname] = TypeIndex; } BinaryModule::BinaryModulesList& BinaryModule::GetModules() @@ -417,6 +412,11 @@ BinaryModule::BinaryModule() GetModules().Add(this); } +void* BinaryModule::FindMethod(const ScriptingTypeHandle& typeHandle, const ScriptingTypeMethodSignature& signature) +{ + return FindMethod(typeHandle, signature.Name, signature.Params.Count()); +} + void BinaryModule::Destroy(bool isReloading) { // Unregister @@ -1070,9 +1070,9 @@ void NativeOnlyBinaryModule::Destroy(bool isReloading) } } -Array& StaticallyLinkedBinaryModuleInitializer::GetStaticallyLinkedBinaryModules() +Array>& StaticallyLinkedBinaryModuleInitializer::GetStaticallyLinkedBinaryModules() { - static Array modules; + static Array> modules; return modules; } diff --git a/Source/Engine/Scripting/BinaryModule.h b/Source/Engine/Scripting/BinaryModule.h index 5d470965b..1bc048055 100644 --- a/Source/Engine/Scripting/BinaryModule.h +++ b/Source/Engine/Scripting/BinaryModule.h @@ -7,6 +7,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Serialization/ISerializable.h" #include "ManagedCLR/MAssemblyOptions.h" @@ -131,10 +132,7 @@ public: /// The type to find method inside it. /// The method signature. /// The method or null if failed to get it. - virtual void* FindMethod(const ScriptingTypeHandle& typeHandle, const ScriptingTypeMethodSignature& signature) - { - return FindMethod(typeHandle, signature.Name, signature.Params.Count()); - } + virtual void* FindMethod(const ScriptingTypeHandle& typeHandle, const ScriptingTypeMethodSignature& signature); /// /// Invokes a given scripting method. @@ -398,7 +396,7 @@ private: public: - static Array& GetStaticallyLinkedBinaryModules(); + static Array>& GetStaticallyLinkedBinaryModules(); explicit StaticallyLinkedBinaryModuleInitializer(GetBinaryModuleFunc getter); ~StaticallyLinkedBinaryModuleInitializer(); }; diff --git a/Source/Engine/Scripting/InternalCalls.h b/Source/Engine/Scripting/InternalCalls.h index 8f8702f56..3a1c33160 100644 --- a/Source/Engine/Scripting/InternalCalls.h +++ b/Source/Engine/Scripting/InternalCalls.h @@ -5,11 +5,11 @@ #include "Engine/Debug/DebugLog.h" #include "Engine/Core/Log.h" #include "ScriptingType.h" -#include +extern "C" FLAXENGINE_API void mono_add_internal_call(const char* name, const void* method); #define ADD_INTERNAL_CALL(fullName, method) mono_add_internal_call(fullName, (const void*)method) -#if BUILD_RELEASE +#if BUILD_RELEASE && 0 // Using invalid handle will crash engine in Release build #define INTERNAL_CALL_CHECK(obj) diff --git a/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h b/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h index 215a723ce..6b9743709 100644 --- a/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h +++ b/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h @@ -23,15 +23,17 @@ struct FLAXENGINE_API ManagedDictionary } template - static MonoObject* ToManaged(const Dictionary& data, MonoClass* keyType, MonoClass* valueType) + static MonoObject* ToManaged(const Dictionary& data, MonoType* keyType, MonoType* valueType) { MConverter keysConverter; MConverter valueConverter; ManagedDictionary result = New(keyType, valueType); + MonoClass* keyClass = mono_type_get_class(keyType); + MonoClass* valueClass = mono_type_get_class(valueType); for (auto i = data.Begin(); i.IsNotEnd(); ++i) { - MonoObject* keyManaged = keysConverter.Box(i->Key, keyType); - MonoObject* valueManaged = valueConverter.Box(i->Value, valueType); + MonoObject* keyManaged = keysConverter.Box(i->Key, keyClass); + MonoObject* valueManaged = valueConverter.Box(i->Value, valueClass); result.Add(keyManaged, valueManaged); } return result.Instance; @@ -71,10 +73,11 @@ struct FLAXENGINE_API ManagedDictionary return result; } - static ManagedDictionary New(MonoClass* keyType, MonoClass* valueType) + static ManagedDictionary New(MonoType* keyType, MonoType* valueType) { ManagedDictionary result; + auto domain = mono_domain_get(); auto scriptingClass = Scripting::GetStaticClass(); CHECK_RETURN(scriptingClass, result); auto makeGenericMethod = scriptingClass->GetMethod("MakeGenericType", 2); @@ -83,9 +86,9 @@ struct FLAXENGINE_API ManagedDictionary CHECK_RETURN(createMethod, result); auto genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass->GetNative()); - auto genericArgs = mono_array_new(mono_domain_get(), mono_get_object_class(), 2); - mono_array_set(genericArgs, MonoReflectionType*, 0, MUtils::GetType(keyType)); - mono_array_set(genericArgs, MonoReflectionType*, 1, MUtils::GetType(valueType)); + auto genericArgs = mono_array_new(domain, mono_get_object_class(), 2); + mono_array_set(genericArgs, MonoReflectionType*, 0, mono_type_get_object(domain, keyType)); + mono_array_set(genericArgs, MonoReflectionType*, 1, mono_type_get_object(domain, valueType)); void* params[2]; params[0] = genericType; diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp index 40eaf3b00..32ae90870 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp @@ -8,6 +8,7 @@ #include "MDomain.h" #include "MUtils.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" #include "Engine/Debug/Exceptions/FileNotFoundException.h" @@ -16,6 +17,7 @@ #include "Engine/Scripting/Scripting.h" #include "Engine/Platform/StringUtils.h" #include "Engine/Platform/File.h" +#include "Engine/Threading/Threading.h" #include #include #include @@ -123,7 +125,6 @@ void MAssembly::Unload(bool isReloading) LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name)); mono_assembly_close(_monoAssembly); - mono_image_close(_monoImage); } else { @@ -306,18 +307,16 @@ bool MAssembly::LoadWithImage(const String& assemblyPath) // Setup assembly const auto assembly = mono_assembly_load_from_full(assemblyImage, name.Substring(0, name.Length() - 3).Get(), &status, false); + mono_image_close(assemblyImage); if (status != MONO_IMAGE_OK || assembly == nullptr) { - // Close image if error occurred - mono_image_close(assemblyImage); - Log::CLRInnerException(TEXT("Mono assembly image is corrupted at ") + assemblyPath); return true; } #if MONO_DEBUG_ENABLE // Try to load debug symbols (use portable PDB format) - const auto pdbPath = StringUtils::GetPathWithoutExtension(assemblyPath) + TEXT(".pdb"); + const auto pdbPath = String(StringUtils::GetPathWithoutExtension(assemblyPath)) + TEXT(".pdb"); if (FileSystem::FileExists(pdbPath)) { // Load .pdb file @@ -329,6 +328,24 @@ bool MAssembly::LoadWithImage(const String& assemblyPath) mono_debug_open_image_from_memory(assemblyImage, _debugData.Get(), _debugData.Count()); } } + +#if 0 + // Hack to load debug information for Newtonsoft.Json (enable it to debug C# code of json lib) + if (assemblyPath.EndsWith(TEXT("FlaxEngine.CSharp.dll"))) + { + static Array NewtonsoftJsonDebugData; + File::ReadAllBytes(String(StringUtils::GetDirectoryName(assemblyPath)) / TEXT("Newtonsoft.Json.pdb"), NewtonsoftJsonDebugData); + if (NewtonsoftJsonDebugData.HasItems()) + { + StringAnsi tmp(String(StringUtils::GetDirectoryName(assemblyPath)) / TEXT("Newtonsoft.Json.dll")); + MonoAssembly* a = mono_assembly_open(tmp.Get(), &status); + if (a) + { + mono_debug_open_image_from_memory(mono_assembly_get_image(a), NewtonsoftJsonDebugData.Get(), NewtonsoftJsonDebugData.Count()); + } + } + } +#endif #endif // Set state diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h index 17cb8b624..ff44f8b12 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h @@ -6,6 +6,7 @@ #include "MAssemblyOptions.h" #include "Engine/Core/Delegate.h" #include "Engine/Core/Types/String.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Platform/CriticalSection.h" diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MClass.Mono.cpp index 9e823ea20..21551afc0 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MClass.Mono.cpp @@ -51,10 +51,12 @@ MClass::MClass(const MAssembly* parentAssembly, MonoClass* monoClass, const MStr case MONO_TYPE_ATTR_NESTED_ASSEMBLY: _visibility = MVisibility::Internal; break; - case MONO_TYPE_ATTR_NESTED_FAM_AND_ASSEM: case MONO_TYPE_ATTR_NESTED_FAM_OR_ASSEM: _visibility = MVisibility::ProtectedInternal; break; + case MONO_TYPE_ATTR_NESTED_FAM_AND_ASSEM: + _visibility = MVisibility::PrivateProtected; + break; default: CRASH; } diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp index a8ad6e49f..a1420fd24 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp @@ -10,11 +10,11 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Engine/Globals.h" #include "Engine/Debug/Exceptions/Exceptions.h" #include "Engine/Threading/Threading.h" #include "Engine/Platform/Thread.h" #include "Engine/Scripting/MException.h" -#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Profiler/ProfilerCPU.h" #include #include @@ -181,7 +181,16 @@ void OnGCAllocation(MonoProfiler* profiler, MonoObject* obj) #endif #if COMPILE_WITH_PROFILER - ProfilerMemory::OnAllocation(size, true); + // Register allocation during the current CPU event + auto thread = ProfilerCPU::GetCurrentThread(); + if (thread != nullptr && thread->Buffer.GetCount() != 0) + { + auto& activeEvent = thread->Buffer.Last().Event(); + if (activeEvent.End < ZeroTolerance) + { + activeEvent.ManagedMemoryAllocation += size; + } + } #endif } @@ -414,7 +423,7 @@ bool MCore::LoadEngine() } char buffer[150]; - sprintf(buffer, "--debugger-agent=transport=dt_socket,address=%s:%d,embedding=1,server=y,suspend=n,loglevel=%d", debuggerIp.Get(), debuggerPort, debuggerLogLevel); + sprintf(buffer, "--debugger-agent=transport=dt_socket,address=%s:%d,embedding=1,server=y,suspend=%s,loglevel=%d", debuggerIp.Get(), debuggerPort, CommandLine::Options.WaitForDebugger ? "y,timeout=5000" : "n", debuggerLogLevel); const char* options[] = { "--soft-breakpoints", @@ -508,8 +517,9 @@ bool MCore::LoadEngine() Thread::ThreadExiting.Bind(); // Info - const String buildInfo(mono_get_runtime_build_info()); - LOG(Info, "Mono version: {0}", buildInfo); + char* buildInfo = mono_get_runtime_build_info(); + LOG(Info, "Mono version: {0}", String(buildInfo)); + mono_free(buildInfo); return false; } diff --git a/Source/Engine/Scripting/ManagedCLR/MField.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MField.Mono.cpp index 3ca21be78..cea174462 100644 --- a/Source/Engine/Scripting/ManagedCLR/MField.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MField.Mono.cpp @@ -28,7 +28,7 @@ MField::MField(MonoClassField* monoField, const char* name, MClass* parentClass) _visibility = MVisibility::Private; break; case MONO_FIELD_ATTR_FAM_AND_ASSEM: - _visibility = MVisibility::ProtectedInternal; + _visibility = MVisibility::PrivateProtected; break; case MONO_FIELD_ATTR_ASSEMBLY: _visibility = MVisibility::Internal; @@ -36,6 +36,9 @@ MField::MField(MonoClassField* monoField, const char* name, MClass* parentClass) case MONO_FIELD_ATTR_FAMILY: _visibility = MVisibility::Protected; break; + case MONO_FIELD_ATTR_FAM_OR_ASSEM: + _visibility = MVisibility::ProtectedInternal; + break; case MONO_FIELD_ATTR_PUBLIC: _visibility = MVisibility::Public; break; diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp index 2013cffd7..3fb8a16a4 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp @@ -6,6 +6,7 @@ #include "MType.h" #include "MClass.h" +#include "Engine/Profiler/ProfilerCPU.h" #include #include @@ -34,7 +35,7 @@ MMethod::MMethod(MonoMethod* monoMethod, const char* name, MClass* parentClass) _visibility = MVisibility::Private; break; case MONO_METHOD_ATTR_FAM_AND_ASSEM: - _visibility = MVisibility::ProtectedInternal; + _visibility = MVisibility::PrivateProtected; break; case MONO_METHOD_ATTR_ASSEM: _visibility = MVisibility::Internal; @@ -42,21 +43,35 @@ MMethod::MMethod(MonoMethod* monoMethod, const char* name, MClass* parentClass) case MONO_METHOD_ATTR_FAMILY: _visibility = MVisibility::Protected; break; + case MONO_METHOD_ATTR_FAM_OR_ASSEM: + _visibility = MVisibility::ProtectedInternal; + break; case MONO_METHOD_ATTR_PUBLIC: _visibility = MVisibility::Public; break; default: CRASH; } + +#if COMPILE_WITH_PROFILER + const MString& className = parentClass->GetFullName(); + ProfilerName.Resize(className.Length() + 2 + _name.Length()); + Platform::MemoryCopy(ProfilerName.Get(), className.Get(), className.Length()); + ProfilerName.Get()[className.Length()] = ':'; + ProfilerName.Get()[className.Length() + 1] = ':'; + Platform::MemoryCopy(ProfilerName.Get() + className.Length() + 2, _name.Get(), _name.Length()); +#endif } MonoObject* MMethod::Invoke(void* instance, void** params, MonoObject** exception) const { + PROFILE_CPU_NAMED(*ProfilerName); return mono_runtime_invoke(_monoMethod, instance, params, exception); } MonoObject* MMethod::InvokeVirtual(MonoObject* instance, void** params, MonoObject** exception) const { + PROFILE_CPU_NAMED(*ProfilerName); MonoMethod* virtualMethod = mono_object_get_virtual_method(instance, _monoMethod); return mono_runtime_invoke(virtualMethod, instance, params, exception); } @@ -87,7 +102,7 @@ int32 MMethod::GetParametersCount() const return mono_signature_get_param_count(sig); } -MType MMethod::GetParameterType(int32 paramIdx) +MType MMethod::GetParameterType(int32 paramIdx) const { MonoMethodSignature* sig = mono_method_signature(_monoMethod); ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < (int32)mono_signature_get_param_count(sig)); @@ -96,7 +111,7 @@ MType MMethod::GetParameterType(int32 paramIdx) return MType(((MonoType**)it)[paramIdx]); } -bool MMethod::GetParameterIsOut(int32 paramIdx) +bool MMethod::GetParameterIsOut(int32 paramIdx) const { MonoMethodSignature* sig = mono_method_signature(_monoMethod); ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < (int32)mono_signature_get_param_count(sig)); diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h index 695ac2de0..60701a9b3 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.h +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h @@ -40,6 +40,10 @@ public: public: +#if COMPILE_WITH_PROFILER + MString ProfilerName; +#endif + #if USE_MONO /// @@ -91,7 +95,6 @@ public: /// /// Gets the method name. /// - /// The name. FORCE_INLINE const MString& GetName() const { return _name; @@ -100,7 +103,6 @@ public: /// /// Returns the parent class that this method is contained with. /// - /// The parent class. FORCE_INLINE MClass* GetParentClass() const { return _parentClass; @@ -109,13 +111,11 @@ public: /// /// Returns the type of the return value. Returns null if method has no return value. /// - /// Returns method return type MType GetReturnType() const; /// /// Returns the number of parameters the method expects. /// - /// The amount of the method parameters. int32 GetParametersCount() const; /// @@ -123,19 +123,18 @@ public: /// /// The parameter type. /// The parameter type. - MType GetParameterType(int32 paramIdx); + MType GetParameterType(int32 paramIdx) const; /// /// Returns the value indicating whenever the method parameter at the specified index is marked as output parameter. /// /// The parameter type. /// True if parameter is marked as output, otherwise false. - bool GetParameterIsOut(int32 paramIdx); + bool GetParameterIsOut(int32 paramIdx) const; /// /// Gets method visibility in the class. /// - /// The method visibility. FORCE_INLINE MVisibility GetVisibility() const { return _visibility; @@ -144,7 +143,6 @@ public: /// /// Returns true if the method doesn't require a class instance. /// - /// True if the method is static, otherwise false. FORCE_INLINE bool IsStatic() const { return _isStatic != 0; @@ -155,7 +153,6 @@ public: /// /// Gets the Mono method handle. /// - /// The native Mono method handle. FORCE_INLINE MonoMethod* GetNative() const { return _monoMethod; diff --git a/Source/Engine/Scripting/ManagedCLR/MTypes.h b/Source/Engine/Scripting/ManagedCLR/MTypes.h index 9ff4062f2..4a79758b4 100644 --- a/Source/Engine/Scripting/ManagedCLR/MTypes.h +++ b/Source/Engine/Scripting/ManagedCLR/MTypes.h @@ -62,8 +62,9 @@ class MType; enum class MVisibility { Private, - Protected, + PrivateProtected, Internal, + Protected, ProtectedInternal, Public, }; diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index 99e5b5f8f..5171efbf7 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -6,6 +6,7 @@ #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingObject.h" #include #include @@ -297,6 +298,41 @@ struct MConverter> } }; +// Converter for Array. +template +struct MConverter> +{ + MonoObject* Box(const Array& data, MonoClass* klass) + { + if (!klass) + return nullptr; + // TODO: use shared empty arrays cache + auto result = mono_array_new(mono_domain_get(), klass, data.Count()); + MConverter converter; + converter.ToManagedArray(result, Span(data.Get(), data.Count())); + return (MonoObject*)result; + } + + void Unbox(Array& result, MonoObject* data) + { + auto length = data ? (int32)mono_array_length((MonoArray*)data) : 0; + result.EnsureCapacity(length); + MConverter converter; + converter.ToNativeArray(result, (MonoArray*)data, length); + } + + void ToManagedArray(MonoArray* result, const Span>& data) + { + CRASH; // Not implemented + } + + template + void ToNativeArray(Array, AllocationType>& result, MonoArray* data, int32 length) + { + CRASH; // Not implemented + } +}; + namespace MUtils { // Outputs the full typename for the type of the specified object. diff --git a/Source/Engine/Scripting/Script.cpp b/Source/Engine/Scripting/Script.cpp index e1d2da73a..98a88975b 100644 --- a/Source/Engine/Scripting/Script.cpp +++ b/Source/Engine/Scripting/Script.cpp @@ -178,13 +178,15 @@ void Script::SetupType() { // Enable tick functions based on the method overriden in C# or Visual Script ScriptingTypeHandle typeHandle = GetTypeHandle(); - _tickUpdate = _tickLateUpdate = _tickFixedUpdate = 0; while (typeHandle != Script::TypeInitializer) { auto& type = typeHandle.GetType(); - _tickUpdate |= type.Script.ScriptVTable[8] != nullptr; - _tickLateUpdate |= type.Script.ScriptVTable[9] != nullptr; - _tickFixedUpdate |= type.Script.ScriptVTable[10] != nullptr; + if (type.Script.ScriptVTable) + { + _tickUpdate |= type.Script.ScriptVTable[8] != nullptr; + _tickLateUpdate |= type.Script.ScriptVTable[9] != nullptr; + _tickFixedUpdate |= type.Script.ScriptVTable[10] != nullptr; + } typeHandle = type.GetBaseType(); } } diff --git a/Source/Engine/Scripting/Scripting.Internal.cpp b/Source/Engine/Scripting/Scripting.Internal.cpp index 225c59b80..79e578401 100644 --- a/Source/Engine/Scripting/Scripting.Internal.cpp +++ b/Source/Engine/Scripting/Scripting.Internal.cpp @@ -9,6 +9,7 @@ #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Profiler/Profiler.h" +#include "Engine/Threading/Threading.h" #include namespace ProfilerInternal diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 2919ce1ae..37a009ebc 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -6,6 +6,7 @@ #include "ScriptingType.h" #include "FlaxEngine.Gen.h" #include "Engine/Threading/Threading.h" +#include "Engine/Threading/ThreadLocal.h" #include "Engine/Threading/IRunnable.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/File.h" @@ -21,13 +22,14 @@ #include "MException.h" #include "Engine/Level/Level.h" #include "Engine/Core/ObjectsRemovalService.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Asset.h" #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Engine/Globals.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Serialization/JsonTools.h" -#include "Engine/Utilities/StringConverter.h" #include #include @@ -110,7 +112,7 @@ Action Scripting::ScriptsLoaded; Action Scripting::ScriptsUnload; Action Scripting::ScriptsReloading; Action Scripting::ScriptsReloaded; -ThreadLocal Scripting::ObjectsLookupIdMapping; +ThreadLocal Scripting::ObjectsLookupIdMapping; ScriptingService ScriptingServiceInstance; bool initFlaxEngine(); @@ -187,35 +189,35 @@ bool ScriptingService::Init() void ScriptingService::Update() { - PROFILE_CPU_NAMED("Scripting.Update"); + PROFILE_CPU_NAMED("Scripting::Update"); INVOKE_EVENT(Update); } void ScriptingService::LateUpdate() { - PROFILE_CPU_NAMED("Scripting.LateUpdate"); + PROFILE_CPU_NAMED("Scripting::LateUpdate"); INVOKE_EVENT(LateUpdate); } void ScriptingService::FixedUpdate() { - PROFILE_CPU_NAMED("Scripting.FixedUpdate"); + PROFILE_CPU_NAMED("Scripting::FixedUpdate"); INVOKE_EVENT(FixedUpdate); } void ScriptingService::Draw() { - PROFILE_CPU_NAMED("Scripting.Draw"); + PROFILE_CPU_NAMED("Scripting::Draw"); INVOKE_EVENT(Draw); } void ScriptingService::BeforeExit() { - PROFILE_CPU_NAMED("Scripting.BeforeExit"); + PROFILE_CPU_NAMED("Scripting::BeforeExit"); INVOKE_EVENT(Exit); } @@ -225,12 +227,6 @@ MDomain* Scripting::GetRootDomain() return _monoRootDomain; } -MonoDomain* Scripting::GetMonoScriptsDomain() -{ - ASSERT(_monoScriptsDomain != nullptr); - return _monoScriptsDomain->GetNative(); -} - MDomain* Scripting::GetScriptsDomain() { return _monoScriptsDomain; @@ -350,7 +346,8 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde const auto startTime = DateTime::NowUTC(); #if PLATFORM_ANDROID // On Android all native binaries are side-by-side with the app - nativePath = StringUtils::GetDirectoryName(Platform::GetExecutableFilePath()) / StringUtils::GetFileName(nativePath); + nativePath = StringUtils::GetDirectoryName(Platform::GetExecutableFilePath()); + nativePath /= StringUtils::GetFileName(nativePath); #endif auto library = Platform::LoadLibrary(nativePath.Get()); if (!library) @@ -518,7 +515,7 @@ void Scripting::Release() module->Destroy(false); } - _nonNativeModules.Clear(); + _nonNativeModules.ClearDelete(); _hasGameModulesLoaded = false; } @@ -619,7 +616,7 @@ void Scripting::Reload(bool canTriggerSceneReload) module->Destroy(true); } modules.Clear(); - _nonNativeModules.Clear(); + _nonNativeModules.ClearDelete(); _hasGameModulesLoaded = false; // Give GC a try to cleanup old user objects and the other mess @@ -643,7 +640,7 @@ MClass* Scripting::FindClass(MonoClass* monoClass) if (monoClass == nullptr) return nullptr; - PROFILE_CPU_NAMED("FindClass"); + PROFILE_CPU(); auto& modules = BinaryModule::GetModules(); for (auto module : modules) @@ -665,7 +662,7 @@ MClass* Scripting::FindClass(const StringAnsiView& fullname) if (fullname.IsEmpty()) return nullptr; - PROFILE_CPU_NAMED("FindClass"); + PROFILE_CPU(); auto& modules = BinaryModule::GetModules(); for (auto module : modules) @@ -687,7 +684,7 @@ MonoClass* Scripting::FindClassNative(const StringAnsiView& fullname) if (fullname.IsEmpty()) return nullptr; - PROFILE_CPU_NAMED("FindClassNative"); + PROFILE_CPU(); auto& modules = BinaryModule::GetModules(); for (auto module : modules) @@ -709,7 +706,7 @@ ScriptingTypeHandle Scripting::FindScriptingType(const StringAnsiView& fullname) if (fullname.IsEmpty()) return ScriptingTypeHandle(); - PROFILE_CPU_NAMED("FindScriptingType"); + PROFILE_CPU(); auto& modules = BinaryModule::GetModules(); for (auto module : modules) @@ -734,7 +731,7 @@ ScriptingObject* Scripting::FindObject(Guid id, MClass* type) if (!id.IsValid()) return nullptr; - PROFILE_CPU_NAMED("FindObject"); + PROFILE_CPU(); // Try to map object id const auto idsMapping = ObjectsLookupIdMapping.Get(); @@ -814,7 +811,7 @@ ScriptingObject* Scripting::FindObject(const MonoObject* managedInstance) if (managedInstance == nullptr) return nullptr; - PROFILE_CPU_NAMED("FindObject"); + PROFILE_CPU(); // TODO: optimize it by reading the unmanagedPtr or _internalId from managed Object property @@ -831,7 +828,7 @@ ScriptingObject* Scripting::FindObject(const MonoObject* managedInstance) void Scripting::OnManagedInstanceDeleted(ScriptingObject* obj) { - PROFILE_CPU_NAMED("OnManagedInstanceDeleted"); + PROFILE_CPU(); ASSERT(obj); // Validate if object still exists @@ -943,7 +940,7 @@ bool initFlaxEngine() if (exception) { MException ex(exception); - ex.Log(LogType::Fatal, TEXT("FlaxEngine.ClassLibraryInitializer.Init")); + ex.Log(LogType::Fatal, TEXT("FlaxEngine.Scripting.Init")); return true; } diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 6cb04b813..ae7821526 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -158,13 +158,22 @@ namespace FlaxEngine AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; + Localization.LocalizationChanged += OnLocalizationChanged; + OnLocalizationChanged(); if (!Engine.IsEditor) { CreateGuiStyle(); } } + private static void OnLocalizationChanged() + { + var currentThread = Thread.CurrentThread; + currentThread.CurrentUICulture = Localization.CurrentLanguage; + currentThread.CurrentCulture = Localization.CurrentCulture; + } + /// /// Sets the managed window as a main game window. Called after creating game window by the native code. /// diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index f3c7d972c..98fddfc7f 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -2,11 +2,12 @@ #pragma once -#include "Engine/Core/Collections/Dictionary.h" -#include "Engine/Threading/ThreadLocal.h" -#include "ScriptingObject.h" +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Scripting/ScriptingType.h" +#include "Types.h" -class BinaryModule; +template +class ThreadLocal; /// /// Embedded managed scripting runtime service. @@ -26,41 +27,33 @@ public: /// /// Action fired on scripting engine loaded (always main thread). /// - static Action ScriptsLoaded; + static Delegate<> ScriptsLoaded; /// /// Action fired on scripting engine unloading start (always main thread). /// - static Action ScriptsUnload; + static Delegate<> ScriptsUnload; /// /// Action fired on scripting engine reload start (always main thread). /// - static Action ScriptsReloading; + static Delegate<> ScriptsReloading; /// /// Action fired on scripting engine reload start (always main thread). /// - static Action ScriptsReloaded; + static Delegate<> ScriptsReloaded; public: /// /// Gets mono root domain /// - /// The Mono root domain. static MDomain* GetRootDomain(); - /// - /// Gets mono scripts domain - /// - /// The Mono domain. - static MonoDomain* GetMonoScriptsDomain(); - /// /// Gets scripts domain /// - /// The domain. static MDomain* GetScriptsDomain(); public: @@ -123,7 +116,7 @@ public: /// /// The objects lookup identifier mapping used to override the object ids on FindObject call (used by the object references deserialization). /// - static ThreadLocal ObjectsLookupIdMapping; + static ThreadLocal ObjectsLookupIdMapping; /// /// Finds the object by the given identifier. Searches registered scene objects and optionally assets. Logs warning if fails. diff --git a/Source/Engine/Scripting/ScriptingCalls.h b/Source/Engine/Scripting/ScriptingCalls.h index 1a48755e8..d76361bee 100644 --- a/Source/Engine/Scripting/ScriptingCalls.h +++ b/Source/Engine/Scripting/ScriptingCalls.h @@ -2,6 +2,8 @@ #pragma once +#include "Engine/Scripting/ManagedCLR/MTypes.h" + typedef void (*Thunk_Void_0)(MonoObject** exception); typedef void (*Thunk_Void_1)(void* param_1, MonoObject** exception); typedef void (*Thunk_Void_2)(void* param_1, void* param_2, MonoObject** exception); diff --git a/Source/Engine/Scripting/ScriptingObjectReference.h b/Source/Engine/Scripting/ScriptingObjectReference.h index eeed0dcc4..fd132d308 100644 --- a/Source/Engine/Scripting/ScriptingObjectReference.h +++ b/Source/Engine/Scripting/ScriptingObjectReference.h @@ -318,3 +318,9 @@ public: OnSet(object); } }; + +template +uint32 GetHash(const ScriptingObjectReference& key) +{ + return GetHash(key.GetID()); +} diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h index b6f08db4a..e03ee81d0 100644 --- a/Source/Engine/Scripting/SoftObjectReference.h +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -326,3 +326,9 @@ public: OnSet(object); } }; + +template +uint32 GetHash(const SoftObjectReference& key) +{ + return GetHash(key.GetID()); +} diff --git a/Source/Engine/Scripting/Types.h b/Source/Engine/Scripting/Types.h index 8ee3c7693..8f52524ec 100644 --- a/Source/Engine/Scripting/Types.h +++ b/Source/Engine/Scripting/Types.h @@ -6,6 +6,7 @@ class Scripting; struct ScriptingType; class BinaryModule; class ScriptingObject; +class MDomain; class MException; class MAssembly; class MClass; diff --git a/Source/Engine/Serialization/ISerializeModifier.h b/Source/Engine/Serialization/ISerializeModifier.h index bd0826933..a26af02d6 100644 --- a/Source/Engine/Serialization/ISerializeModifier.h +++ b/Source/Engine/Serialization/ISerializeModifier.h @@ -16,36 +16,10 @@ public: /// /// Number of engine build when data was serialized. Useful to upgrade data from the older storage format. /// - uint32 EngineBuild; + uint32 EngineBuild = FLAXENGINE_VERSION_BUILD; /// /// The object IDs mapping. Key is a serialized object id, value is mapped value to use. /// Dictionary IdsMapping; - -public: - - /// - /// Initializes a new instance of the class. - /// - ISerializeModifier() - : EngineBuild(FLAXENGINE_VERSION_BUILD) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The engine build. - ISerializeModifier(uint32 engineBuild) - : EngineBuild(engineBuild) - { - } - - /// - /// Finalizes an instance of the class. - /// - virtual ~ISerializeModifier() - { - } }; diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs new file mode 100644 index 000000000..2ebf16a79 --- /dev/null +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -0,0 +1,331 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using FlaxEngine.GUI; +using Newtonsoft.Json; + +namespace FlaxEngine.Json +{ + /// + /// Serialize references to the FlaxEngine.Object as Guid. + /// + /// + internal class FlaxObjectConverter : JsonConverter + { + /// + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + Guid id = Guid.Empty; + if (value is Object obj) + id = obj.ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + { + JsonSerializer.ParseID((string)reader.Value, out var id); + return Object.Find(ref id, objectType); + } + return null; + } + + /// + public override bool CanConvert(Type objectType) + { + // Skip serialization as reference id for the root object serialization (eg. Script) + var cache = JsonSerializer.Current.Value; + if (cache != null && cache.IsDuringSerialization && cache.SerializerWriter.SerializeStackSize == 0) + { + return false; + } + return typeof(Object).IsAssignableFrom(objectType); + } + } + + /// + /// Serialize SceneReference as Guid in internal format. + /// + /// + internal class SceneReferenceConverter : JsonConverter + { + /// + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + Guid id = ((SceneReference)value).ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + SceneReference result = new SceneReference(); + + if (reader.TokenType == JsonToken.String) + { + JsonSerializer.ParseID((string)reader.Value, out result.ID); + } + + return result; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(SceneReference); + } + } + + /// + /// Serialize SoftObjectReference as Guid in internal format. + /// + /// + internal class SoftObjectReferenceConverter : JsonConverter + { + /// + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + var id = ((SoftObjectReference)value).ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var result = new SoftObjectReference(); + if (reader.TokenType == JsonToken.String) + { + JsonSerializer.ParseID((string)reader.Value, out var id); + result.ID = id; + } + return result; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(SoftObjectReference); + } + } + + /// + /// Serialize SoftObjectReference as Guid in internal format. + /// + /// + internal class MarginConverter : JsonConverter + { + /// + public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + var valueMargin = (Margin)value; + + writer.WriteStartObject(); + { + writer.WritePropertyName("Left"); + writer.WriteValue(valueMargin.Left); + writer.WritePropertyName("Right"); + writer.WriteValue(valueMargin.Right); + writer.WritePropertyName("Top"); + writer.WriteValue(valueMargin.Top); + writer.WritePropertyName("Bottom"); + writer.WriteValue(valueMargin.Bottom); + } + writer.WriteEndObject(); + } + + /// + public override void WriteJsonDiff(JsonWriter writer, object value, object other, Newtonsoft.Json.JsonSerializer serializer) + { + var valueMargin = (Margin)value; + var otherMargin = (Margin)other; + writer.WriteStartObject(); + if (!Mathf.NearEqual(valueMargin.Left, otherMargin.Left)) + { + writer.WritePropertyName("Left"); + writer.WriteValue(valueMargin.Left); + } + if (!Mathf.NearEqual(valueMargin.Right, otherMargin.Right)) + { + writer.WritePropertyName("Right"); + writer.WriteValue(valueMargin.Right); + } + if (!Mathf.NearEqual(valueMargin.Top, otherMargin.Top)) + { + writer.WritePropertyName("Top"); + writer.WriteValue(valueMargin.Top); + } + if (!Mathf.NearEqual(valueMargin.Bottom, otherMargin.Bottom)) + { + writer.WritePropertyName("Bottom"); + writer.WriteValue(valueMargin.Bottom); + } + writer.WriteEndObject(); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var value = (Margin?)existingValue ?? new Margin(); + if (reader.TokenType == JsonToken.StartObject) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + { + var propertyName = (string)reader.Value; + var propertyValue = (float)reader.ReadAsDouble(); + switch (propertyName) + { + case "Left": + value.Left = propertyValue; + break; + case "Right": + value.Right = propertyValue; + break; + case "Top": + value.Top = propertyValue; + break; + case "Bottom": + value.Bottom = propertyValue; + break; + } + break; + } + case JsonToken.Comment: break; + default: return value; + } + } + } + return value; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Margin); + } + + /// + public override bool CanRead => true; + + /// + public override bool CanWrite => true; + + /// + public override bool CanWriteDiff => true; + } + + /// + /// Serialize LocalizedString as inlined text is not using localization (Id member is empty). + /// + /// + internal class LocalizedStringConverter : JsonConverter + { + /// + public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + var str = (LocalizedString)value; + if (string.IsNullOrEmpty(str.Id)) + { + writer.WriteValue(str.Value); + } + else + { + writer.WriteStartObject(); +#if FLAX_EDITOR + if ((serializer.TypeNameHandling & TypeNameHandling.Objects) == TypeNameHandling.Objects) + { + writer.WritePropertyName("$type"); + writer.WriteValue("FlaxEngine.LocalizedString, FlaxEngine.CSharp"); + } +#endif + writer.WritePropertyName("Id"); + writer.WriteValue(str.Id); + writer.WritePropertyName("Value"); + writer.WriteValue(str.Value); + writer.WriteEndObject(); + } + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var str = existingValue as LocalizedString ?? new LocalizedString(); + if (reader.TokenType == JsonToken.String) + { + str.Id = null; + str.Value = (string)reader.Value; + } + else if (reader.TokenType == JsonToken.StartObject) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + { + var propertyName = (string)reader.Value; + switch (propertyName) + { + case "Id": + str.Id = reader.ReadAsString(); + break; + case "Value": + str.Value = reader.ReadAsString(); + break; + } + break; + } + case JsonToken.Comment: break; + default: return str; + } + } + } + return str; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(LocalizedString); + } + } + + /* + /// + /// Serialize Guid values using `N` format + /// + /// + internal class GuidConverter : JsonConverter + { + /// + public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + Guid id = (Guid)value; + writer.WriteValue(id.ToString("N")); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + { + var id = Guid.Parse((string)reader.Value); + return id; + } + + return Guid.Empty; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Guid); + } + } + */ +} diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index d3300dd23..a89ade8ef 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -15,144 +15,6 @@ using Newtonsoft.Json.Serialization; namespace FlaxEngine.Json { - /// - /// Serialize references to the FlaxEngine.Object as Guid. - /// - /// - internal class FlaxObjectConverter : JsonConverter - { - /// - public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) - { - Guid id = Guid.Empty; - if (value is Object obj) - id = obj.ID; - writer.WriteValue(JsonSerializer.GetStringID(&id)); - } - - /// - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.String) - { - JsonSerializer.ParseID((string)reader.Value, out var id); - return Object.Find(ref id, objectType); - } - return null; - } - - /// - public override bool CanConvert(Type objectType) - { - // Skip serialization as reference id for the root object serialization (eg. Script) - var cache = JsonSerializer.Current.Value; - if (cache != null && cache.IsDuringSerialization && cache.SerializerWriter.SerializeStackSize == 0) - { - return false; - } - return typeof(Object).IsAssignableFrom(objectType); - } - } - - /// - /// Serialize SceneReference as Guid in internal format. - /// - /// - internal class SceneReferenceConverter : JsonConverter - { - /// - public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) - { - Guid id = ((SceneReference)value).ID; - writer.WriteValue(JsonSerializer.GetStringID(&id)); - } - - /// - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) - { - SceneReference result = new SceneReference(); - - if (reader.TokenType == JsonToken.String) - { - JsonSerializer.ParseID((string)reader.Value, out result.ID); - } - - return result; - } - - /// - public override bool CanConvert(Type objectType) - { - return objectType == typeof(SceneReference); - } - } - - /// - /// Serialize SoftObjectReference as Guid in internal format. - /// - /// - internal class SoftObjectReferenceConverter : JsonConverter - { - /// - public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) - { - var id = ((SoftObjectReference)value).ID; - writer.WriteValue(JsonSerializer.GetStringID(&id)); - } - - /// - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) - { - var result = new SoftObjectReference(); - if (reader.TokenType == JsonToken.String) - { - JsonSerializer.ParseID((string)reader.Value, out var id); - result.ID = id; - } - return result; - } - - /// - public override bool CanConvert(Type objectType) - { - return objectType == typeof(SoftObjectReference); - } - } - - /* - /// - /// Serialize Guid values using `N` format - /// - /// - internal class GuidConverter : JsonConverter - { - /// - public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) - { - Guid id = (Guid)value; - writer.WriteValue(id.ToString("N")); - } - - /// - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.String) - { - var id = Guid.Parse((string)reader.Value); - return id; - } - - return Guid.Empty; - } - - /// - public override bool CanConvert(Type objectType) - { - return objectType == typeof(Guid); - } - } - */ - /// /// Objects serialization tool (json format). /// @@ -165,17 +27,19 @@ namespace FlaxEngine.Json public StringWriter StringWriter; public JsonTextWriter JsonWriter; public JsonSerializerInternalWriter SerializerWriter; - public UnmanagedStringReader StringReader; + public UnmanagedMemoryStream MemoryStream; + public StreamReader Reader; public bool IsDuringSerialization; - public SerializerCache(JsonSerializerSettings settings) + public unsafe SerializerCache(JsonSerializerSettings settings) { JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings); JsonSerializer.Formatting = Formatting.Indented; StringBuilder = new StringBuilder(256); StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture); SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer); - StringReader = new UnmanagedStringReader(); + MemoryStream = new UnmanagedMemoryStream((byte*)0, 0); + Reader = new StreamReader(MemoryStream, Encoding.UTF8, false); JsonWriter = new JsonTextWriter(StringWriter) { IndentChar = '\t', @@ -217,7 +81,9 @@ namespace FlaxEngine.Json settings.Converters.Add(ObjectConverter); settings.Converters.Add(new SceneReferenceConverter()); settings.Converters.Add(new SoftObjectReferenceConverter()); + settings.Converters.Add(new MarginConverter()); settings.Converters.Add(new VersionConverter()); + settings.Converters.Add(new LocalizedStringConverter()); //settings.Converters.Add(new GuidConverter()); return settings; } @@ -404,14 +270,15 @@ namespace FlaxEngine.Json /// The object. /// The input json data buffer (raw, fixed memory buffer). /// The input json data buffer length (characters count). - public static unsafe void Deserialize(object input, void* jsonBuffer, int jsonLength) + public static unsafe void Deserialize(object input, byte* jsonBuffer, int jsonLength) { var cache = Cache.Value; cache.IsDuringSerialization = false; Current.Value = cache; - cache.StringReader.Initialize(jsonBuffer, jsonLength); - var jsonReader = new JsonTextReader(cache.StringReader); + cache.MemoryStream.Initialize(jsonBuffer, jsonLength); + cache.Reader.DiscardBufferedData(); + var jsonReader = new JsonTextReader(cache.Reader); cache.JsonSerializer.Populate(jsonReader, input); if (!cache.JsonSerializer.CheckAdditionalContent) diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index fad907f45..1a8da00eb 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -100,6 +100,8 @@ void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, c void JsonTools::ChangeIds(Document& doc, const Dictionary& mapping) { + if (mapping.IsEmpty()) + return; ::ChangeIds(doc, doc, mapping); } diff --git a/Source/Engine/Serialization/JsonTools.h b/Source/Engine/Serialization/JsonTools.h index 5f1314595..2703b2b63 100644 --- a/Source/Engine/Serialization/JsonTools.h +++ b/Source/Engine/Serialization/JsonTools.h @@ -386,6 +386,8 @@ public: DECLARE_GETTER(Plane); DECLARE_GETTER(DateTime); +#undef DECLARE_GETTER + #define DECLARE_GETTER(type) \ FORCE_INLINE static void Get##type(type& result, const Value& node, const char* name) \ { \ diff --git a/Source/Engine/Serialization/JsonWriter.cpp b/Source/Engine/Serialization/JsonWriter.cpp index de90b49b4..1a5586fd4 100644 --- a/Source/Engine/Serialization/JsonWriter.cpp +++ b/Source/Engine/Serialization/JsonWriter.cpp @@ -4,6 +4,12 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/CommonValue.h" #include "Engine/Content/Content.h" +#include "Engine/Core/Math/Int2.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Math/Int4.h" +#include "Engine/Core/Math/Color.h" +#include "Engine/Core/Math/Plane.h" +#include "Engine/Core/Types/DateTime.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Level/SceneObject.h" #include "Engine/Utilities/Encryption.h" @@ -16,6 +22,121 @@ void JsonWriter::Blob(const void* data, int32 length) String(base64.Get(), base64.Count()); } +void JsonWriter::DateTime(const ::DateTime& value) +{ + Int64(value.Ticks); +} + +void JsonWriter::Vector2(const ::Vector2& value) +{ + StartObject(); + JKEY("X"); + Float(value.X); + JKEY("Y"); + Float(value.Y); + EndObject(); +} + +void JsonWriter::Vector3(const ::Vector3& value) +{ + StartObject(); + JKEY("X"); + Float(value.X); + JKEY("Y"); + Float(value.Y); + JKEY("Z"); + Float(value.Z); + EndObject(); +} + +void JsonWriter::Vector4(const ::Vector4& value) +{ + StartObject(); + JKEY("X"); + Float(value.X); + JKEY("Y"); + Float(value.Y); + JKEY("Z"); + Float(value.Z); + JKEY("W"); + Float(value.W); + EndObject(); +} + +void JsonWriter::Int2(const ::Int2& value) +{ + StartObject(); + JKEY("X"); + Int(value.X); + JKEY("Y"); + Int(value.Y); + EndObject(); +} + +void JsonWriter::Int3(const ::Int3& value) +{ + StartObject(); + JKEY("X"); + Int(value.X); + JKEY("Y"); + Int(value.Y); + JKEY("Z"); + Int(value.Z); + EndObject(); +} + +void JsonWriter::Int4(const ::Int4& value) +{ + StartObject(); + JKEY("X"); + Int(value.X); + JKEY("Y"); + Int(value.Y); + JKEY("Z"); + Int(value.Z); + JKEY("W"); + Int(value.W); + EndObject(); +} + +void JsonWriter::Color(const ::Color& value) +{ + StartObject(); + JKEY("R"); + Float(value.R); + JKEY("G"); + Float(value.G); + JKEY("B"); + Float(value.B); + JKEY("A"); + Float(value.A); + EndObject(); +} + +void JsonWriter::Quaternion(const ::Quaternion& value) +{ + StartObject(); + JKEY("X"); + Float(value.X); + JKEY("Y"); + Float(value.Y); + JKEY("Z"); + Float(value.Z); + JKEY("W"); + Float(value.W); + EndObject(); +} + +void JsonWriter::Ray(const ::Ray& value) +{ + StartObject(); + JKEY("Position"); + Vector3(value.Position); + JKEY("Direction"); + Vector3(value.Direction); + EndObject(); +} + void JsonWriter::Matrix(const ::Matrix& value) { StartObject(); @@ -127,13 +248,95 @@ void JsonWriter::CommonValue(const ::CommonValue& value) Guid(value.GetObjectId()); break; default: - CRASH; + CRASH; break; } EndObject(); } +void JsonWriter::Transform(const ::Transform& value) +{ + StartObject(); + if (!value.Translation.IsZero()) + { + JKEY("Translation"); + Vector3(value.Translation); + } + if (!value.Orientation.IsIdentity()) + { + JKEY("Orientation"); + Quaternion(value.Orientation); + } + if (!value.Scale.IsOne()) + { + JKEY("Scale"); + Vector3(value.Scale); + } + EndObject(); +} + +void JsonWriter::Transform(const ::Transform& value, const ::Transform* other) +{ + StartObject(); + if (!other || !Vector3::NearEqual(value.Translation, other->Translation)) + { + JKEY("Translation"); + Vector3(value.Translation); + } + if (!other || !Quaternion::NearEqual(value.Orientation, other->Orientation)) + { + JKEY("Orientation"); + Quaternion(value.Orientation); + } + if (!other || !Vector3::NearEqual(value.Scale, other->Scale)) + { + JKEY("Scale"); + Vector3(value.Scale); + } + EndObject(); +} + +void JsonWriter::Plane(const ::Plane& value) +{ + StartObject(); + JKEY("Normal"); + Vector3(value.Normal); + JKEY("D"); + Float(value.D); + EndObject(); +} + +void JsonWriter::Rectangle(const ::Rectangle& value) +{ + StartObject(); + JKEY("Location"); + Vector2(value.Location); + JKEY("Size"); + Vector2(value.Size); + EndObject(); +} + +void JsonWriter::BoundingSphere(const ::BoundingSphere& value) +{ + StartObject(); + JKEY("Center"); + Vector3(value.Center); + JKEY("Radius"); + Float(value.Radius); + EndObject(); +} + +void JsonWriter::BoundingBox(const ::BoundingBox& value) +{ + StartObject(); + JKEY("Minimum"); + Vector3(value.Minimum); + JKEY("Maximum"); + Vector3(value.Maximum); + EndObject(); +} + void JsonWriter::Guid(const ::Guid& value) { // Unoptimized version: diff --git a/Source/Engine/Serialization/JsonWriter.h b/Source/Engine/Serialization/JsonWriter.h index 7182ec69a..b77b92695 100644 --- a/Source/Engine/Serialization/JsonWriter.h +++ b/Source/Engine/Serialization/JsonWriter.h @@ -4,24 +4,11 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" -#include "Engine/Core/Types/Guid.h" -#include "Engine/Core/Types/DateTime.h" -#include "Engine/Core/Math/BoundingBox.h" -#include "Engine/Core/Math/BoundingSphere.h" -#include "Engine/Core/Math/Vector2.h" -#include "Engine/Core/Math/Vector3.h" -#include "Engine/Core/Math/Vector4.h" -#include "Engine/Core/Math/Color.h" -#include "Engine/Core/Math/Quaternion.h" -#include "Engine/Core/Math/Ray.h" -#include "Engine/Core/Math/Transform.h" -#include "Engine/Core/Math/Rectangle.h" -#include "Engine/Core/Math/Plane.h" #include "Engine/Utilities/StringConverter.h" -#include "ISerializable.h" struct CommonValue; struct Matrix; +struct Transform; class ISerializable; // Helper macro for JSON serialization keys (reduces allocations count) @@ -61,6 +48,12 @@ public: Key(str.Get(), static_cast(str.Length())); } + FORCE_INLINE void Key(const StringView& str) + { + const StringAsUTF8<256> buf(*str, str.Length()); + Key(buf.Get(), buf.Length()); + } + FORCE_INLINE void String(const char* str) { String(str, StringUtils::Length(str)); @@ -99,11 +92,6 @@ public: RawValue(json, StringUtils::Length(json)); } - FORCE_INLINE void DateTime(const DateTime& value) - { - Int64(value.Ticks); - } - // Raw bytes blob serialized as base64 string void Blob(const void* data, int32 length); @@ -113,181 +101,27 @@ public: Int(static_cast(value)); } - void Vector2(const Vector2& value) - { - StartObject(); - JKEY("X"); - Float(value.X); - JKEY("Y"); - Float(value.Y); - EndObject(); - } - - void Vector3(const Vector3& value) - { - StartObject(); - JKEY("X"); - Float(value.X); - JKEY("Y"); - Float(value.Y); - JKEY("Z"); - Float(value.Z); - EndObject(); - } - - void Vector4(const Vector4& value) - { - StartObject(); - JKEY("X"); - Float(value.X); - JKEY("Y"); - Float(value.Y); - JKEY("Z"); - Float(value.Z); - JKEY("W"); - Float(value.W); - EndObject(); - } - - void Color(const Color& value) - { - StartObject(); - JKEY("R"); - Float(value.R); - JKEY("G"); - Float(value.G); - JKEY("B"); - Float(value.B); - JKEY("A"); - Float(value.A); - EndObject(); - } - - void Quaternion(const Quaternion& value) - { - StartObject(); - JKEY("X"); - Float(value.X); - JKEY("Y"); - Float(value.Y); - JKEY("Z"); - Float(value.Z); - JKEY("W"); - Float(value.W); - EndObject(); - } - - void Ray(const Ray& value) - { - StartObject(); - JKEY("Position"); - Vector3(value.Position); - JKEY("Direction"); - Vector3(value.Direction); - EndObject(); - } - + void DateTime(const DateTime& value); + void Vector2(const Vector2& value); + void Vector3(const Vector3& value); + void Vector4(const Vector4& value); + void Int2(const Int2& value); + void Int3(const Int3& value); + void Int4(const Int4& value); + void Color(const Color& value); + void Quaternion(const Quaternion& value); + void Ray(const Ray& value); void Matrix(const Matrix& value); void CommonValue(const CommonValue& value); - - void Transform(const ::Transform& value) - { - StartObject(); - if (!value.Translation.IsZero()) - { - JKEY("Translation"); - Vector3(value.Translation); - } - if (!value.Orientation.IsIdentity()) - { - JKEY("Orientation"); - Quaternion(value.Orientation); - } - if (!value.Scale.IsOne()) - { - JKEY("Scale"); - Vector3(value.Scale); - } - EndObject(); - } - - void Transform(const ::Transform& value, const ::Transform* other) - { - StartObject(); - if (!other || !Vector3::NearEqual(value.Translation, other->Translation)) - { - JKEY("Translation"); - Vector3(value.Translation); - } - if (!other || !Quaternion::NearEqual(value.Orientation, other->Orientation)) - { - JKEY("Orientation"); - Quaternion(value.Orientation); - } - if (!other || !Vector3::NearEqual(value.Scale, other->Scale)) - { - JKEY("Scale"); - Vector3(value.Scale); - } - EndObject(); - } - - void Plane(const Plane& value) - { - StartObject(); - JKEY("Normal"); - Vector3(value.Normal); - JKEY("D"); - Float(value.D); - EndObject(); - } - - void Rectangle(const Rectangle& value) - { - StartObject(); - JKEY("Location"); - Vector2(value.Location); - JKEY("Size"); - Vector2(value.Size); - EndObject(); - } - - void BoundingSphere(const BoundingSphere& value) - { - StartObject(); - JKEY("Center"); - Vector3(value.Center); - JKEY("Radius"); - Float(value.Radius); - EndObject(); - } - - void BoundingBox(const BoundingBox& value) - { - StartObject(); - JKEY("Minimum"); - Vector3(value.Minimum); - JKEY("Maximum"); - Vector3(value.Maximum); - EndObject(); - } - + void Transform(const ::Transform& value); + void Transform(const ::Transform& value, const ::Transform* other); + void Plane(const Plane& value); + void Rectangle(const Rectangle& value); + void BoundingSphere(const BoundingSphere& value); + void BoundingBox(const BoundingBox& value); void Guid(const Guid& value); - void Object(ISerializable* value, const void* otherObj); // Serializes scene object (handles prefab with diff serialization) void SceneObject(class SceneObject* obj); - -public: - - void Array(const ::Guid* value, int32 count) - { - StartArray(); - for (int32 i = 0; i < count; i++) - { - Guid(value[i]); - } - EndArray(count); - } }; diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index 7eb48a22c..bafc52077 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -3,6 +3,21 @@ #include "Serialization.h" #include "Engine/Core/Types/Version.h" #include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Math/Ray.h" +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/BoundingSphere.h" +#include "Engine/Core/Math/Rectangle.h" +#include "Engine/Core/Math/Transform.h" +#include "Engine/Core/Math/Int2.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Math/Int4.h" +#include "Engine/Core/Math/Color.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Utilities/Encryption.h" @@ -58,7 +73,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian else v.Type = VariantType::Null; const auto mTypeName = SERIALIZE_FIND_MEMBER(stream, "TypeName"); - if (mTypeName != stream.MemberEnd()) + if (mTypeName != stream.MemberEnd() && mTypeName->value.IsString()) v.SetTypeName(StringAnsiView(mTypeName->value.GetString(), mTypeName->value.GetStringLength())); } else @@ -138,6 +153,15 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari case VariantType::Vector4: stream.Vector4(*(Vector4*)v.AsData); break; + case VariantType::Int2: + stream.Int2(*(Int2*)v.AsData); + break; + case VariantType::Int3: + stream.Int3(*(Int3*)v.AsData); + break; + case VariantType::Int4: + stream.Int4(*(Int4*)v.AsData); + break; case VariantType::Color: stream.Color(*(Color*)v.AsData); break; @@ -232,6 +256,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian v.AsPointer = (void*)(uintptr)value.GetUint64(); break; case VariantType::String: + CHECK(value.IsString()); v.SetString(StringAnsiView(value.GetString(), value.GetStringLength())); break; case VariantType::Object: @@ -245,6 +270,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian break; case VariantType::Structure: case VariantType::Blob: + CHECK(value.IsString()); id.A = value.GetStringLength(); v.SetBlob(id.A); Encryption::Base64Decode(value.GetString(), id.A, (byte*)v.AsBlob.Data); @@ -258,6 +284,15 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian case VariantType::Vector4: Deserialize(value, *(Vector4*)v.AsData, modifier); break; + case VariantType::Int2: + Deserialize(value, *(Int2*)v.AsData, modifier); + break; + case VariantType::Int3: + Deserialize(value, *(Int3*)v.AsData, modifier); + break; + case VariantType::Int4: + Deserialize(value, *(Int4*)v.AsData, modifier); + break; case VariantType::Color: Deserialize(value, *(Color*)v.AsData, modifier); break; @@ -292,6 +327,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian Deserialize(value, *v.AsDictionary, modifier); break; case VariantType::Typename: + CHECK(value.IsString()); v.SetTypename(StringAnsiView(value.GetString(), value.GetStringLength())); break; default: @@ -311,7 +347,7 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Guid void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Guid& v, ISerializeModifier* modifier) { - if (stream.GetStringLength() != 32) + if (!stream.IsString() || stream.GetStringLength() != 32) { v = Guid::Empty; return; @@ -445,6 +481,51 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Vector v.W = mW != stream.MemberEnd() ? mW->value.GetFloat() : 0.0f; } +bool Serialization::ShouldSerialize(const Int2& v, const void* otherObj) +{ + return !otherObj || !(v == *(Int2*)otherObj); +} + +void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Int2& v, ISerializeModifier* modifier) +{ + const auto mX = SERIALIZE_FIND_MEMBER(stream, "X"); + const auto mY = SERIALIZE_FIND_MEMBER(stream, "Y"); + v.X = mX != stream.MemberEnd() ? mX->value.GetInt() : 0; + v.Y = mY != stream.MemberEnd() ? mY->value.GetInt() : 0; +} + +bool Serialization::ShouldSerialize(const Int3& v, const void* otherObj) +{ + return !otherObj || !(v == *(Int3*)otherObj); +} + +void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Int3& v, ISerializeModifier* modifier) +{ + const auto mX = SERIALIZE_FIND_MEMBER(stream, "X"); + const auto mY = SERIALIZE_FIND_MEMBER(stream, "Y"); + const auto mZ = SERIALIZE_FIND_MEMBER(stream, "Z"); + v.X = mX != stream.MemberEnd() ? mX->value.GetInt() : 0; + v.Y = mY != stream.MemberEnd() ? mY->value.GetInt() : 0; + v.Z = mZ != stream.MemberEnd() ? mZ->value.GetInt() : 0; +} + +bool Serialization::ShouldSerialize(const Int4& v, const void* otherObj) +{ + return !otherObj || !(v == *(Int4*)otherObj); +} + +void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Int4& v, ISerializeModifier* modifier) +{ + const auto mX = SERIALIZE_FIND_MEMBER(stream, "X"); + const auto mY = SERIALIZE_FIND_MEMBER(stream, "Y"); + const auto mZ = SERIALIZE_FIND_MEMBER(stream, "Z"); + const auto mW = SERIALIZE_FIND_MEMBER(stream, "W"); + v.X = mX != stream.MemberEnd() ? mX->value.GetInt() : 0; + v.Y = mY != stream.MemberEnd() ? mY->value.GetInt() : 0; + v.Z = mZ != stream.MemberEnd() ? mZ->value.GetInt() : 0; + v.W = mW != stream.MemberEnd() ? mW->value.GetInt() : 0; +} + bool Serialization::ShouldSerialize(const Quaternion& v, const void* otherObj) { return !otherObj || !Quaternion::NearEqual(v, *(Quaternion*)otherObj, SERIALIZE_EPSILON); diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 4aec76397..1413525e7 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -9,6 +9,7 @@ #include "Engine/Scripting/SoftObjectReference.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/WeakAssetReference.h" +#include "Engine/Utilities/Encryption.h" struct Version; struct VariantType; @@ -164,7 +165,7 @@ namespace Serialization inline bool ShouldSerialize(const float& v, const void* otherObj) { - return !otherObj || Math::Abs(v - *(float*)otherObj) > SERIALIZE_EPSILON; + return !otherObj || abs(v - *(float*)otherObj) > SERIALIZE_EPSILON; } inline void Serialize(ISerializable::SerializeStream& stream, const float& v, const void* otherObj) { @@ -268,6 +269,27 @@ namespace Serialization } FLAXENGINE_API void Deserialize(ISerializable::DeserializeStream& stream, Vector4& v, ISerializeModifier* modifier); + FLAXENGINE_API bool ShouldSerialize(const Int2& v, const void* otherObj); + inline void Serialize(ISerializable::SerializeStream& stream, const Int2& v, const void* otherObj) + { + stream.Int2(v); + } + FLAXENGINE_API void Deserialize(ISerializable::DeserializeStream& stream, Int2& v, ISerializeModifier* modifier); + + FLAXENGINE_API bool ShouldSerialize(const Int3& v, const void* otherObj); + inline void Serialize(ISerializable::SerializeStream& stream, const Int3& v, const void* otherObj) + { + stream.Int3(v); + } + FLAXENGINE_API void Deserialize(ISerializable::DeserializeStream& stream, Int3& v, ISerializeModifier* modifier); + + FLAXENGINE_API bool ShouldSerialize(const Int4& v, const void* otherObj); + inline void Serialize(ISerializable::SerializeStream& stream, const Int4& v, const void* otherObj) + { + stream.Int4(v); + } + FLAXENGINE_API void Deserialize(ISerializable::DeserializeStream& stream, Int4& v, ISerializeModifier* modifier); + FLAXENGINE_API bool ShouldSerialize(const Quaternion& v, const void* otherObj); inline void Serialize(ISerializable::SerializeStream& stream, const Quaternion& v, const void* otherObj) { @@ -345,6 +367,24 @@ namespace Serialization v.Deserialize(stream, modifier); } + template + inline typename TEnableIf::Value, bool>::Type ShouldSerialize(ISerializable& v, const void* otherObj) + { + return true; + } + template + inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, ISerializable& v, const void* otherObj) + { + stream.StartObject(); + v.Serialize(stream, otherObj); + stream.EndObject(); + } + template + inline typename TEnableIf::Value>::Type Deserialize(ISerializable::DeserializeStream& stream, ISerializable& v, ISerializeModifier* modifier) + { + v.Deserialize(stream, modifier); + } + // Scripting Object template @@ -460,7 +500,7 @@ namespace Serialization return true; for (int32 i = 0; i < v.Count(); i++) { - if (ShouldSerialize(v[i], &other->At(i))) + if (ShouldSerialize((T&)v[i], (const void*)&other->At(i))) return true; } return false; @@ -470,7 +510,7 @@ namespace Serialization { stream.StartArray(); for (int32 i = 0; i < v.Count(); i++) - Serialize(stream, v[i], nullptr); + Serialize(stream, (T&)v[i], nullptr); stream.EndArray(); } template @@ -481,7 +521,25 @@ namespace Serialization const auto& streamArray = stream.GetArray(); v.Resize(streamArray.Size()); for (int32 i = 0; i < v.Count(); i++) - Deserialize(streamArray[i], v[i], modifier); + Deserialize(streamArray[i], (T&)v[i], modifier); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, Array& v, ISerializeModifier* modifier) + { + if (stream.IsArray()) + { + const auto& streamArray = stream.GetArray(); + v.Resize(streamArray.Size()); + for (int32 i = 0; i < v.Count(); i++) + Deserialize(streamArray[i], v[i], modifier); + } + else if (stream.IsString()) + { + // byte[] encoded as Base64 + const StringAnsiView streamView(stream.GetString(), stream.GetStringLength()); + v.Resize(Encryption::Base64DecodeLength(*streamView, streamView.Length())); + Encryption::Base64Decode(*streamView, streamView.Length(), v.Get()); + } } // Dictionary @@ -496,7 +554,7 @@ namespace Serialization return true; for (auto& i : v) { - if (!other->ContainsKey(i.Key) || ShouldSerialize(i.Value, &other->At(i.Key))) + if (!other->ContainsKey(i.Key) || ShouldSerialize(i.Value, (const void*)&other->At(i.Key))) return true; } return false; diff --git a/Source/Engine/Serialization/UnmanagedMemoryStream.cs b/Source/Engine/Serialization/UnmanagedMemoryStream.cs new file mode 100644 index 000000000..12b7c4634 --- /dev/null +++ b/Source/Engine/Serialization/UnmanagedMemoryStream.cs @@ -0,0 +1,157 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace FlaxEngine.Json +{ + /// + /// Implements a that reads from unmanaged buffer (provided as raw pointer and length). + /// + internal class UnmanagedMemoryStream : Stream + { + private unsafe byte* _ptr; + private int _length; + private int _pos; + private FileAccess _access; + internal bool _isOpen; + private Task _lastReadTask; + + internal UnmanagedMemoryStream() + { + } + + internal unsafe UnmanagedMemoryStream(byte* pointer, int length, FileAccess access = FileAccess.Read) => Initialize(pointer, length, access); + + internal unsafe void Initialize(byte* pointer, int length, FileAccess access = FileAccess.Read) + { + _ptr = pointer; + _length = length; + _pos = 0; + _access = access; + _isOpen = true; + _lastReadTask = null; + } + + public override bool CanRead => _isOpen && (uint)(_access & FileAccess.Read) > 0U; + + public override bool CanSeek => _isOpen; + + public override bool CanWrite => _isOpen && (uint)(_access & FileAccess.Write) > 0U; + + protected override unsafe void Dispose(bool disposing) + { + _isOpen = false; + _ptr = null; + base.Dispose(disposing); + } + + public override void Flush() + { + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + Flush(); + return Task.FromResult(0); + } + + public unsafe byte* Pointer => _ptr; + + public override long Length => _length; + + public long Capacity => _length; + + public override long Position + { + get => _pos; + set => _pos = (int)value; + } + + public unsafe byte* PositionPointer + { + get => _ptr + _pos; + set => _pos = (int)(value - _ptr); + } + + public override unsafe int Read(byte[] buffer, int offset, int count) + { + int toRead = _length - _pos; + if (toRead > count) + toRead = count; + if (toRead <= 0) + return 0; + fixed (byte* bufferPtr = buffer) + Utils.MemoryCopy(new IntPtr(_ptr + _pos), new IntPtr(bufferPtr), toRead); + _pos += toRead; + return toRead; + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + int result = Read(buffer, offset, count); + return _lastReadTask == null || _lastReadTask.Result != result ? (_lastReadTask = Task.FromResult(result)) : _lastReadTask; + } + + public override unsafe int ReadByte() + { + int index = _pos; + if (index >= _length) + return -1; + _pos = index + 1; + return _ptr[index]; + } + + public override long Seek(long offset, SeekOrigin loc) + { + switch (loc) + { + case SeekOrigin.Begin: + _pos = (int)offset; + break; + case SeekOrigin.Current: + _pos += (int)offset; + break; + case SeekOrigin.End: + _pos = _length + (int)offset; + break; + default: throw new ArgumentOutOfRangeException(); + } + return _pos; + } + + public override void SetLength(long value) + { + _length = (int)value; + if (_pos > value) + _pos = _length; + } + + public override unsafe void Write(byte[] buffer, int offset, int count) + { + int newPos = _pos + count; + if (newPos > _length) + _length = newPos; + fixed (byte* bufferPtr = buffer) + Utils.MemoryCopy(new IntPtr(_pos + _pos), new IntPtr(bufferPtr), count); + _pos = newPos; + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + Write(buffer, offset, count); + return Task.FromResult(0); + } + + public override unsafe void WriteByte(byte value) + { + long newPos = _pos + 1; + if (_pos >= _length) + _length = (int)newPos; + _ptr[_pos] = value; + _pos = (int)newPos; + } + } +} diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 6ff65d4ec..06e3b3904 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -154,8 +154,7 @@ public: // @param length Text length void WriteText(const char* text, int32 length) { - for (int32 i = 0; i < length; i++) - WriteChar(text[i]); + WriteBytes((const void*)text, sizeof(char) * length); } // Writes text to the stream @@ -163,8 +162,7 @@ public: // @param length Text length void WriteText(const Char* text, int32 length) { - for (int32 i = 0; i < length; i++) - WriteChar(text[i]); + WriteBytes((const void*)text, sizeof(Char) * length); } template diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 87401e0d0..32decb15f 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -5,6 +5,7 @@ #include "ShaderCompiler.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Engine/Globals.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/RenderTools.h" diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index 8d9907fa4..d39d2a2c5 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -9,6 +9,7 @@ #include "Engine/Threading/Threading.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Parser/ShaderProcessing.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Shaders/GPUShader.h" diff --git a/Source/Engine/ShadowsOfMordor/Builder.BuildCache.cpp b/Source/Engine/ShadowsOfMordor/Builder.BuildCache.cpp index e3610d2e2..5228d7594 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.BuildCache.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.BuildCache.cpp @@ -6,6 +6,9 @@ #include "Engine/Content/Content.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/ImportTexture.h" +#include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/GPUBufferDescription.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/PixelFormatExtensions.h" ShadowsOfMordor::Builder::LightmapBuildCache::~LightmapBuildCache() diff --git a/Source/Engine/ShadowsOfMordor/Builder.Charts.cpp b/Source/Engine/ShadowsOfMordor/Builder.Charts.cpp index cae9b69e7..73b29a0ae 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Charts.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Charts.cpp @@ -1,12 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Builder.h" -#include "Engine/Engine/Engine.h" #include "AtlasChartsPacker.h" #include "Engine/Level/Scene/SceneLightmapsData.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/ContentImporters/ImportTexture.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Level/SceneQuery.h" #include "Engine/Level/Scene/Lightmap.h" diff --git a/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp b/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp index 0f7281dd7..fe388c039 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp @@ -1,11 +1,13 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Builder.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Math/Math.h" #include "Engine/Level/Actors/BoxBrush.h" #include "Engine/Level/SceneQuery.h" #include "Engine/Renderer/Renderer.h" #include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Engine/Globals.h" #define STEPS_SLEEP_TIME 20 #define RUN_STEP(handler) handler(); if (checkBuildCancelled()) return true; Platform::Sleep(STEPS_SLEEP_TIME) diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 20c2fb325..305cc71b8 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Builder.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Engine/Engine.h" #include "Engine/Renderer/Renderer.h" #include "Engine/Level/Scene/Lightmap.h" @@ -8,9 +9,10 @@ #include "Engine/Level/Actors/BoxBrush.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Graphics/RenderTargetPool.h" -#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Terrain/Terrain.h" #include "Engine/Terrain/TerrainPatch.h" #include "Engine/Terrain/TerrainManager.h" diff --git a/Source/Engine/ShadowsOfMordor/Builder.cpp b/Source/Engine/ShadowsOfMordor/Builder.cpp index b9cb57376..6225e36f6 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.cpp @@ -6,10 +6,13 @@ #include "Engine/Level/Level.h" #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Engine/Globals.h" #include "Engine/Threading/ThreadSpawner.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUPipelineState.h" #include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/Shaders/GPUShader.h" namespace ShadowsOfMordor { diff --git a/Source/Engine/ShadowsOfMordor/Builder.h b/Source/Engine/ShadowsOfMordor/Builder.h index 7d4b9812d..c8c84f5bf 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.h +++ b/Source/Engine/ShadowsOfMordor/Builder.h @@ -9,8 +9,11 @@ #if COMPILE_WITH_GI_BAKING +#include "Engine/Graphics/RenderTask.h" + // Forward declarations #if COMPILE_WITH_ASSETS_IMPORTER + namespace DirectX { class ScratchImage; @@ -20,6 +23,7 @@ class Actor; class Terrain; class Foliage; class StaticModel; +class GPUPipelineState; namespace ShadowsOfMordor { diff --git a/Source/Engine/Streaming/StreamableResource.h b/Source/Engine/Streaming/StreamableResource.h index d0cef9dd5..fab245791 100644 --- a/Source/Engine/Streaming/StreamableResource.h +++ b/Source/Engine/Streaming/StreamableResource.h @@ -26,18 +26,16 @@ protected: public: /// - /// Gets resource group + /// Gets resource group. /// - /// Streaming Group FORCE_INLINE StreamingGroup* GetGroup() const { return _group; } /// - /// Gets value indicating whenever resource can be used in dynamic streaming (otherwise use always the best quality) + /// Gets value indicating whenever resource can be used in dynamic streaming (otherwise use always the best quality). /// - /// Is dynamic streamable FORCE_INLINE bool IsDynamic() const { #if ENABLE_RESOURCES_DYNAMIC_STREAMING @@ -50,35 +48,15 @@ public: /// /// Gets resource streaming quality level /// - /// Streaming Quality level FORCE_INLINE StreamingQuality GetStreamingQuality() const { return _streamingQuality; } - /// - /// Gets resource maximum residency level. - /// - /// Residency - virtual int32 GetMaxResidency() const = 0; - - /// - /// Gets resource current residency level. - /// - /// Residency - virtual int32 GetCurrentResidency() const = 0; - - /// - /// Gets resource allocated residency level. - /// - /// Residency - virtual int32 GetAllocatedResidency() const = 0; - /// /// Gets resource target residency level. /// - /// Residency - int32 GetTargetResidency() const + FORCE_INLINE int32 GetTargetResidency() const { return Streaming.TargetResidency; } @@ -91,6 +69,21 @@ public: return GetAllocatedResidency() != 0; } + /// + /// Gets resource maximum residency level. + /// + virtual int32 GetMaxResidency() const = 0; + + /// + /// Gets resource current residency level. + /// + virtual int32 GetCurrentResidency() const = 0; + + /// + /// Gets resource allocated residency level. + /// + virtual int32 GetAllocatedResidency() const = 0; + public: /// diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index e87de12a3..2ff053432 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -2,7 +2,7 @@ #include "Terrain.h" #include "TerrainPatch.h" -#include "Engine/Content/Assets/RawDataAsset.h" +#include "Engine/Core/Math/Ray.h" #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Physics.h" @@ -10,6 +10,7 @@ #include "Engine/Physics/PhysicalMaterial.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Profiler/ProfilerCPU.h" #include @@ -37,7 +38,7 @@ Terrain::~Terrain() void Terrain::UpdateBounds() { PROFILE_CPU(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); for (int32 i = 0; i < _patches.Count(); i++) { auto patch = _patches[i]; diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 2d0c9cfb1..ee8caf181 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -6,6 +6,8 @@ #include "Terrain.h" #include "TerrainManager.h" #include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Level/Scene/Scene.h" diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 133b44c6a..f96fec22d 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -3,7 +3,6 @@ #include "TerrainPatch.h" #include "Terrain.h" #include "Engine/Serialization/Serialization.h" -#include "Engine/Graphics/RenderView.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Physics/Utilities.h" @@ -15,6 +14,8 @@ #include "Engine/Core/Math/Packed.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/TextureData.h" #if USE_EDITOR #include "Editor/Editor.h" @@ -1683,7 +1684,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int else { // Prepare asset path for the non-virtual asset - const String cacheDir = StringUtils::GetDirectoryName(Heightmap->GetPath()) / _terrain->GetID().ToString(Guid::FormatType::N); + const String cacheDir = String(StringUtils::GetDirectoryName(Heightmap->GetPath())) / _terrain->GetID().ToString(Guid::FormatType::N); const String splatMapPath = cacheDir + String::Format(TEXT("_{0:2}_{1:2}_Splatmap{3}.{2}"), _x, _z, ASSET_FILES_EXTENSION, index); // Import data to the asset file @@ -2183,6 +2184,9 @@ void TerrainPatch::CreateCollision() _physicsActor = CPhysX->createRigidStatic(trans); ASSERT(_physicsActor); _physicsActor->userData = _terrain; +#if WITH_PVD + _physicsActor->setActorFlag(PxActorFlag::eVISUALIZATION, true); +#endif _physicsActor->attachShape(*_physicsShape); Physics::AddActor(_physicsActor); diff --git a/Source/Engine/Threading/ConcurrentQueue.h b/Source/Engine/Threading/ConcurrentQueue.h index b4e9d4306..f206a38eb 100644 --- a/Source/Engine/Threading/ConcurrentQueue.h +++ b/Source/Engine/Threading/ConcurrentQueue.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Memory/Memory.h" +#define MOODYCAMEL_EXCEPTIONS_ENABLED 0 #include /// @@ -42,7 +43,6 @@ public: /// /// Gets an estimate of the total number of elements currently in the queue. /// - /// The items count. FORCE_INLINE int32 Count() const { return static_cast(Base::size_approx()); diff --git a/Source/Engine/Threading/ThreadPool.cpp b/Source/Engine/Threading/ThreadPool.cpp index ffd80d655..f16e5c2a3 100644 --- a/Source/Engine/Threading/ThreadPool.cpp +++ b/Source/Engine/Threading/ThreadPool.cpp @@ -7,11 +7,17 @@ #include "ConcurrentTaskQueue.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Engine/Globals.h" #include "Engine/Engine/EngineService.h" #include "Engine/Platform/ConditionVariable.h" #include "Engine/Platform/CPUInfo.h" #include "Engine/Platform/Thread.h" +FLAXENGINE_API bool IsInMainThread() +{ + return Globals::MainThreadID == Platform::GetCurrentThreadID(); +} + namespace ThreadPoolImpl { volatile int64 ExitFlag = 0; diff --git a/Source/Engine/Threading/Threading.h b/Source/Engine/Threading/Threading.h index 4de78fb28..d982232da 100644 --- a/Source/Engine/Threading/Threading.h +++ b/Source/Engine/Threading/Threading.h @@ -2,18 +2,12 @@ #pragma once -#include "Engine/Platform/Platform.h" #include "Engine/Platform/CriticalSection.h" -#include "Engine/Engine/Globals.h" /// /// Checks if current execution in on the main thread. /// -/// True if running on the main thread, otherwise false. -inline bool IsInMainThread() -{ - return Globals::MainThreadID == Platform::GetCurrentThreadID(); -} +FLAXENGINE_API bool IsInMainThread(); /// /// Scope locker for critical section. @@ -31,20 +25,9 @@ private: public: /// - /// Init, enters critical section + /// Init, enters critical section. /// - /// The synchronization object to manage - ScopeLock(const CriticalSection* section) - : _section(section) - { - ASSERT_LOW_LAYER(_section); - _section->Lock(); - } - - /// - /// Init, enters critical section - /// - /// The synchronization object to manage + /// The synchronization object to lock. ScopeLock(const CriticalSection& section) : _section(§ion) { @@ -52,7 +35,7 @@ public: } /// - /// Destructor, releases critical section + /// Destructor, releases critical section. /// ~ScopeLock() { diff --git a/Source/Engine/Tools/AudioTool/OggVorbisEncoder.cpp b/Source/Engine/Tools/AudioTool/OggVorbisEncoder.cpp index d3f4fb450..3e59e0caf 100644 --- a/Source/Engine/Tools/AudioTool/OggVorbisEncoder.cpp +++ b/Source/Engine/Tools/AudioTool/OggVorbisEncoder.cpp @@ -5,6 +5,7 @@ #include "OggVorbisEncoder.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Collections/Array.h" #include "AudioTool.h" #include diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp index 8f01ef271..587afd7a9 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp @@ -3,6 +3,8 @@ #if COMPILE_WITH_MATERIAL_GRAPH #include "MaterialGenerator.h" +#include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Math/Matrix.h" #include "Engine/Content/Assets/MaterialInstance.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Serialization/MemoryReadStream.h" @@ -250,7 +252,7 @@ void MaterialGenerator::prepareLayer(MaterialLayer* layer, bool allowVisiblePara break; case VariantType::Vector4: mp.Type = MaterialParameterType::Vector4; - mp.AsVector4 = param->Value.AsVector4(); + *(Vector4*)&mp.AsData = param->Value.AsVector4(); break; case VariantType::Color: mp.Type = MaterialParameterType::Color; @@ -258,7 +260,7 @@ void MaterialGenerator::prepareLayer(MaterialLayer* layer, bool allowVisiblePara break; case VariantType::Matrix: mp.Type = MaterialParameterType::Matrix; - mp.AsMatrix = *(Matrix*)param->Value.AsBlob.Data; + *(Matrix*)&mp.AsData = *(Matrix*)param->Value.AsBlob.Data; break; case VariantType::Asset: if (!param->Type.TypeName) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index b6b5306e2..2669fae79 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -7,6 +7,7 @@ #include "Engine/Platform/File.h" #include "Engine/Graphics/Materials/MaterialShader.h" #include "Engine/Graphics/Materials/MaterialShaderFeatures.h" +#include "Engine/Engine/Globals.h" /// /// Material shader source code template has special marks for generated code. @@ -54,6 +55,7 @@ namespace { // Loaded and parsed features data cache Dictionary Features; + CriticalSection FeaturesLock; } bool FeatureData::Init() @@ -174,6 +176,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo features.Add(typeName); \ if (!Features.ContainsKey(typeName)) \ { \ + ScopeLock lock(FeaturesLock); \ auto& feature = Features[typeName]; \ type::Generate(feature.Data); \ if (feature.Init()) \ @@ -388,7 +391,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Update material usage based on material generator outputs materialInfo.UsageFlags = baseLayer->UsageFlags; -#define WRITE_FEATURES(input) for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); +#define WRITE_FEATURES(input) FeaturesLock.Lock(); for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); FeaturesLock.Unlock(); // Defines { _writer.Write(TEXT("#define MATERIAL_MASK_THRESHOLD ({0})\n"), baseLayer->MaskThreshold); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index 50914ea2e..04101bc0b 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -518,29 +518,33 @@ bool ImportMaterials(AssimpImporterData& data, String& errorMsg) if (aMaterial->Get(AI_MATKEY_NAME, aName) == AI_SUCCESS) materialSlot.Name = String(aName.C_Str()).TrimTrailing(); materialSlot.AssetID = Guid::Empty; - aiColor3D aColor; - if (aMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, aColor) == AI_SUCCESS) - materialSlot.Diffuse.Color = ToColor(aColor); - bool aBoolean; - if (aMaterial->Get(AI_MATKEY_TWOSIDED, aBoolean) == AI_SUCCESS) - materialSlot.TwoSided = aBoolean; - bool aFloat; - if (aMaterial->Get(AI_MATKEY_OPACITY, aFloat) == AI_SUCCESS) - materialSlot.Opacity.Value = aFloat; - if (data.Model.Types & ImportDataTypes::Textures) + if (data.Model.Types & ImportDataTypes::Materials) { - ImportMaterialTexture(data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB); - ImportMaterialTexture(data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB); - ImportMaterialTexture(data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals); - ImportMaterialTexture(data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA); + aiColor3D aColor; + if (aMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, aColor) == AI_SUCCESS) + materialSlot.Diffuse.Color = ToColor(aColor); + bool aBoolean; + if (aMaterial->Get(AI_MATKEY_TWOSIDED, aBoolean) == AI_SUCCESS) + materialSlot.TwoSided = aBoolean; + bool aFloat; + if (aMaterial->Get(AI_MATKEY_OPACITY, aFloat) == AI_SUCCESS) + materialSlot.Opacity.Value = aFloat; - if (materialSlot.Diffuse.TextureIndex != -1) + if (data.Model.Types & ImportDataTypes::Textures) { - // Detect using alpha mask in diffuse texture - materialSlot.Diffuse.HasAlphaMask = TextureTool::HasAlpha(data.Model.Textures[materialSlot.Diffuse.TextureIndex].FilePath); - if (materialSlot.Diffuse.HasAlphaMask) - data.Model.Textures[materialSlot.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA; + ImportMaterialTexture(data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB); + ImportMaterialTexture(data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB); + ImportMaterialTexture(data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals); + ImportMaterialTexture(data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA); + + if (materialSlot.Diffuse.TextureIndex != -1) + { + // Detect using alpha mask in diffuse texture + materialSlot.Diffuse.HasAlphaMask = TextureTool::HasAlpha(data.Model.Textures[materialSlot.Diffuse.TextureIndex].FilePath); + if (materialSlot.Diffuse.HasAlphaMask) + data.Model.Textures[materialSlot.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA; + } } } } @@ -691,13 +695,10 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, cons ProcessNodes(assimpData, scene->mRootNode, -1); // Import materials - if (data.Types & ImportDataTypes::Materials) + if (ImportMaterials(assimpData, errorMsg)) { - if (ImportMaterials(assimpData, errorMsg)) - { - LOG(Warning, "Failed to import materials."); - return true; - } + LOG(Warning, "Failed to import materials."); + return true; } // Import geometry diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 2e77267b5..db8660b77 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -1085,10 +1085,12 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, con aFilename.toString(filenameData); if (outputPath.IsEmpty()) { - outputPath = StringUtils::GetDirectoryName(String(path)) / TEXT("textures"); + String pathStr(path); + outputPath = String(StringUtils::GetDirectoryName(pathStr)) / TEXT("textures"); FileSystem::CreateDirectory(outputPath); } - String embeddedPath = outputPath / StringUtils::GetFileName(String(filenameData)); + const String filenameStr(filenameData); + String embeddedPath = outputPath / StringUtils::GetFileName(filenameStr); if (FileSystem::FileExists(embeddedPath)) continue; LOG(Info, "Extracing embedded resource to {0}", embeddedPath); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index d452f5f78..06c5bac13 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -5,6 +5,7 @@ #include "ModelTool.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Pair.h" #include "Engine/Graphics/Models/SkeletonUpdater.h" #include "Engine/Graphics/Models/SkeletonMapping.h" @@ -486,7 +487,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options opt // Auto-import textures if (autoImportOutput.IsEmpty() || (data.Types & ImportDataTypes::Textures) == 0 || texture.FilePath.IsEmpty()) continue; - auto filename = StringUtils::GetFileNameWithoutExtension(texture.FilePath); + String filename = StringUtils::GetFileNameWithoutExtension(texture.FilePath); for (int32 j = filename.Length() - 1; j >= 0; j--) { if (EditorUtilities::IsInvalidPathChar(filename[j])) @@ -497,7 +498,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options opt int32 counter = 1; do { - filename = StringUtils::GetFileNameWithoutExtension(texture.FilePath) + TEXT(" ") + StringUtils::ToString(counter); + filename = String(StringUtils::GetFileNameWithoutExtension(texture.FilePath)) + TEXT(" ") + StringUtils::ToString(counter); counter++; } while (importedFileNames.Contains(filename)); } diff --git a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs index c584b6c31..6d98a0651 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs +++ b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs @@ -48,6 +48,11 @@ public class TextureTool : EngineModule { options.PrivateDependencies.Add("stb"); options.SourceFiles.Add(Path.Combine(FolderPath, "TextureTool.stb.cpp")); + if (options.Target.IsEditor) + { + // Use helper lib for decompression + options.PrivateDependencies.Add("detex"); + } } options.PublicDefinitions.Add("COMPILE_WITH_TEXTURE_TOOL"); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index e98cde7bf..bd8970c59 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -5,6 +5,7 @@ #include "TextureTool.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Math/Packed.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Math/Int2.h" @@ -250,6 +251,8 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData bool hasAlpha = false; #if COMPILE_WITH_DIRECTXTEX const auto failed = ImportTextureDirectXTex(type, path, textureData, options, errorMsg, hasAlpha); +#elif COMPILE_WITH_STB + const auto failed = ImportTextureStb(type, path, textureData, options, errorMsg, hasAlpha); #else const auto failed = true; LOG(Warning, "Importing textures is not supported on this platform."); @@ -327,6 +330,8 @@ bool TextureTool::Convert(TextureData& dst, const TextureData& src, const PixelF #if COMPILE_WITH_DIRECTXTEX return ConvertDirectXTex(dst, src, dstFormat); +#elif COMPILE_WITH_STB + return ConvertStb(dst, src, dstFormat); #else LOG(Warning, "Converting textures is not supported on this platform."); return true; @@ -441,11 +446,12 @@ TextureTool::PixelFormatSampler PixelFormatSamplers[] = sizeof(Color32), [](const void* ptr) { - return Color(*(Color32*)ptr); + return Color::SrgbToLinear(Color(*(Color32*)ptr)); }, [](const void* ptr, const Color& color) { - *(Color32*)ptr = Color32(color); + Color srgb = Color::LinearToSrgb(color); + *(Color32*)ptr = Color32(srgb); }, }, { @@ -553,11 +559,12 @@ TextureTool::PixelFormatSampler PixelFormatSamplers[] = [](const void* ptr) { const Color32 bgra = *(Color32*)ptr; - return Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A)); + return Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A))); }, [](const void* ptr, const Color& color) { - *(Color32*)ptr = Color32(byte(color.B * MAX_uint8), byte(color.G * MAX_uint8), byte(color.R * MAX_uint8), byte(color.A * MAX_uint8)); + Color srgb = Color::LinearToSrgb(color); + *(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), byte(srgb.A * MAX_uint8)); }, }, { @@ -579,11 +586,12 @@ TextureTool::PixelFormatSampler PixelFormatSamplers[] = [](const void* ptr) { const Color32 bgra = *(Color32*)ptr; - return Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8)); + return Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8))); }, [](const void* ptr, const Color& color) { - *(Color32*)ptr = Color32(byte(color.B * MAX_uint8), byte(color.G * MAX_uint8), byte(color.R * MAX_uint8), MAX_uint8); + Color srgb = Color::LinearToSrgb(color); + *(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), MAX_uint8); }, }, { diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index 447bf30ea..6337406ae 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -218,7 +218,7 @@ public: /// The Y texture coordinates (normalized to range 0-height). /// The data pointer for the texture slice (1D or 2D image). /// The row pitch (in bytes). The offset between each image rows. - /// The color to store. + /// The color to store (linear). static void Store(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch, const Color& color); /// @@ -232,7 +232,7 @@ public: /// The data pointer for the texture slice (1D or 2D image). /// The size of the input texture (in pixels). /// The row pitch (in bytes). The offset between each image rows. - /// The sampled color. + /// The sampled color (linear). static Color SamplePoint(const PixelFormatSampler* sampler, const Vector2& uv, const void* data, const Int2& size, int32 rowPitch); /// @@ -246,7 +246,7 @@ public: /// The Y texture coordinates (normalized to range 0-height). /// The data pointer for the texture slice (1D or 2D image). /// The row pitch (in bytes). The offset between each image rows. - /// The sampled color. + /// The sampled color (linear). static Color SamplePoint(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch); /// @@ -260,7 +260,7 @@ public: /// The data pointer for the texture slice (1D or 2D image). /// The size of the input texture (in pixels). /// The row pitch (in bytes). The offset between each image rows. - /// The sampled color. + /// The sampled color (linear). static Color SampleLinear(const PixelFormatSampler* sampler, const Vector2& uv, const void* data, const Int2& size, int32 rowPitch); private: @@ -291,6 +291,9 @@ private: #if COMPILE_WITH_STB static bool ExportTextureStb(ImageType type, const StringView& path, const TextureData& textureData); static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha); + static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha); + static bool ConvertStb(TextureData& dst, const TextureData& src, const PixelFormat dstFormat); + static bool ResizeStb(PixelFormat format, TextureMipData& dstMip, const TextureMipData& srcMip, int32 dstMipWidth, int32 dstMipHeight); static bool ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight); #endif }; diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index 3baa47f6a..cf018776e 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -6,7 +6,9 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Serialization/FileWriteStream.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Graphics/Textures/TextureUtils.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Platform/File.h" @@ -37,6 +39,17 @@ #define STB_IMAGE_RESIZE_IMPLEMENTATION #include +#define STBD_ABS(i) Math::Abs(i) +#define STBD_FABS(x) Math::Abs(x) +#define STB_DXT_IMPLEMENTATION +#include + +#if USE_EDITOR + +#include + +#endif + static void stbWrite(void* context, void* data, int size) { auto file = (FileWriteStream*)context; @@ -50,46 +63,142 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const LOG(Warning, "Exporting texture arrays and cubemaps is not supported by stb library."); } + TextureData const* texture = &textureData; + +#if USE_EDITOR + // Handle compressed textures + TextureData decompressed; + if (PixelFormatExtensions::IsCompressed(textureData.Format)) + { + decompressed.Format = PixelFormatExtensions::IsSRGB(textureData.Format) ? PixelFormat::R8G8B8A8_UNorm_sRGB : PixelFormat::R8G8B8A8_UNorm; + decompressed.Width = textureData.Width; + decompressed.Height = textureData.Height; + decompressed.Depth = textureData.Depth; + decompressed.Items.Resize(1); + decompressed.Items[0].Mips.Resize(1); + + auto decompressedData = decompressed.GetData(0, 0); + decompressedData->RowPitch = textureData.Width * sizeof(Color32); + decompressedData->Lines = textureData.Height; + decompressedData->DepthPitch = decompressedData->RowPitch * decompressedData->Lines; + decompressedData->Data.Allocate(decompressedData->DepthPitch); + + Color32 colors[16]; + int32 blocksWidth = textureData.Width / 4; + int32 blocksHeight = textureData.Height / 4; + const auto blocksData = texture->GetData(0, 0); + byte* decompressedBytes = decompressedData->Data.Get(); + + switch (textureData.Format) + { + case PixelFormat::BC1_UNorm: + case PixelFormat::BC1_UNorm_sRGB: + { + for (int32 yBlock = 0; yBlock < blocksHeight; yBlock++) + { + for (int32 xBlock = 0; xBlock < blocksWidth; xBlock++) + { + const byte* block = blocksData->Data.Get() + yBlock * 4 * blocksData->RowPitch + xBlock * 8; + detexDecompressBlockBC1(block, 0, 0, (byte*)&colors); + for (int32 y = 0; y < 4; y++) + { + for (int32 x = 0; x < 4; x++) + { + *((Color32*)decompressedBytes + (yBlock * 4 + y) * textureData.Width + (xBlock * 4 + x)) = colors[y * 4 + x]; + } + } + } + } + break; + } + case PixelFormat::BC2_UNorm: + case PixelFormat::BC2_UNorm_sRGB: + { + for (int32 yBlock = 0; yBlock < blocksHeight; yBlock++) + { + for (int32 xBlock = 0; xBlock < blocksWidth; xBlock++) + { + const byte* block = blocksData->Data.Get() + yBlock * 4 * blocksData->RowPitch + xBlock * 16; + detexDecompressBlockBC2(block, 0, 0, (byte*)&colors); + for (int32 y = 0; y < 4; y++) + { + for (int32 x = 0; x < 4; x++) + { + *((Color32*)decompressedBytes + (yBlock * 4 + y) * textureData.Width + (xBlock * 4 + x)) = colors[y * 4 + x]; + } + } + } + } + break; + } + case PixelFormat::BC3_UNorm: + case PixelFormat::BC3_UNorm_sRGB: + { + for (int32 yBlock = 0; yBlock < blocksHeight; yBlock++) + { + for (int32 xBlock = 0; xBlock < blocksWidth; xBlock++) + { + const byte* block = blocksData->Data.Get() + yBlock * 4 * blocksData->RowPitch + xBlock * 16; + detexDecompressBlockBC3(block, 0, 0, (byte*)&colors); + for (int32 y = 0; y < 4; y++) + { + for (int32 x = 0; x < 4; x++) + { + *((Color32*)decompressedBytes + (yBlock * 4 + y) * textureData.Width + (xBlock * 4 + x)) = colors[y * 4 + x]; + } + } + } + } + break; + } + default: + LOG(Warning, "Texture data format {0} is not supported by stb library.", (int32)textureData.Format); + return true; + } + texture = &decompressed; + } +#endif + // Convert into RGBA8 - const auto sampler = GetSampler(textureData.Format); + const auto sampler = GetSampler(texture->Format); if (sampler == nullptr) { LOG(Warning, "Texture data format {0} is not supported by stb library.", (int32)textureData.Format); return true; } - const auto srcData = textureData.GetData(0, 0); + const auto srcData = texture->GetData(0, 0); const int comp = 4; Array data; - bool sRGB = PixelFormatExtensions::IsSRGB(textureData.Format); + bool sRGB = PixelFormatExtensions::IsSRGB(texture->Format); if (type == ImageType::HDR) { - data.Resize(sizeof(float) * comp * textureData.Width * textureData.Height); + data.Resize(sizeof(float) * comp * texture->Width * texture->Height); auto ptr = (Vector4*)data.Get(); - for (int32 y = 0; y < textureData.Height; y++) + for (int32 y = 0; y < texture->Height; y++) { - for (int32 x = 0; x < textureData.Width; x++) + for (int32 x = 0; x < texture->Width; x++) { Color color = SamplePoint(sampler, x, y, srcData->Data.Get(), srcData->RowPitch); if (sRGB) color = Color::SrgbToLinear(color); - *(ptr + x + y * textureData.Width) = color.ToVector4(); + *(ptr + x + y * texture->Width) = color.ToVector4(); } } } else { - data.Resize(sizeof(Color32) * comp * textureData.Width * textureData.Height); + data.Resize(sizeof(Color32) * comp * texture->Width * texture->Height); auto ptr = (Color32*)data.Get(); - for (int32 y = 0; y < textureData.Height; y++) + for (int32 y = 0; y < texture->Height; y++) { - for (int32 x = 0; x < textureData.Width; x++) + for (int32 x = 0; x < texture->Width; x++) { Color color = SamplePoint(sampler, x, y, srcData->Data.Get(), srcData->RowPitch); if (sRGB) color = Color::SrgbToLinear(color); - *(ptr + x + y * textureData.Width) = Color32(color); + *(ptr + x + y * texture->Width) = Color32(color); } } } @@ -109,21 +218,21 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const switch (type) { case ImageType::BMP: - result = stbi_write_bmp_core(&s, textureData.Width, textureData.Height, comp, data.Get()); + result = stbi_write_bmp_core(&s, texture->Width, texture->Height, comp, data.Get()); break; case ImageType::JPEG: - result = stbi_write_jpg_core(&s, textureData.Width, textureData.Height, comp, data.Get(), 90); + result = stbi_write_jpg_core(&s, texture->Width, texture->Height, comp, data.Get(), 90); break; case ImageType::TGA: - result = stbi_write_tga_core(&s, textureData.Width, textureData.Height, comp, data.Get()); + result = stbi_write_tga_core(&s, texture->Width, texture->Height, comp, data.Get()); break; case ImageType::HDR: - result = stbi_write_hdr_core(&s, textureData.Width, textureData.Height, comp, (float*)data.Get()); + result = stbi_write_hdr_core(&s, texture->Width, texture->Height, comp, (float*)data.Get()); break; case ImageType::PNG: { int32 ptrSize = 0; - const auto ptr = stbi_write_png_to_mem(data.Get(), 0, textureData.Width, textureData.Height, comp, &ptrSize); + const auto ptr = stbi_write_png_to_mem(data.Get(), 0, texture->Width, texture->Height, comp, &ptrSize); if (ptr) { file->WriteBytes(ptr, ptrSize); @@ -185,9 +294,10 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu stbi_uc* stbData = stbi_load_from_memory(fileData.Get(), fileData.Count(), &width, &height, &components, 4); if (!stbData) { - LOG(Warning, "Failed to load image."); + LOG(Warning, "Failed to load image. {0}", String(stbi_failure_reason())); return false; } + fileData.Resize(0); // Setup texture data textureData.Width = width; @@ -260,6 +370,370 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu return false; } +bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha) +{ + // Load image data + if (type == ImageType::Internal) + { + if (options.FlipY) + { + errorMsg = TEXT("Flipping images imported from Internal source is not supported by stb."); + return true; + } + + MISSING_CODE("Importing internal textures with STB."); + return true; + } + else + { + stbi_set_flip_vertically_on_load_thread(options.FlipY); + bool failed = ImportTextureStb(type, path, textureData, hasAlpha); + stbi_set_flip_vertically_on_load_thread(false); + if (failed) + { + return true; + } + } + + // Use two data containers for texture importing for more optimzied performance + TextureData textureDataTmp; + TextureData* textureDataSrc = &textureData; + TextureData* textureDataDst = &textureDataTmp; + + // Check if resize source image + const int32 sourceWidth = textureData.Width; + const int32 sourceHeight = textureData.Height; + int32 width = Math::Clamp(options.Resize ? options.SizeX : static_cast(sourceWidth * options.Scale), 1, options.MaxSize); + int32 height = Math::Clamp(options.Resize ? options.SizeY : static_cast(sourceHeight * options.Scale), 1, options.MaxSize); + if (sourceWidth != width || sourceHeight != height) + { + // During resizing we need to keep texture aspect ratio + const bool keepAspectRatio = false; // TODO: expose as import option + if (keepAspectRatio) + { + const float aspectRatio = static_cast(sourceWidth) / sourceHeight; + if (width >= height) + height = Math::CeilToInt(width / aspectRatio); + else + width = Math::CeilToInt(height / aspectRatio); + } + + // Resize source texture + LOG(Info, "Resizing texture from {0}x{1} to {2}x{3}.", sourceWidth, sourceHeight, width, height); + if (ResizeStb(*textureDataDst, *textureDataSrc, width, height)) + { + errorMsg = String::Format(TEXT("Cannot resize texture.")); + return true; + } + ::Swap(textureDataSrc, textureDataDst); + } + + // Cache data + float alphaThreshold = 0.3f; + bool isPowerOfTwo = Math::IsPowerOfTwo(width) && Math::IsPowerOfTwo(height); + PixelFormat targetFormat = TextureUtils::ToPixelFormat(options.Type, width, height, options.Compress); + if (options.sRGB) + targetFormat = PixelFormatExtensions::TosRGB(targetFormat); + + // Check mip levels + int32 sourceMipLevels = textureDataSrc->GetMipLevels(); + bool hasSourceMipLevels = isPowerOfTwo && sourceMipLevels > 1; + bool useMipLevels = isPowerOfTwo && (options.GenerateMipMaps || hasSourceMipLevels) && (width > 1 || height > 1); + int32 arraySize = (int32)textureDataSrc->GetArraySize(); + int32 mipLevels = MipLevelsCount(width, height, useMipLevels); + if (useMipLevels && !options.GenerateMipMaps && mipLevels != sourceMipLevels) + { + errorMsg = String::Format(TEXT("Imported texture has not full mip chain, loaded mips count: {0}, expected: {1}"), sourceMipLevels, mipLevels); + return true; + } + + // Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing) + if (PixelFormatExtensions::IsCompressed(textureDataSrc->Format)) + { + // TODO: implement texture decompression + errorMsg = String::Format(TEXT("Imported texture used compressed format {0}. Not supported for importing on this platform.."), (int32)textureDataSrc->Format); + return true; + } + + // Generate mip maps chain + if (useMipLevels && options.GenerateMipMaps) + { + for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) + { + auto& slice = textureDataSrc->Items[arrayIndex]; + slice.Mips.Resize(mipLevels); + for (int32 mipIndex = 1; mipIndex < mipLevels; mipIndex++) + { + const auto& srcMip = slice.Mips[mipIndex - 1]; + auto& dstMip = slice.Mips[mipIndex]; + auto dstMipWidth = Math::Max(textureDataSrc->Width >> mipIndex, 1); + auto dstMipHeight = Math::Max(textureDataSrc->Height >> mipIndex, 1); + if (ResizeStb(textureDataSrc->Format, dstMip, srcMip, dstMipWidth, dstMipHeight)) + { + errorMsg = TEXT("Failed to generate mip texture."); + return true; + } + } + } + } + + // Preserve mipmap alpha coverage (if requested) + if (PixelFormatExtensions::HasAlpha(textureDataSrc->Format) && options.PreserveAlphaCoverage && useMipLevels) + { + // TODO: implement alpha coverage preserving + errorMsg = TEXT("Importing textures with alpha coverage preserving is not supported on this platform."); + return true; + } + + // Compress mip maps or convert image + if (targetFormat != textureDataSrc->Format) + { + if (ConvertStb(*textureDataDst, *textureDataSrc, targetFormat)) + { + errorMsg = String::Format(TEXT("Cannot convert/compress texture.")); + return true; + } + ::Swap(textureDataSrc, textureDataDst); + } + + // Copy data to the output if not in the result container + if (textureDataSrc != &textureData) + { + textureData = textureDataTmp; + } + + return false; +} + +bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const PixelFormat dstFormat) +{ + // Setup + auto arraySize = src.GetArraySize(); + dst.Width = src.Width; + dst.Height = src.Height; + dst.Depth = src.Depth; + dst.Format = dstFormat; + dst.Items.Resize(arraySize, false); + auto formatSize = PixelFormatExtensions::SizeInBytes(src.Format); + auto components = PixelFormatExtensions::ComputeComponentsCount(src.Format); + auto sampler = TextureTool::GetSampler(src.Format); + if (!sampler) + { + LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast(src.Format)); + return true; + } + + if (PixelFormatExtensions::IsCompressed(dstFormat)) + { + int32 bytesPerBlock; + switch (dstFormat) + { + case PixelFormat::BC1_UNorm: + case PixelFormat::BC1_UNorm_sRGB: + case PixelFormat::BC4_UNorm: + bytesPerBlock = 8; + break; + default: + bytesPerBlock = 16; + break; + } + bool isDstSRGB = PixelFormatExtensions::IsSRGB(dstFormat); + + // Compress all array slices + for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) + { + const auto& srcSlice = src.Items[arrayIndex]; + auto& dstSlice = dst.Items[arrayIndex]; + auto mipLevels = srcSlice.Mips.Count(); + dstSlice.Mips.Resize(mipLevels, false); + + // Compress all mip levels + for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + const auto& srcMip = srcSlice.Mips[mipIndex]; + auto& dstMip = dstSlice.Mips[mipIndex]; + auto mipWidth = Math::Max(src.Width >> mipIndex, 1); + auto mipHeight = Math::Max(src.Height >> mipIndex, 1); + auto blocksWidth = Math::Max(Math::DivideAndRoundUp(mipWidth, 4), 1); + auto blocksHeight = Math::Max(Math::DivideAndRoundUp(mipHeight, 4), 1); + + // Allocate memory + dstMip.RowPitch = blocksWidth * bytesPerBlock; + dstMip.DepthPitch = dstMip.RowPitch * blocksHeight; + dstMip.Lines = blocksHeight; + dstMip.Data.Allocate(dstMip.DepthPitch); + + // Compress texture + for (int32 yBlock = 0; yBlock < blocksHeight; yBlock++) + { + for (int32 xBlock = 0; xBlock < blocksWidth; xBlock++) + { + // Sample source texture 4x4 block + Color32 srcBlock[16]; + for (int32 y = 0; y < 4; y++) + { + for (int32 x = 0; x < 4; x++) + { + Color color = TextureTool::SamplePoint(sampler, xBlock * 4 + x, yBlock * 4 + y, srcMip.Data.Get(), srcMip.RowPitch); + if (isDstSRGB) + color = Color::LinearToSrgb(color); + srcBlock[y * 4 + x] = Color32(color); + } + } + + // Compress block + switch (dstFormat) + { + case PixelFormat::BC1_UNorm: + case PixelFormat::BC1_UNorm_sRGB: + stb_compress_dxt_block((byte*)dstMip.Data.Get() + (yBlock * blocksWidth + xBlock) * bytesPerBlock, (byte*)&srcBlock, 0, STB_DXT_HIGHQUAL); + break; + case PixelFormat::BC3_UNorm: + case PixelFormat::BC3_UNorm_sRGB: + stb_compress_dxt_block((byte*)dstMip.Data.Get() + (yBlock * blocksWidth + xBlock) * bytesPerBlock, (byte*)&srcBlock, 1, STB_DXT_HIGHQUAL); + break; + case PixelFormat::BC4_UNorm: + for (int32 i = 1; i < 16; i++) + ((byte*)&srcBlock)[i] = srcBlock[i].R; + stb_compress_bc4_block((byte*)dstMip.Data.Get() + (yBlock * blocksWidth + xBlock) * bytesPerBlock, (byte*)&srcBlock); + break; + case PixelFormat::BC5_UNorm: + for (int32 i = 0; i < 16; i++) + ((uint16*)&srcBlock)[i] = srcBlock[i].R << 8 | srcBlock[i].G; + stb_compress_bc5_block((byte*)dstMip.Data.Get() + (yBlock * blocksWidth + xBlock) * bytesPerBlock, (byte*)&srcBlock); + break; + default: + LOG(Warning, "Cannot compress image. Unsupported format {0}", static_cast(dstFormat)); + return true; + } + } + } + } + } + } + else + { + int32 bytesPerPixel = PixelFormatExtensions::SizeInBytes(dstFormat); + auto dstSampler = TextureTool::GetSampler(dstFormat); + if (!dstSampler) + { + LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast(dstFormat)); + return true; + } + + // Convert all array slices + for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) + { + const auto& srcSlice = src.Items[arrayIndex]; + auto& dstSlice = dst.Items[arrayIndex]; + auto mipLevels = srcSlice.Mips.Count(); + dstSlice.Mips.Resize(mipLevels, false); + + // Convert all mip levels + for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + const auto& srcMip = srcSlice.Mips[mipIndex]; + auto& dstMip = dstSlice.Mips[mipIndex]; + auto mipWidth = Math::Max(src.Width >> mipIndex, 1); + auto mipHeight = Math::Max(src.Height >> mipIndex, 1); + + // Allocate memory + dstMip.RowPitch = mipWidth * bytesPerPixel; + dstMip.DepthPitch = dstMip.RowPitch * mipHeight; + dstMip.Lines = mipHeight; + dstMip.Data.Allocate(dstMip.DepthPitch); + + // Convert texture + for (int32 y = 0; y < mipHeight; y++) + { + for (int32 x = 0; x < mipWidth; x++) + { + // Sample source texture + Color color = TextureTool::SamplePoint(sampler, x, y, srcMip.Data.Get(), srcMip.RowPitch); + + // Store destination texture + TextureTool::Store(dstSampler, x, y, dstMip.Data.Get(), dstMip.RowPitch, color); + } + } + } + } + } + + return false; +} + +bool TextureTool::ResizeStb(PixelFormat format, TextureMipData& dstMip, const TextureMipData& srcMip, int32 dstMipWidth, int32 dstMipHeight) +{ + // Setup + auto formatSize = PixelFormatExtensions::SizeInBytes(format); + auto components = PixelFormatExtensions::ComputeComponentsCount(format); + auto srcMipWidth = srcMip.RowPitch / formatSize; + auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch; + + // Allocate memory + dstMip.RowPitch = dstMipWidth * formatSize; + dstMip.DepthPitch = dstMip.RowPitch * dstMipHeight; + dstMip.Lines = dstMipHeight; + dstMip.Data.Allocate(dstMip.DepthPitch); + + // Resize texture + switch (format) + { + case PixelFormat::R8_Typeless: + case PixelFormat::R8_SInt: + case PixelFormat::R8_SNorm: + case PixelFormat::R8G8_Typeless: + case PixelFormat::R8G8_SInt: + case PixelFormat::R8G8_SNorm: + case PixelFormat::R8G8B8A8_Typeless: + case PixelFormat::R8G8B8A8_UNorm: + case PixelFormat::R8G8B8A8_UInt: + case PixelFormat::R8G8B8A8_SNorm: + case PixelFormat::R8G8B8A8_SInt: + case PixelFormat::B8G8R8A8_UNorm: + case PixelFormat::B8G8R8X8_Typeless: + case PixelFormat::B8G8R8X8_UNorm: + { + if (!stbir_resize_uint8((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components)) + { + LOG(Warning, "Cannot resize image."); + return true; + } + break; + } + case PixelFormat::R8G8B8A8_UNorm_sRGB: + case PixelFormat::B8G8R8A8_UNorm_sRGB: + case PixelFormat::B8G8R8X8_UNorm_sRGB: + { + auto alphaChannel = format == PixelFormat::B8G8R8X8_UNorm_sRGB ? STBIR_ALPHA_CHANNEL_NONE : 3; + if (!stbir_resize_uint8_srgb((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components, alphaChannel, 0)) + { + LOG(Warning, "Cannot resize image."); + return true; + } + break; + } + case PixelFormat::R32_Typeless: + case PixelFormat::R32_Float: + case PixelFormat::R32G32_Float: + case PixelFormat::R32G32B32_Float: + case PixelFormat::R32G32B32A32_Float: + { + if (!stbir_resize_float((const float*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (float*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components)) + { + LOG(Warning, "Cannot resize image."); + return true; + } + break; + } + default: + LOG(Warning, "Cannot resize image. Unsupported format {0}", static_cast(format)); + return true; + } + + return false; +} + bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight) { // Setup @@ -268,7 +742,7 @@ bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstW dst.Height = dstHeight; dst.Depth = src.Depth; dst.Format = src.Format; - dst.Items.Resize(arraySize); + dst.Items.Resize(arraySize, false); auto formatSize = PixelFormatExtensions::SizeInBytes(src.Format); auto components = PixelFormatExtensions::ComputeComponentsCount(src.Format); @@ -278,7 +752,7 @@ bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstW const auto& srcSlice = src.Items[arrayIndex]; auto& dstSlice = dst.Items[arrayIndex]; auto mipLevels = srcSlice.Mips.Count(); - dstSlice.Mips.Resize(mipLevels); + dstSlice.Mips.Resize(mipLevels, false); // Resize all mip levels for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) @@ -287,69 +761,10 @@ bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstW auto& dstMip = dstSlice.Mips[mipIndex]; auto srcMipWidth = srcMip.RowPitch / formatSize; auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch; - auto dstMipWidth = Math::Max(dstWidth << mipIndex, 1); - auto dstMipHeight = Math::Max(dstHeight << mipIndex, 1); - - // Allocate memory - dstMip.RowPitch = dstMipWidth * formatSize; - dstMip.DepthPitch = dstMip.RowPitch * dstMipHeight; - dstMip.Lines = dstMipHeight; - dstMip.Data.Allocate(dstMip.DepthPitch); - - // Resize texture - switch (src.Format) - { - case PixelFormat::R8_Typeless: - case PixelFormat::R8_SInt: - case PixelFormat::R8_SNorm: - case PixelFormat::R8G8_Typeless: - case PixelFormat::R8G8_SInt: - case PixelFormat::R8G8_SNorm: - case PixelFormat::R8G8B8A8_Typeless: - case PixelFormat::R8G8B8A8_UNorm: - case PixelFormat::R8G8B8A8_UInt: - case PixelFormat::R8G8B8A8_SNorm: - case PixelFormat::R8G8B8A8_SInt: - case PixelFormat::B8G8R8A8_UNorm: - case PixelFormat::B8G8R8X8_Typeless: - case PixelFormat::B8G8R8X8_UNorm: - { - if (!stbir_resize_uint8((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components)) - { - LOG(Warning, "Cannot resize image."); - return true; - } - break; - } - case PixelFormat::R8G8B8A8_UNorm_sRGB: - case PixelFormat::B8G8R8A8_UNorm_sRGB: - case PixelFormat::B8G8R8X8_UNorm_sRGB: - { - auto alphaChannel = src.Format == PixelFormat::B8G8R8X8_UNorm_sRGB ? STBIR_ALPHA_CHANNEL_NONE : 3; - if (!stbir_resize_uint8_srgb((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components, alphaChannel, 0)) - { - LOG(Warning, "Cannot resize image."); - return true; - } - break; - } - case PixelFormat::R32_Typeless: - case PixelFormat::R32_Float: - case PixelFormat::R32G32_Float: - case PixelFormat::R32G32B32_Float: - case PixelFormat::R32G32B32A32_Float: - { - if (!stbir_resize_float((const float*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (float*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components)) - { - LOG(Warning, "Cannot resize image."); - return true; - } - break; - } - default: - LOG(Warning, "Cannot resize image. Unsupported format {0}", static_cast(src.Format)); + auto dstMipWidth = Math::Max(dstWidth >> mipIndex, 1); + auto dstMipHeight = Math::Max(dstHeight >> mipIndex, 1); + if (ResizeStb(src.Format, dstMip, srcMip, dstMipWidth, dstMipHeight)) return true; - } } } diff --git a/Source/Engine/UI/GUI/Common/Border.cs b/Source/Engine/UI/GUI/Common/Border.cs index 543735e99..fb699b132 100644 --- a/Source/Engine/UI/GUI/Common/Border.cs +++ b/Source/Engine/UI/GUI/Common/Border.cs @@ -5,7 +5,7 @@ namespace FlaxEngine.GUI /// /// Border control that draws the border around the control edges (inner and outer sides). /// - public class Border : Control + public class Border : ContainerControl { /// /// Gets or sets the color used to draw border lines. @@ -30,9 +30,9 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); + base.DrawSelf(); Render2D.DrawRectangle(new Rectangle(Vector2.Zero, Size), BorderColor, BorderWidth); } diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 9d56a4a36..d983e22e0 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -7,7 +7,7 @@ namespace FlaxEngine.GUI /// /// Button control /// - public class Button : Control + public class Button : ContainerControl { /// /// The default height fro the buttons. @@ -24,11 +24,20 @@ namespace FlaxEngine.GUI /// protected FontReference _font; + /// + /// The text. + /// + protected LocalizedString _text = new LocalizedString(); + /// /// Button text property. /// [EditorOrder(10), Tooltip("The button label text.")] - public string Text { get; set; } + public LocalizedString Text + { + get => _text; + set => _text = value; + } /// /// Gets or sets the font used to draw button text. @@ -171,7 +180,7 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { // Cache data Rectangle clientRect = new Rectangle(Vector2.Zero, Size); @@ -201,7 +210,7 @@ namespace FlaxEngine.GUI Render2D.DrawRectangle(clientRect, borderColor); // Draw text - Render2D.DrawText(_font.GetFont(), TextMaterial, Text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(_font.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); } /// diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 917e51f6e..ea2debb4a 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -8,8 +8,8 @@ namespace FlaxEngine.GUI /// /// Dropdown menu control allows to choose one item from the provided collection of options. /// - /// - public class Dropdown : Control + /// + public class Dropdown : ContainerControl { /// /// The root control used by the to show the items collections and track item selecting event. @@ -18,7 +18,7 @@ namespace FlaxEngine.GUI [HideInEditor] protected class DropdownRoot : Panel { - private bool isMouseDown; + private bool _isMouseDown; /// /// Occurs when item gets clicked. Argument is item index. @@ -38,9 +38,9 @@ namespace FlaxEngine.GUI /// public override bool OnMouseDown(Vector2 location, MouseButton button) { - isMouseDown = true; + _isMouseDown = true; var result = base.OnMouseDown(location, button); - isMouseDown = false; + _isMouseDown = false; if (!result) return false; @@ -57,8 +57,10 @@ namespace FlaxEngine.GUI { base.OnLostFocus(); - if (!isMouseDown) - LostFocus(); + if (!_isMouseDown) + { + LostFocus?.Invoke(); + } } /// @@ -75,14 +77,14 @@ namespace FlaxEngine.GUI /// /// The items. /// - protected List _items = new List(); + protected List _items = new List(); /// /// The popup menu. May be null if has not been used yet. /// protected DropdownRoot _popup; - private bool _mouseDown; + private bool _touchDown; /// /// The selected index of the item (-1 for no selection). @@ -93,7 +95,7 @@ namespace FlaxEngine.GUI /// Gets or sets the items collection. /// [EditorOrder(1), Tooltip("The items collection.")] - public List Items + public List Items { get => _items; set => _items = value; @@ -105,7 +107,17 @@ namespace FlaxEngine.GUI [HideInEditor, NoSerialize] public string SelectedItem { - get => _selectedIndex != -1 ? _items[_selectedIndex] : string.Empty; + get => _selectedIndex != -1 ? _items[_selectedIndex].ToString() : string.Empty; + set => SelectedIndex = _items.IndexOf(value); + } + + /// + /// Gets or sets the selected item (returns if no item is being selected). + /// + [HideInEditor, NoSerialize] + public LocalizedString SelectedItemLocalized + { + get => _selectedIndex != -1 ? _items[_selectedIndex] : LocalizedString.Empty; set => SelectedIndex = _items.IndexOf(value); } @@ -118,13 +130,9 @@ namespace FlaxEngine.GUI get => _selectedIndex; set { - // Clamp index value = Mathf.Min(value, _items.Count - 1); - - // Check if index will change if (value != _selectedIndex) { - // Select _selectedIndex = value; OnSelectedIndexChanged(); } @@ -225,6 +233,8 @@ namespace FlaxEngine.GUI public Dropdown() : base(0, 0, 120, 18.0f) { + AutoFocus = false; + var style = Style.Current; Font = new FontReference(style.FontMedium); TextColor = style.Foreground; @@ -265,7 +275,8 @@ namespace FlaxEngine.GUI /// The items. public void AddItems(IEnumerable items) { - _items.AddRange(items); + foreach (var item in items) + _items.Add(item); } /// @@ -276,7 +287,8 @@ namespace FlaxEngine.GUI { SelectedIndex = -1; _items.Clear(); - _items.AddRange(items); + foreach (var item in items) + _items.Add(item); } /// @@ -334,6 +346,7 @@ namespace FlaxEngine.GUI } */ var itemsWidth = Width; + var height = container.Margin.Height; for (int i = 0; i < _items.Count; i++) { @@ -347,35 +360,51 @@ namespace FlaxEngine.GUI var label = new Label { X = itemsMargin, - Width = itemsWidth - itemsMargin, + Size = new Vector2(itemsWidth - itemsMargin, itemsHeight), Font = Font, TextColor = Color.White * 0.9f, TextColorHighlighted = Color.White, HorizontalAlignment = TextAlignment.Near, - AnchorPreset = AnchorPresets.VerticalStretchRight, Text = _items[i], Parent = item, }; + height += itemsHeight; + if (i != 0) + height += container.Spacing; if (_selectedIndex == i) { var icon = new Image { Brush = CheckedImage, - Width = itemsMargin, + Size = new Vector2(itemsMargin, itemsHeight), Margin = new Margin(4.0f, 6.0f, 4.0f, 4.0f), - AnchorPreset = AnchorPresets.VerticalStretchLeft, + //AnchorPreset = AnchorPresets.VerticalStretchLeft, Parent = item, }; } } - popup.Size = new Vector2(itemsWidth, (itemsHeight + container.Spacing) * _items.Count + container.Margin.Height); + popup.Size = new Vector2(itemsWidth, height); popup.ItemsContainer = container; return popup; } + /// + /// Called when popup menu gets shown. + /// + protected virtual void OnPopupShow() + { + } + + /// + /// Called when popup menu gets hidden. + /// + protected virtual void OnPopupHide() + { + } + /// /// Destroys the popup. /// @@ -383,11 +412,59 @@ namespace FlaxEngine.GUI { if (_popup != null) { + OnPopupHide(); _popup.Dispose(); _popup = null; } } + /// + /// Shows the popup. + /// + public void ShowPopup() + { + var root = Root; + if (_items.Count == 0 || root == null) + return; + + // Setup popup + DestroyPopup(); + _popup = CreatePopup(); + + // Update layout + _popup.UnlockChildrenRecursive(); + _popup.PerformLayout(); + + // Bind events + _popup.ItemClicked += index => + { + OnItemClicked(index); + DestroyPopup(); + }; + _popup.LostFocus += DestroyPopup; + + // Show dropdown popup + Vector2 locationRootSpace = Location + new Vector2(0, Height); + var parent = Parent; + while (parent != null && parent != Root) + { + locationRootSpace = parent.PointToParent(ref locationRootSpace); + parent = parent.Parent; + } + _popup.Location = locationRootSpace; + _popup.Parent = root; + _popup.Focus(); + OnPopupShow(); + } + + /// + /// Hides the popup. + /// + public void HidePopup() + { + DestroyPopup(); + } + /// public override void OnDestroy() { @@ -397,7 +474,7 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { // Cache data var clientRect = new Rectangle(Vector2.Zero, Size); @@ -413,7 +490,7 @@ namespace FlaxEngine.GUI backgroundColor *= 0.5f; arrowColor *= 0.7f; } - else if (isOpened || _mouseDown) + else if (isOpened || _touchDown) { backgroundColor = BackgroundColorSelected; borderColor = BorderColorSelected; @@ -448,17 +525,15 @@ namespace FlaxEngine.GUI /// public override void OnLostFocus() { - base.OnLostFocus(); + _touchDown = false; - // Clear flags - _mouseDown = false; + base.OnLostFocus(); } /// public override void OnMouseLeave() { - // Clear flags - _mouseDown = false; + _touchDown = false; base.OnMouseLeave(); } @@ -466,59 +541,60 @@ namespace FlaxEngine.GUI /// public override bool OnMouseDown(Vector2 location, MouseButton button) { - // Check mouse buttons + if (base.OnMouseDown(location, button)) + return true; + if (button == MouseButton.Left) { - // Set flag - _mouseDown = true; + _touchDown = true; + return true; } - return base.OnMouseDown(location, button); + return false; } /// public override bool OnMouseUp(Vector2 location, MouseButton button) { - // Check flags - if (_mouseDown) + if (_touchDown && button == MouseButton.Left) { - // Clear flag - _mouseDown = false; - - var root = Root; - if (_items.Count > 0 && root != null) - { - // Setup popup - DestroyPopup(); - _popup = CreatePopup(); - - // Update layout - _popup.UnlockChildrenRecursive(); - _popup.PerformLayout(); - - // Bind events - _popup.ItemClicked += (index) => - { - OnItemClicked(index); - DestroyPopup(); - }; - _popup.LostFocus += DestroyPopup; - - // Show dropdown popup - Vector2 locationRootSpace = Location + new Vector2(0, Height); - var parent = Parent; - while (parent != null && parent != Root) - { - locationRootSpace = parent.PointToParent(ref location); - parent = parent.Parent; - } - _popup.Location = locationRootSpace; - _popup.Parent = root; - _popup.Focus(); - } + _touchDown = false; + ShowPopup(); + return true; } + return base.OnMouseUp(location, button); + } + + /// + public override bool OnTouchDown(Vector2 location, int pointerId) + { + if (base.OnTouchDown(location, pointerId)) + return true; + + _touchDown = true; return true; } + + /// + public override bool OnTouchUp(Vector2 location, int pointerId) + { + if (base.OnTouchUp(location, pointerId)) + return true; + + if (_touchDown) + { + ShowPopup(); + } + return true; + } + + /// + public override void OnTouchLeave(int pointerId) + { + _touchDown = false; + + base.OnTouchLeave(pointerId); + } } } diff --git a/Source/Engine/UI/GUI/Common/Image.cs b/Source/Engine/UI/GUI/Common/Image.cs index b94b285b6..264fdf306 100644 --- a/Source/Engine/UI/GUI/Common/Image.cs +++ b/Source/Engine/UI/GUI/Common/Image.cs @@ -80,9 +80,9 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); + base.DrawSelf(); if (Brush == null) return; diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index d797c25f4..1b06e1867 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -10,7 +10,11 @@ namespace FlaxEngine.GUI /// public class Label : ContainerControl { - private string _text; + /// + /// The text. + /// + protected LocalizedString _text = new LocalizedString(); + private bool _autoWidth; private bool _autoHeight; private bool _autoFitText; @@ -26,7 +30,7 @@ namespace FlaxEngine.GUI /// Gets or sets the text. /// [EditorOrder(10), MultilineText, Tooltip("The label text.")] - public string Text + public LocalizedString Text { get => _text; set @@ -77,7 +81,19 @@ namespace FlaxEngine.GUI public FontReference Font { get => _font; - set => _font = value; + set + { + if (_font != value) + { + _font = value; + + if (_autoWidth || _autoHeight || _autoFitText) + { + _textSize = Vector2.Zero; + PerformLayout(); + } + } + } } /// @@ -192,7 +208,7 @@ namespace FlaxEngine.GUI var rect = new Rectangle(new Vector2(Margin.Left, Margin.Top), Size - Margin.Size); if (ClipText) - Render2D.PushClip(ref rect); + Render2D.PushClip(new Rectangle(Vector2.Zero, Size)); var color = IsMouseOver ? TextColorHighlighted : TextColor; @@ -200,8 +216,8 @@ namespace FlaxEngine.GUI color *= 0.6f; var scale = 1.0f; - var hAlignment = _autoWidth ? TextAlignment.Near : HorizontalAlignment; - var wAlignment = _autoHeight ? TextAlignment.Near : VerticalAlignment; + var hAlignment = HorizontalAlignment; + var wAlignment = VerticalAlignment; if (_autoFitText) { hAlignment = TextAlignment.Center; @@ -213,18 +229,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText( - _font.GetFont(), - Material, - Text, - rect, - color, - hAlignment, - wAlignment, - Wrapping, - 1.0f, - scale - ); + Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, 1.0f, scale); if (ClipText) Render2D.PopClip(); @@ -239,7 +244,13 @@ namespace FlaxEngine.GUI if (font) { // Calculate text size - _textSize = font.MeasureText(_text); + var layout = TextLayoutOptions.Default; + layout.TextWrapping = Wrapping; + if (_autoHeight && !_autoWidth) + layout.Bounds.Size.X = Width - Margin.Width; + else if (_autoWidth && !_autoHeight) + layout.Bounds.Size.Y = Height - Margin.Height; + _textSize = font.MeasureText(_text, ref layout); // Check if size is controlled via text if (_autoWidth || _autoHeight) diff --git a/Source/Engine/UI/GUI/Common/ProgressBar.cs b/Source/Engine/UI/GUI/Common/ProgressBar.cs index f68336d24..881bbc161 100644 --- a/Source/Engine/UI/GUI/Common/ProgressBar.cs +++ b/Source/Engine/UI/GUI/Common/ProgressBar.cs @@ -8,7 +8,7 @@ namespace FlaxEngine.GUI /// Progress bar control shows visual progress of the action or set of actions. /// /// - public class ProgressBar : Control + public class ProgressBar : ContainerControl { /// /// The value. @@ -39,9 +39,6 @@ namespace FlaxEngine.GUI /// /// Gets a value indicating whether use progress value smoothing. /// - /// - /// true if use progress value smoothing; otherwise, false. - /// public bool UseSmoothing => !Mathf.IsZero(SmoothingScale); /// @@ -91,8 +88,6 @@ namespace FlaxEngine.GUI if (!Mathf.NearEqual(value, _value)) { _value = value; - - // Check if skip smoothing if (!UseSmoothing) { _current = _value; @@ -138,31 +133,31 @@ namespace FlaxEngine.GUI /// public override void Update(float deltaTime) { - bool isDeltaSlow = deltaTime > (1 / 20.0f); - - // Ensure progress bar is visible if (Visible) { // Value smoothing + var value = _value; if (Mathf.Abs(_current - _value) > 0.01f) { // Lerp or not if running slow - float value; + bool isDeltaSlow = deltaTime > (1 / 20.0f); if (!isDeltaSlow && UseSmoothing) value = Mathf.Lerp(_current, _value, Mathf.Saturate(deltaTime * 5.0f * SmoothingScale)); - else - value = _value; _current = value; } + else + { + _current = _value; + } } base.Update(deltaTime); } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); + base.DrawSelf(); float progressNormalized = (_current - _minimum) / _maximum; if (progressNormalized > 0.001f) diff --git a/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs b/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs index 526390dac..e2e1532f3 100644 --- a/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs +++ b/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs @@ -55,8 +55,9 @@ namespace FlaxEngine.GUI /// public bool AutomaticInvalidate { get; set; } = true; +#if FLAX_EDITOR private bool CanEditTextureSize => !_autoSize; - +#endif /// /// Invalidates the cached image of children controls and invokes the redraw to the texture. /// diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 6a1c4c429..0dcfdd797 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -124,7 +124,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (font) { - height = font.Height; + height = font.Height / DpiScale; return textBlock.Bounds.UpperLeft; } } @@ -136,7 +136,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (font) { - height = font.Height; + height = font.Height / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -151,7 +151,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (!font) break; - height = font.Height; + height = font.Height / DpiScale; return textBlock.Bounds.Location + font.GetCharPosition(_text, ref textBlock.Range, index - textBlock.Range.StartIndex); } } @@ -166,7 +166,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (!font) break; - height = font.Height; + height = font.Height / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -220,7 +220,7 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { // Cache data var rect = new Rectangle(Vector2.Zero, Size); @@ -254,7 +254,7 @@ namespace FlaxEngine.GUI break; } } - var endTextBlock = Mathf.Min(firstTextBlock + 1, textBlocks.Length); + var endTextBlock = Mathf.Min(firstTextBlock + 1, _textBlocks.Count); for (int i = _textBlocks.Count - 1; i > firstTextBlock; i--) { ref TextBlock textBlock = ref textBlocks[i]; @@ -280,10 +280,11 @@ namespace FlaxEngine.GUI { Vector2 leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); Vector2 rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex); + float height = font.Height / DpiScale; float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; Color selectionColor = Color.White * alpha; - Rectangle selectionRect = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, font.Height); + Rectangle selectionRect = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, height); textBlock.Style.BackgroundSelectedBrush.Draw(selectionRect, selectionColor); } } @@ -329,7 +330,8 @@ namespace FlaxEngine.GUI if (textBlock.Style.UnderlineBrush != null) { var underLineHeight = 2.0f; - var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + font.Height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight); + var height = font.Height / DpiScale; + var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight); textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color); } } diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 36460130f..20548eed8 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -1,7 +1,5 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -using FlaxEngine.Assertions; - namespace FlaxEngine.GUI { /// @@ -11,11 +9,20 @@ namespace FlaxEngine.GUI { private TextLayoutOptions _layout; + /// + /// The watermark text. + /// + protected LocalizedString _watermarkText; + /// /// Gets or sets the watermark text to show grayed when textbox is empty. /// [EditorOrder(20), Tooltip("The watermark text to show grayed when textbox is empty.")] - public string WatermarkText { get; set; } + public LocalizedString WatermarkText + { + get => _watermarkText; + set => _watermarkText = value; + } /// /// Gets or sets the text wrapping within the control bounds. @@ -109,7 +116,7 @@ namespace FlaxEngine.GUI return Vector2.Zero; } - height = font.Height; + height = font.Height / DpiScale; return font.GetCharPosition(_text, index, ref _layout); } @@ -134,7 +141,7 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { // Cache data var rect = new Rectangle(Vector2.Zero, Size); @@ -161,7 +168,7 @@ namespace FlaxEngine.GUI { Vector2 leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); Vector2 rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); - float fontHeight = font.Height; + float fontHeight = font.Height / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); @@ -203,9 +210,9 @@ namespace FlaxEngine.GUI color *= 0.6f; Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); } - else if (!string.IsNullOrEmpty(WatermarkText) && !IsFocused) + else if (!string.IsNullOrEmpty(_watermarkText) && !IsFocused) { - Render2D.DrawText(font, WatermarkText, WatermarkTextColor, ref _layout, TextMaterial); + Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); } // Caret diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 1df079b48..ee6aaa7bc 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -9,7 +9,7 @@ namespace FlaxEngine.GUI /// /// Base class for all text box controls which can gather text input from the user. /// - public abstract class TextBoxBase : Control + public abstract class TextBoxBase : ContainerControl { /// /// The text separators (used for words skipping). @@ -261,6 +261,11 @@ namespace FlaxEngine.GUI } } + /// + /// Gets a value indicating whether user is editing the text. + /// + public bool IsEditing => _isEditing; + /// /// Gets or sets text property. /// @@ -279,10 +284,11 @@ namespace FlaxEngine.GUI } /// - /// Sets the text. + /// Sets the text (forced, even if user is editing it). /// /// The value. - protected void SetText(string value) + [NoAnimate] + public void SetText(string value) { // Prevent from null problems if (value == null) @@ -314,6 +320,18 @@ namespace FlaxEngine.GUI } } + /// + /// Sets the text as it was entered by user (focus, change value, defocus). + /// + /// The value. + [NoAnimate] + public void SetTextAsUser(string value) + { + Focus(); + SetText(value); + Defocus(); + } + /// /// Gets length of the text /// @@ -504,9 +522,7 @@ namespace FlaxEngine.GUI if (string.IsNullOrEmpty(clipboardText)) return; - var right = SelectionRight; Insert(clipboardText); - SetSelection(Mathf.Max(right, 0) + clipboardText.Length); } /// @@ -539,6 +555,12 @@ namespace FlaxEngine.GUI return; } + // If it's not selected + if (_selectionStart == -1 && _selectionEnd == -1) + { + return; + } + Rectangle caretBounds = CaretBounds; Rectangle textArea = TextRectangle; @@ -616,11 +638,13 @@ namespace FlaxEngine.GUI { var left = SelectionLeft >= 0 ? SelectionLeft : 0; if (HasSelection) + { _text = _text.Remove(left, selectionLength); - + SetSelection(left); + } _text = _text.Insert(left, str); - SetSelection(left + 1); + SetSelection(left + str.Length); } OnTextChanged(); @@ -806,6 +830,19 @@ namespace FlaxEngine.GUI return spaceLoc; } + private int FindPrevLineBegin() + { + int caretPos = CaretPosition; + if (caretPos - 2 < 0) + return 0; + int newLineLoc = _text.LastIndexOf('\n', caretPos - 2); + if (newLineLoc == -1) + newLineLoc = 0; + else + newLineLoc++; + return newLineLoc; + } + private int FindLineDownChar(int index) { if (!IsMultiline) @@ -981,7 +1018,8 @@ namespace FlaxEngine.GUI /// public override void OnMouseMove(Vector2 location) { - // Check if user is selecting + base.OnMouseMove(location); + if (_isSelecting) { // Find char index at current mouse location @@ -995,6 +1033,9 @@ namespace FlaxEngine.GUI /// public override bool OnMouseDown(Vector2 location, MouseButton button) { + if (base.OnMouseDown(location, button)) + return true; + if (button == MouseButton.Left && _text.Length > 0) { Focus(); @@ -1031,6 +1072,9 @@ namespace FlaxEngine.GUI /// public override bool OnMouseUp(Vector2 location, MouseButton button) { + if (base.OnMouseUp(location, button)) + return true; + if (button == MouseButton.Left) { OnSelectingEnd(); @@ -1060,6 +1104,8 @@ namespace FlaxEngine.GUI /// public override bool OnCharInput(char c) { + if (base.OnCharInput(c)) + return true; Insert(c); return true; } @@ -1175,7 +1221,6 @@ namespace FlaxEngine.GUI return true; } case KeyboardKeys.Return: - { if (IsMultiline) { // Insert new line @@ -1186,15 +1231,22 @@ namespace FlaxEngine.GUI // End editing Defocus(); } - return true; - } case KeyboardKeys.Home: - { - // Move caret to the first character - SetSelection(0); + if (shiftDown) + { + // Select text from the current cursor point back to the beginning of the line + if (_selectionStart != -1) + { + SetSelection(FindPrevLineBegin(), _selectionStart); + } + } + else + { + // Move caret to the first character + SetSelection(0); + } return true; - } case KeyboardKeys.End: { // Move caret after last character diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index fb11f2eff..3519469a2 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -249,6 +249,8 @@ namespace FlaxEngine.GUI internal void ChangeChildIndex(Control child, int newIndex) { int oldIndex = _children.IndexOf(child); + if (oldIndex == newIndex) + return; _children.RemoveAt(oldIndex); // Check if index is invalid @@ -262,6 +264,8 @@ namespace FlaxEngine.GUI // Change order _children.Insert(newIndex, child); } + + PerformLayout(); } /// @@ -272,7 +276,7 @@ namespace FlaxEngine.GUI public int GetChildIndexAt(Vector2 point) { int result = -1; - for (int i = 0; i < _children.Count; i++) + for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; @@ -294,7 +298,7 @@ namespace FlaxEngine.GUI public Control GetChildAt(Vector2 point) { Control result = null; - for (int i = 0; i < _children.Count; i++) + for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; @@ -320,7 +324,7 @@ namespace FlaxEngine.GUI throw new ArgumentNullException(nameof(isValid)); Control result = null; - for (int i = 0; i < _children.Count; i++) + for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; @@ -342,7 +346,7 @@ namespace FlaxEngine.GUI public Control GetChildAtRecursive(Vector2 point) { Control result = null; - for (int i = 0; i < _children.Count; i++) + for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; @@ -614,25 +618,32 @@ namespace FlaxEngine.GUI } } - /// + /// + /// Draw the control and the children. + /// public override void Draw() { - base.Draw(); + DrawSelf(); - // Push clipping mask if (ClipChildren) { GetDesireClientArea(out var clientArea); Render2D.PushClip(ref clientArea); - } - - DrawChildren(); - - // Pop clipping mask - if (ClipChildren) - { + DrawChildren(); Render2D.PopClip(); } + else + { + DrawChildren(); + } + } + + /// + /// Draws the control. + /// + public virtual void DrawSelf() + { + base.Draw(); } /// diff --git a/Source/Engine/UI/GUI/Control.Bounds.cs b/Source/Engine/UI/GUI/Control.Bounds.cs index e74c73e66..717b3c4d7 100644 --- a/Source/Engine/UI/GUI/Control.Bounds.cs +++ b/Source/Engine/UI/GUI/Control.Bounds.cs @@ -26,11 +26,30 @@ namespace FlaxEngine.GUI set => Bounds = new Rectangle(X, value, _bounds.Size); } + /// + /// Gets or sets the local X coordinate of the pivot of the control relative to the anchor in parent of its container. + /// + [HideInEditor, NoSerialize] + public float LocalX + { + get => LocalLocation.X; + set => LocalLocation = new Vector2(value, LocalLocation.Y); + } + + /// + /// Gets or sets the local Y coordinate of the pivot of the control relative to the anchor in parent of its container. + /// + [HideInEditor, NoSerialize] + public float LocalY + { + get => LocalLocation.Y; + set => LocalLocation = new Vector2(LocalLocation.X, value); + } + /// /// Gets or sets the normalized position in the parent control that the upper left corner is anchored to (range 0-1). /// - [Serialize] - [ExpandGroups, Limit(0.0f, 1.0f, 0.01f), EditorDisplay("Transform"), EditorOrder(990), Tooltip("The normalized position in the parent control that the upper left corner is anchored to (range 0-1).")] + [Serialize, HideInEditor] public Vector2 AnchorMin { get => _anchorMin; @@ -49,8 +68,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the normalized position in the parent control that the bottom right corner is anchored to (range 0-1). /// - [Serialize] - [ExpandGroups, Limit(0.0f, 1.0f, 0.01f), EditorDisplay("Transform"), EditorOrder(991), Tooltip("The normalized position in the parent control that the bottom right corner is anchored to (range 0-1).")] + [Serialize, HideInEditor] public Vector2 AnchorMax { get => _anchorMax; @@ -69,8 +87,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the offsets of the corners of the control relative to its anchors. /// - [Serialize] - [ExpandGroups, EditorDisplay("Transform"), EditorOrder(992), Tooltip("The offsets of the corners of the control relative to its anchors.")] + [Serialize, HideInEditor] public Margin Offsets { get => _offsets; @@ -84,17 +101,68 @@ namespace FlaxEngine.GUI } } +#if FLAX_EDITOR + /// + /// Helper for Editor UI (see UIControlControlEditor). + /// + [NoSerialize, HideInEditor] + internal float Proxy_Offset_Left + { + get => _offsets.Left; + set => Offsets = new Margin(value, _offsets.Right, _offsets.Top, _offsets.Bottom); + } + + /// + /// Helper for Editor UI (see UIControlControlEditor). + /// + [NoSerialize, HideInEditor] + internal float Proxy_Offset_Right + { + get => _offsets.Right; + set => Offsets = new Margin(_offsets.Left, value, _offsets.Top, _offsets.Bottom); + } + + /// + /// Helper for Editor UI (see UIControlControlEditor). + /// + [NoSerialize, HideInEditor] + internal float Proxy_Offset_Top + { + get => _offsets.Top; + set => Offsets = new Margin(_offsets.Left, _offsets.Right, value, _offsets.Bottom); + } + + /// + /// Helper for Editor UI (see UIControlControlEditor). + /// + [NoSerialize, HideInEditor] + internal float Proxy_Offset_Bottom + { + get => _offsets.Bottom; + set => Offsets = new Margin(_offsets.Left, _offsets.Right, _offsets.Top, value); + } +#endif + /// /// Gets or sets coordinates of the upper-left corner of the control relative to the upper-left corner of its container. /// - [NoSerialize] - [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1000), Tooltip("The location of the upper-left corner of the control relative to he upper-left corner of its container.")] + [NoSerialize, HideInEditor] public Vector2 Location { get => _bounds.Location; set => Bounds = new Rectangle(value, _bounds.Size); } + /// + /// Gets or sets the local position of the pivot of the control relative to the anchor in parent of its container. + /// + [NoSerialize, HideInEditor] + public Vector2 LocalLocation + { + get => _bounds.Location - (_parent != null ? _parent._bounds.Size * (_anchorMax + _anchorMin) * 0.5f : Vector2.Zero) + _bounds.Size * _pivot; + set => Bounds = new Rectangle(value + (_parent != null ? _parent.Bounds.Size * (_anchorMax + _anchorMin) * 0.5f : Vector2.Zero) - _bounds.Size * _pivot, _bounds.Size); + } + /// /// Gets or sets width of the control. /// @@ -118,8 +186,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets control's size. /// - [NoSerialize] - [EditorDisplay("Transform"), EditorOrder(1010), Tooltip("The size of the control bounds.")] + [NoSerialize, HideInEditor] public Vector2 Size { get => _bounds.Size; @@ -181,57 +248,60 @@ namespace FlaxEngine.GUI set { if (!_bounds.Equals(ref value)) - { - // Calculate anchors based on the parent container client area - Margin anchors; - if (_parent != null) - { - _parent.GetDesireClientArea(out var parentBounds); - anchors = new Margin - ( - _anchorMin.X * parentBounds.Size.X + parentBounds.Location.X, - _anchorMax.X * parentBounds.Size.X, - _anchorMin.Y * parentBounds.Size.Y + parentBounds.Location.Y, - _anchorMax.Y * parentBounds.Size.Y - ); - } - else - { - anchors = Margin.Zero; - } - - // Calculate offsets on X axis - _offsets.Left = value.Location.X - anchors.Left; - if (_anchorMin.X != _anchorMax.X) - { - _offsets.Right = anchors.Right - value.Location.X - value.Size.X; - } - else - { - _offsets.Right = value.Size.X; - } - - // Calculate offsets on Y axis - _offsets.Top = value.Location.Y - anchors.Top; - if (_anchorMin.Y != _anchorMax.Y) - { - _offsets.Bottom = anchors.Bottom - value.Location.Y - value.Size.Y; - } - else - { - _offsets.Bottom = value.Size.Y; - } - - // Flush the control bounds - UpdateBounds(); - } + SetBounds(ref value); } } + private void SetBounds(ref Rectangle value) + { + // Calculate anchors based on the parent container client area + Margin anchors; + if (_parent != null) + { + _parent.GetDesireClientArea(out var parentBounds); + anchors = new Margin + ( + _anchorMin.X * parentBounds.Size.X + parentBounds.Location.X, + _anchorMax.X * parentBounds.Size.X, + _anchorMin.Y * parentBounds.Size.Y + parentBounds.Location.Y, + _anchorMax.Y * parentBounds.Size.Y + ); + } + else + { + anchors = Margin.Zero; + } + + // Calculate offsets on X axis + _offsets.Left = value.Location.X - anchors.Left; + if (_anchorMin.X != _anchorMax.X) + { + _offsets.Right = anchors.Right - value.Location.X - value.Size.X; + } + else + { + _offsets.Right = value.Size.X; + } + + // Calculate offsets on Y axis + _offsets.Top = value.Location.Y - anchors.Top; + if (_anchorMin.Y != _anchorMax.Y) + { + _offsets.Bottom = anchors.Bottom - value.Location.Y - value.Size.Y; + } + else + { + _offsets.Bottom = value.Size.Y; + } + + // Flush the control bounds + UpdateBounds(); + } + /// - /// Gets or sets the scale. + /// Gets or sets the scale. Scales control according to its Pivot which by default is (0.5,0.5) (middle of the control). If you set pivot to (0,0) it will scale the control based on it's upper-left corner. /// - [EditorDisplay("Transform"), Limit(float.MinValue, float.MaxValue, 0.1f), EditorOrder(1020), Tooltip("The control scale parameter.")] + [ExpandGroups, EditorDisplay("Transform"), Limit(float.MinValue, float.MaxValue, 0.1f), EditorOrder(1020), Tooltip("The control scale parameter. Scales control according to its Pivot which by default is (0.5,0.5) (middle of the control). If you set pivot to (0,0) it will scale the control based on it's upper-left corner.")] public Vector2 Scale { get => _scale; @@ -247,7 +317,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the normalized pivot location (used to transform control around it). Point (0,0) is upper left corner, (0.5,0.5) is center, (1,1) is bottom right corner. /// - [EditorDisplay("Transform"), Limit(0.0f, 1.0f, 0.1f), EditorOrder(1030), Tooltip("The control rotation pivot location in normalized control size. Point (0,0) is upper left corner, (0.5,0.5) is center, (1,1) is bottom right corner.")] + [ExpandGroups, EditorDisplay("Transform"), Limit(0.0f, 1.0f, 0.1f), EditorOrder(1030), Tooltip("The control rotation pivot location in normalized control size. Point (0,0) is upper left corner, (0.5,0.5) is center, (1,1) is bottom right corner.")] public Vector2 Pivot { get => _pivot; @@ -261,9 +331,9 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the shear transform angles (x, y). Defined in degrees. + /// Gets or sets the shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point. /// - [EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees.")] + [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.")] public Vector2 Shear { get => _shear; @@ -277,9 +347,9 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the rotation angle (in degrees). + /// Gets or sets the rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default). /// - [EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees).")] + [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).")] public float Rotation { get => _rotation; @@ -421,7 +491,8 @@ namespace FlaxEngine.GUI /// /// The anchor preset to set. /// True if preserve current control bounds, otherwise will align control position accordingly to the anchor location. - public void SetAnchorPreset(AnchorPresets anchorPreset, bool preserveBounds) + /// Whether or not we should set the pivot too, eg left-top 0,0, bottom-right 1,1 + public void SetAnchorPreset(AnchorPresets anchorPreset, bool preserveBounds, bool setPivotToo = false) { for (int i = 0; i < AnchorPresetsData.Length; i++) { @@ -511,8 +582,13 @@ namespace FlaxEngine.GUI } bounds.Location += parentBounds.Location; } - Bounds = bounds; + SetBounds(ref bounds); } + if (setPivotToo) + { + Pivot = (anchorMin + anchorMax) / 2; + } + _parent?.PerformLayout(); return; } } diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index c2ba34f05..afdd86bcb 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -176,7 +176,7 @@ namespace FlaxEngine.GUI /// Gets or sets the anchor preset used by the control anchors (based on and ). /// /// To change anchor preset with current control bounds preservation use . - [NoSerialize, EditorDisplay("Transform"), EditorOrder(980), Tooltip("The anchor preset used by the control anchors.")] + [NoSerialize, EditorDisplay("Transform"), HideInEditor, EditorOrder(980), Tooltip("The anchor preset used by the control anchors.")] public AnchorPresets AnchorPreset { get @@ -209,7 +209,6 @@ namespace FlaxEngine.GUI public bool Enabled { get => _isEnabled; - set { if (_isEnabled != value) @@ -255,7 +254,6 @@ namespace FlaxEngine.GUI public bool Visible { get => _isVisible; - set { if (_isVisible != value) @@ -310,7 +308,12 @@ namespace FlaxEngine.GUI /// /// Gets the GUI window root control which contains that control (or null if not linked to any). /// - public virtual WindowRootControl RootWindow => _parent?.RootWindow; + public virtual WindowRootControl RootWindow => _root?.RootWindow; + + /// + /// Gets the control DPI scale factor (1 is default). Includes custom DPI scale. + /// + public float DpiScale => RootWindow?.Window.DpiScale ?? Platform.DpiScale; /// /// Gets screen position of the control (upper left corner). @@ -453,9 +456,9 @@ namespace FlaxEngine.GUI #region Focus /// - /// Gets a value indicating whether the control can receive automatic focus on user events (eg. mouse down. + /// Gets a value indicating whether the control can receive automatic focus on user events (eg. mouse down). /// - [HideInEditor] + [HideInEditor, NoSerialize] public bool AutoFocus { get => _autoFocus; diff --git a/Source/Engine/UI/GUI/Margin.cs b/Source/Engine/UI/GUI/Margin.cs index 40dab7093..01c0ac3ab 100644 --- a/Source/Engine/UI/GUI/Margin.cs +++ b/Source/Engine/UI/GUI/Margin.cs @@ -47,7 +47,12 @@ namespace FlaxEngine.GUI public float Bottom; /// - /// Gets the margin's total size. Cumulative margin size. + /// Gets the margin's location (Left, Top). + /// + public Vector2 Location => new Vector2(Left, Top); + + /// + /// Gets the margin's total size. Cumulative margin size (Left + Right, Top + Bottom). /// public Vector2 Size => new Vector2(Left + Right, Top + Bottom); diff --git a/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs b/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs index d7b54a835..116fd7c95 100644 --- a/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs +++ b/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs @@ -25,7 +25,7 @@ namespace FlaxEngine.GUI for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; - if (c.Visible) + if (c.Visible && Mathf.IsZero(c.AnchorMin.Y) && Mathf.IsZero(c.AnchorMax.Y)) { c.Height = h; } @@ -35,28 +35,39 @@ namespace FlaxEngine.GUI /// protected override void PerformLayoutAfterChildren() { - // Sort controls from left to right - float x = _margin.Left; + // Sort controls horizontally + float left = _margin.Left; + float right = _margin.Right; float h = Height - _margin.Height; - bool hasAnyItem = false; + bool hasAnyLeft = false, hasAnyRight = false; for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; - if (c.Visible && Mathf.IsZero(c.AnchorMax.X)) + if (c.Visible) { var w = c.Width; - c.Bounds = new Rectangle(x + _offset.X, _margin.Top + _offset.Y, w, h); - x = c.Right + _spacing; - hasAnyItem = true; + if (Mathf.IsZero(c.AnchorMin.X) && Mathf.IsZero(c.AnchorMax.X)) + { + c.Bounds = new Rectangle(left + _offset.X, _margin.Top + _offset.Y, w, h); + left = c.Right + _spacing; + hasAnyLeft = true; + } + else if (Mathf.IsOne(c.AnchorMin.X) && Mathf.IsOne(c.AnchorMax.X)) + { + right += w + _spacing; + c.Bounds = new Rectangle(Width - right + _offset.X, _margin.Top + _offset.Y, w, h); + hasAnyRight = true; + } } } - if (hasAnyItem) - x -= _spacing; - x += _margin.Right; + if (hasAnyLeft) + left -= _spacing; + if (hasAnyRight) + right -= _spacing; // Update size if (_autoSize) - Width = x; + Width = left + right; } } } diff --git a/Source/Engine/UI/GUI/Panels/VerticalPanel.cs b/Source/Engine/UI/GUI/Panels/VerticalPanel.cs index 2c821bd3f..84974cc96 100644 --- a/Source/Engine/UI/GUI/Panels/VerticalPanel.cs +++ b/Source/Engine/UI/GUI/Panels/VerticalPanel.cs @@ -25,7 +25,7 @@ namespace FlaxEngine.GUI for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; - if (c.Visible) + if (c.Visible && Mathf.IsZero(c.AnchorMin.X) && Mathf.IsZero(c.AnchorMax.X)) { c.Width = w; } @@ -35,28 +35,39 @@ namespace FlaxEngine.GUI /// protected override void PerformLayoutAfterChildren() { - // Sort controls from top to bottom - float y = _margin.Top; + // Sort controls vertically + float top = _margin.Top; + float bottom = _margin.Bottom; float w = Width - _margin.Width; - bool hasAnyItem = false; + bool hasAnyTop = false, hasAnyBottom = false; for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; - if (c.Visible && Mathf.IsZero(c.AnchorMax.Y)) + if (c.Visible) { var h = c.Height; - c.Bounds = new Rectangle(_margin.Left + _offset.X, y + _offset.Y, w, h); - y = c.Bottom + _spacing; - hasAnyItem = true; + if (Mathf.IsZero(c.AnchorMin.Y) && Mathf.IsZero(c.AnchorMax.Y)) + { + c.Bounds = new Rectangle(_margin.Left + _offset.X, top + _offset.Y, w, h); + top = c.Bottom + _spacing; + hasAnyTop = true; + } + else if (Mathf.IsOne(c.AnchorMin.Y) && Mathf.IsOne(c.AnchorMax.Y)) + { + bottom += h + _spacing; + c.Bounds = new Rectangle(_margin.Left + _offset.X, Height - bottom + _offset.Y, w, h); + hasAnyBottom = true; + } } } - if (hasAnyItem) - y -= _spacing; - y += _margin.Bottom; + if (hasAnyTop) + top -= _spacing; + if (hasAnyBottom) + bottom -= _spacing; // Update size if (_autoSize) - Height = y; + Height = top + bottom; } } } diff --git a/Source/Engine/UI/GUI/RenderOutputControl.cs b/Source/Engine/UI/GUI/RenderOutputControl.cs index b840e0be8..9a7f6dfb1 100644 --- a/Source/Engine/UI/GUI/RenderOutputControl.cs +++ b/Source/Engine/UI/GUI/RenderOutputControl.cs @@ -224,7 +224,7 @@ namespace FlaxEngine.GUI /// public void SyncBackbufferSize() { - float scale = ResolutionScale * Platform.DpiScale; + float scale = ResolutionScale * DpiScale; int width = Mathf.CeilToInt(Width * scale); int height = Mathf.CeilToInt(Height * scale); if (_customResolution.HasValue) diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index db7561de4..9b4f0f64e 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -224,6 +224,12 @@ namespace FlaxEngine.GUI [EditorOrder(320)] public SpriteHandle Scale; + /// + /// The scalar icon. + /// + [EditorOrder(330)] + public SpriteHandle Scalar; + /// /// The shared tooltip control used by the controls if no custom tooltip is provided. /// diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 5d03bf75c..4541124e7 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -68,18 +68,23 @@ namespace FlaxEngine.GUI var parentWin = target.Root; if (parentWin == null) return; - float dpiScale = Platform.DpiScale; + float dpiScale = target.RootWindow.DpiScale; Vector2 dpiSize = Size * dpiScale; Vector2 locationWS = target.PointToWindow(location); Vector2 locationSS = parentWin.PointToScreen(locationWS); Vector2 screenSize = Platform.VirtualDesktopSize; + Vector2 parentWinLocationSS = parentWin.PointToScreen(Vector2.Zero); + float parentWinRightSS = parentWinLocationSS.Y + parentWin.Size.Y; + float parentWinBottomSS = parentWinLocationSS.X + parentWin.Size.X; Vector2 rightBottomLocationSS = locationSS + dpiSize; - if (screenSize.Y < rightBottomLocationSS.Y) + + // Prioritize tooltip placement within parent window, fall back to virtual desktop + if (parentWinRightSS < rightBottomLocationSS.Y || screenSize.Y < rightBottomLocationSS.Y) { // Direction: up locationSS.Y -= dpiSize.Y; } - if (screenSize.X < rightBottomLocationSS.X) + if (parentWinBottomSS < rightBottomLocationSS.X || screenSize.X < rightBottomLocationSS.X) { // Direction: left locationSS.X -= dpiSize.X; @@ -155,7 +160,7 @@ namespace FlaxEngine.GUI /// The delta time. public void OnMouseOverControl(Control target, float dt) { - if (!Visible) + if (!Visible && _timeToPopupLeft > 0.0f) { _lastTarget = target; _timeToPopupLeft -= dt; @@ -183,7 +188,7 @@ namespace FlaxEngine.GUI { if (_window) { - _window.ClientSize = Size * Platform.DpiScale; + _window.ClientSize = Size * _window.DpiScale; } } diff --git a/Source/Engine/UI/GUI/WindowRootControl.cs b/Source/Engine/UI/GUI/WindowRootControl.cs index 02c996b68..1e9785195 100644 --- a/Source/Engine/UI/GUI/WindowRootControl.cs +++ b/Source/Engine/UI/GUI/WindowRootControl.cs @@ -151,7 +151,7 @@ namespace FlaxEngine.GUI } /// - public override Vector2 TrackingMouseOffset => _window.TrackingMouseOffset / _window._dpiScale; + public override Vector2 TrackingMouseOffset => _window.TrackingMouseOffset / _window.DpiScale; /// public override WindowRootControl RootWindow => this; @@ -159,8 +159,8 @@ namespace FlaxEngine.GUI /// public override Vector2 MousePosition { - get => _window.MousePosition / _window._dpiScale; - set => _window.MousePosition = value * _window._dpiScale; + get => _window.MousePosition / _window.DpiScale; + set => _window.MousePosition = value * _window.DpiScale; } /// @@ -234,13 +234,13 @@ namespace FlaxEngine.GUI /// public override Vector2 PointFromScreen(Vector2 location) { - return _window.ScreenToClient(location) / _window._dpiScale; + return _window.ScreenToClient(location) / _window.DpiScale; } /// public override Vector2 PointToScreen(Vector2 location) { - return _window.ClientToScreen(location * _window._dpiScale); + return _window.ClientToScreen(location * _window.DpiScale); } /// diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp index d2564fc84..e82c523c3 100644 --- a/Source/Engine/UI/SpriteRender.cpp +++ b/Source/Engine/UI/SpriteRender.cpp @@ -7,6 +7,7 @@ #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/MaterialInstance.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Level/Actors/Camera.h" #include "Engine/Serialization/Serialization.h" diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 8e00c2cc3..30335aee3 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -6,6 +6,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Models/Types.h" #include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Render2D/Font.h" #include "Engine/Render2D/FontAsset.h" @@ -16,7 +17,7 @@ #include "Engine/Content/Assets/MaterialInstance.h" #include "Engine/Content/Content.h" #include "Engine/Core/Types/Variant.h" -#include "Engine/Graphics/RenderTask.h" +#include "Engine/Localization/Localization.h" #if USE_EDITOR #include "Editor/Editor.h" #endif @@ -31,7 +32,7 @@ TextRender::TextRender(const SpawnParams& params) { _world = Matrix::Identity; _color = Color::White; - _localBox = BoundingBox(Vector3::Zero, Vector3::Zero); + _localBox = BoundingBox(Vector3::Zero); _layoutOptions.Bounds = Rectangle(-100, -100, 200, 200); _layoutOptions.HorizontalAlignment = TextAlignment::Center; _layoutOptions.VerticalAlignment = TextAlignment::Center; @@ -45,7 +46,12 @@ TextRender::TextRender(const SpawnParams& params) Material.Changed.Bind(this); } -void TextRender::SetText(const StringView& value) +const LocalizedString& TextRender::GetText() const +{ + return _text; +} + +void TextRender::SetText(const LocalizedString& value) { if (_text != value) { @@ -54,6 +60,11 @@ void TextRender::SetText(const StringView& value) } } +Color TextRender::GetColor() const +{ + return _color; +} + void TextRender::SetColor(const Color& value) { if (_color != value) @@ -63,6 +74,11 @@ void TextRender::SetColor(const Color& value) } } +int32 TextRender::GetFontSize() const +{ + return _size; +} + void TextRender::SetFontSize(int32 value) { value = Math::Clamp(value, 1, 1024); @@ -84,15 +100,12 @@ void TextRender::SetLayoutOptions(TextLayoutOptions& value) void TextRender::UpdateLayout() { - if (!_isDirty) - return; - // Clear _ib.Clear(); _vb0.Clear(); _vb1.Clear(); _vb2.Clear(); - _localBox = BoundingBox(Vector3::Zero, Vector3::Zero); + _localBox = BoundingBox(Vector3::Zero); BoundingBox::Transform(_localBox, _world, _box); BoundingSphere::FromBox(_box, _sphere); #if USE_PRECISE_MESH_INTERSECTS @@ -111,8 +124,28 @@ void TextRender::UpdateLayout() _isDirty = false; // Skip if no need to calculate the layout - if (_text.IsEmpty()) - return; + String textData; + String* textPtr = &_text.Value; + if (textPtr->IsEmpty()) + { + if (_text.Id.IsEmpty()) + return; + textData = Localization::GetString(_text.Id); + textPtr = &textData; + if (!_isLocalized) + { + _isLocalized = true; + Localization::LocalizationChanged.Bind(this); + } + if (textPtr->IsEmpty()) + return; + } + else if (_isLocalized) + { + _isLocalized = false; + Localization::LocalizationChanged.Unbind(this); + } + const String& text = *textPtr; // Pick a font (remove DPI text scale as the text is being placed in the world) auto font = Font->CreateFont(_size); @@ -126,13 +159,13 @@ void TextRender::UpdateLayout() // Perform layout Array lines; - font->ProcessText(_text, lines, _layoutOptions); + font->ProcessText(text, lines, _layoutOptions); // Prepare buffers capacity - _ib.Data.EnsureCapacity(_text.Length() * 6 * sizeof(uint16)); - _vb0.Data.EnsureCapacity(_text.Length() * 4 * sizeof(VB0ElementType)); - _vb1.Data.EnsureCapacity(_text.Length() * 4 * sizeof(VB1ElementType)); - _vb2.Data.EnsureCapacity(_text.Length() * 4 * sizeof(VB2ElementType)); + _ib.Data.EnsureCapacity(text.Length() * 6 * sizeof(uint16)); + _vb0.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB0ElementType)); + _vb1.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB1ElementType)); + _vb2.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB2ElementType)); _buffersDirty = true; // Init draw chunks data @@ -155,7 +188,7 @@ void TextRender::UpdateLayout() // Render all characters from the line for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex; charIndex++) { - const Char c = _text[charIndex]; + const Char c = text[charIndex]; if (c != '\n') { font->GetCharacter(c, entry); @@ -177,8 +210,15 @@ void TextRender::UpdateLayout() // Get texture atlas that contains current character drawChunk.FontAtlasIndex = entry.TextureIndex; fontAtlas = FontManager::GetAtlas(drawChunk.FontAtlasIndex); - fontAtlas->EnsureTextureCreated(); - invAtlasSize = 1.0f / fontAtlas->GetSize(); + if (fontAtlas) + { + fontAtlas->EnsureTextureCreated(); + invAtlasSize = 1.0f / fontAtlas->GetSize(); + } + else + { + invAtlasSize = 1.0f; + } // Setup material drawChunk.Material = Content::CreateVirtualAsset(); @@ -191,7 +231,7 @@ void TextRender::UpdateLayout() const auto param = drawChunk.Material->Params.Get(FontParamName); if (param && param->GetParameterType() == MaterialParameterType::Texture) { - param->SetValue(fontAtlas); + param->SetValue(Variant(fontAtlas)); param->SetIsOverride(true); } } @@ -281,6 +321,11 @@ void TextRender::UpdateLayout() #endif // Update text bounds (from build vertex positions) + if (_ib.Data.IsEmpty()) + { + // Empty + box = BoundingBox(_transform.Translation); + } _localBox = box; BoundingBox::Transform(_localBox, _world, _box); BoundingSphere::FromBox(_box, _sphere); @@ -293,7 +338,10 @@ bool TextRender::HasContentLoaded() const void TextRender::Draw(RenderContext& renderContext) { - UpdateLayout(); + if (_isDirty) + { + UpdateLayout(); + } GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); @@ -437,11 +485,19 @@ void TextRender::OnEnable() // Base Actor::OnEnable(); - UpdateLayout(); + if (_isDirty) + { + UpdateLayout(); + } } void TextRender::OnDisable() { + if (_isLocalized) + { + _isLocalized = false; + Localization::LocalizationChanged.Unbind(this); + } GetSceneRendering()->RemoveGeometry(this); // Base diff --git a/Source/Engine/UI/TextRender.h b/Source/Engine/UI/TextRender.h index 07bfcfbe0..fff3915a5 100644 --- a/Source/Engine/UI/TextRender.h +++ b/Source/Engine/UI/TextRender.h @@ -11,6 +11,7 @@ #include "Engine/Content/Assets/MaterialInstance.h" #include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Graphics/Models/Config.h" +#include "Engine/Localization/LocalizedString.h" #if USE_PRECISE_MESH_INTERSECTS #include "Engine/Graphics/Models/CollisionProxy.h" #endif @@ -34,7 +35,8 @@ private: bool _isDirty = false; bool _buffersDirty = false; - String _text; + bool _isLocalized = false; + LocalizedString _text; Color _color; TextLayoutOptions _layoutOptions; int32 _size; @@ -56,25 +58,19 @@ public: /// /// Gets the text. /// - API_PROPERTY(Attributes="EditorOrder(0), DefaultValue(\"\"), MultilineText, EditorDisplay(\"Text\")") - FORCE_INLINE const String& GetText() const - { - return _text; - } + API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Text\")") + const LocalizedString& GetText() const; /// /// Sets the text. /// - API_PROPERTY() void SetText(const StringView& value); + API_PROPERTY() void SetText(const LocalizedString& value); /// /// Gets the color of the text. /// API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Color), \"1,1,1,1\"), EditorDisplay(\"Text\")") - FORCE_INLINE Color GetColor() const - { - return _color; - } + Color GetColor() const; /// /// Sets the color of the text. @@ -97,10 +93,7 @@ public: /// Gets the font characters size. /// API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(32), Limit(1, 1000), EditorDisplay(\"Text\")") - FORCE_INLINE int32 GetFontSize() const - { - return _size; - } + int32 GetFontSize() const; /// /// Sets the font characters size. @@ -123,7 +116,7 @@ public: /// Gets the layout options. Layout is defined in local space of the object (on XY plane). /// API_PROPERTY(Attributes="EditorOrder(100), EditorDisplay(\"Text\")") - FORCE_INLINE TextLayoutOptions GetLayoutOptions() const + FORCE_INLINE const TextLayoutOptions& GetLayoutOptions() const { return _layoutOptions; } diff --git a/Source/Engine/UI/UI.Build.cs b/Source/Engine/UI/UI.Build.cs index 86c6f23c8..b72b7ca01 100644 --- a/Source/Engine/UI/UI.Build.cs +++ b/Source/Engine/UI/UI.Build.cs @@ -1,10 +1,18 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using Flax.Build; +using Flax.Build.NativeCpp; /// /// User Interface module. /// public class UI : EngineModule { + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.PublicDependencies.Add("Localization"); + } } diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index ccd0561ad..3d6310ee4 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -14,7 +14,11 @@ MMethod* UICanvas_Deserialize = nullptr; MMethod* UICanvas_PostDeserialize = nullptr; MMethod* UICanvas_OnEnable = nullptr; MMethod* UICanvas_OnDisable = nullptr; +#if USE_EDITOR +MMethod* UICanvas_OnActiveInTreeChanged = nullptr; +#endif MMethod* UICanvas_EndPlay = nullptr; +MMethod* UICanvas_ParentChanged = nullptr; #define UICANVAS_INVOKE(event) \ auto instance = GetManagedInstance(); \ @@ -42,7 +46,11 @@ UICanvas::UICanvas(const SpawnParams& params) UICanvas_PostDeserialize = mclass->GetMethod("PostDeserialize"); UICanvas_OnEnable = mclass->GetMethod("OnEnable"); UICanvas_OnDisable = mclass->GetMethod("OnDisable"); +#if USE_EDITOR + UICanvas_OnActiveInTreeChanged = mclass->GetMethod("OnActiveInTreeChanged"); +#endif UICanvas_EndPlay = mclass->GetMethod("EndPlay"); + UICanvas_ParentChanged = mclass->GetMethod("ParentChanged"); } } @@ -133,6 +141,14 @@ void UICanvas::EndPlay() Actor::EndPlay(); } +void UICanvas::OnParentChanged() +{ + // Base + Actor::OnParentChanged(); + + UICANVAS_INVOKE(ParentChanged); +} + void UICanvas::OnEnable() { UICANVAS_INVOKE(OnEnable); @@ -154,6 +170,18 @@ void UICanvas::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } + +#if USE_EDITOR + +void UICanvas::OnActiveInTreeChanged() +{ + UICANVAS_INVOKE(OnActiveInTreeChanged); + + // Base + Actor::OnActiveInTreeChanged(); +} + +#endif diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index 99bc35672..fdb413837 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -31,6 +31,12 @@ namespace FlaxEngine /// [Tooltip("The world space rendering mode that places Canvas as any other object in the scene. The size of the Canvas can be set manually using its Transform, and UI elements will render in front of or behind other objects in the scene based on 3D placement. This is useful for UIs that are meant to be a part of the world. This is also known as a 'diegetic interface'.")] WorldSpace = 2, + + /// + /// The world space rendering mode that places Canvas as any other object in the scene and orients it to face the camera. The size of the Canvas can be set manually using its Transform, and UI elements will render in front of or behind other objects in the scene based on 3D placement. This is useful for UIs that are meant to be a part of the world. This is also known as a 'diegetic interface'. + /// + [Tooltip("The world space rendering mode that places Canvas as any other object in the scene and orients canvas to face the camera. The size of the Canvas can be set manually using its Transform, and UI elements will render in front of or behind other objects in the scene based on 3D placement. This is useful for UIs that are meant to be a part of the world. This is also known as a 'diegetic interface'.")] + WorldSpaceFaceCamera = 3, } /// @@ -57,7 +63,8 @@ namespace FlaxEngine /// public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output) { - // TODO: apply frustum culling to skip rendering if canvas is not in a viewport + if (renderContext.View.Frustum.Contains(Canvas.Bounds.GetBoundingBox()) == ContainmentType.Disjoint) + return; Profiler.BeginEventGPU("UI Canvas"); @@ -81,7 +88,7 @@ namespace FlaxEngine private CanvasRenderMode _renderMode; private readonly CanvasRootControl _guiRoot; private CanvasRenderer _renderer; - private bool _isLoading; + private bool _isLoading, _isRegisteredForTick; /// /// Gets or sets the canvas rendering mode. @@ -101,16 +108,16 @@ namespace FlaxEngine Setup(); // Reset size - if (previous == CanvasRenderMode.ScreenSpace && _renderMode == CanvasRenderMode.WorldSpace) + if (previous == CanvasRenderMode.ScreenSpace || (_renderMode == CanvasRenderMode.WorldSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera)) Size = new Vector2(500, 500); } } } /// - /// Gets or sets the canvas rendering location within rendering pipeline. Used only in or . + /// Gets or sets the canvas rendering location within rendering pipeline. Used only in or or . /// - [EditorOrder(13), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_Is3D)), Tooltip("Canvas rendering location within the rendering pipeline. Change this if you want GUI to affect the lighting or post processing effects like bloom.")] + [EditorOrder(13), EditorDisplay("Canvas"), VisibleIf("Editor_Is3D"), Tooltip("Canvas rendering location within the rendering pipeline. Change this if you want GUI to affect the lighting or post processing effects like bloom.")] public PostProcessEffectLocation RenderLocation { get; set; } = PostProcessEffectLocation.Default; private int _order; @@ -138,22 +145,26 @@ namespace FlaxEngine [EditorOrder(15), EditorDisplay("Canvas"), Tooltip("If checked, canvas can receive the input events.")] public bool ReceivesEvents { get; set; } = true; +#if FLAX_EDITOR private bool Editor_Is3D => _renderMode != CanvasRenderMode.ScreenSpace; - private bool Editor_IsWorldSpace => _renderMode == CanvasRenderMode.WorldSpace; + private bool Editor_IsWorldSpace => _renderMode == CanvasRenderMode.WorldSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera; private bool Editor_IsCameraSpace => _renderMode == CanvasRenderMode.CameraSpace; + private bool Editor_UseRenderCamera => _renderMode == CanvasRenderMode.CameraSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera; +#endif + /// - /// Gets or sets the size of the canvas. Used only in or . + /// Gets or sets the size of the canvas. Used only in or . /// - [EditorOrder(20), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_IsWorldSpace)), Tooltip("Canvas size.")] + [EditorOrder(20), EditorDisplay("Canvas"), VisibleIf("Editor_IsWorldSpace"), Tooltip("Canvas size.")] public Vector2 Size { get => _guiRoot.Size; set { - if (_renderMode == CanvasRenderMode.WorldSpace || _isLoading) + if (_renderMode == CanvasRenderMode.WorldSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera || _isLoading) { _guiRoot.Size = value; } @@ -163,19 +174,19 @@ namespace FlaxEngine /// /// Gets or sets a value indicating whether ignore scene depth when rendering the GUI (scene objects won't cover the interface). /// - [EditorOrder(30), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_Is3D)), Tooltip("If checked, scene depth will be ignored when rendering the GUI (scene objects won't cover the interface).")] + [EditorOrder(30), EditorDisplay("Canvas"), VisibleIf("Editor_Is3D"), Tooltip("If checked, scene depth will be ignored when rendering the GUI (scene objects won't cover the interface).")] public bool IgnoreDepth { get; set; } = false; /// - /// Gets or sets the camera used to place the GUI when render mode is set to . + /// Gets or sets the camera used to place the GUI when render mode is set to or . /// - [EditorOrder(50), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_IsCameraSpace)), Tooltip("Camera used to place the GUI when RenderMode is set to CameraSpace")] + [EditorOrder(50), EditorDisplay("Canvas"), VisibleIf("Editor_UseRenderCamera"), Tooltip("Camera used to place the GUI when RenderMode is set to CameraSpace or WorldSpaceFaceCamera.")] public Camera RenderCamera { get; set; } /// /// Gets or sets the distance from the to place the plane with GUI. If the screen is resized, changes resolution, or the camera frustum changes, the Canvas will automatically change size to match as well. /// - [EditorOrder(60), Limit(0.01f), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_IsCameraSpace)), Tooltip("Distance from the RenderCamera to place the plane with GUI. If the screen is resized, changes resolution, or the camera frustum changes, the Canvas will automatically change size to match as well.")] + [EditorOrder(60), Limit(0.01f), EditorDisplay("Canvas"), VisibleIf("Editor_IsCameraSpace"), Tooltip("Distance from the RenderCamera to place the plane with GUI. If the screen is resized, changes resolution, or the camera frustum changes, the Canvas will automatically change size to match as well.")] public float Distance { get; set; } = 500; /// @@ -218,7 +229,7 @@ namespace FlaxEngine var camera = Camera.MainCamera; if (camera) { - ray = camera.ConvertMouseToRay(location); + ray = camera.ConvertMouseToRay(location * Platform.DpiScale); } else { @@ -237,6 +248,18 @@ namespace FlaxEngine }; } + /// + /// Finalizes an instance of the class. + /// + ~UICanvas() + { + if (_isRegisteredForTick) + { + _isRegisteredForTick = false; + Scripting.Update -= OnUpdate; + } + } + /// /// Gets the world-space oriented bounding box that contains a 3D canvas. /// @@ -271,24 +294,29 @@ namespace FlaxEngine /// The world. public void GetWorldMatrix(out Matrix world) { - if (_renderMode == CanvasRenderMode.WorldSpace) - { - // In 3D world - GetLocalToWorldMatrix(out world); - } - else if (_renderMode == CanvasRenderMode.CameraSpace) - { - Matrix tmp1, tmp2; - Vector3 viewPos, viewUp, viewForward, pos; - Quaternion viewRot; - - // Use default camera is not specified - var camera = RenderCamera ?? Camera.MainCamera; - #if FLAX_EDITOR - if (_editorTask) + // Override projection for editor preview + if (_editorTask) + { + if (_renderMode == CanvasRenderMode.WorldSpace) + { + GetLocalToWorldMatrix(out world); + } + else if (_renderMode == CanvasRenderMode.WorldSpaceFaceCamera) + { + var view = _editorTask.View; + var transform = Transform; + Matrix.Translation(_guiRoot.Width * -0.5f, _guiRoot.Height * -0.5f, 0, out var m1); + Matrix.Scaling(ref transform.Scale, out var m2); + Matrix.Multiply(ref m1, ref m2, out var m3); + Quaternion.Euler(180, 180, 0, out var quat); + Matrix.RotationQuaternion(ref quat, out m2); + Matrix.Multiply(ref m3, ref m2, out m1); + m2 = Matrix.Transformation(Vector3.One, Quaternion.FromDirection(-view.Direction), transform.Translation); + Matrix.Multiply(ref m1, ref m2, out world); + } + else if (_renderMode == CanvasRenderMode.CameraSpace) { - // Use editor viewport task to override Camera Space placement var view = _editorTask.View; var frustum = view.Frustum; if (!frustum.IsOrthographic) @@ -296,19 +324,50 @@ namespace FlaxEngine else _guiRoot.Size = _editorTask.Viewport.Size; Matrix.Translation(_guiRoot.Width / -2.0f, _guiRoot.Height / -2.0f, 0, out world); - Matrix.RotationYawPitchRoll(Mathf.Pi, Mathf.Pi, 0, out tmp2); - Matrix.Multiply(ref world, ref tmp2, out tmp1); - viewPos = view.Position; - viewRot = view.Direction != Vector3.Up ? Quaternion.LookRotation(view.Direction, Vector3.Up) : Quaternion.LookRotation(view.Direction, Vector3.Right); - viewUp = Vector3.Up * viewRot; - viewForward = view.Direction; - pos = view.Position + view.Direction * Distance; + Matrix.RotationYawPitchRoll(Mathf.Pi, Mathf.Pi, 0, out var tmp2); + Matrix.Multiply(ref world, ref tmp2, out var tmp1); + var viewPos = view.Position; + var viewRot = view.Direction != Vector3.Up ? Quaternion.LookRotation(view.Direction, Vector3.Up) : Quaternion.LookRotation(view.Direction, Vector3.Right); + var viewUp = Vector3.Up * viewRot; + var viewForward = view.Direction; + var pos = view.Position + view.Direction * Distance; Matrix.Billboard(ref pos, ref viewPos, ref viewUp, ref viewForward, out tmp2); Matrix.Multiply(ref tmp1, ref tmp2, out world); return; } + else + { + world = Matrix.Identity; + } + return; + } #endif + // Use default camera is not specified + var camera = RenderCamera ?? Camera.MainCamera; + + if (_renderMode == CanvasRenderMode.WorldSpace || (_renderMode == CanvasRenderMode.WorldSpaceFaceCamera && !camera)) + { + // In 3D world + GetLocalToWorldMatrix(out world); + } + else if (_renderMode == CanvasRenderMode.WorldSpaceFaceCamera) + { + // In 3D world face camera + var transform = Transform; + Matrix.Translation(_guiRoot.Width * -0.5f, _guiRoot.Height * -0.5f, 0, out var m1); + Matrix.Scaling(ref transform.Scale, out var m2); + Matrix.Multiply(ref m1, ref m2, out var m3); + Quaternion.Euler(180, 180, 0, out var quat); + Matrix.RotationQuaternion(ref quat, out m2); + Matrix.Multiply(ref m3, ref m2, out m1); + m2 = Matrix.Transformation(Vector3.One, Quaternion.FromDirection(-camera.Direction), transform.Translation); + Matrix.Multiply(ref m1, ref m2, out world); + } + else if (_renderMode == CanvasRenderMode.CameraSpace && camera) + { + Matrix tmp1, tmp2; + // Adjust GUI size to the viewport size at the given distance form the camera var viewport = camera.Viewport; if (camera.UsePerspective) @@ -329,11 +388,11 @@ namespace FlaxEngine Matrix.Multiply(ref world, ref tmp2, out tmp1); // In front of the camera - viewPos = camera.Position; - viewRot = camera.Orientation; - viewUp = Vector3.Up * viewRot; - viewForward = Vector3.Forward * viewRot; - pos = viewPos + viewForward * Distance; + var viewPos = camera.Position; + var viewRot = camera.Orientation; + var viewUp = Vector3.Up * viewRot; + var viewForward = Vector3.Forward * viewRot; + var pos = viewPos + viewForward * Distance; Matrix.Billboard(ref pos, ref viewPos, ref viewUp, ref viewForward, out tmp2); Matrix.Multiply(ref tmp1, ref tmp2, out world); @@ -368,13 +427,22 @@ namespace FlaxEngine _renderer = null; } #if FLAX_EDITOR - if (_editorRoot != null) + if (_editorRoot != null && IsActiveInHierarchy) + { _guiRoot.Parent = _editorRoot; + _guiRoot.IndexInParent = 0; + } #endif + if (_isRegisteredForTick) + { + _isRegisteredForTick = false; + Scripting.Update -= OnUpdate; + } break; } case CanvasRenderMode.CameraSpace: case CanvasRenderMode.WorldSpace: + case CanvasRenderMode.WorldSpaceFaceCamera: { // Render canvas manually _guiRoot.AnchorPreset = AnchorPresets.TopLeft; @@ -404,11 +472,32 @@ namespace FlaxEngine } #endif } + if (!_isRegisteredForTick) + { + _isRegisteredForTick = true; + Scripting.Update += OnUpdate; + } break; } } } + private void OnUpdate() + { + if (this && IsActiveInHierarchy && _renderMode != CanvasRenderMode.ScreenSpace) + { + try + { + Profiler.BeginEvent(Name); + _guiRoot.Update(Time.UnscaledDeltaTime); + } + finally + { + Profiler.EndEvent(); + } + } + } + internal string Serialize() { StringBuilder sb = new StringBuilder(256); @@ -442,13 +531,16 @@ namespace FlaxEngine jsonWriter.WritePropertyName("Distance"); jsonWriter.WriteValue(Distance); - jsonWriter.WritePropertyName("Size"); - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("X"); - jsonWriter.WriteValue(Size.X); - jsonWriter.WritePropertyName("Y"); - jsonWriter.WriteValue(Size.Y); - jsonWriter.WriteEndObject(); + if (RenderMode == CanvasRenderMode.WorldSpace || RenderMode == CanvasRenderMode.WorldSpaceFaceCamera) + { + jsonWriter.WritePropertyName("Size"); + jsonWriter.WriteStartObject(); + jsonWriter.WritePropertyName("X"); + jsonWriter.WriteValue(Size.X); + jsonWriter.WritePropertyName("Y"); + jsonWriter.WriteValue(Size.Y); + jsonWriter.WriteEndObject(); + } jsonWriter.WriteEndObject(); } @@ -510,7 +602,10 @@ namespace FlaxEngine jsonWriter.WriteValue(Distance); } - if (Size != other.Size) + if ((RenderMode == CanvasRenderMode.WorldSpace || + RenderMode == CanvasRenderMode.WorldSpaceFaceCamera || + other.RenderMode == CanvasRenderMode.WorldSpace || + other.RenderMode == CanvasRenderMode.WorldSpaceFaceCamera) && Size != other.Size) { jsonWriter.WritePropertyName("Size"); jsonWriter.WriteStartObject(); @@ -539,10 +634,29 @@ namespace FlaxEngine Setup(); } + internal void ParentChanged() + { +#if FLAX_EDITOR + if (RenderMode == CanvasRenderMode.ScreenSpace && _editorRoot != null && _guiRoot != null) + { + _guiRoot.Parent = HasParent && IsActiveInHierarchy ? _editorRoot : null; + _guiRoot.IndexInParent = 0; + } +#endif + } + internal void OnEnable() { #if FLAX_EDITOR - _guiRoot.Parent = _editorRoot ?? RootControl.CanvasRoot; + if (_editorRoot != null) + { + _guiRoot.Parent = _editorRoot; + _guiRoot.IndexInParent = 0; + } + else + { + _guiRoot.Parent = RootControl.CanvasRoot; + } #else _guiRoot.Parent = RootControl.CanvasRoot; #endif @@ -570,8 +684,25 @@ namespace FlaxEngine } } +#if FLAX_EDITOR + internal void OnActiveInTreeChanged() + { + if (RenderMode == CanvasRenderMode.ScreenSpace && _editorRoot != null && _guiRoot != null) + { + _guiRoot.Parent = HasParent && IsActiveInHierarchy ? _editorRoot : null; + _guiRoot.IndexInParent = 0; + } + } +#endif + internal void EndPlay() { + if (_isRegisteredForTick) + { + _isRegisteredForTick = false; + Scripting.Update -= OnUpdate; + } + if (_renderer) { SceneRenderTask.GlobalCustomPostFx.Remove(_renderer); @@ -587,6 +718,8 @@ namespace FlaxEngine internal void EditorOverride(SceneRenderTask task, ContainerControl root) { + if (_editorTask == task && _editorRoot == root) + return; if (_editorTask != null && _renderer != null) _editorTask.CustomPostFx.Remove(_renderer); if (_editorRoot != null && _guiRoot != null) @@ -596,8 +729,11 @@ namespace FlaxEngine _editorRoot = root; Setup(); - if (RenderMode == CanvasRenderMode.ScreenSpace && _editorRoot != null && _guiRoot != null) + if (RenderMode == CanvasRenderMode.ScreenSpace && _editorRoot != null && _guiRoot != null && IsActiveInHierarchy) + { _guiRoot.Parent = _editorRoot; + _guiRoot.IndexInParent = 0; + } } #endif } diff --git a/Source/Engine/UI/UICanvas.h b/Source/Engine/UI/UICanvas.h index 8e8b9b0d1..4980f7d28 100644 --- a/Source/Engine/UI/UICanvas.h +++ b/Source/Engine/UI/UICanvas.h @@ -24,7 +24,11 @@ protected: // [Actor] void BeginPlay(SceneBeginData* data) final override; void EndPlay() final override; + void OnParentChanged() override; void OnEnable() override; void OnDisable() override; void OnTransformChanged() final override; +#if USE_EDITOR + void OnActiveInTreeChanged() override; +#endif }; diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index 51312ae15..b9fef02f8 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -155,9 +155,6 @@ void UIControl::OnParentChanged() // Base Actor::OnParentChanged(); - if (!IsDuringPlay()) - return; - UICONTROL_INVOKE(ParentChanged); } @@ -166,7 +163,7 @@ void UIControl::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); UICONTROL_INVOKE(TransformChanged); @@ -198,8 +195,8 @@ void UIControl::OnOrderInParentChanged() void UIControl::OnActiveInTreeChanged() { + UICONTROL_INVOKE(ActiveInTreeChanged); + // Base Actor::OnActiveInTreeChanged(); - - UICONTROL_INVOKE(ActiveInTreeChanged); } diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index 8fd4f8f72..d981c1b6c 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -52,7 +52,6 @@ namespace FlaxEngine _control.Parent = GetParent(); _control.IndexInParent = OrderInParent; _control.Location = new Vector2(LocalPosition); - // TODO: sync control order in parent with actor order in parent (think about special cases like Panel with scroll bars used as internal controls) _control.LocationChanged += OnControlLocationChanged; // Link children UI controls @@ -199,6 +198,11 @@ namespace FlaxEngine // Don't link disabled actors if (!IsActiveInHierarchy) return null; +#if FLAX_EDITOR + // Prefab editor doesn't fire BeginPlay so for disabled actors we don't unlink them so do it here + if (!IsActive) + return null; +#endif var parent = Parent; if (parent is UIControl uiControl && uiControl.Control is ContainerControl uiContainerControl) @@ -296,6 +300,9 @@ namespace FlaxEngine if (_control != null) { Json.JsonSerializer.Deserialize(_control, json); + + // Synchronize actor with control location + OnControlLocationChanged(_control); } } @@ -320,6 +327,12 @@ namespace FlaxEngine { if (_control != null && !_blockEvents) { + // Skip if this control is inactive and it's parent too (parent will unlink from hierarchy but children will stay connected while being inactive) + if (!IsActiveInHierarchy && Parent && !Parent.IsActive) + { + return; + } + // Link or unlink control (won't modify Enable/Visible state) _control.Parent = GetParent(); _control.IndexInParent = OrderInParent; @@ -339,6 +352,7 @@ namespace FlaxEngine if (_control != null) { _control.Parent = GetParent(); + _control.IndexInParent = OrderInParent; } } diff --git a/Source/Engine/Utilities/Extensions.cs b/Source/Engine/Utilities/Extensions.cs index b3f6d2053..0fc46efe7 100644 --- a/Source/Engine/Utilities/Extensions.cs +++ b/Source/Engine/Utilities/Extensions.cs @@ -44,6 +44,21 @@ namespace FlaxEngine.Utilities } } + /// + /// Checks if the text is multiline. + /// + /// Text to check. + /// True if text is a multiline, otherwise false. + public static bool IsMultiline(this string str) + { + for (int i = 0; i < str.Length; i++) + { + if (str[i] == '\n') + return true; + } + return false; + } + /// /// Splits string into lines /// @@ -211,6 +226,17 @@ namespace FlaxEngine.Utilities /// A thats either true or false. public static bool NextBool(this Random random, float weight = 0.5f) => random.NextDouble() < weight; + /// + /// Generates a random value up until an exclusive maximum. + /// + /// An instance of . + /// The maximum value. If it's zero, a maximum of 256 is used + /// A random between min and max. + public static byte NextByte(this Random random, byte max = 0) + { + return max == 0 ? (byte)(random.Next() % 256) : (byte)random.Next(max); + } + /// /// Generates a random value between min and max. /// @@ -218,9 +244,9 @@ namespace FlaxEngine.Utilities /// The minimum value. /// The maximum value. /// A random between min and max. - public static byte NextByte(this Random random, byte min = 0, byte max = byte.MaxValue) + public static byte NextByte(this Random random, byte min, byte max) { - return (byte)random.Next(min, max == byte.MaxValue ? byte.MaxValue + 1 : max); + return (byte)random.Next(min, max); } /// diff --git a/Source/Engine/Utilities/Screenshot.cpp b/Source/Engine/Utilities/Screenshot.cpp index bbf0f52d8..133824e3e 100644 --- a/Source/Engine/Utilities/Screenshot.cpp +++ b/Source/Engine/Utilities/Screenshot.cpp @@ -11,6 +11,7 @@ #include "Engine/Graphics/GPUSwapChain.h" #include "Engine/Engine/Engine.h" #include "Engine/Threading/ThreadPoolTask.h" +#include "Engine/Engine/Globals.h" #if COMPILE_WITH_TEXTURE_TOOL #include "Engine/Tools/TextureTool/TextureTool.h" #endif diff --git a/Source/Engine/Utilities/StringUtils.cs b/Source/Engine/Utilities/StringUtils.cs index cb04afbfc..ff6beb4ec 100644 --- a/Source/Engine/Utilities/StringUtils.cs +++ b/Source/Engine/Utilities/StringUtils.cs @@ -232,20 +232,30 @@ namespace FlaxEngine return result; } + private static IEnumerable GraphemeClusters(this string s) + { + var enumerator = System.Globalization.StringInfo.GetTextElementEnumerator(s); + while (enumerator.MoveNext()) + { + yield return (string)enumerator.Current; + } + } + /// /// Reverses the specified input string. /// + /// Correctly handles all UTF-16 strings /// The string to reverse. /// The reversed string. public static string Reverse(this string s) { - char[] charArray = s.ToCharArray(); - Array.Reverse(charArray); - return new string(charArray); + string[] graphemes = s.GraphemeClusters().ToArray(); + Array.Reverse(graphemes); + return string.Concat(graphemes); } - private static readonly Regex IncNameRegex1 = new Regex("^(\\d+)"); - private static readonly Regex IncNameRegex2 = new Regex("^\\)(\\d+)\\("); + private static readonly Regex IncNameRegex1 = new Regex("(\\d+)$"); + private static readonly Regex IncNameRegex2 = new Regex("\\((\\d+)\\)$"); /// /// Tries to parse number in the name brackets at the end of the value and then increment it to create a new name. @@ -264,14 +274,13 @@ namespace FlaxEngine int index; int MaxChecks = 10000; string result; - string reversed = name.Reverse(); // Find '' case - var match = IncNameRegex1.Match(reversed); + var match = IncNameRegex1.Match(name); if (match.Success && match.Groups.Count == 2) { // Get result - string num = match.Groups[0].Value.Reverse(); + string num = match.Groups[0].Value; // Parse value if (int.TryParse(num, out index)) @@ -294,12 +303,12 @@ namespace FlaxEngine } // Find ' ()' case - match = IncNameRegex2.Match(reversed); + match = IncNameRegex2.Match(name); if (match.Success && match.Groups.Count == 2) { // Get result string num = match.Groups[0].Value; - num = num.Substring(1, num.Length - 2).Reverse(); + num = num.Substring(1, num.Length - 2); // Parse value if (int.TryParse(num, out index)) diff --git a/Source/Engine/Utilities/TextWriter.h b/Source/Engine/Utilities/TextWriter.h index 54f2e996a..4885bfce2 100644 --- a/Source/Engine/Utilities/TextWriter.h +++ b/Source/Engine/Utilities/TextWriter.h @@ -92,7 +92,7 @@ public: template void WriteLine(const CharType* format, const Args& ... args) { - fmt::basic_memory_buffer w; + fmt::basic_memory_buffer> w; format_to(w, format, args...); const int32 len = (int32)w.size(); _buffer.WriteBytes((void*)w.data(), len * sizeof(CharType)); @@ -132,7 +132,7 @@ public: template void Write(const CharType* format, const Args& ... args) { - fmt::basic_memory_buffer w; + fmt::basic_memory_buffer> w; format_to(w, format, args...); const int32 len = (int32)w.size(); _buffer.WriteBytes((void*)w.data(), len * sizeof(CharType)); diff --git a/Source/Engine/Utilities/Utils.cs b/Source/Engine/Utilities/Utils.cs index a72b4e676..ab4fdf17b 100644 --- a/Source/Engine/Utilities/Utils.cs +++ b/Source/Engine/Utilities/Utils.cs @@ -240,6 +240,36 @@ namespace FlaxEngine return new Vector4(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()); } + /// + /// Reads the Int2 from the binary stream. + /// + /// The stream. + /// The value. + public static Int2 ReadInt2(this BinaryReader stream) + { + return new Int2(stream.ReadInt32(), stream.ReadInt32()); + } + + /// + /// Reads the Int3 from the binary stream. + /// + /// The stream. + /// The value. + public static Int3 ReadInt3(this BinaryReader stream) + { + return new Int3(stream.ReadInt32(), stream.ReadInt32(), stream.ReadInt32()); + } + + /// + /// Reads the Int4 from the binary stream. + /// + /// The stream. + /// The value. + public static Int4 ReadInt4(this BinaryReader stream) + { + return new Int4(stream.ReadInt32(), stream.ReadInt32(), stream.ReadInt32(), stream.ReadInt32()); + } + /// /// Reads the Quaternion from the binary stream. /// @@ -362,6 +392,42 @@ namespace FlaxEngine stream.Write(value.W); } + /// + /// Writes the Int2 to the binary stream. + /// + /// The stream. + /// The value to write. + public static void Write(this BinaryWriter stream, Int2 value) + { + stream.Write(value.X); + stream.Write(value.Y); + } + + /// + /// Writes the Int3 to the binary stream. + /// + /// The stream. + /// The value to write. + public static void Write(this BinaryWriter stream, Int3 value) + { + stream.Write(value.X); + stream.Write(value.Y); + stream.Write(value.Z); + } + + /// + /// Writes the Int4 to the binary stream. + /// + /// The stream. + /// The value to write. + public static void Write(this BinaryWriter stream, Int4 value) + { + stream.Write(value.X); + stream.Write(value.Y); + stream.Write(value.Z); + stream.Write(value.W); + } + /// /// Writes the Quaternion to the binary stream. /// diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index b3f221617..754c1aac9 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -1118,7 +1118,8 @@ ShaderGenerator::Value ShaderGenerator::writeLocal(ValueType type, const String& ShaderGenerator::Value ShaderGenerator::writeOperation2(Node* caller, const Value& valueA, const Value& valueB, Char op1) { - const String value = String::Format(TEXT("{0} {1} {2}"), valueA.Value, op1, Value::Cast(valueB, valueA.Type).Value); + const Char op1Str[2] = { op1, 0}; + const String value = String::Format(TEXT("{0} {1} {2}"), valueA.Value, op1Str, Value::Cast(valueB, valueA.Type).Value); return writeLocal(valueA.Type, value, caller); } diff --git a/Source/Engine/Visject/ShaderGraph.h b/Source/Engine/Visject/ShaderGraph.h index fe24602cc..1bcfd747a 100644 --- a/Source/Engine/Visject/ShaderGraph.h +++ b/Source/Engine/Visject/ShaderGraph.h @@ -8,6 +8,7 @@ #include "ShaderGraphValue.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/HashSet.h" +#include "Engine/Core/Math/Vector4.h" #include "Engine/Utilities/TextWriter.h" #include "Engine/Graphics/Materials/MaterialParams.h" #include "Engine/Content/AssetsContainer.h" diff --git a/Source/Engine/Visject/ShaderGraphUtilities.cpp b/Source/Engine/Visject/ShaderGraphUtilities.cpp index 079b6e602..c698164d2 100644 --- a/Source/Engine/Visject/ShaderGraphUtilities.cpp +++ b/Source/Engine/Visject/ShaderGraphUtilities.cpp @@ -5,8 +5,10 @@ #include "ShaderGraphUtilities.h" #include "ShaderGraphValue.h" #include "Engine/Core/Types/StringBuilder.h" +#include "Engine/Core/Math/Vector4.h" #include "Engine/Content/Content.h" #include "Engine/Engine/GameplayGlobals.h" +#include "Engine/Graphics/Config.h" void ShaderGraphUtilities::GenerateShaderConstantBuffer(TextWriterUnicode& writer, Array& parameters) { diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index 3a9feca06..62d40ad9a 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857c262d00f6")] -[assembly: AssemblyVersion("1.0.6216")] -[assembly: AssemblyFileVersion("1.0.6216")] +[assembly: AssemblyVersion("1.1.6218")] +[assembly: AssemblyFileVersion("1.1.6218")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index 94eb7c467..eff8449cc 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 0, 6216) -#define FLAXENGINE_VERSION_TEXT "1.0.6216" +#define FLAXENGINE_VERSION Version(1, 1, 6218) +#define FLAXENGINE_VERSION_TEXT "1.1.6218" #define FLAXENGINE_VERSION_MAJOR 1 -#define FLAXENGINE_VERSION_MINOR 0 -#define FLAXENGINE_VERSION_BUILD 6216 +#define FLAXENGINE_VERSION_MINOR 1 +#define FLAXENGINE_VERSION_BUILD 6218 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved." diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.dll b/Source/Platforms/DotNet/Newtonsoft.Json.dll index 17df31d86..05dcf0c44 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.dll +++ b/Source/Platforms/DotNet/Newtonsoft.Json.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:616780417730a6371909713a2d8150347870e67ed52803642caef37e8bda1891 -size 630272 +oid sha256:eed043b9e8a7982f4a60ab332e833bd8834d6d43814773d30fda48a22eda1d8e +size 641536 diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.pdb b/Source/Platforms/DotNet/Newtonsoft.Json.pdb index 68e8489b6..3848081ec 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.pdb +++ b/Source/Platforms/DotNet/Newtonsoft.Json.pdb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d4fc76d2ef9b0cd7b88da95f4894d9cc924703af0b02e052f8e78446f75e2d9 -size 239080 +oid sha256:882eecaf201c6d6e7c724e24259df9d23a6c7d8be1b9d407ff646fa24da680ca +size 253816 diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.xml b/Source/Platforms/DotNet/Newtonsoft.Json.xml index fb9404249..316a82975 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.xml +++ b/Source/Platforms/DotNet/Newtonsoft.Json.xml @@ -1691,6 +1691,15 @@ The value. The calling serializer. + + + Writes the JSON representation of the object diff compared to other instance of the object (the same type). + + The to write to. + The value. + The other value (the same type). + The calling serializer. + Reads the JSON representation of the object. @@ -1722,6 +1731,12 @@ true if this can write JSON; otherwise, false. + + + Gets a value indicating whether this can write JSON for object difference. + + true if this can write JSON diff; otherwise, false. + Converts an object to and from JSON. @@ -1850,6 +1865,15 @@ The error message that explains the reason for the exception. The exception that is the cause of the current exception, or null if no inner exception is specified. + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + Instructs the to deserialize properties with no matching class member into the specified collection @@ -2456,6 +2480,15 @@ The error message that explains the reason for the exception. The exception that is the cause of the current exception, or null if no inner exception is specified. + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + Initializes a new instance of the class @@ -2497,6 +2530,15 @@ The error message that explains the reason for the exception. The exception that is the cause of the current exception, or null if no inner exception is specified. + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + Serializes and deserializes objects into and from the JSON format. @@ -2538,7 +2580,7 @@ Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . + The default value is . The type name assembly format. @@ -2605,6 +2647,12 @@ serializing .NET objects to JSON and vice versa. + + + Gets or sets the used by the serializer when invoking serialization callback methods. + + The context. + Indicates how JSON text output is formatted. @@ -2988,6 +3036,12 @@ The error handler called during serialization and deserialization. + + + Gets or sets the used by the serializer when invoking serialization callback methods. + + The context. + Gets or sets how and values are formatted when writing JSON text, @@ -5464,6 +5518,15 @@ The error message that explains the reason for the exception. The exception that is the cause of the current exception, or null if no inner exception is specified. + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + Initializes a new instance of the class @@ -8883,6 +8946,15 @@ The error message that explains the reason for the exception. The exception that is the cause of the current exception, or null if no inner exception is specified. + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + @@ -9173,10 +9245,10 @@ - Gets or sets a value indicating whether to ignore the interface when serializing and deserializing types. + Gets or sets a value indicating whether to ignore the interface when serializing and deserializing types. - true if the interface will be ignored when serializing and deserializing types; otherwise, false. + true if the interface will be ignored when serializing and deserializing types; otherwise, false. @@ -9286,6 +9358,13 @@ Type of the object. A for the given type. + + + Creates a for the given type. + + Type of the object. + A for the given type. + Creates a for the given type. @@ -9692,6 +9771,21 @@ The underlying type for the contract. + + + Handles serialization callback events. + + The object that raised the callback event. + The streaming context. + + + + Handles serialization error callback events. + + The object that raised the callback event. + The streaming context. + The error context. + Sets extension data for an object during deserialization. @@ -9735,6 +9829,36 @@ The converter. + + + Gets or sets all methods called immediately after deserialization of the object. + + The methods called immediately after deserialization of the object. + + + + Gets or sets all methods called during deserialization of the object. + + The methods called during deserialization of the object. + + + + Gets or sets all methods called after serialization of the object graph. + + The methods called after serialization of the object graph. + + + + Gets or sets all methods called before serialization of the object. + + The methods called before serialization of the object. + + + + Gets or sets all method called when an error is thrown during the serialization of the object. + + The methods called when an error is thrown during the serialization of the object. + Gets or sets the default creator method used to create the object. @@ -9811,6 +9935,23 @@ The underlying type for the contract. + + + Contract details for a used by the . + + + + + Gets or sets the object constructor. + + The object constructor. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + Contract details for a used by the . diff --git a/Source/Platforms/Editor/Linux/Mono/lib/libmono-native.so b/Source/Platforms/Editor/Linux/Mono/lib/libmono-native.so deleted file mode 120000 index 35c4facf6..000000000 --- a/Source/Platforms/Editor/Linux/Mono/lib/libmono-native.so +++ /dev/null @@ -1 +0,0 @@ -libmono-native.so.0.0.0 \ No newline at end of file diff --git a/Source/Platforms/Editor/Linux/Mono/lib/libmono-native.so b/Source/Platforms/Editor/Linux/Mono/lib/libmono-native.so new file mode 100644 index 000000000..b2f93e404 --- /dev/null +++ b/Source/Platforms/Editor/Linux/Mono/lib/libmono-native.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c398b22f122c52e8554af5560e9788648941f0f9598df5c430fdda676413db2 +size 181240 diff --git a/Source/Platforms/Linux/Binaries/Mono/lib/libmono-native.so b/Source/Platforms/Linux/Binaries/Mono/lib/libmono-native.so deleted file mode 120000 index 35c4facf6..000000000 --- a/Source/Platforms/Linux/Binaries/Mono/lib/libmono-native.so +++ /dev/null @@ -1 +0,0 @@ -libmono-native.so.0.0.0 \ No newline at end of file diff --git a/Source/Platforms/Linux/Binaries/Mono/lib/libmono-native.so b/Source/Platforms/Linux/Binaries/Mono/lib/libmono-native.so new file mode 100644 index 000000000..b2f93e404 --- /dev/null +++ b/Source/Platforms/Linux/Binaries/Mono/lib/libmono-native.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c398b22f122c52e8554af5560e9788648941f0f9598df5c430fdda676413db2 +size 181240 diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libIrrXML.a b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libIrrXML.a new file mode 100644 index 000000000..92c1baf0b --- /dev/null +++ b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libIrrXML.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4eb25101716011a5b4c872c5cd303c3292a61e5f661e9296d95502b5705e2e53 +size 181458 diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a new file mode 100644 index 000000000..4dccc7f4b --- /dev/null +++ b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cbd73a154e270cc03d3da292b2c3cc73ee473b221f43722d060dc114916d6d5 +size 7996212 diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so deleted file mode 120000 index 9a02130ad..000000000 --- a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so +++ /dev/null @@ -1 +0,0 @@ -libassimp.so.4.1.0 \ No newline at end of file diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so.4 b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so.4 deleted file mode 120000 index 9a02130ad..000000000 --- a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so.4 +++ /dev/null @@ -1 +0,0 @@ -libassimp.so.4.1.0 \ No newline at end of file diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so.4.1.0 b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so.4.1.0 deleted file mode 100755 index 45891b71d..000000000 Binary files a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.so.4.1.0 and /dev/null differ diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libcurl.a b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libcurl.a new file mode 100644 index 000000000..f722e3a16 --- /dev/null +++ b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libcurl.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b7f66f58d5e7dabe0a0ec7e01ae5dd3683c2ae474ac6dbc133393b2be715bbf +size 574086 diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so deleted file mode 120000 index eeb2a3c1c..000000000 --- a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so +++ /dev/null @@ -1 +0,0 @@ -libmonosgen-2.0.so.1.0.0 \ No newline at end of file diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so new file mode 100755 index 000000000..578a2c79f --- /dev/null +++ b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26d47044044a8c49c30003b8a2b73e7f01ea17ea225e25c99548ba93b826eb84 +size 19098552 diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so.1 b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so.1 deleted file mode 120000 index eeb2a3c1c..000000000 --- a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so.1 +++ /dev/null @@ -1 +0,0 @@ -libmonosgen-2.0.so.1.0.0 \ No newline at end of file diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so.1.0.0 b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so.1.0.0 deleted file mode 100755 index f6233b55d..000000000 Binary files a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libmonosgen-2.0.so.1.0.0 and /dev/null differ diff --git a/Source/Platforms/UWP/Binaries/Newtonsoft.Json.dll b/Source/Platforms/UWP/Binaries/Newtonsoft.Json.dll index 4c908a1ce..1885aed9a 100644 --- a/Source/Platforms/UWP/Binaries/Newtonsoft.Json.dll +++ b/Source/Platforms/UWP/Binaries/Newtonsoft.Json.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a753e82f0d128171be8df8693872615974526e41200105c2853d54283014e1d3 -size 624640 +oid sha256:c43903a192d31dac91b7744e2b1a2e8cec21983ceed630261fd7b80a433fee20 +size 635392 diff --git a/Source/Platforms/UWP/Binaries/Newtonsoft.Json.pdb b/Source/Platforms/UWP/Binaries/Newtonsoft.Json.pdb deleted file mode 100644 index ae45100f6..000000000 --- a/Source/Platforms/UWP/Binaries/Newtonsoft.Json.pdb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b999346525f40ccb72ecbc560462d94c8c2cce6fa515722b8f2a53f0fa334b4f -size 236420 diff --git a/Source/Platforms/UWP/Binaries/Newtonsoft.Json.xml b/Source/Platforms/UWP/Binaries/Newtonsoft.Json.xml deleted file mode 100644 index 5e472c8a0..000000000 --- a/Source/Platforms/UWP/Binaries/Newtonsoft.Json.xml +++ /dev/null @@ -1,10588 +0,0 @@ - - - - Newtonsoft.Json - - - - - Represents a BSON Oid (object id). - - - - - Gets or sets the value of the Oid. - - The value of the Oid. - - - - Initializes a new instance of the class. - - The Oid value. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized BSON data. - - - - - Gets or sets a value indicating whether binary data reading should be compatible with incorrect Json.NET 3.5 written binary. - - - true if binary data reading will be compatible with incorrect Json.NET 3.5 written binary; otherwise, false. - - - - - Gets or sets a value indicating whether the root object will be read as a JSON array. - - - true if the root object will be read as a JSON array; otherwise, false. - - - - - Gets or sets the used when reading values from BSON. - - The used when reading values from BSON. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - if set to true the root object will be read as a JSON array. - The used when reading values from BSON. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - if set to true the root object will be read as a JSON array. - The used when reading values from BSON. - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating BSON data. - - - - - Gets or sets the used when writing values to BSON. - When set to no conversion will occur. - - The used when writing values to BSON. - - - - Initializes a new instance of the class. - - The to write to. - - - - Initializes a new instance of the class. - - The to write to. - - - - Flushes whatever is in the buffer to the underlying and also flushes the underlying stream. - - - - - Writes the end. - - The token. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - - - - Writes the beginning of a JSON array. - - - - - Writes the beginning of a JSON object. - - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Closes this writer. - If is set to true, the underlying is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value that represents a BSON object id. - - The Object ID value to write. - - - - Writes a BSON regex. - - The regex pattern. - The regex options. - - - - Specifies how constructors are used when initializing objects during deserialization by the . - - - - - First attempt to use the public default constructor, then fall back to a single parameterized constructor, then to the non-public default constructor. - - - - - Json.NET will use a non-public default constructor before falling back to a parameterized constructor. - - - - - Converts a binary value to and from a base 64 string value. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from JSON and BSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Creates a custom object. - - The object type to convert. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Creates an object which will then be populated by the serializer. - - Type of the object. - The created object. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can write JSON. - - - true if this can write JSON; otherwise, false. - - - - - Provides a base class for converting a to and from JSON. - - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts an to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can write JSON. - - - true if this can write JSON; otherwise, false. - - - - - Converts a to and from the ISO 8601 date format (e.g. "2008-04-12T12:53Z"). - - - - - Gets or sets the date time styles used when converting a date to and from JSON. - - The date time styles used when converting a date to and from JSON. - - - - Gets or sets the date time format used when converting a date to and from JSON. - - The date time format used when converting a date to and from JSON. - - - - Gets or sets the culture used when converting a date to and from JSON. - - The culture used when converting a date to and from JSON. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Converts a to and from a JavaScript Date constructor (e.g. new Date(52231943)). - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Converts a to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from JSON and BSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts an to and from its name string value. - - - - - Gets or sets a value indicating whether the written enum text should be camel case. - The default value is false. - - true if the written enum text will be camel case; otherwise, false. - - - - Gets or sets a value indicating whether integer values are allowed when deserializing. - The default value is true. - - true if integers are allowed when deserializing; otherwise, false. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - true if the written enum text will be camel case; otherwise, false. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from Unix epoch time - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Converts a to and from a string (e.g. "1.2.3.4"). - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts XML to and from JSON. - - - - - Gets or sets the name of the root element to insert when deserializing to XML if the JSON structure has produced multiple root elements. - - The name of the deserialized root element. - - - - Gets or sets a flag to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - true if the array attribute is written to the XML; otherwise, false. - - - - Gets or sets a value indicating whether to write the root JSON object. - - true if the JSON root object is omitted; otherwise, false. - - - - Writes the JSON representation of the object. - - The to write to. - The calling serializer. - The value. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Checks if the is a namespace attribute. - - Attribute name to test. - The attribute name prefix if it has one, otherwise an empty string. - true if attribute name is for a namespace attribute, otherwise false. - - - - Determines whether this instance can convert the specified value type. - - Type of the value. - - true if this instance can convert the specified value type; otherwise, false. - - - - - Specifies how dates are formatted when writing JSON text. - - - - - Dates are written in the ISO 8601 format, e.g. "2012-03-21T05:40Z". - - - - - Dates are written in the Microsoft JSON format, e.g. "\/Date(1198908717056)\/". - - - - - Specifies how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON text. - - - - - Date formatted strings are not parsed to a date type and are read as strings. - - - - - Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . - - - - - Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . - - - - - Specifies how to treat the time value when converting between string and . - - - - - Treat as local time. If the object represents a Coordinated Universal Time (UTC), it is converted to the local time. - - - - - Treat as a UTC. If the object represents a local time, it is converted to a UTC. - - - - - Treat as a local time if a is being converted to a string. - If a string is being converted to , convert to a local time if a time zone is specified. - - - - - Time zone information should be preserved when converting. - - - - - Specifies default value handling options for the . - - - - - - - - - Include members where the member value is the same as the member's default value when serializing objects. - Included members are written to JSON. Has no effect when deserializing. - - - - - Ignore members where the member value is the same as the member's default value when serializing objects - so that it is not written to JSON. - This option will ignore all default values (e.g. null for objects and nullable types; 0 for integers, - decimals and floating point numbers; and false for booleans). The default value ignored can be changed by - placing the on the property. - - - - - Members with a default value but no JSON will be set to their default value when deserializing. - - - - - Ignore members where the member value is the same as the member's default value when serializing objects - and set members to their default value when deserializing. - - - - - Specifies float format handling options when writing special floating point numbers, e.g. , - and with . - - - - - Write special floating point values as strings in JSON, e.g. "NaN", "Infinity", "-Infinity". - - - - - Write special floating point values as symbols in JSON, e.g. NaN, Infinity, -Infinity. - Note that this will produce non-valid JSON. - - - - - Write special floating point values as the property's default value in JSON, e.g. 0.0 for a property, null for a of property. - - - - - Specifies how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - - - - - Floating point numbers are parsed to . - - - - - Floating point numbers are parsed to . - - - - - Specifies formatting options for the . - - - - - No special formatting is applied. This is the default. - - - - - Causes child objects to be indented according to the and settings. - - - - - Provides an interface for using pooled arrays. - - The array type content. - - - - Rent an array from the pool. This array must be returned when it is no longer needed. - - The minimum required length of the array. The returned array may be longer. - The rented array from the pool. This array must be returned when it is no longer needed. - - - - Return an array to the pool. - - The array that is being returned. - - - - Provides an interface to enable a class to return line and position information. - - - - - Gets a value indicating whether the class can return line information. - - - true if and can be provided; otherwise, false. - - - - - Gets the current line number. - - The current line number or 0 if no line information is available (for example, when returns false). - - - - Gets the current line position. - - The current line position or 0 if no line information is available (for example, when returns false). - - - - Instructs the how to serialize the collection. - - - - - Gets or sets a value indicating whether null items are allowed in the collection. - - true if null items are allowed in the collection; otherwise, false. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a flag indicating whether the array can contain null items. - - A flag indicating whether the array can contain null items. - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Instructs the to use the specified constructor when deserializing that object. - - - - - Instructs the how to serialize the object. - - - - - Gets or sets the id. - - The id. - - - - Gets or sets the title. - - The title. - - - - Gets or sets the description. - - The description. - - - - Gets or sets the collection's items converter. - - The collection's items converter. - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonContainer(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonContainer(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets a value that indicates whether to preserve object references. - - - true to keep object reference; otherwise, false. The default is false. - - - - - Gets or sets a value that indicates whether to preserve collection's items references. - - - true to keep collection's items object references; otherwise, false. The default is false. - - - - - Gets or sets the reference loop handling used when serializing the collection's items. - - The reference loop handling. - - - - Gets or sets the type name handling used when serializing the collection's items. - - The type name handling. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Provides methods for converting between .NET types and JSON types. - - - - - - - - Gets or sets a function that creates default . - Default settings are automatically used by serialization methods on , - and and on . - To serialize without using any default settings create a with - . - - - - - Represents JavaScript's boolean value true as a string. This field is read-only. - - - - - Represents JavaScript's boolean value false as a string. This field is read-only. - - - - - Represents JavaScript's null as a string. This field is read-only. - - - - - Represents JavaScript's undefined as a string. This field is read-only. - - - - - Represents JavaScript's positive infinity as a string. This field is read-only. - - - - - Represents JavaScript's negative infinity as a string. This field is read-only. - - - - - Represents JavaScript's NaN as a string. This field is read-only. - - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation using the specified. - - The value to convert. - The format the date will be converted to. - The time zone handling when the date is converted to a string. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation using the specified. - - The value to convert. - The format the date will be converted to. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - The string delimiter character. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - The string delimiter character. - The string escape handling. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Serializes the specified object to a JSON string. - - The object to serialize. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using formatting. - - The object to serialize. - Indicates how the output should be formatted. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a collection of . - - The object to serialize. - A collection of converters used while serializing. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using formatting and a collection of . - - The object to serialize. - Indicates how the output should be formatted. - A collection of converters used while serializing. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using . - - The object to serialize. - The used to serialize the object. - If this is null, default serialization settings will be used. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a type, formatting and . - - The object to serialize. - The used to serialize the object. - If this is null, default serialization settings will be used. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using formatting and . - - The object to serialize. - Indicates how the output should be formatted. - The used to serialize the object. - If this is null, default serialization settings will be used. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a type, formatting and . - - The object to serialize. - Indicates how the output should be formatted. - The used to serialize the object. - If this is null, default serialization settings will be used. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - A JSON string representation of the object. - - - - - Deserializes the JSON to a .NET object. - - The JSON to deserialize. - The deserialized object from the JSON string. - - - - Deserializes the JSON to a .NET object using . - - The JSON to deserialize. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type. - - The JSON to deserialize. - The of object being deserialized. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type. - - The type of the object to deserialize to. - The JSON to deserialize. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the given anonymous type. - - - The anonymous type to deserialize to. This can't be specified - traditionally and must be inferred from the anonymous type passed - as a parameter. - - The JSON to deserialize. - The anonymous type object. - The deserialized anonymous type from the JSON string. - - - - Deserializes the JSON to the given anonymous type using . - - - The anonymous type to deserialize to. This can't be specified - traditionally and must be inferred from the anonymous type passed - as a parameter. - - The JSON to deserialize. - The anonymous type object. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized anonymous type from the JSON string. - - - - Deserializes the JSON to the specified .NET type using a collection of . - - The type of the object to deserialize to. - The JSON to deserialize. - Converters to use while deserializing. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using . - - The type of the object to deserialize to. - The object to deserialize. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using a collection of . - - The JSON to deserialize. - The type of the object to deserialize. - Converters to use while deserializing. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using . - - The JSON to deserialize. - The type of the object to deserialize to. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Populates the object with values from the JSON string. - - The JSON to populate values from. - The target object to populate values onto. - - - - Populates the object with values from the JSON string using . - - The JSON to populate values from. - The target object to populate values onto. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - - - - Serializes the to a JSON string. - - The node to serialize. - A JSON string of the . - - - - Serializes the to a JSON string using formatting. - - The node to serialize. - Indicates how the output should be formatted. - A JSON string of the . - - - - Serializes the to a JSON string using formatting and omits the root object if is true. - - The node to serialize. - Indicates how the output should be formatted. - Omits writing the root object. - A JSON string of the . - - - - Deserializes the from a JSON string. - - The JSON string. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by . - - The JSON string. - The name of the root element to append when deserializing. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by - and writes a Json.NET array attribute for collections. - - The JSON string. - The name of the root element to append when deserializing. - - A flag to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - The deserialized . - - - - Serializes the to a JSON string. - - The node to convert to JSON. - A JSON string of the . - - - - Serializes the to a JSON string using formatting. - - The node to convert to JSON. - Indicates how the output should be formatted. - A JSON string of the . - - - - Serializes the to a JSON string using formatting and omits the root object if is true. - - The node to serialize. - Indicates how the output should be formatted. - Omits writing the root object. - A JSON string of the . - - - - Deserializes the from a JSON string. - - The JSON string. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by . - - The JSON string. - The name of the root element to append when deserializing. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by - and writes a Json.NET array attribute for collections. - - The JSON string. - The name of the root element to append when deserializing. - - A flag to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - The deserialized . - - - - Converts an object to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can read JSON. - - true if this can read JSON; otherwise, false. - - - - Gets a value indicating whether this can write JSON. - - true if this can write JSON; otherwise, false. - - - - Converts an object to and from JSON. - - The object type to convert. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. If there is no existing value then null will be used. - The existing value has a value. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Instructs the to use the specified when serializing the member or class. - - - - - Gets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - - - - - Initializes a new instance of the class. - - Type of the . - - - - Initializes a new instance of the class. - - Type of the . - Parameter list to use when constructing the . Can be null. - - - - Represents a collection of . - - - - - Instructs the how to serialize the collection. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - The exception thrown when an error occurs during JSON serialization or deserialization. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Instructs the to deserialize properties with no matching class member into the specified collection - and write values during serialization. - - - - - Gets or sets a value that indicates whether to write extension data when serializing the object. - - - true to write extension data when serializing the object; otherwise, false. The default is true. - - - - - Gets or sets a value that indicates whether to read extension data when deserializing the object. - - - true to read extension data when deserializing the object; otherwise, false. The default is true. - - - - - Initializes a new instance of the class. - - - - - Instructs the not to serialize the public field or public read/write property value. - - - - - Instructs the how to serialize the object. - - - - - Gets or sets the member serialization. - - The member serialization. - - - - Gets or sets how the object's properties with null values are handled during serialization and deserialization. - - How the object's properties with null values are handled during serialization and deserialization. - - - - Gets or sets a value that indicates whether the object's properties are required. - - - A value indicating whether the object's properties are required. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified member serialization. - - The member serialization. - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Instructs the to always serialize the member with the specified name. - - - - - Gets or sets the used when serializing the property's collection items. - - The collection's items . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonProperty(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonProperty(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the null value handling used when serializing this property. - - The null value handling. - - - - Gets or sets the default value handling used when serializing this property. - - The default value handling. - - - - Gets or sets the reference loop handling used when serializing this property. - - The reference loop handling. - - - - Gets or sets the object creation handling used when deserializing this property. - - The object creation handling. - - - - Gets or sets the type name handling used when serializing this property. - - The type name handling. - - - - Gets or sets whether this property's value is serialized as a reference. - - Whether this property's value is serialized as a reference. - - - - Gets or sets the order of serialization of a member. - - The numeric order of serialization. - - - - Gets or sets a value indicating whether this property is required. - - - A value indicating whether this property is required. - - - - - Gets or sets the name of the property. - - The name of the property. - - - - Gets or sets the reference loop handling used when serializing the property's collection items. - - The collection's items reference loop handling. - - - - Gets or sets the type name handling used when serializing the property's collection items. - - The collection's items type name handling. - - - - Gets or sets whether this property's collection items are serialized as a reference. - - Whether this property's collection items are serialized as a reference. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified name. - - Name of the property. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. - - - - - Asynchronously reads the next JSON token from the source. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns true if the next token was read successfully; false if there are no more tokens to read. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously skips the children of the current token. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a []. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the []. This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Specifies the state of the reader. - - - - - A read method has not been called. - - - - - The end of the file has been reached successfully. - - - - - Reader is at a property. - - - - - Reader is at the start of an object. - - - - - Reader is in an object. - - - - - Reader is at the start of an array. - - - - - Reader is in an array. - - - - - The method has been called. - - - - - Reader has just read a value. - - - - - Reader is at the start of a constructor. - - - - - Reader is in a constructor. - - - - - An error occurred that prevents the read operation from continuing. - - - - - The end of the file has been reached successfully. - - - - - Gets the current reader state. - - The current reader state. - - - - Gets or sets a value indicating whether the source should be closed when this reader is closed. - - - true to close the source when this reader is closed; otherwise false. The default is true. - - - - - Gets or sets a value indicating whether multiple pieces of JSON content can - be read from a continuous stream without erroring. - - - true to support reading multiple pieces of JSON content; otherwise false. - The default is false. - - - - - Gets the quotation mark character used to enclose the value of a string. - - - - - Gets or sets how time zones are handled when reading JSON. - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - - - - - Gets or sets how custom date formatted strings are parsed when reading JSON. - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - - - - - Gets the type of the current JSON token. - - - - - Gets the text value of the current JSON token. - - - - - Gets the .NET type for the current JSON token. - - - - - Gets the depth of the current token in the JSON document. - - The depth of the current token in the JSON document. - - - - Gets the path of the current JSON token. - - - - - Gets or sets the culture used when reading JSON. Defaults to . - - - - - Initializes a new instance of the class. - - - - - Reads the next JSON token from the source. - - true if the next token was read successfully; false if there are no more tokens to read. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a []. - - A [] or null if the next JSON token is null. This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Skips the children of the current token. - - - - - Sets the current token. - - The new token. - - - - Sets the current token and value. - - The new token. - The value. - - - - Sets the current token and value. - - The new token. - The value. - A flag indicating whether the position index inside an array should be updated. - - - - Sets the state based on current token type. - - - - - Releases unmanaged and - optionally - managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Changes the reader's state to . - If is set to true, the source is also closed. - - - - - The exception thrown when an error occurs while reading JSON text. - - - - - Gets the line number indicating where the error occurred. - - The line number indicating where the error occurred. - - - - Gets the line position indicating where the error occurred. - - The line position indicating where the error occurred. - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class - with a specified error message, JSON path, line number, line position, and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The path to the JSON where the error occurred. - The line number indicating where the error occurred. - The line position indicating where the error occurred. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Instructs the to always serialize the member, and to require that the member has a value. - - - - - The exception thrown when an error occurs during JSON serialization or deserialization. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Serializes and deserializes objects into and from the JSON format. - The enables you to control how objects are encoded into JSON. - - - - - Occurs when the errors during serialization and deserialization. - - - - - Gets or sets the used by the serializer when resolving references. - - - - - Gets or sets the used by the serializer when resolving type names. - - - - - Gets or sets the equality comparer used by the serializer when comparing references. - - The equality comparer. - - - - Gets or sets how type name writing and reading is handled by the serializer. - The default value is . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - - - - Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . - - The type name assembly format. - - - - Gets or sets how object references are preserved by the serializer. - The default value is . - - - - - Gets or sets how reference loops (e.g. a class referencing itself) is handled. - The default value is . - - - - - Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. - The default value is . - - - - - Gets or sets how null values are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how default values are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how objects are created during deserialization. - The default value is . - - The object creation handling. - - - - Gets or sets how constructors are used during deserialization. - The default value is . - - The constructor handling. - - - - Gets or sets how metadata properties are used during deserialization. - The default value is . - - The metadata properties handling. - - - - Gets a collection that will be used during serialization. - - Collection that will be used during serialization. - - - - Gets or sets the contract resolver used by the serializer when - serializing .NET objects to JSON and vice versa. - - - - - Indicates how JSON text output is formatted. - The default value is . - - - - - Gets or sets how dates are written to JSON text. - The default value is . - - - - - Gets or sets how time zones are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - The default value is . - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - The default value is . - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written as JSON text. - The default value is . - - - - - Gets or sets how strings are escaped when writing JSON text. - The default value is . - - - - - Gets or sets how and values are formatted when writing JSON text, - and the expected date format when reading JSON text. - The default value is "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK". - - - - - Gets or sets the culture used when reading JSON. - The default value is . - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - A null value means there is no maximum. - The default value is null. - - - - - Gets a value indicating whether there will be a check for additional JSON content after deserializing an object. - The default value is false. - - - true if there will be a check for additional JSON content after deserializing an object; otherwise, false. - - - - - Initializes a new instance of the class. - - - - - Clears all the cached types, attributes and methods to release references to any types of the other assemblies. - - - - - Creates a new instance. - The will not use default settings - from . - - - A new instance. - The will not use default settings - from . - - - - - Creates a new instance using the specified . - The will not use default settings - from . - - The settings to be applied to the . - - A new instance using the specified . - The will not use default settings - from . - - - - - Creates a new instance. - The will use default settings - from . - - - A new instance. - The will use default settings - from . - - - - - Creates a new instance using the specified . - The will use default settings - from as well as the specified . - - The settings to be applied to the . - - A new instance using the specified . - The will use default settings - from as well as the specified . - - - - - Populates the JSON values onto the target object. - - The that contains the JSON structure to reader values from. - The target object to populate values onto. - - - - Populates the JSON values onto the target object. - - The that contains the JSON structure to reader values from. - The target object to populate values onto. - - - - Deserializes the JSON structure contained by the specified . - - The that contains the JSON structure to deserialize. - The being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The of object being deserialized. - The instance of being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The type of the object to deserialize. - The instance of being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The of object being deserialized. - The instance of being deserialized. - - - - Serializes the specified difference to the other object of the same type. and writes the JSON structure using the specified . - - The used to write the JSON structure. - The to serialize. - The reference object. - - - - Serializes the specified difference to the other object of the same type. Writes the JSON structure using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - The reference object. - - - - Serializes the specified difference to the other object of the same type. Writes the JSON structure using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is Auto to write out the type name if the type of the value does not match. - Specifying the type is optional. - - The reference object. - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - The reference object. - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is Auto to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - - - Specifies the settings on a object. - - - - - Gets or sets how reference loops (e.g. a class referencing itself) are handled. - The default value is . - - Reference loop handling. - - - - Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. - The default value is . - - Missing member handling. - - - - Gets or sets how objects are created during deserialization. - The default value is . - - The object creation handling. - - - - Gets or sets how null values are handled during serialization and deserialization. - The default value is . - - Null value handling. - - - - Gets or sets how default values are handled during serialization and deserialization. - The default value is . - - The default value handling. - - - - Gets or sets a collection that will be used during serialization. - - The converters. - - - - Gets or sets how object references are preserved by the serializer. - The default value is . - - The preserve references handling. - - - - Gets or sets how type name writing and reading is handled by the serializer. - The default value is . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - The type name handling. - - - - Gets or sets how metadata properties are used during deserialization. - The default value is . - - The metadata properties handling. - - - - Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . - - The type name assembly format. - - - - Gets or sets how constructors are used during deserialization. - The default value is . - - The constructor handling. - - - - Gets or sets the contract resolver used by the serializer when - serializing .NET objects to JSON and vice versa. - - The contract resolver. - - - - Gets or sets the equality comparer used by the serializer when comparing references. - - The equality comparer. - - - - Gets or sets a function that creates the used by the serializer when resolving references. - - A function that creates the used by the serializer when resolving references. - - - - Gets or sets the used by the serializer when resolving type names. - - The binder. - - - - Gets or sets the error handler called during serialization and deserialization. - - The error handler called during serialization and deserialization. - - - - Gets or sets how and values are formatted when writing JSON text, - and the expected date format when reading JSON text. - The default value is "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK". - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - A null value means there is no maximum. - The default value is null. - - - - - Indicates how JSON text output is formatted. - The default value is . - - - - - Gets or sets how dates are written to JSON text. - The default value is . - - - - - Gets or sets how time zones are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - The default value is . - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written as JSON. - The default value is . - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - The default value is . - - - - - Gets or sets how strings are escaped when writing JSON text. - The default value is . - - - - - Gets or sets the culture used when reading JSON. - The default value is . - - - - - Gets a value indicating whether there will be a check for additional content after deserializing an object. - The default value is false. - - - true if there will be a check for additional content after deserializing an object; otherwise, false. - - - - - Initializes a new instance of the class. - - - - - Represents a reader that provides fast, non-cached, forward-only access to JSON text data. - - - - - Asynchronously reads the next JSON token from the source. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns true if the next token was read successfully; false if there are no more tokens to read. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a []. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the []. This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Initializes a new instance of the class with the specified . - - The containing the JSON data to read. - - - - Gets or sets the reader's character buffer pool. - - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a []. - - A [] or null if the next JSON token is null. This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Gets a value indicating whether the class can return line information. - - - true if and can be provided; otherwise, false. - - - - - Gets the current line number. - - - The current line number or 0 if no line information is available (for example, returns false). - - - - - Gets the current line position. - - - The current line position or 0 if no line information is available (for example, returns false). - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Asynchronously flushes whatever is in the buffer to the destination and also flushes the destination. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the JSON value delimiter. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the specified end token. - - The end token to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously closes this writer. - If is set to true, the destination is also closed. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of the current JSON object or array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes indent characters. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes an indent space. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes raw JSON without changing the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a null value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the beginning of a JSON array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the beginning of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the start of a constructor with the given name. - - The name of the constructor. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes an undefined value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the given white space. - - The string of white space characters. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a [] value. - - The [] value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of an array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of a constructor. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Gets or sets the writer's character array pool. - - - - - Gets or sets how many s to write for each level in the hierarchy when is set to . - - - - - Gets or sets which character to use to quote attribute values. - - - - - Gets or sets which character to use for indenting when is set to . - - - - - Gets or sets a value indicating whether object names will be surrounded with quotes. - - - - - Initializes a new instance of the class using the specified . - - The to write to. - - - - Flushes whatever is in the buffer to the underlying and also flushes the underlying . - - - - - Closes this writer. - If is set to true, the underlying is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the specified end token. - - The end token to write. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - - - - Writes indent characters. - - - - - Writes the JSON value delimiter. - - - - - Writes an indent space. - - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the given white space. - - The string of white space characters. - - - - Specifies the type of JSON token. - - - - - This is returned by the if a read method has not been called. - - - - - An object start token. - - - - - An array start token. - - - - - A constructor start token. - - - - - An object property name. - - - - - A comment. - - - - - Raw JSON. - - - - - An integer. - - - - - A float. - - - - - A string. - - - - - A boolean. - - - - - A null token. - - - - - An undefined token. - - - - - An object end token. - - - - - An array end token. - - - - - A constructor end token. - - - - - A Date. - - - - - Byte data. - - - - - - Represents a reader that provides validation. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Sets an event handler for receiving schema validation errors. - - - - - Gets the text value of the current JSON token. - - - - - - Gets the depth of the current token in the JSON document. - - The depth of the current token in the JSON document. - - - - Gets the path of the current JSON token. - - - - - Gets the quotation mark character used to enclose the value of a string. - - - - - - Gets the type of the current JSON token. - - - - - - Gets the .NET type for the current JSON token. - - - - - - Initializes a new instance of the class that - validates the content returned from the given . - - The to read from while validating. - - - - Gets or sets the schema. - - The schema. - - - - Gets the used to construct this . - - The specified in the constructor. - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a []. - - - A [] or null if the next JSON token is null. - - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Asynchronously closes this writer. - If is set to true, the destination is also closed. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously flushes whatever is in the buffer to the destination and also flushes the destination. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the specified end token. - - The end token to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes indent characters. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the JSON value delimiter. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes an indent space. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes raw JSON without changing the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of the current JSON object or array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of an array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of a constructor. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a null value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the beginning of a JSON array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the start of a constructor with the given name. - - The name of the constructor. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the beginning of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the current token. - - The to read the token from. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the current token. - - The to read the token from. - A flag indicating whether the current token's children should be written. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the token and its value. - - The to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the token and its value. - - The to write. - - The value to write. - A value is only required for tokens that have an associated value, e.g. the property name for . - null can be passed to the method for tokens that don't have a value, e.g. . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a [] value. - - The [] value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes an undefined value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the given white space. - - The string of white space characters. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously ets the state of the . - - The being written. - The value being written. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Gets or sets a value indicating whether the destination should be closed when this writer is closed. - - - true to close the destination when this writer is closed; otherwise false. The default is true. - - - - - Gets or sets a value indicating whether the JSON should be auto-completed when this writer is closed. - - - true to auto-complete the JSON when this writer is closed; otherwise false. The default is true. - - - - - Gets the top. - - The top. - - - - Gets the state of the writer. - - - - - Gets the path of the writer. - - - - - Gets or sets a value indicating how JSON text output should be formatted. - - - - - Gets or sets how dates are written to JSON text. - - - - - Gets or sets how time zones are handled when writing JSON text. - - - - - Gets or sets how strings are escaped when writing JSON text. - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written to JSON text. - - - - - Gets or sets how and values are formatted when writing JSON text. - - - - - Gets or sets the culture used when writing JSON. Defaults to . - - - - - Initializes a new instance of the class. - - - - - Flushes whatever is in the buffer to the destination and also flushes the destination. - - - - - Closes this writer. - If is set to true, the destination is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the end of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the end of an array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the end constructor. - - - - - Writes the property name of a name/value pair of a JSON object. - - The name of the property. - - - - Writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - - - - Writes the end of the current JSON object or array. - - - - - Writes the current token and its children. - - The to read the token from. - - - - Writes the current token. - - The to read the token from. - A flag indicating whether the current token's children should be written. - - - - Writes the token and its value. - - The to write. - - The value to write. - A value is only required for tokens that have an associated value, e.g. the property name for . - null can be passed to the method for tokens that don't have a value, e.g. . - - - - - Writes the token. - - The to write. - - - - Writes the specified end token. - - The end token to write. - - - - Writes indent characters. - - - - - Writes the JSON value delimiter. - - - - - Writes an indent space. - - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON without changing the writer's state. - - The raw JSON to write. - - - - Writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the given white space. - - The string of white space characters. - - - - Releases unmanaged and - optionally - managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Sets the state of the . - - The being written. - The value being written. - - - - The exception thrown when an error occurs while writing JSON text. - - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class - with a specified error message, JSON path and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The path to the JSON where the error occurred. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Specifies how JSON comments are handled when loading JSON. - - - - - Ignore comments. - - - - - Load comments as a with type . - - - - - Contains the LINQ to JSON extension methods. - - - - - Returns a collection of tokens that contains the ancestors of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains the ancestors of every token in the source collection. - - - - Returns a collection of tokens that contains every token in the source collection, and the ancestors of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains every token in the source collection, the ancestors of every token in the source collection. - - - - Returns a collection of tokens that contains the descendants of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains the descendants of every token in the source collection. - - - - Returns a collection of tokens that contains every token in the source collection, and the descendants of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains every token in the source collection, and the descendants of every token in the source collection. - - - - Returns a collection of child properties of every object in the source collection. - - An of that contains the source collection. - An of that contains the properties of every object in the source collection. - - - - Returns a collection of child values of every object in the source collection with the given key. - - An of that contains the source collection. - The token key. - An of that contains the values of every token in the source collection with the given key. - - - - Returns a collection of child values of every object in the source collection. - - An of that contains the source collection. - An of that contains the values of every token in the source collection. - - - - Returns a collection of converted child values of every object in the source collection with the given key. - - The type to convert the values to. - An of that contains the source collection. - The token key. - An that contains the converted values of every token in the source collection with the given key. - - - - Returns a collection of converted child values of every object in the source collection. - - The type to convert the values to. - An of that contains the source collection. - An that contains the converted values of every token in the source collection. - - - - Converts the value. - - The type to convert the value to. - A cast as a of . - A converted value. - - - - Converts the value. - - The source collection type. - The type to convert the value to. - A cast as a of . - A converted value. - - - - Returns a collection of child tokens of every array in the source collection. - - The source collection type. - An of that contains the source collection. - An of that contains the values of every token in the source collection. - - - - Returns a collection of converted child tokens of every array in the source collection. - - An of that contains the source collection. - The type to convert the values to. - The source collection type. - An that contains the converted values of every token in the source collection. - - - - Returns the input typed as . - - An of that contains the source collection. - The input typed as . - - - - Returns the input typed as . - - The source collection type. - An of that contains the source collection. - The input typed as . - - - - Represents a collection of objects. - - The type of token. - - - - Gets the of with the specified key. - - - - - - Represents a JSON array. - - - - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous load. The property contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous load. The property contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified content. - - The contents of the array. - - - - Initializes a new instance of the class with the specified content. - - The contents of the array. - - - - Loads an from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads an from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - - - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - - - - - - Creates a from an object. - - The object that will be used to create . - A with the values of the specified object. - - - - Creates a from an object. - - The object that will be used to create . - The that will be used to read the object. - A with the values of the specified object. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets or sets the at the specified index. - - - - - - Determines the index of a specific item in the . - - The object to locate in the . - - The index of if found in the list; otherwise, -1. - - - - - Inserts an item to the at the specified index. - - The zero-based index at which should be inserted. - The object to insert into the . - - is not a valid index in the . - - - - - Removes the item at the specified index. - - The zero-based index of the item to remove. - - is not a valid index in the . - - - - - Returns an enumerator that iterates through the collection. - - - A of that can be used to iterate through the collection. - - - - - Adds an item to the . - - The object to add to the . - - - - Removes all items from the . - - - - - Determines whether the contains a specific value. - - The object to locate in the . - - true if is found in the ; otherwise, false. - - - - - Copies the elements of the to an array, starting at a particular array index. - - The array. - Index of the array. - - - - Gets a value indicating whether the is read-only. - - true if the is read-only; otherwise, false. - - - - Removes the first occurrence of a specific object from the . - - The object to remove from the . - - true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - - - - - Represents a JSON constructor. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets or sets the name of this constructor. - - The constructor name. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified name and content. - - The constructor name. - The contents of the constructor. - - - - Initializes a new instance of the class with the specified name and content. - - The constructor name. - The contents of the constructor. - - - - Initializes a new instance of the class with the specified name. - - The constructor name. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified key. - - The with the specified key. - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Represents a token that can contain other tokens. - - - - - Occurs when the list changes or an item in the list changes. - - - - - Occurs before an item is added to the collection. - - - - - Occurs when the items list of the collection has changed, or the collection is reset. - - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Raises the event. - - The instance containing the event data. - - - - Raises the event. - - The instance containing the event data. - - - - Raises the event. - - The instance containing the event data. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Get the first child token of this token. - - - A containing the first child token of the . - - - - - Get the last child token of this token. - - - A containing the last child token of the . - - - - - Returns a collection of the child tokens of this token, in document order. - - - An of containing the child tokens of this , in document order. - - - - - Returns a collection of the child values of this token, in document order. - - The type to convert the values to. - - A containing the child values of this , in document order. - - - - - Returns a collection of the descendant tokens for this token in document order. - - An of containing the descendant tokens of the . - - - - Returns a collection of the tokens that contain this token, and all descendant tokens of this token, in document order. - - An of containing this token, and all the descendant tokens of the . - - - - Adds the specified content as children of this . - - The content to be added. - - - - Adds the specified content as the first children of this . - - The content to be added. - - - - Creates a that can be used to add tokens to the . - - A that is ready to have content written to it. - - - - Replaces the child nodes of this token with the specified content. - - The content. - - - - Removes the child nodes from this token. - - - - - Merge the specified content into this . - - The content to be merged. - - - - Merge the specified content into this using . - - The content to be merged. - The used to merge the content. - - - - Gets the count of child JSON tokens. - - The count of child JSON tokens. - - - - Represents a collection of objects. - - The type of token. - - - - An empty collection of objects. - - - - - Initializes a new instance of the struct. - - The enumerable. - - - - Returns an enumerator that can be used to iterate through the collection. - - - A that can be used to iterate through the collection. - - - - - Gets the of with the specified key. - - - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Represents a JSON object. - - - - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Occurs when a property value changes. - - - - - Occurs when a property value is changing. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified content. - - The contents of the object. - - - - Initializes a new instance of the class with the specified content. - - The contents of the object. - - - - Gets the node type for this . - - The type. - - - - Gets an of of this object's properties. - - An of of this object's properties. - - - - Gets a the specified name. - - The property name. - A with the specified name or null. - - - - Gets a of of this object's property values. - - A of of this object's property values. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets or sets the with the specified property name. - - - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - is not valid JSON. - - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - is not valid JSON. - - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - is not valid JSON. - - - - - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - is not valid JSON. - - - - - - - - Creates a from an object. - - The object that will be used to create . - A with the values of the specified object. - - - - Creates a from an object. - - The object that will be used to create . - The that will be used to read the object. - A with the values of the specified object. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified property name. - - Name of the property. - The with the specified property name. - - - - Gets the with the specified property name. - The exact property name will be searched for first and if no matching property is found then - the will be used to match a property. - - Name of the property. - One of the enumeration values that specifies how the strings will be compared. - The with the specified property name. - - - - Tries to get the with the specified property name. - The exact property name will be searched for first and if no matching property is found then - the will be used to match a property. - - Name of the property. - The value. - One of the enumeration values that specifies how the strings will be compared. - true if a value was successfully retrieved; otherwise, false. - - - - Adds the specified property name. - - Name of the property. - The value. - - - - Determines whether the JSON object has the specified property name. - - Name of the property. - true if the JSON object has the specified property name; otherwise, false. - - - - Removes the property with the specified name. - - Name of the property. - true if item was successfully removed; otherwise, false. - - - - Tries to get the with the specified property name. - - Name of the property. - The value. - true if a value was successfully retrieved; otherwise, false. - - - - Returns an enumerator that can be used to iterate through the collection. - - - A that can be used to iterate through the collection. - - - - - Raises the event with the provided arguments. - - Name of the property. - - - - Raises the event with the provided arguments. - - Name of the property. - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Represents a JSON property. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets the property name. - - The property name. - - - - Gets or sets the property value. - - The property value. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - The property name. - The property content. - - - - Initializes a new instance of the class. - - The property name. - The property content. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Represents a view of a . - - - - - Initializes a new instance of the class. - - The name. - - - - When overridden in a derived class, returns whether resetting an object changes its value. - - - true if resetting the component changes its value; otherwise, false. - - The component to test for reset capability. - - - - When overridden in a derived class, gets the current value of the property on a component. - - - The value of a property for a given component. - - The component with the property for which to retrieve the value. - - - - When overridden in a derived class, resets the value for this property of the component to the default value. - - The component with the property value that is to be reset to the default value. - - - - When overridden in a derived class, sets the value of the component to a different value. - - The component with the property value that is to be set. - The new value. - - - - When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted. - - - true if the property should be persisted; otherwise, false. - - The component with the property to be examined for persistence. - - - - When overridden in a derived class, gets the type of the component this property is bound to. - - - A that represents the type of component this property is bound to. - When the or - - methods are invoked, the object specified might be an instance of this type. - - - - - When overridden in a derived class, gets a value indicating whether this property is read-only. - - - true if the property is read-only; otherwise, false. - - - - - When overridden in a derived class, gets the type of the property. - - - A that represents the type of the property. - - - - - Gets the hash code for the name of the member. - - - - The hash code for the name of the member. - - - - - Represents a raw JSON string. - - - - - Asynchronously creates an instance of with the content of the reader's current token. - - The reader. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns an instance of with the content of the reader's current token. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class. - - The raw json. - - - - Creates an instance of with the content of the reader's current token. - - The reader. - An instance of with the content of the reader's current token. - - - - Specifies the settings used when loading JSON. - - - - - Initializes a new instance of the class. - - - - - Gets or sets how JSON comments are handled when loading JSON. - - The JSON comment handling. - - - - Gets or sets how JSON line info is handled when loading JSON. - - The JSON line info handling. - - - - Specifies the settings used when merging JSON. - - - - - Gets or sets the method used when merging JSON arrays. - - The method used when merging JSON arrays. - - - - Gets or sets how null value properties are merged. - - How null value properties are merged. - - - - Represents an abstract JSON token. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Writes this token to a asynchronously. - - A into which this method will write. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously creates a from a . - - An positioned at the token to read into this . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains - the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - An positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains - the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - A positioned at the token to read into this . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - A positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Gets a comparer that can compare two tokens for value equality. - - A that can compare two nodes for value equality. - - - - Gets or sets the parent. - - The parent. - - - - Gets the root of this . - - The root of this . - - - - Gets the node type for this . - - The type. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Compares the values of two tokens, including the values of all descendant tokens. - - The first to compare. - The second to compare. - true if the tokens are equal; otherwise false. - - - - Gets the next sibling token of this node. - - The that contains the next sibling token. - - - - Gets the previous sibling token of this node. - - The that contains the previous sibling token. - - - - Gets the path of the JSON token. - - - - - Adds the specified content immediately after this token. - - A content object that contains simple content or a collection of content objects to be added after this token. - - - - Adds the specified content immediately before this token. - - A content object that contains simple content or a collection of content objects to be added before this token. - - - - Returns a collection of the ancestor tokens of this token. - - A collection of the ancestor tokens of this token. - - - - Returns a collection of tokens that contain this token, and the ancestors of this token. - - A collection of tokens that contain this token, and the ancestors of this token. - - - - Returns a collection of the sibling tokens after this token, in document order. - - A collection of the sibling tokens after this tokens, in document order. - - - - Returns a collection of the sibling tokens before this token, in document order. - - A collection of the sibling tokens before this token, in document order. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets the with the specified key converted to the specified type. - - The type to convert the token to. - The token key. - The converted token value. - - - - Get the first child token of this token. - - A containing the first child token of the . - - - - Get the last child token of this token. - - A containing the last child token of the . - - - - Returns a collection of the child tokens of this token, in document order. - - An of containing the child tokens of this , in document order. - - - - Returns a collection of the child tokens of this token, in document order, filtered by the specified type. - - The type to filter the child tokens on. - A containing the child tokens of this , in document order. - - - - Returns a collection of the child values of this token, in document order. - - The type to convert the values to. - A containing the child values of this , in document order. - - - - Removes this token from its parent. - - - - - Replaces this token with the specified token. - - The value. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Returns the indented JSON for this token. - - - The indented JSON for this token. - - - - - Returns the JSON for this token using the given formatting and converters. - - Indicates how the output should be formatted. - A collection of s which will be used when writing the token. - The JSON for this token using the given formatting and converters. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to []. - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from [] to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Creates a for this token. - - A that can be used to read this token and its descendants. - - - - Creates a from an object. - - The object that will be used to create . - A with the value of the specified object. - - - - Creates a from an object using the specified . - - The object that will be used to create . - The that will be used when reading the object. - A with the value of the specified object. - - - - Creates an instance of the specified .NET type from the . - - The object type that the token will be deserialized to. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the . - - The object type that the token will be deserialized to. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the using the specified . - - The object type that the token will be deserialized to. - The that will be used when creating the object. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the using the specified . - - The object type that the token will be deserialized to. - The that will be used when creating the object. - The new object created from the JSON value. - - - - Creates a from a . - - A positioned at the token to read into this . - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Creates a from a . - - An positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - - - Creates a from a . - - A positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Creates a from a . - - A positioned at the token to read into this . - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Selects a using a JPath expression. Selects the token that matches the object path. - - - A that contains a JPath expression. - - A , or null. - - - - Selects a using a JPath expression. Selects the token that matches the object path. - - - A that contains a JPath expression. - - A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. - A . - - - - Selects a collection of elements using a JPath expression. - - - A that contains a JPath expression. - - An of that contains the selected elements. - - - - Selects a collection of elements using a JPath expression. - - - A that contains a JPath expression. - - A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. - An of that contains the selected elements. - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Creates a new instance of the . All child tokens are recursively cloned. - - A new instance of the . - - - - Adds an object to the annotation list of this . - - The annotation to add. - - - - Get the first annotation object of the specified type from this . - - The type of the annotation to retrieve. - The first annotation object that matches the specified type, or null if no annotation is of the specified type. - - - - Gets the first annotation object of the specified type from this . - - The of the annotation to retrieve. - The first annotation object that matches the specified type, or null if no annotation is of the specified type. - - - - Gets a collection of annotations of the specified type for this . - - The type of the annotations to retrieve. - An that contains the annotations for this . - - - - Gets a collection of annotations of the specified type for this . - - The of the annotations to retrieve. - An of that contains the annotations that match the specified type for this . - - - - Removes the annotations of the specified type from this . - - The type of annotations to remove. - - - - Removes the annotations of the specified type from this . - - The of annotations to remove. - - - - Compares tokens to determine whether they are equal. - - - - - Determines whether the specified objects are equal. - - The first object of type to compare. - The second object of type to compare. - - true if the specified objects are equal; otherwise, false. - - - - - Returns a hash code for the specified object. - - The for which a hash code is to be returned. - A hash code for the specified object. - The type of is a reference type and is null. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. - - - - - Gets the at the reader's current position. - - - - - Initializes a new instance of the class. - - The token to read from. - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Gets the path of the current JSON token. - - - - - Specifies the type of token. - - - - - No token type has been set. - - - - - A JSON object. - - - - - A JSON array. - - - - - A JSON constructor. - - - - - A JSON object property. - - - - - A comment. - - - - - An integer value. - - - - - A float value. - - - - - A string value. - - - - - A boolean value. - - - - - A null value. - - - - - An undefined value. - - - - - A date value. - - - - - A raw JSON value. - - - - - A collection of bytes value. - - - - - A Guid value. - - - - - A Uri value. - - - - - A TimeSpan value. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Gets the at the writer's current position. - - - - - Gets the token being written. - - The token being written. - - - - Initializes a new instance of the class writing to the given . - - The container being written to. - - - - Initializes a new instance of the class. - - - - - Flushes whatever is in the buffer to the underlying . - - - - - Closes this writer. - If is set to true, the JSON is auto-completed. - - - Setting to true has no additional effect, since the underlying is a type that cannot be closed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the end. - - The token. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Writes a value. - An error will be raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Represents a value in JSON (string, integer, date, etc). - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Creates a comment with the given value. - - The value. - A comment with the given value. - - - - Creates a string with the given value. - - The value. - A string with the given value. - - - - Creates a null value. - - A null value. - - - - Creates a undefined value. - - A undefined value. - - - - Gets the node type for this . - - The type. - - - - Gets or sets the underlying token value. - - The underlying token value. - - - - Writes this token to a . - - A into which this method will write. - A collection of s which will be used when writing the token. - - - - Indicates whether the current object is equal to another object of the same type. - - - true if the current object is equal to the parameter; otherwise, false. - - An object to compare with this object. - - - - Determines whether the specified is equal to the current . - - The to compare with the current . - - true if the specified is equal to the current ; otherwise, false. - - - - - Serves as a hash function for a particular type. - - - A hash code for the current . - - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format. - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format provider. - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format. - The format provider. - - A that represents this instance. - - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - - An object to compare with this instance. - - A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: - Value - Meaning - Less than zero - This instance is less than . - Zero - This instance is equal to . - Greater than zero - This instance is greater than . - - - is not of the same type as this instance. - - - - - Specifies how line information is handled when loading JSON. - - - - - Ignore line information. - - - - - Load line information. - - - - - Specifies how JSON arrays are merged together. - - - - Concatenate arrays. - - - Union arrays, skipping items that already exist. - - - Replace all array items. - - - Merge array items together, matched by index. - - - - Specifies how null value properties are merged. - - - - - The content's null value properties will be ignored during merging. - - - - - The content's null value properties will be merged. - - - - - Specifies the member serialization options for the . - - - - - All public members are serialized by default. Members can be excluded using or . - This is the default member serialization mode. - - - - - Only members marked with or are serialized. - This member serialization mode can also be set by marking the class with . - - - - - All public and private fields are serialized. Members can be excluded using or . - This member serialization mode can also be set by marking the class with - and setting IgnoreSerializableAttribute on to false. - - - - - Specifies metadata property handling options for the . - - - - - Read metadata properties located at the start of a JSON object. - - - - - Read metadata properties located anywhere in a JSON object. Note that this setting will impact performance. - - - - - Do not try to read metadata properties. - - - - - Specifies missing member handling options for the . - - - - - Ignore a missing member and do not attempt to deserialize it. - - - - - Throw a when a missing member is encountered during deserialization. - - - - - Specifies null value handling options for the . - - - - - - - - - Include null values when serializing and deserializing objects. - - - - - Ignore null values when serializing and deserializing objects. - - - - - Specifies how object creation is handled by the . - - - - - Reuse existing objects, create new objects when needed. - - - - - Only reuse existing objects. - - - - - Always create new objects. - - - - - Specifies reference handling options for the . - Note that references cannot be preserved when a value is set via a non-default constructor such as types that implement . - - - - - - - - Do not preserve references when serializing types. - - - - - Preserve references when serializing into a JSON object structure. - - - - - Preserve references when serializing into a JSON array structure. - - - - - Preserve references when serializing. - - - - - Specifies reference loop handling options for the . - - - - - Throw a when a loop is encountered. - - - - - Ignore loop references and do not serialize. - - - - - Serialize loop references. - - - - - Indicating whether a property is required. - - - - - The property is not required. The default state. - - - - - The property must be defined in JSON but can be a null value. - - - - - The property must be defined in JSON and cannot be a null value. - - - - - The property is not required but it cannot be a null value. - - - - - - Contains the JSON schema extension methods. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - - Determines whether the is valid. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - - true if the specified is valid; otherwise, false. - - - - - - Determines whether the is valid. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - When this method returns, contains any error messages generated while validating. - - true if the specified is valid; otherwise, false. - - - - - - Validates the specified . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - - - - - Validates the specified . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - The validation event handler. - - - - - An in-memory representation of a JSON Schema. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets the id. - - - - - Gets or sets the title. - - - - - Gets or sets whether the object is required. - - - - - Gets or sets whether the object is read-only. - - - - - Gets or sets whether the object is visible to users. - - - - - Gets or sets whether the object is transient. - - - - - Gets or sets the description of the object. - - - - - Gets or sets the types of values allowed by the object. - - The type. - - - - Gets or sets the pattern. - - The pattern. - - - - Gets or sets the minimum length. - - The minimum length. - - - - Gets or sets the maximum length. - - The maximum length. - - - - Gets or sets a number that the value should be divisible by. - - A number that the value should be divisible by. - - - - Gets or sets the minimum. - - The minimum. - - - - Gets or sets the maximum. - - The maximum. - - - - Gets or sets a flag indicating whether the value can not equal the number defined by the minimum attribute (). - - A flag indicating whether the value can not equal the number defined by the minimum attribute (). - - - - Gets or sets a flag indicating whether the value can not equal the number defined by the maximum attribute (). - - A flag indicating whether the value can not equal the number defined by the maximum attribute (). - - - - Gets or sets the minimum number of items. - - The minimum number of items. - - - - Gets or sets the maximum number of items. - - The maximum number of items. - - - - Gets or sets the of items. - - The of items. - - - - Gets or sets a value indicating whether items in an array are validated using the instance at their array position from . - - - true if items are validated using their array position; otherwise, false. - - - - - Gets or sets the of additional items. - - The of additional items. - - - - Gets or sets a value indicating whether additional items are allowed. - - - true if additional items are allowed; otherwise, false. - - - - - Gets or sets whether the array items must be unique. - - - - - Gets or sets the of properties. - - The of properties. - - - - Gets or sets the of additional properties. - - The of additional properties. - - - - Gets or sets the pattern properties. - - The pattern properties. - - - - Gets or sets a value indicating whether additional properties are allowed. - - - true if additional properties are allowed; otherwise, false. - - - - - Gets or sets the required property if this property is present. - - The required property if this property is present. - - - - Gets or sets the a collection of valid enum values allowed. - - A collection of valid enum values allowed. - - - - Gets or sets disallowed types. - - The disallowed types. - - - - Gets or sets the default value. - - The default value. - - - - Gets or sets the collection of that this schema extends. - - The collection of that this schema extends. - - - - Gets or sets the format. - - The format. - - - - Initializes a new instance of the class. - - - - - Reads a from the specified . - - The containing the JSON Schema to read. - The object representing the JSON Schema. - - - - Reads a from the specified . - - The containing the JSON Schema to read. - The to use when resolving schema references. - The object representing the JSON Schema. - - - - Load a from a string that contains JSON Schema. - - A that contains JSON Schema. - A populated from the string that contains JSON Schema. - - - - Load a from a string that contains JSON Schema using the specified . - - A that contains JSON Schema. - The resolver. - A populated from the string that contains JSON Schema. - - - - Writes this schema to a . - - A into which this method will write. - - - - Writes this schema to a using the specified . - - A into which this method will write. - The resolver used. - - - - Returns a that represents the current . - - - A that represents the current . - - - - - - Returns detailed information about the schema exception. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets the line number indicating where the error occurred. - - The line number indicating where the error occurred. - - - - Gets the line position indicating where the error occurred. - - The line position indicating where the error occurred. - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - - Generates a from a specified . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets how undefined schemas are handled by the serializer. - - - - - Gets or sets the contract resolver. - - The contract resolver. - - - - Generate a from the specified type. - - The type to generate a from. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - The used to resolve schema references. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - Specify whether the generated root will be nullable. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - The used to resolve schema references. - Specify whether the generated root will be nullable. - A generated from the specified type. - - - - - Resolves from an id. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets the loaded schemas. - - The loaded schemas. - - - - Initializes a new instance of the class. - - - - - Gets a for the specified reference. - - The id. - A for the specified reference. - - - - - The value types allowed by the . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - No type specified. - - - - - String type. - - - - - Float type. - - - - - Integer type. - - - - - Boolean type. - - - - - Object type. - - - - - Array type. - - - - - Null type. - - - - - Any type. - - - - - - Specifies undefined schema Id handling options for the . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Do not infer a schema Id. - - - - - Use the .NET type name as the schema Id. - - - - - Use the assembly qualified .NET type name as the schema Id. - - - - - - Returns detailed information related to the . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets the associated with the validation error. - - The JsonSchemaException associated with the validation error. - - - - Gets the path of the JSON location where the validation error occurred. - - The path of the JSON location where the validation error occurred. - - - - Gets the text description corresponding to the validation error. - - The text description. - - - - - Represents the callback method that will handle JSON schema validation events and the . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - A camel case naming strategy. - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - A flag indicating whether extension data names should be processed. - - - - - Initializes a new instance of the class. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Resolves member mappings for a type, camel casing property names. - - - - - Initializes a new instance of the class. - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Used by to resolve a for a given . - - - - - Gets a value indicating whether members are being get and set using dynamic code generation. - This value is determined by the runtime permissions available. - - - true if using dynamic code generation; otherwise, false. - - - - - Gets or sets the default members search flags. - - The default members search flags. - - - - Gets or sets a value indicating whether compiler generated members should be serialized. - - - true if serialized compiler generated members; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore the interface when serializing and deserializing types. - - - true if the interface will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore the attribute when serializing and deserializing types. - - - true if the attribute will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore IsSpecified members when serializing and deserializing types. - - - true if the IsSpecified members will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore ShouldSerialize members when serializing and deserializing types. - - - true if the ShouldSerialize members will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets the naming strategy used to resolve how property names and dictionary keys are serialized. - - The naming strategy used to resolve how property names and dictionary keys are serialized. - - - - Initializes a new instance of the class. - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Gets the serializable members for the type. - - The type to get serializable members for. - The serializable members for the type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates the constructor parameters. - - The constructor to create properties for. - The type's member properties. - Properties for the given . - - - - Creates a for the given . - - The matching member property. - The constructor parameter. - A created for the given . - - - - Resolves the default for the contract. - - Type of the object. - The contract's default . - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Determines which contract type is created for the given type. - - Type of the object. - A for the given type. - - - - Creates properties for the given . - - The type to create properties for. - /// The member serialization mode for the type. - Properties for the given . - - - - Creates the used by the serializer to get and set values from a member. - - The member. - The used by the serializer to get and set values from a member. - - - - Creates a for the given . - - The member's parent . - The member to create a for. - A created for the given . - - - - Resolves the name of the property. - - Name of the property. - Resolved name of the property. - - - - Resolves the name of the extension data. By default no changes are made to extension data names. - - Name of the extension data. - Resolved name of the extension data. - - - - Resolves the key of the dictionary. By default is used to resolve dictionary keys. - - Key of the dictionary. - Resolved key of the dictionary. - - - - Gets the resolved name of the property. - - Name of the property. - Name of the property. - - - - The default naming strategy. Property names and dictionary keys are unchanged. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - The default serialization binder used when resolving and loading classes from type names. - - - - - Initializes a new instance of the class. - - - - - When overridden in a derived class, controls the binding of a serialized object to a type. - - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - The type of the object the formatter creates a new instance of. - - - - - When overridden in a derived class, controls the binding of a serialized object to a type. - - The type of the object the formatter creates a new instance of. - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - - - Provides information surrounding an error. - - - - - Gets the error. - - The error. - - - - Gets the original object that caused the error. - - The original object that caused the error. - - - - Gets the member that caused the error. - - The member that caused the error. - - - - Gets the path of the JSON location where the error occurred. - - The path of the JSON location where the error occurred. - - - - Gets or sets a value indicating whether this is handled. - - true if handled; otherwise, false. - - - - Provides data for the Error event. - - - - - Gets the current object the error event is being raised against. - - The current object the error event is being raised against. - - - - Gets the error context. - - The error context. - - - - Initializes a new instance of the class. - - The current object. - The error context. - - - - Get and set values for a using dynamic methods. - - - - - Initializes a new instance of the class. - - The member info. - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - Provides methods to get attributes. - - - - - Returns a collection of all of the attributes, or an empty collection if there are no attributes. - - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. - - The type of the attributes. - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Used by to resolve a for a given . - - - - - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Used to resolve references when serializing and deserializing JSON by the . - - - - - Resolves a reference to its object. - - The serialization context. - The reference to resolve. - The object that was resolved from the reference. - - - - Gets the reference for the specified object. - - The serialization context. - The object to get a reference for. - The reference to the object. - - - - Determines whether the specified object is referenced. - - The serialization context. - The object to test for a reference. - - true if the specified object is referenced; otherwise, false. - - - - - Adds a reference to the specified object. - - The serialization context. - The reference. - The object to reference. - - - - Allows users to control class loading and mandate what class to load. - - - - - When implemented, controls the binding of a serialized object to a type. - - Specifies the name of the serialized object. - Specifies the name of the serialized object - The type of the object the formatter creates a new instance of. - - - - When implemented, controls the binding of a serialized object to a type. - - The type of the object the formatter creates a new instance of. - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - - - Provides methods to get and set values. - - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - Contract details for a used by the . - - - - - Gets the of the collection items. - - The of the collection items. - - - - Gets a value indicating whether the collection type is a multidimensional array. - - true if the collection type is a multidimensional array; otherwise, false. - - - - Gets or sets the function used to create the object. When set this function will override . - - The function used to create the object. - - - - Gets a value indicating whether the creator has a parameter with the collection values. - - true if the creator has a parameter with the collection values; otherwise, false. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets or sets the default collection items . - - The converter. - - - - Gets or sets a value indicating whether the collection items preserve object references. - - true if collection items preserve object references; otherwise, false. - - - - Gets or sets the collection item reference loop handling. - - The reference loop handling. - - - - Gets or sets the collection item type name handling. - - The type name handling. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Sets extension data for an object during deserialization. - - The object to set extension data on. - The extension data key. - The extension data value. - - - - Gets extension data for an object during serialization. - - The object to set extension data on. - - - - Contract details for a used by the . - - - - - Gets the underlying type for the contract. - - The underlying type for the contract. - - - - Gets or sets the type created during deserialization. - - The type created during deserialization. - - - - Gets or sets whether this type contract is serialized as a reference. - - Whether this type contract is serialized as a reference. - - - - Gets or sets the default for this contract. - - The converter. - - - - Gets or sets the default creator method used to create the object. - - The default creator method used to create the object. - - - - Gets or sets a value indicating whether the default creator is non-public. - - true if the default object creator is non-public; otherwise, false. - - - - Contract details for a used by the . - - - - - Gets or sets the dictionary key resolver. - - The dictionary key resolver. - - - - Gets the of the dictionary keys. - - The of the dictionary keys. - - - - Gets the of the dictionary values. - - The of the dictionary values. - - - - Gets or sets the function used to create the object. When set this function will override . - - The function used to create the object. - - - - Gets a value indicating whether the creator has a parameter with the dictionary values. - - true if the creator has a parameter with the dictionary values; otherwise, false. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets the object's properties. - - The object's properties. - - - - Gets or sets the property name resolver. - - The property name resolver. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets or sets the object member serialization. - - The member object serialization. - - - - Gets or sets a value that indicates whether the object's properties are required. - - - A value indicating whether the object's properties are required. - - - - - Gets or sets how the object's properties with null values are handled during serialization and deserialization. - - How the object's properties with null values are handled during serialization and deserialization. - - - - Gets the object's properties. - - The object's properties. - - - - Gets a collection of instances that define the parameters used with . - - - - - Gets or sets the function used to create the object. When set this function will override . - This function is called with a collection of arguments which are defined by the collection. - - The function used to create the object. - - - - Gets or sets the extension data setter. - - - - - Gets or sets the extension data getter. - - - - - Gets or sets the extension data value type. - - - - - Gets or sets the extension data name resolver. - - The extension data name resolver. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Maps a JSON property to a .NET member or constructor parameter. - - - - - Gets or sets the name of the property. - - The name of the property. - - - - Gets or sets the type that declared this property. - - The type that declared this property. - - - - Gets or sets the order of serialization of a member. - - The numeric order of serialization. - - - - Gets or sets the name of the underlying member or parameter. - - The name of the underlying member or parameter. - - - - Gets the that will get and set the during serialization. - - The that will get and set the during serialization. - - - - Gets or sets the for this property. - - The for this property. - - - - Gets or sets the type of the property. - - The type of the property. - - - - Gets or sets the for the property. - If set this converter takes precedence over the contract converter for the property type. - - The converter. - - - - Gets or sets the member converter. - - The member converter. - - - - Gets or sets a value indicating whether this is ignored. - - true if ignored; otherwise, false. - - - - Gets or sets a value indicating whether this is readable. - - true if readable; otherwise, false. - - - - Gets or sets a value indicating whether this is writable. - - true if writable; otherwise, false. - - - - Gets or sets a value indicating whether this has a member attribute. - - true if has a member attribute; otherwise, false. - - - - Gets the default value. - - The default value. - - - - Gets or sets a value indicating whether this is required. - - A value indicating whether this is required. - - - - Gets or sets a value indicating whether this property preserves object references. - - - true if this instance is reference; otherwise, false. - - - - - Gets or sets the property null value handling. - - The null value handling. - - - - Gets or sets the property default value handling. - - The default value handling. - - - - Gets or sets the property reference loop handling. - - The reference loop handling. - - - - Gets or sets the property object creation handling. - - The object creation handling. - - - - Gets or sets or sets the type name handling. - - The type name handling. - - - - Gets or sets a predicate used to determine whether the property should be serialized. - - A predicate used to determine whether the property should be serialized. - - - - Gets or sets a predicate used to determine whether the property should be deserialized. - - A predicate used to determine whether the property should be deserialized. - - - - Gets or sets a predicate used to determine whether the property should be serialized. - - A predicate used to determine whether the property should be serialized. - - - - Gets or sets an action used to set whether the property has been deserialized. - - An action used to set whether the property has been deserialized. - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets or sets the converter used when serializing the property's collection items. - - The collection's items converter. - - - - Gets or sets whether this property's collection items are serialized as a reference. - - Whether this property's collection items are serialized as a reference. - - - - Gets or sets the type name handling used when serializing the property's collection items. - - The collection's items type name handling. - - - - Gets or sets the reference loop handling used when serializing the property's collection items. - - The collection's items reference loop handling. - - - - A collection of objects. - - - - - Initializes a new instance of the class. - - The type. - - - - When implemented in a derived class, extracts the key from the specified element. - - The element from which to extract the key. - The key for the specified element. - - - - Adds a object. - - The property to add to the collection. - - - - Gets the closest matching object. - First attempts to get an exact case match of and then - a case insensitive match. - - Name of the property. - A matching property if found. - - - - Gets a property by property name. - - The name of the property to get. - Type property name string comparison. - A matching property if found. - - - - The JSON writer. - - - - - - Gets the size of the serialize stack (amount of the objects serialized in the hierachy before the current). - - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Lookup and create an instance of the type described by the argument. - - The type to create. - Optional arguments to pass to an initializing constructor of the JsonConverter. - If null, the default constructor is used. - - - - A base class for resolving how property names and dictionary keys are serialized. - - - - - A flag indicating whether dictionary keys should be processed. - Defaults to false. - - - - - A flag indicating whether extension data names should be processed. - Defaults to false. - - - - - A flag indicating whether explicitly specified property names, - e.g. a property name customized with a , should be processed. - Defaults to false. - - - - - Gets the serialized name for a given property name. - - The initial property name. - A flag indicating whether the property has had a name explicitly specified. - The serialized property name. - - - - Gets the serialized name for a given extension data name. - - The initial extension data name. - The serialized extension data name. - - - - Gets the serialized key for a given dictionary key. - - The initial dictionary key. - The serialized dictionary key. - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Represents a method that constructs an object. - - The object type to create. - - - - When applied to a method, specifies that the method is called when an error occurs serializing an object. - - - - - Provides methods to get attributes from a , , or . - - - - - Initializes a new instance of the class. - - The instance to get attributes for. This parameter should be a , , or . - - - - Returns a collection of all of the attributes, or an empty collection if there are no attributes. - - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. - - The type of the attributes. - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Get and set values for a using reflection. - - - - - Initializes a new instance of the class. - - The member info. - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - A snake case naming strategy. - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - A flag indicating whether extension data names should be processed. - - - - - Initializes a new instance of the class. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Specifies how strings are escaped when writing JSON text. - - - - - Only control characters (e.g. newline) are escaped. - - - - - All non-ASCII and control characters (e.g. newline) are escaped. - - - - - HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped. - - - - - Indicates the method that will be used during deserialization for locating and loading assemblies. - - - - - In simple mode, the assembly used during deserialization need not match exactly the assembly used during serialization. Specifically, the version numbers need not match as the LoadWithPartialName method of the class is used to load the assembly. - - - - - In full mode, the assembly used during deserialization must match exactly the assembly used during serialization. The Load method of the class is used to load the assembly. - - - - - Specifies type name handling options for the . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - - - - Do not include the .NET type name when serializing types. - - - - - Include the .NET type name when serializing into a JSON object structure. - - - - - Include the .NET type name when serializing into a JSON array structure. - - - - - Always include the .NET type name when serializing. - - - - - Include the .NET type name when the type of the object being serialized is not the same as its declared type. - Note that this doesn't include the root serialized object by default. To include the root object's type name in JSON - you must specify a root type object with - or . - - - - - Determines whether the collection is null or empty. - - The collection. - - true if the collection is null or empty; otherwise, false. - - - - - Adds the elements of the specified collection to the specified generic . - - The list to add to. - The collection of elements to add. - - - - Converts the value to the specified type. If the value is unable to be converted, the - value is checked whether it assignable to the specified type. - - The value to convert. - The culture to use when converting. - The type to convert or cast the value to. - - The converted type. If conversion was unsuccessful, the initial value - is returned if assignable to the target type. - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic that returns a result - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic, but uses one of the arguments for - the result. - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic, but uses one of the arguments for - the result. - - - - - Returns a Restrictions object which includes our current restrictions merged - with a restriction limiting our type - - - - - Helper class for serializing immutable collections. - Note that this is used by all builds, even those that don't support immutable collections, in case the DLL is GACed - https://github.com/JamesNK/Newtonsoft.Json/issues/652 - - - - - Helper utilities. - - - - - Compares two objects data. - - The object a. - The object b. - True if both objects are equal, otherwise false. - - - - The custom value comparision callback. - - - - - The default implementation of the values comparision function. - - The object a. - The object b. - True if both objects are equal, otherwise false. - - - - Gets the type of the typed collection's items. - - The type. - The type of the typed collection's items. - - - - Gets the member's underlying type. - - The member. - The underlying type of the member. - - - - Determines whether the member is an indexed property. - - The member. - - true if the member is an indexed property; otherwise, false. - - - - - Determines whether the property is an indexed property. - - The property. - - true if the property is an indexed property; otherwise, false. - - - - - Gets the member's value on the object. - - The member. - The target object. - The member's value on the object. - - - - Sets the member's value on the target object. - - The member. - The target. - The value. - - - - Determines whether the specified MemberInfo can be read. - - The MemberInfo to determine whether can be read. - /// if set to true then allow the member to be gotten non-publicly. - - true if the specified MemberInfo can be read; otherwise, false. - - - - - Determines whether the specified MemberInfo can be set. - - The MemberInfo to determine whether can be set. - if set to true then allow the member to be set non-publicly. - if set to true then allow the member to be set if read-only. - - true if the specified MemberInfo can be set; otherwise, false. - - - - - Builds a string. Unlike this class lets you reuse its internal buffer. - - - - - Determines whether the string is all white space. Empty string will return false. - - The string to test whether it is all white space. - - true if the string is all white space; otherwise, false. - - - - - Specifies the state of the . - - - - - An exception has been thrown, which has left the in an invalid state. - You may call the method to put the in the Closed state. - Any other method calls result in an being thrown. - - - - - The method has been called. - - - - - An object is being written. - - - - - An array is being written. - - - - - A constructor is being written. - - - - - A property is being written. - - - - - A write method has not been called. - - - - diff --git a/Source/Platforms/UWP/Binaries/Project.csproj b/Source/Platforms/UWP/Binaries/Project.csproj index c6c48c200..1895423a0 100644 --- a/Source/Platforms/UWP/Binaries/Project.csproj +++ b/Source/Platforms/UWP/Binaries/Project.csproj @@ -54,6 +54,7 @@ Designer + {3} @@ -70,7 +71,7 @@ - + diff --git a/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.dll b/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.dll index 4c908a1ce..1885aed9a 100644 --- a/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.dll +++ b/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a753e82f0d128171be8df8693872615974526e41200105c2853d54283014e1d3 -size 624640 +oid sha256:c43903a192d31dac91b7744e2b1a2e8cec21983ceed630261fd7b80a433fee20 +size 635392 diff --git a/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.pdb b/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.pdb deleted file mode 100644 index ae45100f6..000000000 --- a/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.pdb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b999346525f40ccb72ecbc560462d94c8c2cce6fa515722b8f2a53f0fa334b4f -size 236420 diff --git a/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.xml b/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.xml deleted file mode 100644 index 5e472c8a0..000000000 --- a/Source/Platforms/XboxOne/Binaries/Newtonsoft.Json.xml +++ /dev/null @@ -1,10588 +0,0 @@ - - - - Newtonsoft.Json - - - - - Represents a BSON Oid (object id). - - - - - Gets or sets the value of the Oid. - - The value of the Oid. - - - - Initializes a new instance of the class. - - The Oid value. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized BSON data. - - - - - Gets or sets a value indicating whether binary data reading should be compatible with incorrect Json.NET 3.5 written binary. - - - true if binary data reading will be compatible with incorrect Json.NET 3.5 written binary; otherwise, false. - - - - - Gets or sets a value indicating whether the root object will be read as a JSON array. - - - true if the root object will be read as a JSON array; otherwise, false. - - - - - Gets or sets the used when reading values from BSON. - - The used when reading values from BSON. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - if set to true the root object will be read as a JSON array. - The used when reading values from BSON. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - if set to true the root object will be read as a JSON array. - The used when reading values from BSON. - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating BSON data. - - - - - Gets or sets the used when writing values to BSON. - When set to no conversion will occur. - - The used when writing values to BSON. - - - - Initializes a new instance of the class. - - The to write to. - - - - Initializes a new instance of the class. - - The to write to. - - - - Flushes whatever is in the buffer to the underlying and also flushes the underlying stream. - - - - - Writes the end. - - The token. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - - - - Writes the beginning of a JSON array. - - - - - Writes the beginning of a JSON object. - - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Closes this writer. - If is set to true, the underlying is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value that represents a BSON object id. - - The Object ID value to write. - - - - Writes a BSON regex. - - The regex pattern. - The regex options. - - - - Specifies how constructors are used when initializing objects during deserialization by the . - - - - - First attempt to use the public default constructor, then fall back to a single parameterized constructor, then to the non-public default constructor. - - - - - Json.NET will use a non-public default constructor before falling back to a parameterized constructor. - - - - - Converts a binary value to and from a base 64 string value. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from JSON and BSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Creates a custom object. - - The object type to convert. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Creates an object which will then be populated by the serializer. - - Type of the object. - The created object. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can write JSON. - - - true if this can write JSON; otherwise, false. - - - - - Provides a base class for converting a to and from JSON. - - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts an to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can write JSON. - - - true if this can write JSON; otherwise, false. - - - - - Converts a to and from the ISO 8601 date format (e.g. "2008-04-12T12:53Z"). - - - - - Gets or sets the date time styles used when converting a date to and from JSON. - - The date time styles used when converting a date to and from JSON. - - - - Gets or sets the date time format used when converting a date to and from JSON. - - The date time format used when converting a date to and from JSON. - - - - Gets or sets the culture used when converting a date to and from JSON. - - The culture used when converting a date to and from JSON. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Converts a to and from a JavaScript Date constructor (e.g. new Date(52231943)). - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Converts a to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from JSON and BSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts an to and from its name string value. - - - - - Gets or sets a value indicating whether the written enum text should be camel case. - The default value is false. - - true if the written enum text will be camel case; otherwise, false. - - - - Gets or sets a value indicating whether integer values are allowed when deserializing. - The default value is true. - - true if integers are allowed when deserializing; otherwise, false. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - true if the written enum text will be camel case; otherwise, false. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from Unix epoch time - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Converts a to and from a string (e.g. "1.2.3.4"). - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts XML to and from JSON. - - - - - Gets or sets the name of the root element to insert when deserializing to XML if the JSON structure has produced multiple root elements. - - The name of the deserialized root element. - - - - Gets or sets a flag to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - true if the array attribute is written to the XML; otherwise, false. - - - - Gets or sets a value indicating whether to write the root JSON object. - - true if the JSON root object is omitted; otherwise, false. - - - - Writes the JSON representation of the object. - - The to write to. - The calling serializer. - The value. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Checks if the is a namespace attribute. - - Attribute name to test. - The attribute name prefix if it has one, otherwise an empty string. - true if attribute name is for a namespace attribute, otherwise false. - - - - Determines whether this instance can convert the specified value type. - - Type of the value. - - true if this instance can convert the specified value type; otherwise, false. - - - - - Specifies how dates are formatted when writing JSON text. - - - - - Dates are written in the ISO 8601 format, e.g. "2012-03-21T05:40Z". - - - - - Dates are written in the Microsoft JSON format, e.g. "\/Date(1198908717056)\/". - - - - - Specifies how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON text. - - - - - Date formatted strings are not parsed to a date type and are read as strings. - - - - - Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . - - - - - Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . - - - - - Specifies how to treat the time value when converting between string and . - - - - - Treat as local time. If the object represents a Coordinated Universal Time (UTC), it is converted to the local time. - - - - - Treat as a UTC. If the object represents a local time, it is converted to a UTC. - - - - - Treat as a local time if a is being converted to a string. - If a string is being converted to , convert to a local time if a time zone is specified. - - - - - Time zone information should be preserved when converting. - - - - - Specifies default value handling options for the . - - - - - - - - - Include members where the member value is the same as the member's default value when serializing objects. - Included members are written to JSON. Has no effect when deserializing. - - - - - Ignore members where the member value is the same as the member's default value when serializing objects - so that it is not written to JSON. - This option will ignore all default values (e.g. null for objects and nullable types; 0 for integers, - decimals and floating point numbers; and false for booleans). The default value ignored can be changed by - placing the on the property. - - - - - Members with a default value but no JSON will be set to their default value when deserializing. - - - - - Ignore members where the member value is the same as the member's default value when serializing objects - and set members to their default value when deserializing. - - - - - Specifies float format handling options when writing special floating point numbers, e.g. , - and with . - - - - - Write special floating point values as strings in JSON, e.g. "NaN", "Infinity", "-Infinity". - - - - - Write special floating point values as symbols in JSON, e.g. NaN, Infinity, -Infinity. - Note that this will produce non-valid JSON. - - - - - Write special floating point values as the property's default value in JSON, e.g. 0.0 for a property, null for a of property. - - - - - Specifies how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - - - - - Floating point numbers are parsed to . - - - - - Floating point numbers are parsed to . - - - - - Specifies formatting options for the . - - - - - No special formatting is applied. This is the default. - - - - - Causes child objects to be indented according to the and settings. - - - - - Provides an interface for using pooled arrays. - - The array type content. - - - - Rent an array from the pool. This array must be returned when it is no longer needed. - - The minimum required length of the array. The returned array may be longer. - The rented array from the pool. This array must be returned when it is no longer needed. - - - - Return an array to the pool. - - The array that is being returned. - - - - Provides an interface to enable a class to return line and position information. - - - - - Gets a value indicating whether the class can return line information. - - - true if and can be provided; otherwise, false. - - - - - Gets the current line number. - - The current line number or 0 if no line information is available (for example, when returns false). - - - - Gets the current line position. - - The current line position or 0 if no line information is available (for example, when returns false). - - - - Instructs the how to serialize the collection. - - - - - Gets or sets a value indicating whether null items are allowed in the collection. - - true if null items are allowed in the collection; otherwise, false. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a flag indicating whether the array can contain null items. - - A flag indicating whether the array can contain null items. - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Instructs the to use the specified constructor when deserializing that object. - - - - - Instructs the how to serialize the object. - - - - - Gets or sets the id. - - The id. - - - - Gets or sets the title. - - The title. - - - - Gets or sets the description. - - The description. - - - - Gets or sets the collection's items converter. - - The collection's items converter. - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonContainer(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonContainer(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets a value that indicates whether to preserve object references. - - - true to keep object reference; otherwise, false. The default is false. - - - - - Gets or sets a value that indicates whether to preserve collection's items references. - - - true to keep collection's items object references; otherwise, false. The default is false. - - - - - Gets or sets the reference loop handling used when serializing the collection's items. - - The reference loop handling. - - - - Gets or sets the type name handling used when serializing the collection's items. - - The type name handling. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Provides methods for converting between .NET types and JSON types. - - - - - - - - Gets or sets a function that creates default . - Default settings are automatically used by serialization methods on , - and and on . - To serialize without using any default settings create a with - . - - - - - Represents JavaScript's boolean value true as a string. This field is read-only. - - - - - Represents JavaScript's boolean value false as a string. This field is read-only. - - - - - Represents JavaScript's null as a string. This field is read-only. - - - - - Represents JavaScript's undefined as a string. This field is read-only. - - - - - Represents JavaScript's positive infinity as a string. This field is read-only. - - - - - Represents JavaScript's negative infinity as a string. This field is read-only. - - - - - Represents JavaScript's NaN as a string. This field is read-only. - - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation using the specified. - - The value to convert. - The format the date will be converted to. - The time zone handling when the date is converted to a string. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation using the specified. - - The value to convert. - The format the date will be converted to. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - The string delimiter character. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - The string delimiter character. - The string escape handling. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Serializes the specified object to a JSON string. - - The object to serialize. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using formatting. - - The object to serialize. - Indicates how the output should be formatted. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a collection of . - - The object to serialize. - A collection of converters used while serializing. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using formatting and a collection of . - - The object to serialize. - Indicates how the output should be formatted. - A collection of converters used while serializing. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using . - - The object to serialize. - The used to serialize the object. - If this is null, default serialization settings will be used. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a type, formatting and . - - The object to serialize. - The used to serialize the object. - If this is null, default serialization settings will be used. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using formatting and . - - The object to serialize. - Indicates how the output should be formatted. - The used to serialize the object. - If this is null, default serialization settings will be used. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a type, formatting and . - - The object to serialize. - Indicates how the output should be formatted. - The used to serialize the object. - If this is null, default serialization settings will be used. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - A JSON string representation of the object. - - - - - Deserializes the JSON to a .NET object. - - The JSON to deserialize. - The deserialized object from the JSON string. - - - - Deserializes the JSON to a .NET object using . - - The JSON to deserialize. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type. - - The JSON to deserialize. - The of object being deserialized. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type. - - The type of the object to deserialize to. - The JSON to deserialize. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the given anonymous type. - - - The anonymous type to deserialize to. This can't be specified - traditionally and must be inferred from the anonymous type passed - as a parameter. - - The JSON to deserialize. - The anonymous type object. - The deserialized anonymous type from the JSON string. - - - - Deserializes the JSON to the given anonymous type using . - - - The anonymous type to deserialize to. This can't be specified - traditionally and must be inferred from the anonymous type passed - as a parameter. - - The JSON to deserialize. - The anonymous type object. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized anonymous type from the JSON string. - - - - Deserializes the JSON to the specified .NET type using a collection of . - - The type of the object to deserialize to. - The JSON to deserialize. - Converters to use while deserializing. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using . - - The type of the object to deserialize to. - The object to deserialize. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using a collection of . - - The JSON to deserialize. - The type of the object to deserialize. - Converters to use while deserializing. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using . - - The JSON to deserialize. - The type of the object to deserialize to. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Populates the object with values from the JSON string. - - The JSON to populate values from. - The target object to populate values onto. - - - - Populates the object with values from the JSON string using . - - The JSON to populate values from. - The target object to populate values onto. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - - - - Serializes the to a JSON string. - - The node to serialize. - A JSON string of the . - - - - Serializes the to a JSON string using formatting. - - The node to serialize. - Indicates how the output should be formatted. - A JSON string of the . - - - - Serializes the to a JSON string using formatting and omits the root object if is true. - - The node to serialize. - Indicates how the output should be formatted. - Omits writing the root object. - A JSON string of the . - - - - Deserializes the from a JSON string. - - The JSON string. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by . - - The JSON string. - The name of the root element to append when deserializing. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by - and writes a Json.NET array attribute for collections. - - The JSON string. - The name of the root element to append when deserializing. - - A flag to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - The deserialized . - - - - Serializes the to a JSON string. - - The node to convert to JSON. - A JSON string of the . - - - - Serializes the to a JSON string using formatting. - - The node to convert to JSON. - Indicates how the output should be formatted. - A JSON string of the . - - - - Serializes the to a JSON string using formatting and omits the root object if is true. - - The node to serialize. - Indicates how the output should be formatted. - Omits writing the root object. - A JSON string of the . - - - - Deserializes the from a JSON string. - - The JSON string. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by . - - The JSON string. - The name of the root element to append when deserializing. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by - and writes a Json.NET array attribute for collections. - - The JSON string. - The name of the root element to append when deserializing. - - A flag to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - The deserialized . - - - - Converts an object to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can read JSON. - - true if this can read JSON; otherwise, false. - - - - Gets a value indicating whether this can write JSON. - - true if this can write JSON; otherwise, false. - - - - Converts an object to and from JSON. - - The object type to convert. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. If there is no existing value then null will be used. - The existing value has a value. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Instructs the to use the specified when serializing the member or class. - - - - - Gets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - - - - - Initializes a new instance of the class. - - Type of the . - - - - Initializes a new instance of the class. - - Type of the . - Parameter list to use when constructing the . Can be null. - - - - Represents a collection of . - - - - - Instructs the how to serialize the collection. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - The exception thrown when an error occurs during JSON serialization or deserialization. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Instructs the to deserialize properties with no matching class member into the specified collection - and write values during serialization. - - - - - Gets or sets a value that indicates whether to write extension data when serializing the object. - - - true to write extension data when serializing the object; otherwise, false. The default is true. - - - - - Gets or sets a value that indicates whether to read extension data when deserializing the object. - - - true to read extension data when deserializing the object; otherwise, false. The default is true. - - - - - Initializes a new instance of the class. - - - - - Instructs the not to serialize the public field or public read/write property value. - - - - - Instructs the how to serialize the object. - - - - - Gets or sets the member serialization. - - The member serialization. - - - - Gets or sets how the object's properties with null values are handled during serialization and deserialization. - - How the object's properties with null values are handled during serialization and deserialization. - - - - Gets or sets a value that indicates whether the object's properties are required. - - - A value indicating whether the object's properties are required. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified member serialization. - - The member serialization. - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Instructs the to always serialize the member with the specified name. - - - - - Gets or sets the used when serializing the property's collection items. - - The collection's items . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonProperty(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonProperty(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the null value handling used when serializing this property. - - The null value handling. - - - - Gets or sets the default value handling used when serializing this property. - - The default value handling. - - - - Gets or sets the reference loop handling used when serializing this property. - - The reference loop handling. - - - - Gets or sets the object creation handling used when deserializing this property. - - The object creation handling. - - - - Gets or sets the type name handling used when serializing this property. - - The type name handling. - - - - Gets or sets whether this property's value is serialized as a reference. - - Whether this property's value is serialized as a reference. - - - - Gets or sets the order of serialization of a member. - - The numeric order of serialization. - - - - Gets or sets a value indicating whether this property is required. - - - A value indicating whether this property is required. - - - - - Gets or sets the name of the property. - - The name of the property. - - - - Gets or sets the reference loop handling used when serializing the property's collection items. - - The collection's items reference loop handling. - - - - Gets or sets the type name handling used when serializing the property's collection items. - - The collection's items type name handling. - - - - Gets or sets whether this property's collection items are serialized as a reference. - - Whether this property's collection items are serialized as a reference. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified name. - - Name of the property. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. - - - - - Asynchronously reads the next JSON token from the source. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns true if the next token was read successfully; false if there are no more tokens to read. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously skips the children of the current token. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a []. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the []. This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Specifies the state of the reader. - - - - - A read method has not been called. - - - - - The end of the file has been reached successfully. - - - - - Reader is at a property. - - - - - Reader is at the start of an object. - - - - - Reader is in an object. - - - - - Reader is at the start of an array. - - - - - Reader is in an array. - - - - - The method has been called. - - - - - Reader has just read a value. - - - - - Reader is at the start of a constructor. - - - - - Reader is in a constructor. - - - - - An error occurred that prevents the read operation from continuing. - - - - - The end of the file has been reached successfully. - - - - - Gets the current reader state. - - The current reader state. - - - - Gets or sets a value indicating whether the source should be closed when this reader is closed. - - - true to close the source when this reader is closed; otherwise false. The default is true. - - - - - Gets or sets a value indicating whether multiple pieces of JSON content can - be read from a continuous stream without erroring. - - - true to support reading multiple pieces of JSON content; otherwise false. - The default is false. - - - - - Gets the quotation mark character used to enclose the value of a string. - - - - - Gets or sets how time zones are handled when reading JSON. - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - - - - - Gets or sets how custom date formatted strings are parsed when reading JSON. - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - - - - - Gets the type of the current JSON token. - - - - - Gets the text value of the current JSON token. - - - - - Gets the .NET type for the current JSON token. - - - - - Gets the depth of the current token in the JSON document. - - The depth of the current token in the JSON document. - - - - Gets the path of the current JSON token. - - - - - Gets or sets the culture used when reading JSON. Defaults to . - - - - - Initializes a new instance of the class. - - - - - Reads the next JSON token from the source. - - true if the next token was read successfully; false if there are no more tokens to read. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a []. - - A [] or null if the next JSON token is null. This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Skips the children of the current token. - - - - - Sets the current token. - - The new token. - - - - Sets the current token and value. - - The new token. - The value. - - - - Sets the current token and value. - - The new token. - The value. - A flag indicating whether the position index inside an array should be updated. - - - - Sets the state based on current token type. - - - - - Releases unmanaged and - optionally - managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Changes the reader's state to . - If is set to true, the source is also closed. - - - - - The exception thrown when an error occurs while reading JSON text. - - - - - Gets the line number indicating where the error occurred. - - The line number indicating where the error occurred. - - - - Gets the line position indicating where the error occurred. - - The line position indicating where the error occurred. - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class - with a specified error message, JSON path, line number, line position, and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The path to the JSON where the error occurred. - The line number indicating where the error occurred. - The line position indicating where the error occurred. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Instructs the to always serialize the member, and to require that the member has a value. - - - - - The exception thrown when an error occurs during JSON serialization or deserialization. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Serializes and deserializes objects into and from the JSON format. - The enables you to control how objects are encoded into JSON. - - - - - Occurs when the errors during serialization and deserialization. - - - - - Gets or sets the used by the serializer when resolving references. - - - - - Gets or sets the used by the serializer when resolving type names. - - - - - Gets or sets the equality comparer used by the serializer when comparing references. - - The equality comparer. - - - - Gets or sets how type name writing and reading is handled by the serializer. - The default value is . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - - - - Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . - - The type name assembly format. - - - - Gets or sets how object references are preserved by the serializer. - The default value is . - - - - - Gets or sets how reference loops (e.g. a class referencing itself) is handled. - The default value is . - - - - - Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. - The default value is . - - - - - Gets or sets how null values are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how default values are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how objects are created during deserialization. - The default value is . - - The object creation handling. - - - - Gets or sets how constructors are used during deserialization. - The default value is . - - The constructor handling. - - - - Gets or sets how metadata properties are used during deserialization. - The default value is . - - The metadata properties handling. - - - - Gets a collection that will be used during serialization. - - Collection that will be used during serialization. - - - - Gets or sets the contract resolver used by the serializer when - serializing .NET objects to JSON and vice versa. - - - - - Indicates how JSON text output is formatted. - The default value is . - - - - - Gets or sets how dates are written to JSON text. - The default value is . - - - - - Gets or sets how time zones are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - The default value is . - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - The default value is . - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written as JSON text. - The default value is . - - - - - Gets or sets how strings are escaped when writing JSON text. - The default value is . - - - - - Gets or sets how and values are formatted when writing JSON text, - and the expected date format when reading JSON text. - The default value is "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK". - - - - - Gets or sets the culture used when reading JSON. - The default value is . - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - A null value means there is no maximum. - The default value is null. - - - - - Gets a value indicating whether there will be a check for additional JSON content after deserializing an object. - The default value is false. - - - true if there will be a check for additional JSON content after deserializing an object; otherwise, false. - - - - - Initializes a new instance of the class. - - - - - Clears all the cached types, attributes and methods to release references to any types of the other assemblies. - - - - - Creates a new instance. - The will not use default settings - from . - - - A new instance. - The will not use default settings - from . - - - - - Creates a new instance using the specified . - The will not use default settings - from . - - The settings to be applied to the . - - A new instance using the specified . - The will not use default settings - from . - - - - - Creates a new instance. - The will use default settings - from . - - - A new instance. - The will use default settings - from . - - - - - Creates a new instance using the specified . - The will use default settings - from as well as the specified . - - The settings to be applied to the . - - A new instance using the specified . - The will use default settings - from as well as the specified . - - - - - Populates the JSON values onto the target object. - - The that contains the JSON structure to reader values from. - The target object to populate values onto. - - - - Populates the JSON values onto the target object. - - The that contains the JSON structure to reader values from. - The target object to populate values onto. - - - - Deserializes the JSON structure contained by the specified . - - The that contains the JSON structure to deserialize. - The being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The of object being deserialized. - The instance of being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The type of the object to deserialize. - The instance of being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The of object being deserialized. - The instance of being deserialized. - - - - Serializes the specified difference to the other object of the same type. and writes the JSON structure using the specified . - - The used to write the JSON structure. - The to serialize. - The reference object. - - - - Serializes the specified difference to the other object of the same type. Writes the JSON structure using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - The reference object. - - - - Serializes the specified difference to the other object of the same type. Writes the JSON structure using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is Auto to write out the type name if the type of the value does not match. - Specifying the type is optional. - - The reference object. - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - The reference object. - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is Auto to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - - - Specifies the settings on a object. - - - - - Gets or sets how reference loops (e.g. a class referencing itself) are handled. - The default value is . - - Reference loop handling. - - - - Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. - The default value is . - - Missing member handling. - - - - Gets or sets how objects are created during deserialization. - The default value is . - - The object creation handling. - - - - Gets or sets how null values are handled during serialization and deserialization. - The default value is . - - Null value handling. - - - - Gets or sets how default values are handled during serialization and deserialization. - The default value is . - - The default value handling. - - - - Gets or sets a collection that will be used during serialization. - - The converters. - - - - Gets or sets how object references are preserved by the serializer. - The default value is . - - The preserve references handling. - - - - Gets or sets how type name writing and reading is handled by the serializer. - The default value is . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - The type name handling. - - - - Gets or sets how metadata properties are used during deserialization. - The default value is . - - The metadata properties handling. - - - - Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . - - The type name assembly format. - - - - Gets or sets how constructors are used during deserialization. - The default value is . - - The constructor handling. - - - - Gets or sets the contract resolver used by the serializer when - serializing .NET objects to JSON and vice versa. - - The contract resolver. - - - - Gets or sets the equality comparer used by the serializer when comparing references. - - The equality comparer. - - - - Gets or sets a function that creates the used by the serializer when resolving references. - - A function that creates the used by the serializer when resolving references. - - - - Gets or sets the used by the serializer when resolving type names. - - The binder. - - - - Gets or sets the error handler called during serialization and deserialization. - - The error handler called during serialization and deserialization. - - - - Gets or sets how and values are formatted when writing JSON text, - and the expected date format when reading JSON text. - The default value is "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK". - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - A null value means there is no maximum. - The default value is null. - - - - - Indicates how JSON text output is formatted. - The default value is . - - - - - Gets or sets how dates are written to JSON text. - The default value is . - - - - - Gets or sets how time zones are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - The default value is . - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written as JSON. - The default value is . - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - The default value is . - - - - - Gets or sets how strings are escaped when writing JSON text. - The default value is . - - - - - Gets or sets the culture used when reading JSON. - The default value is . - - - - - Gets a value indicating whether there will be a check for additional content after deserializing an object. - The default value is false. - - - true if there will be a check for additional content after deserializing an object; otherwise, false. - - - - - Initializes a new instance of the class. - - - - - Represents a reader that provides fast, non-cached, forward-only access to JSON text data. - - - - - Asynchronously reads the next JSON token from the source. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns true if the next token was read successfully; false if there are no more tokens to read. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a []. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the []. This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Initializes a new instance of the class with the specified . - - The containing the JSON data to read. - - - - Gets or sets the reader's character buffer pool. - - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a []. - - A [] or null if the next JSON token is null. This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Gets a value indicating whether the class can return line information. - - - true if and can be provided; otherwise, false. - - - - - Gets the current line number. - - - The current line number or 0 if no line information is available (for example, returns false). - - - - - Gets the current line position. - - - The current line position or 0 if no line information is available (for example, returns false). - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Asynchronously flushes whatever is in the buffer to the destination and also flushes the destination. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the JSON value delimiter. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the specified end token. - - The end token to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously closes this writer. - If is set to true, the destination is also closed. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of the current JSON object or array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes indent characters. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes an indent space. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes raw JSON without changing the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a null value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the beginning of a JSON array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the beginning of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the start of a constructor with the given name. - - The name of the constructor. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes an undefined value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the given white space. - - The string of white space characters. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a [] value. - - The [] value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of an array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of a constructor. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Gets or sets the writer's character array pool. - - - - - Gets or sets how many s to write for each level in the hierarchy when is set to . - - - - - Gets or sets which character to use to quote attribute values. - - - - - Gets or sets which character to use for indenting when is set to . - - - - - Gets or sets a value indicating whether object names will be surrounded with quotes. - - - - - Initializes a new instance of the class using the specified . - - The to write to. - - - - Flushes whatever is in the buffer to the underlying and also flushes the underlying . - - - - - Closes this writer. - If is set to true, the underlying is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the specified end token. - - The end token to write. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - - - - Writes indent characters. - - - - - Writes the JSON value delimiter. - - - - - Writes an indent space. - - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the given white space. - - The string of white space characters. - - - - Specifies the type of JSON token. - - - - - This is returned by the if a read method has not been called. - - - - - An object start token. - - - - - An array start token. - - - - - A constructor start token. - - - - - An object property name. - - - - - A comment. - - - - - Raw JSON. - - - - - An integer. - - - - - A float. - - - - - A string. - - - - - A boolean. - - - - - A null token. - - - - - An undefined token. - - - - - An object end token. - - - - - An array end token. - - - - - A constructor end token. - - - - - A Date. - - - - - Byte data. - - - - - - Represents a reader that provides validation. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Sets an event handler for receiving schema validation errors. - - - - - Gets the text value of the current JSON token. - - - - - - Gets the depth of the current token in the JSON document. - - The depth of the current token in the JSON document. - - - - Gets the path of the current JSON token. - - - - - Gets the quotation mark character used to enclose the value of a string. - - - - - - Gets the type of the current JSON token. - - - - - - Gets the .NET type for the current JSON token. - - - - - - Initializes a new instance of the class that - validates the content returned from the given . - - The to read from while validating. - - - - Gets or sets the schema. - - The schema. - - - - Gets the used to construct this . - - The specified in the constructor. - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a []. - - - A [] or null if the next JSON token is null. - - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Asynchronously closes this writer. - If is set to true, the destination is also closed. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously flushes whatever is in the buffer to the destination and also flushes the destination. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the specified end token. - - The end token to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes indent characters. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the JSON value delimiter. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes an indent space. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes raw JSON without changing the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of the current JSON object or array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of an array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of a constructor. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a null value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the beginning of a JSON array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the start of a constructor with the given name. - - The name of the constructor. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the beginning of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the current token. - - The to read the token from. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the current token. - - The to read the token from. - A flag indicating whether the current token's children should be written. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the token and its value. - - The to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the token and its value. - - The to write. - - The value to write. - A value is only required for tokens that have an associated value, e.g. the property name for . - null can be passed to the method for tokens that don't have a value, e.g. . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a [] value. - - The [] value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes an undefined value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the given white space. - - The string of white space characters. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously ets the state of the . - - The being written. - The value being written. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Gets or sets a value indicating whether the destination should be closed when this writer is closed. - - - true to close the destination when this writer is closed; otherwise false. The default is true. - - - - - Gets or sets a value indicating whether the JSON should be auto-completed when this writer is closed. - - - true to auto-complete the JSON when this writer is closed; otherwise false. The default is true. - - - - - Gets the top. - - The top. - - - - Gets the state of the writer. - - - - - Gets the path of the writer. - - - - - Gets or sets a value indicating how JSON text output should be formatted. - - - - - Gets or sets how dates are written to JSON text. - - - - - Gets or sets how time zones are handled when writing JSON text. - - - - - Gets or sets how strings are escaped when writing JSON text. - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written to JSON text. - - - - - Gets or sets how and values are formatted when writing JSON text. - - - - - Gets or sets the culture used when writing JSON. Defaults to . - - - - - Initializes a new instance of the class. - - - - - Flushes whatever is in the buffer to the destination and also flushes the destination. - - - - - Closes this writer. - If is set to true, the destination is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the end of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the end of an array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the end constructor. - - - - - Writes the property name of a name/value pair of a JSON object. - - The name of the property. - - - - Writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - - - - Writes the end of the current JSON object or array. - - - - - Writes the current token and its children. - - The to read the token from. - - - - Writes the current token. - - The to read the token from. - A flag indicating whether the current token's children should be written. - - - - Writes the token and its value. - - The to write. - - The value to write. - A value is only required for tokens that have an associated value, e.g. the property name for . - null can be passed to the method for tokens that don't have a value, e.g. . - - - - - Writes the token. - - The to write. - - - - Writes the specified end token. - - The end token to write. - - - - Writes indent characters. - - - - - Writes the JSON value delimiter. - - - - - Writes an indent space. - - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON without changing the writer's state. - - The raw JSON to write. - - - - Writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the given white space. - - The string of white space characters. - - - - Releases unmanaged and - optionally - managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Sets the state of the . - - The being written. - The value being written. - - - - The exception thrown when an error occurs while writing JSON text. - - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class - with a specified error message, JSON path and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The path to the JSON where the error occurred. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Specifies how JSON comments are handled when loading JSON. - - - - - Ignore comments. - - - - - Load comments as a with type . - - - - - Contains the LINQ to JSON extension methods. - - - - - Returns a collection of tokens that contains the ancestors of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains the ancestors of every token in the source collection. - - - - Returns a collection of tokens that contains every token in the source collection, and the ancestors of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains every token in the source collection, the ancestors of every token in the source collection. - - - - Returns a collection of tokens that contains the descendants of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains the descendants of every token in the source collection. - - - - Returns a collection of tokens that contains every token in the source collection, and the descendants of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains every token in the source collection, and the descendants of every token in the source collection. - - - - Returns a collection of child properties of every object in the source collection. - - An of that contains the source collection. - An of that contains the properties of every object in the source collection. - - - - Returns a collection of child values of every object in the source collection with the given key. - - An of that contains the source collection. - The token key. - An of that contains the values of every token in the source collection with the given key. - - - - Returns a collection of child values of every object in the source collection. - - An of that contains the source collection. - An of that contains the values of every token in the source collection. - - - - Returns a collection of converted child values of every object in the source collection with the given key. - - The type to convert the values to. - An of that contains the source collection. - The token key. - An that contains the converted values of every token in the source collection with the given key. - - - - Returns a collection of converted child values of every object in the source collection. - - The type to convert the values to. - An of that contains the source collection. - An that contains the converted values of every token in the source collection. - - - - Converts the value. - - The type to convert the value to. - A cast as a of . - A converted value. - - - - Converts the value. - - The source collection type. - The type to convert the value to. - A cast as a of . - A converted value. - - - - Returns a collection of child tokens of every array in the source collection. - - The source collection type. - An of that contains the source collection. - An of that contains the values of every token in the source collection. - - - - Returns a collection of converted child tokens of every array in the source collection. - - An of that contains the source collection. - The type to convert the values to. - The source collection type. - An that contains the converted values of every token in the source collection. - - - - Returns the input typed as . - - An of that contains the source collection. - The input typed as . - - - - Returns the input typed as . - - The source collection type. - An of that contains the source collection. - The input typed as . - - - - Represents a collection of objects. - - The type of token. - - - - Gets the of with the specified key. - - - - - - Represents a JSON array. - - - - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous load. The property contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous load. The property contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified content. - - The contents of the array. - - - - Initializes a new instance of the class with the specified content. - - The contents of the array. - - - - Loads an from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads an from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - - - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - - - - - - Creates a from an object. - - The object that will be used to create . - A with the values of the specified object. - - - - Creates a from an object. - - The object that will be used to create . - The that will be used to read the object. - A with the values of the specified object. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets or sets the at the specified index. - - - - - - Determines the index of a specific item in the . - - The object to locate in the . - - The index of if found in the list; otherwise, -1. - - - - - Inserts an item to the at the specified index. - - The zero-based index at which should be inserted. - The object to insert into the . - - is not a valid index in the . - - - - - Removes the item at the specified index. - - The zero-based index of the item to remove. - - is not a valid index in the . - - - - - Returns an enumerator that iterates through the collection. - - - A of that can be used to iterate through the collection. - - - - - Adds an item to the . - - The object to add to the . - - - - Removes all items from the . - - - - - Determines whether the contains a specific value. - - The object to locate in the . - - true if is found in the ; otherwise, false. - - - - - Copies the elements of the to an array, starting at a particular array index. - - The array. - Index of the array. - - - - Gets a value indicating whether the is read-only. - - true if the is read-only; otherwise, false. - - - - Removes the first occurrence of a specific object from the . - - The object to remove from the . - - true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - - - - - Represents a JSON constructor. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets or sets the name of this constructor. - - The constructor name. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified name and content. - - The constructor name. - The contents of the constructor. - - - - Initializes a new instance of the class with the specified name and content. - - The constructor name. - The contents of the constructor. - - - - Initializes a new instance of the class with the specified name. - - The constructor name. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified key. - - The with the specified key. - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Represents a token that can contain other tokens. - - - - - Occurs when the list changes or an item in the list changes. - - - - - Occurs before an item is added to the collection. - - - - - Occurs when the items list of the collection has changed, or the collection is reset. - - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Raises the event. - - The instance containing the event data. - - - - Raises the event. - - The instance containing the event data. - - - - Raises the event. - - The instance containing the event data. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Get the first child token of this token. - - - A containing the first child token of the . - - - - - Get the last child token of this token. - - - A containing the last child token of the . - - - - - Returns a collection of the child tokens of this token, in document order. - - - An of containing the child tokens of this , in document order. - - - - - Returns a collection of the child values of this token, in document order. - - The type to convert the values to. - - A containing the child values of this , in document order. - - - - - Returns a collection of the descendant tokens for this token in document order. - - An of containing the descendant tokens of the . - - - - Returns a collection of the tokens that contain this token, and all descendant tokens of this token, in document order. - - An of containing this token, and all the descendant tokens of the . - - - - Adds the specified content as children of this . - - The content to be added. - - - - Adds the specified content as the first children of this . - - The content to be added. - - - - Creates a that can be used to add tokens to the . - - A that is ready to have content written to it. - - - - Replaces the child nodes of this token with the specified content. - - The content. - - - - Removes the child nodes from this token. - - - - - Merge the specified content into this . - - The content to be merged. - - - - Merge the specified content into this using . - - The content to be merged. - The used to merge the content. - - - - Gets the count of child JSON tokens. - - The count of child JSON tokens. - - - - Represents a collection of objects. - - The type of token. - - - - An empty collection of objects. - - - - - Initializes a new instance of the struct. - - The enumerable. - - - - Returns an enumerator that can be used to iterate through the collection. - - - A that can be used to iterate through the collection. - - - - - Gets the of with the specified key. - - - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Represents a JSON object. - - - - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Occurs when a property value changes. - - - - - Occurs when a property value is changing. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified content. - - The contents of the object. - - - - Initializes a new instance of the class with the specified content. - - The contents of the object. - - - - Gets the node type for this . - - The type. - - - - Gets an of of this object's properties. - - An of of this object's properties. - - - - Gets a the specified name. - - The property name. - A with the specified name or null. - - - - Gets a of of this object's property values. - - A of of this object's property values. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets or sets the with the specified property name. - - - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - is not valid JSON. - - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - is not valid JSON. - - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - is not valid JSON. - - - - - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - is not valid JSON. - - - - - - - - Creates a from an object. - - The object that will be used to create . - A with the values of the specified object. - - - - Creates a from an object. - - The object that will be used to create . - The that will be used to read the object. - A with the values of the specified object. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified property name. - - Name of the property. - The with the specified property name. - - - - Gets the with the specified property name. - The exact property name will be searched for first and if no matching property is found then - the will be used to match a property. - - Name of the property. - One of the enumeration values that specifies how the strings will be compared. - The with the specified property name. - - - - Tries to get the with the specified property name. - The exact property name will be searched for first and if no matching property is found then - the will be used to match a property. - - Name of the property. - The value. - One of the enumeration values that specifies how the strings will be compared. - true if a value was successfully retrieved; otherwise, false. - - - - Adds the specified property name. - - Name of the property. - The value. - - - - Determines whether the JSON object has the specified property name. - - Name of the property. - true if the JSON object has the specified property name; otherwise, false. - - - - Removes the property with the specified name. - - Name of the property. - true if item was successfully removed; otherwise, false. - - - - Tries to get the with the specified property name. - - Name of the property. - The value. - true if a value was successfully retrieved; otherwise, false. - - - - Returns an enumerator that can be used to iterate through the collection. - - - A that can be used to iterate through the collection. - - - - - Raises the event with the provided arguments. - - Name of the property. - - - - Raises the event with the provided arguments. - - Name of the property. - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Represents a JSON property. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets the property name. - - The property name. - - - - Gets or sets the property value. - - The property value. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - The property name. - The property content. - - - - Initializes a new instance of the class. - - The property name. - The property content. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Represents a view of a . - - - - - Initializes a new instance of the class. - - The name. - - - - When overridden in a derived class, returns whether resetting an object changes its value. - - - true if resetting the component changes its value; otherwise, false. - - The component to test for reset capability. - - - - When overridden in a derived class, gets the current value of the property on a component. - - - The value of a property for a given component. - - The component with the property for which to retrieve the value. - - - - When overridden in a derived class, resets the value for this property of the component to the default value. - - The component with the property value that is to be reset to the default value. - - - - When overridden in a derived class, sets the value of the component to a different value. - - The component with the property value that is to be set. - The new value. - - - - When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted. - - - true if the property should be persisted; otherwise, false. - - The component with the property to be examined for persistence. - - - - When overridden in a derived class, gets the type of the component this property is bound to. - - - A that represents the type of component this property is bound to. - When the or - - methods are invoked, the object specified might be an instance of this type. - - - - - When overridden in a derived class, gets a value indicating whether this property is read-only. - - - true if the property is read-only; otherwise, false. - - - - - When overridden in a derived class, gets the type of the property. - - - A that represents the type of the property. - - - - - Gets the hash code for the name of the member. - - - - The hash code for the name of the member. - - - - - Represents a raw JSON string. - - - - - Asynchronously creates an instance of with the content of the reader's current token. - - The reader. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns an instance of with the content of the reader's current token. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class. - - The raw json. - - - - Creates an instance of with the content of the reader's current token. - - The reader. - An instance of with the content of the reader's current token. - - - - Specifies the settings used when loading JSON. - - - - - Initializes a new instance of the class. - - - - - Gets or sets how JSON comments are handled when loading JSON. - - The JSON comment handling. - - - - Gets or sets how JSON line info is handled when loading JSON. - - The JSON line info handling. - - - - Specifies the settings used when merging JSON. - - - - - Gets or sets the method used when merging JSON arrays. - - The method used when merging JSON arrays. - - - - Gets or sets how null value properties are merged. - - How null value properties are merged. - - - - Represents an abstract JSON token. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Writes this token to a asynchronously. - - A into which this method will write. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously creates a from a . - - An positioned at the token to read into this . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains - the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - An positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains - the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - A positioned at the token to read into this . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - A positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Gets a comparer that can compare two tokens for value equality. - - A that can compare two nodes for value equality. - - - - Gets or sets the parent. - - The parent. - - - - Gets the root of this . - - The root of this . - - - - Gets the node type for this . - - The type. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Compares the values of two tokens, including the values of all descendant tokens. - - The first to compare. - The second to compare. - true if the tokens are equal; otherwise false. - - - - Gets the next sibling token of this node. - - The that contains the next sibling token. - - - - Gets the previous sibling token of this node. - - The that contains the previous sibling token. - - - - Gets the path of the JSON token. - - - - - Adds the specified content immediately after this token. - - A content object that contains simple content or a collection of content objects to be added after this token. - - - - Adds the specified content immediately before this token. - - A content object that contains simple content or a collection of content objects to be added before this token. - - - - Returns a collection of the ancestor tokens of this token. - - A collection of the ancestor tokens of this token. - - - - Returns a collection of tokens that contain this token, and the ancestors of this token. - - A collection of tokens that contain this token, and the ancestors of this token. - - - - Returns a collection of the sibling tokens after this token, in document order. - - A collection of the sibling tokens after this tokens, in document order. - - - - Returns a collection of the sibling tokens before this token, in document order. - - A collection of the sibling tokens before this token, in document order. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets the with the specified key converted to the specified type. - - The type to convert the token to. - The token key. - The converted token value. - - - - Get the first child token of this token. - - A containing the first child token of the . - - - - Get the last child token of this token. - - A containing the last child token of the . - - - - Returns a collection of the child tokens of this token, in document order. - - An of containing the child tokens of this , in document order. - - - - Returns a collection of the child tokens of this token, in document order, filtered by the specified type. - - The type to filter the child tokens on. - A containing the child tokens of this , in document order. - - - - Returns a collection of the child values of this token, in document order. - - The type to convert the values to. - A containing the child values of this , in document order. - - - - Removes this token from its parent. - - - - - Replaces this token with the specified token. - - The value. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Returns the indented JSON for this token. - - - The indented JSON for this token. - - - - - Returns the JSON for this token using the given formatting and converters. - - Indicates how the output should be formatted. - A collection of s which will be used when writing the token. - The JSON for this token using the given formatting and converters. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to []. - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from [] to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Creates a for this token. - - A that can be used to read this token and its descendants. - - - - Creates a from an object. - - The object that will be used to create . - A with the value of the specified object. - - - - Creates a from an object using the specified . - - The object that will be used to create . - The that will be used when reading the object. - A with the value of the specified object. - - - - Creates an instance of the specified .NET type from the . - - The object type that the token will be deserialized to. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the . - - The object type that the token will be deserialized to. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the using the specified . - - The object type that the token will be deserialized to. - The that will be used when creating the object. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the using the specified . - - The object type that the token will be deserialized to. - The that will be used when creating the object. - The new object created from the JSON value. - - - - Creates a from a . - - A positioned at the token to read into this . - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Creates a from a . - - An positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - - - Creates a from a . - - A positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Creates a from a . - - A positioned at the token to read into this . - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Selects a using a JPath expression. Selects the token that matches the object path. - - - A that contains a JPath expression. - - A , or null. - - - - Selects a using a JPath expression. Selects the token that matches the object path. - - - A that contains a JPath expression. - - A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. - A . - - - - Selects a collection of elements using a JPath expression. - - - A that contains a JPath expression. - - An of that contains the selected elements. - - - - Selects a collection of elements using a JPath expression. - - - A that contains a JPath expression. - - A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. - An of that contains the selected elements. - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Creates a new instance of the . All child tokens are recursively cloned. - - A new instance of the . - - - - Adds an object to the annotation list of this . - - The annotation to add. - - - - Get the first annotation object of the specified type from this . - - The type of the annotation to retrieve. - The first annotation object that matches the specified type, or null if no annotation is of the specified type. - - - - Gets the first annotation object of the specified type from this . - - The of the annotation to retrieve. - The first annotation object that matches the specified type, or null if no annotation is of the specified type. - - - - Gets a collection of annotations of the specified type for this . - - The type of the annotations to retrieve. - An that contains the annotations for this . - - - - Gets a collection of annotations of the specified type for this . - - The of the annotations to retrieve. - An of that contains the annotations that match the specified type for this . - - - - Removes the annotations of the specified type from this . - - The type of annotations to remove. - - - - Removes the annotations of the specified type from this . - - The of annotations to remove. - - - - Compares tokens to determine whether they are equal. - - - - - Determines whether the specified objects are equal. - - The first object of type to compare. - The second object of type to compare. - - true if the specified objects are equal; otherwise, false. - - - - - Returns a hash code for the specified object. - - The for which a hash code is to be returned. - A hash code for the specified object. - The type of is a reference type and is null. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. - - - - - Gets the at the reader's current position. - - - - - Initializes a new instance of the class. - - The token to read from. - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Gets the path of the current JSON token. - - - - - Specifies the type of token. - - - - - No token type has been set. - - - - - A JSON object. - - - - - A JSON array. - - - - - A JSON constructor. - - - - - A JSON object property. - - - - - A comment. - - - - - An integer value. - - - - - A float value. - - - - - A string value. - - - - - A boolean value. - - - - - A null value. - - - - - An undefined value. - - - - - A date value. - - - - - A raw JSON value. - - - - - A collection of bytes value. - - - - - A Guid value. - - - - - A Uri value. - - - - - A TimeSpan value. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Gets the at the writer's current position. - - - - - Gets the token being written. - - The token being written. - - - - Initializes a new instance of the class writing to the given . - - The container being written to. - - - - Initializes a new instance of the class. - - - - - Flushes whatever is in the buffer to the underlying . - - - - - Closes this writer. - If is set to true, the JSON is auto-completed. - - - Setting to true has no additional effect, since the underlying is a type that cannot be closed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the end. - - The token. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Writes a value. - An error will be raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Represents a value in JSON (string, integer, date, etc). - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Creates a comment with the given value. - - The value. - A comment with the given value. - - - - Creates a string with the given value. - - The value. - A string with the given value. - - - - Creates a null value. - - A null value. - - - - Creates a undefined value. - - A undefined value. - - - - Gets the node type for this . - - The type. - - - - Gets or sets the underlying token value. - - The underlying token value. - - - - Writes this token to a . - - A into which this method will write. - A collection of s which will be used when writing the token. - - - - Indicates whether the current object is equal to another object of the same type. - - - true if the current object is equal to the parameter; otherwise, false. - - An object to compare with this object. - - - - Determines whether the specified is equal to the current . - - The to compare with the current . - - true if the specified is equal to the current ; otherwise, false. - - - - - Serves as a hash function for a particular type. - - - A hash code for the current . - - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format. - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format provider. - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format. - The format provider. - - A that represents this instance. - - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - - An object to compare with this instance. - - A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: - Value - Meaning - Less than zero - This instance is less than . - Zero - This instance is equal to . - Greater than zero - This instance is greater than . - - - is not of the same type as this instance. - - - - - Specifies how line information is handled when loading JSON. - - - - - Ignore line information. - - - - - Load line information. - - - - - Specifies how JSON arrays are merged together. - - - - Concatenate arrays. - - - Union arrays, skipping items that already exist. - - - Replace all array items. - - - Merge array items together, matched by index. - - - - Specifies how null value properties are merged. - - - - - The content's null value properties will be ignored during merging. - - - - - The content's null value properties will be merged. - - - - - Specifies the member serialization options for the . - - - - - All public members are serialized by default. Members can be excluded using or . - This is the default member serialization mode. - - - - - Only members marked with or are serialized. - This member serialization mode can also be set by marking the class with . - - - - - All public and private fields are serialized. Members can be excluded using or . - This member serialization mode can also be set by marking the class with - and setting IgnoreSerializableAttribute on to false. - - - - - Specifies metadata property handling options for the . - - - - - Read metadata properties located at the start of a JSON object. - - - - - Read metadata properties located anywhere in a JSON object. Note that this setting will impact performance. - - - - - Do not try to read metadata properties. - - - - - Specifies missing member handling options for the . - - - - - Ignore a missing member and do not attempt to deserialize it. - - - - - Throw a when a missing member is encountered during deserialization. - - - - - Specifies null value handling options for the . - - - - - - - - - Include null values when serializing and deserializing objects. - - - - - Ignore null values when serializing and deserializing objects. - - - - - Specifies how object creation is handled by the . - - - - - Reuse existing objects, create new objects when needed. - - - - - Only reuse existing objects. - - - - - Always create new objects. - - - - - Specifies reference handling options for the . - Note that references cannot be preserved when a value is set via a non-default constructor such as types that implement . - - - - - - - - Do not preserve references when serializing types. - - - - - Preserve references when serializing into a JSON object structure. - - - - - Preserve references when serializing into a JSON array structure. - - - - - Preserve references when serializing. - - - - - Specifies reference loop handling options for the . - - - - - Throw a when a loop is encountered. - - - - - Ignore loop references and do not serialize. - - - - - Serialize loop references. - - - - - Indicating whether a property is required. - - - - - The property is not required. The default state. - - - - - The property must be defined in JSON but can be a null value. - - - - - The property must be defined in JSON and cannot be a null value. - - - - - The property is not required but it cannot be a null value. - - - - - - Contains the JSON schema extension methods. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - - Determines whether the is valid. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - - true if the specified is valid; otherwise, false. - - - - - - Determines whether the is valid. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - When this method returns, contains any error messages generated while validating. - - true if the specified is valid; otherwise, false. - - - - - - Validates the specified . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - - - - - Validates the specified . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - The validation event handler. - - - - - An in-memory representation of a JSON Schema. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets the id. - - - - - Gets or sets the title. - - - - - Gets or sets whether the object is required. - - - - - Gets or sets whether the object is read-only. - - - - - Gets or sets whether the object is visible to users. - - - - - Gets or sets whether the object is transient. - - - - - Gets or sets the description of the object. - - - - - Gets or sets the types of values allowed by the object. - - The type. - - - - Gets or sets the pattern. - - The pattern. - - - - Gets or sets the minimum length. - - The minimum length. - - - - Gets or sets the maximum length. - - The maximum length. - - - - Gets or sets a number that the value should be divisible by. - - A number that the value should be divisible by. - - - - Gets or sets the minimum. - - The minimum. - - - - Gets or sets the maximum. - - The maximum. - - - - Gets or sets a flag indicating whether the value can not equal the number defined by the minimum attribute (). - - A flag indicating whether the value can not equal the number defined by the minimum attribute (). - - - - Gets or sets a flag indicating whether the value can not equal the number defined by the maximum attribute (). - - A flag indicating whether the value can not equal the number defined by the maximum attribute (). - - - - Gets or sets the minimum number of items. - - The minimum number of items. - - - - Gets or sets the maximum number of items. - - The maximum number of items. - - - - Gets or sets the of items. - - The of items. - - - - Gets or sets a value indicating whether items in an array are validated using the instance at their array position from . - - - true if items are validated using their array position; otherwise, false. - - - - - Gets or sets the of additional items. - - The of additional items. - - - - Gets or sets a value indicating whether additional items are allowed. - - - true if additional items are allowed; otherwise, false. - - - - - Gets or sets whether the array items must be unique. - - - - - Gets or sets the of properties. - - The of properties. - - - - Gets or sets the of additional properties. - - The of additional properties. - - - - Gets or sets the pattern properties. - - The pattern properties. - - - - Gets or sets a value indicating whether additional properties are allowed. - - - true if additional properties are allowed; otherwise, false. - - - - - Gets or sets the required property if this property is present. - - The required property if this property is present. - - - - Gets or sets the a collection of valid enum values allowed. - - A collection of valid enum values allowed. - - - - Gets or sets disallowed types. - - The disallowed types. - - - - Gets or sets the default value. - - The default value. - - - - Gets or sets the collection of that this schema extends. - - The collection of that this schema extends. - - - - Gets or sets the format. - - The format. - - - - Initializes a new instance of the class. - - - - - Reads a from the specified . - - The containing the JSON Schema to read. - The object representing the JSON Schema. - - - - Reads a from the specified . - - The containing the JSON Schema to read. - The to use when resolving schema references. - The object representing the JSON Schema. - - - - Load a from a string that contains JSON Schema. - - A that contains JSON Schema. - A populated from the string that contains JSON Schema. - - - - Load a from a string that contains JSON Schema using the specified . - - A that contains JSON Schema. - The resolver. - A populated from the string that contains JSON Schema. - - - - Writes this schema to a . - - A into which this method will write. - - - - Writes this schema to a using the specified . - - A into which this method will write. - The resolver used. - - - - Returns a that represents the current . - - - A that represents the current . - - - - - - Returns detailed information about the schema exception. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets the line number indicating where the error occurred. - - The line number indicating where the error occurred. - - - - Gets the line position indicating where the error occurred. - - The line position indicating where the error occurred. - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - - Generates a from a specified . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets how undefined schemas are handled by the serializer. - - - - - Gets or sets the contract resolver. - - The contract resolver. - - - - Generate a from the specified type. - - The type to generate a from. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - The used to resolve schema references. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - Specify whether the generated root will be nullable. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - The used to resolve schema references. - Specify whether the generated root will be nullable. - A generated from the specified type. - - - - - Resolves from an id. - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets the loaded schemas. - - The loaded schemas. - - - - Initializes a new instance of the class. - - - - - Gets a for the specified reference. - - The id. - A for the specified reference. - - - - - The value types allowed by the . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - No type specified. - - - - - String type. - - - - - Float type. - - - - - Integer type. - - - - - Boolean type. - - - - - Object type. - - - - - Array type. - - - - - Null type. - - - - - Any type. - - - - - - Specifies undefined schema Id handling options for the . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Do not infer a schema Id. - - - - - Use the .NET type name as the schema Id. - - - - - Use the assembly qualified .NET type name as the schema Id. - - - - - - Returns detailed information related to the . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets the associated with the validation error. - - The JsonSchemaException associated with the validation error. - - - - Gets the path of the JSON location where the validation error occurred. - - The path of the JSON location where the validation error occurred. - - - - Gets the text description corresponding to the validation error. - - The text description. - - - - - Represents the callback method that will handle JSON schema validation events and the . - - - JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. - - - - - - A camel case naming strategy. - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - A flag indicating whether extension data names should be processed. - - - - - Initializes a new instance of the class. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Resolves member mappings for a type, camel casing property names. - - - - - Initializes a new instance of the class. - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Used by to resolve a for a given . - - - - - Gets a value indicating whether members are being get and set using dynamic code generation. - This value is determined by the runtime permissions available. - - - true if using dynamic code generation; otherwise, false. - - - - - Gets or sets the default members search flags. - - The default members search flags. - - - - Gets or sets a value indicating whether compiler generated members should be serialized. - - - true if serialized compiler generated members; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore the interface when serializing and deserializing types. - - - true if the interface will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore the attribute when serializing and deserializing types. - - - true if the attribute will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore IsSpecified members when serializing and deserializing types. - - - true if the IsSpecified members will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore ShouldSerialize members when serializing and deserializing types. - - - true if the ShouldSerialize members will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets the naming strategy used to resolve how property names and dictionary keys are serialized. - - The naming strategy used to resolve how property names and dictionary keys are serialized. - - - - Initializes a new instance of the class. - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Gets the serializable members for the type. - - The type to get serializable members for. - The serializable members for the type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates the constructor parameters. - - The constructor to create properties for. - The type's member properties. - Properties for the given . - - - - Creates a for the given . - - The matching member property. - The constructor parameter. - A created for the given . - - - - Resolves the default for the contract. - - Type of the object. - The contract's default . - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Determines which contract type is created for the given type. - - Type of the object. - A for the given type. - - - - Creates properties for the given . - - The type to create properties for. - /// The member serialization mode for the type. - Properties for the given . - - - - Creates the used by the serializer to get and set values from a member. - - The member. - The used by the serializer to get and set values from a member. - - - - Creates a for the given . - - The member's parent . - The member to create a for. - A created for the given . - - - - Resolves the name of the property. - - Name of the property. - Resolved name of the property. - - - - Resolves the name of the extension data. By default no changes are made to extension data names. - - Name of the extension data. - Resolved name of the extension data. - - - - Resolves the key of the dictionary. By default is used to resolve dictionary keys. - - Key of the dictionary. - Resolved key of the dictionary. - - - - Gets the resolved name of the property. - - Name of the property. - Name of the property. - - - - The default naming strategy. Property names and dictionary keys are unchanged. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - The default serialization binder used when resolving and loading classes from type names. - - - - - Initializes a new instance of the class. - - - - - When overridden in a derived class, controls the binding of a serialized object to a type. - - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - The type of the object the formatter creates a new instance of. - - - - - When overridden in a derived class, controls the binding of a serialized object to a type. - - The type of the object the formatter creates a new instance of. - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - - - Provides information surrounding an error. - - - - - Gets the error. - - The error. - - - - Gets the original object that caused the error. - - The original object that caused the error. - - - - Gets the member that caused the error. - - The member that caused the error. - - - - Gets the path of the JSON location where the error occurred. - - The path of the JSON location where the error occurred. - - - - Gets or sets a value indicating whether this is handled. - - true if handled; otherwise, false. - - - - Provides data for the Error event. - - - - - Gets the current object the error event is being raised against. - - The current object the error event is being raised against. - - - - Gets the error context. - - The error context. - - - - Initializes a new instance of the class. - - The current object. - The error context. - - - - Get and set values for a using dynamic methods. - - - - - Initializes a new instance of the class. - - The member info. - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - Provides methods to get attributes. - - - - - Returns a collection of all of the attributes, or an empty collection if there are no attributes. - - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. - - The type of the attributes. - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Used by to resolve a for a given . - - - - - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Used to resolve references when serializing and deserializing JSON by the . - - - - - Resolves a reference to its object. - - The serialization context. - The reference to resolve. - The object that was resolved from the reference. - - - - Gets the reference for the specified object. - - The serialization context. - The object to get a reference for. - The reference to the object. - - - - Determines whether the specified object is referenced. - - The serialization context. - The object to test for a reference. - - true if the specified object is referenced; otherwise, false. - - - - - Adds a reference to the specified object. - - The serialization context. - The reference. - The object to reference. - - - - Allows users to control class loading and mandate what class to load. - - - - - When implemented, controls the binding of a serialized object to a type. - - Specifies the name of the serialized object. - Specifies the name of the serialized object - The type of the object the formatter creates a new instance of. - - - - When implemented, controls the binding of a serialized object to a type. - - The type of the object the formatter creates a new instance of. - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - - - Provides methods to get and set values. - - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - Contract details for a used by the . - - - - - Gets the of the collection items. - - The of the collection items. - - - - Gets a value indicating whether the collection type is a multidimensional array. - - true if the collection type is a multidimensional array; otherwise, false. - - - - Gets or sets the function used to create the object. When set this function will override . - - The function used to create the object. - - - - Gets a value indicating whether the creator has a parameter with the collection values. - - true if the creator has a parameter with the collection values; otherwise, false. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets or sets the default collection items . - - The converter. - - - - Gets or sets a value indicating whether the collection items preserve object references. - - true if collection items preserve object references; otherwise, false. - - - - Gets or sets the collection item reference loop handling. - - The reference loop handling. - - - - Gets or sets the collection item type name handling. - - The type name handling. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Sets extension data for an object during deserialization. - - The object to set extension data on. - The extension data key. - The extension data value. - - - - Gets extension data for an object during serialization. - - The object to set extension data on. - - - - Contract details for a used by the . - - - - - Gets the underlying type for the contract. - - The underlying type for the contract. - - - - Gets or sets the type created during deserialization. - - The type created during deserialization. - - - - Gets or sets whether this type contract is serialized as a reference. - - Whether this type contract is serialized as a reference. - - - - Gets or sets the default for this contract. - - The converter. - - - - Gets or sets the default creator method used to create the object. - - The default creator method used to create the object. - - - - Gets or sets a value indicating whether the default creator is non-public. - - true if the default object creator is non-public; otherwise, false. - - - - Contract details for a used by the . - - - - - Gets or sets the dictionary key resolver. - - The dictionary key resolver. - - - - Gets the of the dictionary keys. - - The of the dictionary keys. - - - - Gets the of the dictionary values. - - The of the dictionary values. - - - - Gets or sets the function used to create the object. When set this function will override . - - The function used to create the object. - - - - Gets a value indicating whether the creator has a parameter with the dictionary values. - - true if the creator has a parameter with the dictionary values; otherwise, false. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets the object's properties. - - The object's properties. - - - - Gets or sets the property name resolver. - - The property name resolver. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets or sets the object member serialization. - - The member object serialization. - - - - Gets or sets a value that indicates whether the object's properties are required. - - - A value indicating whether the object's properties are required. - - - - - Gets or sets how the object's properties with null values are handled during serialization and deserialization. - - How the object's properties with null values are handled during serialization and deserialization. - - - - Gets the object's properties. - - The object's properties. - - - - Gets a collection of instances that define the parameters used with . - - - - - Gets or sets the function used to create the object. When set this function will override . - This function is called with a collection of arguments which are defined by the collection. - - The function used to create the object. - - - - Gets or sets the extension data setter. - - - - - Gets or sets the extension data getter. - - - - - Gets or sets the extension data value type. - - - - - Gets or sets the extension data name resolver. - - The extension data name resolver. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Maps a JSON property to a .NET member or constructor parameter. - - - - - Gets or sets the name of the property. - - The name of the property. - - - - Gets or sets the type that declared this property. - - The type that declared this property. - - - - Gets or sets the order of serialization of a member. - - The numeric order of serialization. - - - - Gets or sets the name of the underlying member or parameter. - - The name of the underlying member or parameter. - - - - Gets the that will get and set the during serialization. - - The that will get and set the during serialization. - - - - Gets or sets the for this property. - - The for this property. - - - - Gets or sets the type of the property. - - The type of the property. - - - - Gets or sets the for the property. - If set this converter takes precedence over the contract converter for the property type. - - The converter. - - - - Gets or sets the member converter. - - The member converter. - - - - Gets or sets a value indicating whether this is ignored. - - true if ignored; otherwise, false. - - - - Gets or sets a value indicating whether this is readable. - - true if readable; otherwise, false. - - - - Gets or sets a value indicating whether this is writable. - - true if writable; otherwise, false. - - - - Gets or sets a value indicating whether this has a member attribute. - - true if has a member attribute; otherwise, false. - - - - Gets the default value. - - The default value. - - - - Gets or sets a value indicating whether this is required. - - A value indicating whether this is required. - - - - Gets or sets a value indicating whether this property preserves object references. - - - true if this instance is reference; otherwise, false. - - - - - Gets or sets the property null value handling. - - The null value handling. - - - - Gets or sets the property default value handling. - - The default value handling. - - - - Gets or sets the property reference loop handling. - - The reference loop handling. - - - - Gets or sets the property object creation handling. - - The object creation handling. - - - - Gets or sets or sets the type name handling. - - The type name handling. - - - - Gets or sets a predicate used to determine whether the property should be serialized. - - A predicate used to determine whether the property should be serialized. - - - - Gets or sets a predicate used to determine whether the property should be deserialized. - - A predicate used to determine whether the property should be deserialized. - - - - Gets or sets a predicate used to determine whether the property should be serialized. - - A predicate used to determine whether the property should be serialized. - - - - Gets or sets an action used to set whether the property has been deserialized. - - An action used to set whether the property has been deserialized. - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets or sets the converter used when serializing the property's collection items. - - The collection's items converter. - - - - Gets or sets whether this property's collection items are serialized as a reference. - - Whether this property's collection items are serialized as a reference. - - - - Gets or sets the type name handling used when serializing the property's collection items. - - The collection's items type name handling. - - - - Gets or sets the reference loop handling used when serializing the property's collection items. - - The collection's items reference loop handling. - - - - A collection of objects. - - - - - Initializes a new instance of the class. - - The type. - - - - When implemented in a derived class, extracts the key from the specified element. - - The element from which to extract the key. - The key for the specified element. - - - - Adds a object. - - The property to add to the collection. - - - - Gets the closest matching object. - First attempts to get an exact case match of and then - a case insensitive match. - - Name of the property. - A matching property if found. - - - - Gets a property by property name. - - The name of the property to get. - Type property name string comparison. - A matching property if found. - - - - The JSON writer. - - - - - - Gets the size of the serialize stack (amount of the objects serialized in the hierachy before the current). - - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Lookup and create an instance of the type described by the argument. - - The type to create. - Optional arguments to pass to an initializing constructor of the JsonConverter. - If null, the default constructor is used. - - - - A base class for resolving how property names and dictionary keys are serialized. - - - - - A flag indicating whether dictionary keys should be processed. - Defaults to false. - - - - - A flag indicating whether extension data names should be processed. - Defaults to false. - - - - - A flag indicating whether explicitly specified property names, - e.g. a property name customized with a , should be processed. - Defaults to false. - - - - - Gets the serialized name for a given property name. - - The initial property name. - A flag indicating whether the property has had a name explicitly specified. - The serialized property name. - - - - Gets the serialized name for a given extension data name. - - The initial extension data name. - The serialized extension data name. - - - - Gets the serialized key for a given dictionary key. - - The initial dictionary key. - The serialized dictionary key. - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Represents a method that constructs an object. - - The object type to create. - - - - When applied to a method, specifies that the method is called when an error occurs serializing an object. - - - - - Provides methods to get attributes from a , , or . - - - - - Initializes a new instance of the class. - - The instance to get attributes for. This parameter should be a , , or . - - - - Returns a collection of all of the attributes, or an empty collection if there are no attributes. - - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. - - The type of the attributes. - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Get and set values for a using reflection. - - - - - Initializes a new instance of the class. - - The member info. - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - A snake case naming strategy. - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - A flag indicating whether extension data names should be processed. - - - - - Initializes a new instance of the class. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Specifies how strings are escaped when writing JSON text. - - - - - Only control characters (e.g. newline) are escaped. - - - - - All non-ASCII and control characters (e.g. newline) are escaped. - - - - - HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped. - - - - - Indicates the method that will be used during deserialization for locating and loading assemblies. - - - - - In simple mode, the assembly used during deserialization need not match exactly the assembly used during serialization. Specifically, the version numbers need not match as the LoadWithPartialName method of the class is used to load the assembly. - - - - - In full mode, the assembly used during deserialization must match exactly the assembly used during serialization. The Load method of the class is used to load the assembly. - - - - - Specifies type name handling options for the . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - - - - Do not include the .NET type name when serializing types. - - - - - Include the .NET type name when serializing into a JSON object structure. - - - - - Include the .NET type name when serializing into a JSON array structure. - - - - - Always include the .NET type name when serializing. - - - - - Include the .NET type name when the type of the object being serialized is not the same as its declared type. - Note that this doesn't include the root serialized object by default. To include the root object's type name in JSON - you must specify a root type object with - or . - - - - - Determines whether the collection is null or empty. - - The collection. - - true if the collection is null or empty; otherwise, false. - - - - - Adds the elements of the specified collection to the specified generic . - - The list to add to. - The collection of elements to add. - - - - Converts the value to the specified type. If the value is unable to be converted, the - value is checked whether it assignable to the specified type. - - The value to convert. - The culture to use when converting. - The type to convert or cast the value to. - - The converted type. If conversion was unsuccessful, the initial value - is returned if assignable to the target type. - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic that returns a result - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic, but uses one of the arguments for - the result. - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic, but uses one of the arguments for - the result. - - - - - Returns a Restrictions object which includes our current restrictions merged - with a restriction limiting our type - - - - - Helper class for serializing immutable collections. - Note that this is used by all builds, even those that don't support immutable collections, in case the DLL is GACed - https://github.com/JamesNK/Newtonsoft.Json/issues/652 - - - - - Helper utilities. - - - - - Compares two objects data. - - The object a. - The object b. - True if both objects are equal, otherwise false. - - - - The custom value comparision callback. - - - - - The default implementation of the values comparision function. - - The object a. - The object b. - True if both objects are equal, otherwise false. - - - - Gets the type of the typed collection's items. - - The type. - The type of the typed collection's items. - - - - Gets the member's underlying type. - - The member. - The underlying type of the member. - - - - Determines whether the member is an indexed property. - - The member. - - true if the member is an indexed property; otherwise, false. - - - - - Determines whether the property is an indexed property. - - The property. - - true if the property is an indexed property; otherwise, false. - - - - - Gets the member's value on the object. - - The member. - The target object. - The member's value on the object. - - - - Sets the member's value on the target object. - - The member. - The target. - The value. - - - - Determines whether the specified MemberInfo can be read. - - The MemberInfo to determine whether can be read. - /// if set to true then allow the member to be gotten non-publicly. - - true if the specified MemberInfo can be read; otherwise, false. - - - - - Determines whether the specified MemberInfo can be set. - - The MemberInfo to determine whether can be set. - if set to true then allow the member to be set non-publicly. - if set to true then allow the member to be set if read-only. - - true if the specified MemberInfo can be set; otherwise, false. - - - - - Builds a string. Unlike this class lets you reuse its internal buffer. - - - - - Determines whether the string is all white space. Empty string will return false. - - The string to test whether it is all white space. - - true if the string is all white space; otherwise, false. - - - - - Specifies the state of the . - - - - - An exception has been thrown, which has left the in an invalid state. - You may call the method to put the in the Closed state. - Any other method calls result in an being thrown. - - - - - The method has been called. - - - - - An object is being written. - - - - - An array is being written. - - - - - A constructor is being written. - - - - - A property is being written. - - - - - A write method has not been called. - - - - diff --git a/Source/Platforms/XboxOne/Binaries/Project.csproj b/Source/Platforms/XboxOne/Binaries/Project.csproj index c6c48c200..1895423a0 100644 --- a/Source/Platforms/XboxOne/Binaries/Project.csproj +++ b/Source/Platforms/XboxOne/Binaries/Project.csproj @@ -54,6 +54,7 @@ Designer + {3} @@ -70,7 +71,7 @@ - + diff --git a/Source/Shaders/BitonicSort.shader b/Source/Shaders/BitonicSort.shader index 8a53802d0..8d2172b77 100644 --- a/Source/Shaders/BitonicSort.shader +++ b/Source/Shaders/BitonicSort.shader @@ -10,7 +10,8 @@ struct Item }; META_CB_BEGIN(0, Data) -Item NullItem; +float NullItemKey; +uint NullItemValue; uint CounterOffset; uint MaxIterations; uint LoopK; @@ -95,10 +96,17 @@ groupshared Item SortData[2048]; void LoadItem(uint element, uint count) { // Unused elements must sort to the end + Item item; if (element < count) - SortData[element & 2047] = SortBuffer[element]; + { + item = SortBuffer[element]; + } else - SortData[element & 2047] = NullItem; + { + item.Key = NullItemKey; + item.Value = NullItemValue; + } + SortData[element & 2047] = item; } void StoreItem(uint element, uint count) diff --git a/Source/Shaders/DepthOfField.shader b/Source/Shaders/DepthOfField.shader index 737b7c4cd..f8753bbba 100644 --- a/Source/Shaders/DepthOfField.shader +++ b/Source/Shaders/DepthOfField.shader @@ -137,17 +137,17 @@ META_CS(true, FEATURE_LEVEL_SM5) void CS_DepthOfFieldH(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThreadID) { // These positions are relative to the "grid", AKA the horizontal group of pixels that this thread group is writing to - const int gridStartX = groupID.x * DOF_GRID_SIZE; - const int gridX = groupThreadID.x - DOF_APRON_SIZE; + const uint gridStartX = groupID.x * DOF_GRID_SIZE; + const uint gridX = groupThreadID.x - DOF_APRON_SIZE; // These positions are relative to the pixel coordinates - const int sampleX = gridStartX + gridX; - const int sampleY = groupID.y; + const uint sampleX = gridStartX + gridX; + const uint sampleY = groupID.y; uint2 textureSize; Input0.GetDimensions(textureSize.x, textureSize.y); - const int2 samplePos = int2(sampleX, sampleY); + const uint2 samplePos = uint2(sampleX, sampleY); // Sample the textures #if USE_CS_HALF_PIXEL_OFFSET @@ -186,7 +186,7 @@ void CS_DepthOfFieldH(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_Group for (int x = -DOF_MAX_SAMPLE_RADIUS; x <= DOF_MAX_SAMPLE_RADIUS; x++) { // Grab the sample from shared memory - int groupTapX = groupThreadID.x + x; + uint groupTapX = groupThreadID.x + x; DOFSample tap = Samples[groupTapX]; // Reject the sample if it's outside the CoC radius @@ -218,17 +218,17 @@ META_CS(true, FEATURE_LEVEL_SM5) void CS_DepthOfFieldV(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThreadID) { // These positions are relative to the "grid", AKA the vertical group of pixels that this thread group is writing to - const int gridStartY = groupID.y * DOF_GRID_SIZE; - const int gridY = groupThreadID.y - DOF_APRON_SIZE; + const uint gridStartY = groupID.y * DOF_GRID_SIZE; + const uint gridY = groupThreadID.y - DOF_APRON_SIZE; // These positions are relative to the pixel coordinates - const int sampleX = groupID.x; - const int sampleY = gridStartY + gridY; + const uint sampleX = groupID.x; + const uint sampleY = gridStartY + gridY; uint2 textureSize; Input0.GetDimensions(textureSize.x, textureSize.y); - const int2 samplePos = int2(sampleX, sampleY); + const uint2 samplePos = uint2(sampleX, sampleY); // Sample the textures #if USE_CS_HALF_PIXEL_OFFSET @@ -267,7 +267,7 @@ void CS_DepthOfFieldV(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_Group for (int y = -DOF_MAX_SAMPLE_RADIUS; y <= DOF_MAX_SAMPLE_RADIUS; y++) { // Grab the sample from shared memory - int groupTapY = groupThreadID.y + y; + uint groupTapY = groupThreadID.y + y; DOFSample tap = Samples[groupTapY]; // Reject the sample if it's outside the CoC radius @@ -311,17 +311,17 @@ META_CS(true, FEATURE_LEVEL_SM5) void CS_CoCSpreadH(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThreadID) { // These positions are relative to the "grid", AKA the horizontal group of pixels that this thread group is writing to - const int gridStartX = groupID.x * DOF_GRID_SIZE; - const int gridX = groupThreadID.x - DOF_APRON_SIZE; + const uint gridStartX = groupID.x * DOF_GRID_SIZE; + const uint gridX = groupThreadID.x - DOF_APRON_SIZE; // These positions are relative to the pixel coordinates - const int sampleX = gridStartX + gridX; - const int sampleY = groupID.y; + const uint sampleX = gridStartX + gridX; + const uint sampleY = groupID.y; uint2 textureSize; Input0.GetDimensions(textureSize.x, textureSize.y); - const int2 samplePos = int2(sampleX, sampleY); + const uint2 samplePos = uint2(sampleX, sampleY); // Sample the textures #if USE_CS_HALF_PIXEL_OFFSET @@ -355,7 +355,7 @@ void CS_CoCSpreadH(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThr for (int x = -DOF_MAX_SAMPLE_RADIUS; x <= DOF_MAX_SAMPLE_RADIUS; x++) { // Grab the sample from shared memory - int groupTapX = groupThreadID.x + x; + uint groupTapX = groupThreadID.x + x; CoCSample tap = Samples[groupTapX]; // Only accept samples if they're from the foreground, and have a higher blur amount @@ -382,17 +382,17 @@ META_CS(true, FEATURE_LEVEL_SM5) void CS_CoCSpreadV(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThreadID) { // These positions are relative to the "grid", AKA the vertical group of pixels that this thread group is writing to - const int gridStartY = groupID.y * DOF_GRID_SIZE; - const int gridY = groupThreadID.y - DOF_APRON_SIZE; + const uint gridStartY = groupID.y * DOF_GRID_SIZE; + const uint gridY = groupThreadID.y - DOF_APRON_SIZE; // These positions are relative to the pixel coordinates - const int sampleX = groupID.x; - const int sampleY = gridStartY + gridY; + const uint sampleX = groupID.x; + const uint sampleY = gridStartY + gridY; uint2 textureSize; Input0.GetDimensions(textureSize.x, textureSize.y); - const int2 samplePos = int2(sampleX, sampleY); + const uint2 samplePos = uint2(sampleX, sampleY); // Sample the textures #if USE_CS_HALF_PIXEL_OFFSET @@ -425,7 +425,7 @@ void CS_CoCSpreadV(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThr for (int y = -DOF_MAX_SAMPLE_RADIUS; y <= DOF_MAX_SAMPLE_RADIUS; y++) { // Grab the sample from shared memory - int groupTapY = groupThreadID.y + y; + uint groupTapY = groupThreadID.y + y; CoCSample tap = Samples[groupTapY]; // Only accept samples if they're from the foreground, and have a higher blur amount @@ -605,7 +605,7 @@ void GS_Bokeh(point BokehVSOutput input[1], inout TriangleStream // Emit 4 new verts, and 2 new triangles UNROLL - for (int i = 0; i < 4; i++) + for (uint i = 0; i < 4; i++) { output.PositionCS = float4(input[0].Position.xy, 1.0f, 1.0f); output.PositionCS.xy += Offsets[i] * input[0].Size; diff --git a/Source/Shaders/EyeAdaptation.shader b/Source/Shaders/EyeAdaptation.shader index fca80b7fc..b9d5544a4 100644 --- a/Source/Shaders/EyeAdaptation.shader +++ b/Source/Shaders/EyeAdaptation.shader @@ -31,7 +31,7 @@ float AdaptLuminance(float currentLum, Texture2D previousLuminance) float luminance = previousLum + delta * (1.0f - exp2(-DeltaTime * adaptionSpeed)); luminance = lerp(luminance, currentLum, DropHistory); - return clamp(luminance, MinBrightness, MaxBrightness).xxxx; + return clamp(luminance, MinBrightness, MaxBrightness); } #ifdef _PS_Manual diff --git a/Source/Shaders/GUI.shader b/Source/Shaders/GUI.shader index 3057dfbae..b5023d0e9 100644 --- a/Source/Shaders/GUI.shader +++ b/Source/Shaders/GUI.shader @@ -14,7 +14,7 @@ META_CB_END META_CB_BEGIN(1, BlurData) float2 InvBufferSize; -int SampleCount; +uint SampleCount; float Dummy0; float4 Bounds; float4 WeightAndOffsets[MAX_SAMPLES / 2]; @@ -148,9 +148,9 @@ float4 PS_Blur(Quad_VS2PS input) : SV_Target0 result += GetSample(weight, offset, uv); } - for (int i = 2; i < SampleCount; i += 2) + for (uint i = 2; i < SampleCount; i += 2) { - int index = i / 2; + uint index = i / 2; { float weight = WeightAndOffsets[index].x; float offset = WeightAndOffsets[index].y; diff --git a/Source/Shaders/VolumetricFog.shader b/Source/Shaders/VolumetricFog.shader index 79b055732..efacd448f 100644 --- a/Source/Shaders/VolumetricFog.shader +++ b/Source/Shaders/VolumetricFog.shader @@ -39,7 +39,7 @@ float HistoryWeight; float3 GridSize; uint MissedHistorySamplesCount; -int3 GridSizeInt; +uint3 GridSizeInt; float PhaseG; float2 Dummy0; @@ -276,7 +276,7 @@ void CS_Initialize(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_Dispa float3 scattering = GlobalAlbedo * extinction; float absorption = max(0.0f, extinction - Luminance(scattering)); - if (all((int3)gridCoordinate < GridSizeInt)) + if (all(gridCoordinate < GridSizeInt)) { RWVBufferA[gridCoordinate] = float4(scattering, absorption); RWVBufferB[gridCoordinate] = float4(GlobalEmissive, 0); diff --git a/Source/ThirdParty/OpenFBX/ofbx.cpp b/Source/ThirdParty/OpenFBX/ofbx.cpp index 36f942cf8..94fc6684b 100644 --- a/Source/ThirdParty/OpenFBX/ofbx.cpp +++ b/Source/ThirdParty/OpenFBX/ofbx.cpp @@ -3172,6 +3172,8 @@ static bool parseObjects(const Element& root, Scene* scene, u64 flags, Allocator node->bone_link_property = con.property; } break; + default: + break; } switch (parent->getType()) @@ -3190,6 +3192,8 @@ static bool parseObjects(const Element& root, Scene* scene, u64 flags, Allocator mesh->geometry = (Geometry*)child; break; case Object::Type::MATERIAL: mesh->materials.push_back((Material*)child); break; + default: + break; } break; } @@ -3323,6 +3327,8 @@ static bool parseObjects(const Element& root, Scene* scene, u64 flags, Allocator } break; } + default: + break; } } @@ -3350,6 +3356,8 @@ static bool parseObjects(const Element& root, Scene* scene, u64 flags, Allocator return false; } break; + default: + break; } } } diff --git a/Source/ThirdParty/PhysX/PhysX.Build.cs b/Source/ThirdParty/PhysX/PhysX.Build.cs index 3c57c5cb4..d9ef72882 100644 --- a/Source/ThirdParty/PhysX/PhysX.Build.cs +++ b/Source/ThirdParty/PhysX/PhysX.Build.cs @@ -38,7 +38,7 @@ public class PhysX : DepsModule bool useDynamicLinking = false; bool usePVD = false; - bool useVehicle = false; + bool useVehicle = true; bool usePhysicsCooking = Physics.WithCooking; var depsRoot = options.DepsFolder; @@ -96,7 +96,7 @@ public class PhysX : DepsModule if (useVehicle) { - AddLib(options, depsRoot, string.Format("PhysXVehicle_static_{0}", archPostFix)); + AddLib(options, depsRoot, string.Format("PhysXVehicle_static{0}", archPostFix)); } } } diff --git a/Source/ThirdParty/assimp/assimp.Build.cs b/Source/ThirdParty/assimp/assimp.Build.cs index 4f5b1ea41..dd6da9d7c 100644 --- a/Source/ThirdParty/assimp/assimp.Build.cs +++ b/Source/ThirdParty/assimp/assimp.Build.cs @@ -35,10 +35,8 @@ public class assimp : DepsModule options.DelayLoadLibraries.Add("assimp-vc140-md.dll"); break; case TargetPlatform.Linux: - options.DependencyFiles.Add(Path.Combine(depsRoot, "libassimp.so")); - options.DependencyFiles.Add(Path.Combine(depsRoot, "libassimp.so.4")); - options.DependencyFiles.Add(Path.Combine(depsRoot, "libassimp.so.4.1.0")); - options.Libraries.Add(Path.Combine(depsRoot, "libassimp.so")); + options.OutputFiles.Add(Path.Combine(depsRoot, "libassimp.a")); + options.OutputFiles.Add(Path.Combine(depsRoot, "libIrrXML.a")); break; default: throw new InvalidPlatformException(options.Platform.Target); } diff --git a/Source/ThirdParty/concurrentqueue.h b/Source/ThirdParty/concurrentqueue.h index f6c1e8481..baa4a6c21 100644 --- a/Source/ThirdParty/concurrentqueue.h +++ b/Source/ThirdParty/concurrentqueue.h @@ -36,36 +36,21 @@ // upon assigning any computed values) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" - -#ifdef MCDBGQ_USE_RELACY -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" -#endif #endif #if defined(__APPLE__) #include "TargetConditionals.h" #endif -#ifdef MCDBGQ_USE_RELACY -#include "relacy/relacy_std.hpp" -#include "relacy_shims.h" -// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. -// We'll override the default trait malloc ourselves without a macro. -#undef new -#undef delete -#undef malloc -#undef free -#else -#include // Requires C++11. Sorry VS2010. +#include #include -#endif +#include #include #include -//#include #include -#include #include -#include // for CHAR_BIT +#include +#include // Platform-specific definitions of a numeric thread ID type and an invalid value namespace moodycamel { namespace details { @@ -75,14 +60,7 @@ namespace moodycamel { namespace details { static thread_id_hash_t prehash(thread_id_t const& x) { return x; } }; } } -#if defined(MCDBGQ_USE_RELACY) -namespace moodycamel { namespace details { - typedef uint32_t thread_id_t; - static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; - static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; - static inline thread_id_t thread_id() { return rl::thread_index(); } -} } -#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +#if defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) // No sense pulling in windows.h in a header, we'll manually declare the function // we use and rely on backwards-compatibility for this not to break extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); @@ -148,50 +126,7 @@ namespace moodycamel { namespace details { } } #endif -// Exceptions -#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED -#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) -#define MOODYCAMEL_EXCEPTIONS_ENABLED -#endif -#endif -#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED -#define MOODYCAMEL_TRY try -#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) -#define MOODYCAMEL_RETHROW throw -#define MOODYCAMEL_THROW(expr) throw (expr) -#else -#define MOODYCAMEL_TRY if (true) -#define MOODYCAMEL_CATCH(...) else if (false) -#define MOODYCAMEL_RETHROW -#define MOODYCAMEL_THROW(expr) -#endif - -#ifndef MOODYCAMEL_NOEXCEPT -#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) -#define MOODYCAMEL_NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true -#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 -// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( -// We have to assume *all* non-trivial constructors may throw on VS2012! -#define MOODYCAMEL_NOEXCEPT _NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) -#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 -#define MOODYCAMEL_NOEXCEPT _NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) -#else -#define MOODYCAMEL_NOEXCEPT noexcept -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) -#endif -#endif - #ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#ifdef MCDBGQ_USE_RELACY -#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#else // VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 // g++ <=4.7 doesn't support thread_local either. // Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work @@ -200,17 +135,6 @@ namespace moodycamel { namespace details { //#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // always disabled for now since several users report having problems with it on #endif #endif -#endif - -// VS2012 doesn't support deleted functions. -// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. -#ifndef MOODYCAMEL_DELETE_FUNCTION -#if defined(_MSC_VER) && _MSC_VER < 1800 -#define MOODYCAMEL_DELETE_FUNCTION -#else -#define MOODYCAMEL_DELETE_FUNCTION = delete -#endif -#endif // Compiler-specific likely/unlikely hints namespace moodycamel { namespace details { @@ -223,10 +147,6 @@ namespace moodycamel { namespace details { #endif } } -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG -#include "internal/concurrentqueue_internal_debug.h" -#endif - namespace moodycamel { namespace details { template @@ -315,7 +235,6 @@ struct ConcurrentQueueDefaultTraits static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; -#ifndef MCDBGQ_USE_RELACY // Memory allocation can be customized if needed. // malloc should return nullptr on failure, and handle alignment like std::malloc. #if defined(malloc) || defined(free) @@ -329,12 +248,6 @@ struct ConcurrentQueueDefaultTraits static inline void* malloc(size_t size) { return std::malloc(size); } static inline void free(void* ptr) { return std::free(ptr); } #endif -#else - // Debug versions when running under the Relacy race detector (ignore - // these in user code) - static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } - static inline void free(void* ptr) { return rl::rl_free(ptr, $); } -#endif }; @@ -474,7 +387,7 @@ namespace details }; template - static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) + static inline auto deref_noexcept(It& it) noexcept -> decltype(*it) { return *it; } @@ -486,10 +399,6 @@ namespace details #endif #ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#ifdef MCDBGQ_USE_RELACY - typedef RelacyThreadExitListener ThreadExitListener; - typedef RelacyThreadExitNotifier ThreadExitNotifier; -#else struct ThreadExitListener { typedef void (*callback_t)(void*); @@ -525,8 +434,8 @@ namespace details private: ThreadExitNotifier() : tail(nullptr) { } - ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; - ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier(ThreadExitNotifier const&) = delete; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) = delete; ~ThreadExitNotifier() { @@ -547,7 +456,6 @@ namespace details private: ThreadExitListener* tail; }; -#endif #endif template struct static_is_lock_free_num { enum { value = 0 }; }; @@ -570,7 +478,7 @@ struct ProducerToken template explicit ProducerToken(BlockingConcurrentQueue& queue); - ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + ProducerToken(ProducerToken&& other) noexcept : producer(other.producer) { other.producer = nullptr; @@ -579,13 +487,13 @@ struct ProducerToken } } - inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + inline ProducerToken& operator=(ProducerToken&& other) noexcept { swap(other); return *this; } - void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT + void swap(ProducerToken& other) noexcept { std::swap(producer, other.producer); if (producer != nullptr) { @@ -615,8 +523,8 @@ struct ProducerToken } // Disable copying and assignment - ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; - ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ProducerToken(ProducerToken const&) = delete; + ProducerToken& operator=(ProducerToken const&) = delete; private: template friend class ConcurrentQueue; @@ -635,18 +543,18 @@ struct ConsumerToken template explicit ConsumerToken(BlockingConcurrentQueue& q); - ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + ConsumerToken(ConsumerToken&& other) noexcept : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) { } - inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + inline ConsumerToken& operator=(ConsumerToken&& other) noexcept { swap(other); return *this; } - void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT + void swap(ConsumerToken& other) noexcept { std::swap(initialOffset, other.initialOffset); std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); @@ -656,8 +564,8 @@ struct ConsumerToken } // Disable copying and assignment - ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; - ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ConsumerToken(ConsumerToken const&) = delete; + ConsumerToken& operator=(ConsumerToken const&) = delete; private: template friend class ConcurrentQueue; @@ -674,7 +582,7 @@ private: // but shared with ConcurrentQueue // Need to forward-declare this swap because it's in a namespace. // See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces template -inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) noexcept; template @@ -734,15 +642,6 @@ public: implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); populate_initial_implicit_producer_hash(); populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - // Track all the producers using a fully-resolved typed list for - // each kind; this makes it possible to debug them starting from - // the root queue object (otherwise wacky casts are needed that - // don't compile in the debugger's expression evaluator). - explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif } // Computes the correct amount of pre-allocated blocks for you based @@ -759,11 +658,6 @@ public: populate_initial_implicit_producer_hash(); size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers); populate_initial_block_list(blocks); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif } // Note: The queue should not be accessed concurrently while it's @@ -813,8 +707,8 @@ public: } // Disable copying and copy assignment - ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + ConcurrentQueue(ConcurrentQueue const&) = delete; + ConcurrentQueue& operator=(ConcurrentQueue const&) = delete; // Moving is supported, but note that it is *not* a thread-safe operation. // Nobody can use the queue while it's being moved, and the memory effects @@ -822,7 +716,7 @@ public: // Note: When a queue is moved, its tokens are still valid but can only be // used with the destination queue (i.e. semantically they are moved along // with the queue itself). - ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + ConcurrentQueue(ConcurrentQueue&& other) noexcept : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), producerCount(other.producerCount.load(std::memory_order_relaxed)), initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), @@ -842,13 +736,6 @@ public: other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); - other.explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); - other.implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif - other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); other.initialBlockPoolSize = 0; other.initialBlockPool = nullptr; @@ -856,7 +743,7 @@ public: reown_producers(); } - inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + inline ConcurrentQueue& operator=(ConcurrentQueue&& other) noexcept { return swap_internal(other); } @@ -866,7 +753,7 @@ public: // the tokens that were created for one queue must be used with // only the swapped queue (i.e. the tokens are tied to the // queue's movable state, not the object itself). - inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + inline void swap(ConcurrentQueue& other) noexcept { swap_internal(other); } @@ -892,11 +779,6 @@ private: reown_producers(); other.reown_producers(); -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - details::swap_relaxed(explicitProducers, other.explicitProducers); - details::swap_relaxed(implicitProducers, other.implicitProducers); -#endif - return *this; } @@ -1367,14 +1249,11 @@ private: FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } - FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; - FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + FreeList(FreeList const&) = delete; + FreeList& operator=(FreeList const&) = delete; inline void add(N* node) { -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugLock lock(mutex); -#endif // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to // set it using a fetch_add if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { @@ -1385,10 +1264,7 @@ private: } inline N* try_get() - { -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugLock lock(mutex); -#endif + { auto head = freeListHead.load(std::memory_order_acquire); while (head != nullptr) { auto prevHead = head; @@ -1457,10 +1333,6 @@ private: static const uint32_t REFS_MASK = 0x7FFFFFFF; static const uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; - -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugMutex mutex; -#endif }; @@ -1475,9 +1347,6 @@ private: Block() : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true) { -#ifdef MCDBGQ_TRACKMEM - owner = nullptr; -#endif } template @@ -1577,8 +1446,8 @@ private: } } - inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } - inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T* operator[](index_t idx) noexcept { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const noexcept { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } private: // IMPORTANT: This must be the first member in Block, so that if T depends on the alignment of @@ -1604,19 +1473,9 @@ private: std::atomic freeListNext; std::atomic shouldBeOnFreeList; bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' - -#ifdef MCDBGQ_TRACKMEM - void* owner; -#endif }; static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); - -#ifdef MCDBGQ_TRACKMEM -public: - struct MemStats; -private: -#endif /////////////////////////// // Producer base @@ -1681,11 +1540,6 @@ private: public: bool isExplicit; ConcurrentQueue* parent; - - protected: -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif }; @@ -1826,9 +1680,6 @@ private: if (newBlock == nullptr) { return false; } -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif newBlock->ConcurrentQueue::Block::template reset_empty(); if (this->tailBlock == nullptr) { newBlock->next = newBlock; @@ -1841,19 +1692,8 @@ private: ++pr_blockIndexSlotsUsed; } - if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new ((T*)nullptr) T(std::forward(element)))) { - // The constructor may throw. We want the element not to appear in the queue in - // that case (without corrupting the queue): - MOODYCAMEL_TRY { - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); - } - MOODYCAMEL_CATCH (...) { - // Revert change to the current block, but leave the new block available - // for next time - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; - MOODYCAMEL_RETHROW; - } + if (!noexcept(new ((T*)nullptr) T(std::forward(element)))) { + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); } else { (void)startBlock; @@ -1867,7 +1707,7 @@ private: blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); - if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new ((T*)nullptr) T(std::forward(element)))) { + if (!noexcept(new ((T*)nullptr) T(std::forward(element)))) { this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } @@ -1947,7 +1787,7 @@ private: // Dequeue auto& el = *((*block)[index]); - if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { + if (!noexcept(element = std::move(el))) { // Make sure the element is still fully dequeued and destroyed even if the assignment // throws struct Guard { @@ -2043,9 +1883,6 @@ private: return false; } -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif newBlock->ConcurrentQueue::Block::template set_all_empty(); if (this->tailBlock == nullptr) { newBlock->next = newBlock; @@ -2076,7 +1913,7 @@ private: block = block->next; } - if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) { + if (noexcept(new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) { blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); } } @@ -2095,58 +1932,23 @@ private: if (details::circular_less_than(newTailIndex, stopIndex)) { stopIndex = newTailIndex; } - if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) { + if (noexcept(new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) { while (currentTailIndex != stopIndex) { new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); } } else { - MOODYCAMEL_TRY { - while (currentTailIndex != stopIndex) { - // Must use copy constructor even if move constructor is available - // because we may have to revert if there's an exception. - // Sorry about the horrible templated next line, but it was the only way - // to disable moving *at compile time*, which is important because a type - // may only define a (noexcept) move constructor, and so calls to the - // cctor will not compile, even if they are in an if branch that will never - // be executed - new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); - ++currentTailIndex; - ++itemFirst; - } - } - MOODYCAMEL_CATCH (...) { - // Oh dear, an exception's been thrown -- destroy the elements that - // were enqueued so far and revert the entire bulk operation (we'll keep - // any allocated blocks in our linked list for later, though). - auto constructedStopIndex = currentTailIndex; - auto lastBlockEnqueued = this->tailBlock; - - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - - if (!details::is_trivially_destructible::value) { - auto block = startBlock; - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - block = firstAllocatedBlock; - } - currentTailIndex = startTailIndex; - while (true) { - stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(constructedStopIndex, stopIndex)) { - stopIndex = constructedStopIndex; - } - while (currentTailIndex != stopIndex) { - (*block)[currentTailIndex++]->~T(); - } - if (block == lastBlockEnqueued) { - break; - } - block = block->next; - } - } - MOODYCAMEL_RETHROW; + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!noexcept(new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; } } @@ -2157,7 +1959,7 @@ private: this->tailBlock = this->tailBlock->next; } - if (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst))) && firstAllocatedBlock != nullptr) { + if (!noexcept(new ((T*)nullptr) T(details::deref_noexcept(itemFirst))) && firstAllocatedBlock != nullptr) { blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); } @@ -2205,7 +2007,7 @@ private: auto endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; auto block = localBlockIndex->entries[indexIndex].block; - if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + if (noexcept(details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { while (index != endIndex) { auto& el = *((*block)[index]); *itemFirst++ = std::move(el); @@ -2214,33 +2016,12 @@ private: } } else { - MOODYCAMEL_TRY { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst = std::move(el); - ++itemFirst; - el.~T(); - ++index; - } - } - MOODYCAMEL_CATCH (...) { - // It's too late to revert the dequeue, but we can make sure that all - // the dequeued objects are properly destroyed and the block index - // (and empty count) are properly updated before we propagate the exception - do { - block = localBlockIndex->entries[indexIndex].block; - while (index != endIndex) { - (*block)[index++]->~T(); - } - block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); - indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); - - firstIndexInBlock = index; - endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - } while (index != firstIndex + actualCount); - - MOODYCAMEL_RETHROW; + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; } } block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); @@ -2322,16 +2103,6 @@ private: size_t pr_blockIndexFront; // Next slot (not current) BlockIndexEntry* pr_blockIndexEntries; void* pr_blockIndexRaw; - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - public: - ExplicitProducer* nextExplicitProducer; - private: -#endif - -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif }; @@ -2416,9 +2187,6 @@ private: if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { return false; } -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif // Find out where we'll be inserting this block in the block index BlockIndexEntry* idxEntry; if (!insert_block_index_entry(idxEntry, currentTailIndex)) { @@ -2432,22 +2200,10 @@ private: idxEntry->value.store(nullptr, std::memory_order_relaxed); return false; } -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif newBlock->ConcurrentQueue::Block::template reset_empty(); - if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new ((T*)nullptr) T(std::forward(element)))) { - // May throw, try to insert now before we publish the fact that we have this new block - MOODYCAMEL_TRY { - new ((*newBlock)[currentTailIndex]) T(std::forward(element)); - } - MOODYCAMEL_CATCH (...) { - rewind_block_index_tail(); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - this->parent->add_block_to_free_list(newBlock); - MOODYCAMEL_RETHROW; - } + if (!noexcept(new ((T*)nullptr) T(std::forward(element)))) { + new ((*newBlock)[currentTailIndex]) T(std::forward(element)); } // Insert the new block into the index @@ -2455,7 +2211,7 @@ private: this->tailBlock = newBlock; - if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new ((T*)nullptr) T(std::forward(element)))) { + if (!noexcept(new ((T*)nullptr) T(std::forward(element)))) { this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } @@ -2489,12 +2245,7 @@ private: auto block = entry->value.load(std::memory_order_relaxed); auto& el = *((*block)[index]); - if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - // Note: Acquiring the mutex with every dequeue instead of only when a block - // is released is very sub-optimal, but it is, after all, purely debug code. - debug::DebugLock lock(producer->mutex); -#endif + if (!noexcept(element = std::move(el))) { struct Guard { Block* block; index_t index; @@ -2519,9 +2270,6 @@ private: if (block->ConcurrentQueue::Block::template set_empty(index)) { { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif // Add the block back into the global free pool (and remove from block index) entry->value.store(nullptr, std::memory_order_relaxed); } @@ -2560,9 +2308,6 @@ private: size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); if (blockBaseDiff > 0) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif do { blockBaseDiff -= static_cast(BLOCK_SIZE); currentTailIndex += static_cast(BLOCK_SIZE); @@ -2594,9 +2339,6 @@ private: return false; } -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif newBlock->ConcurrentQueue::Block::template reset_empty(); newBlock->next = nullptr; @@ -2628,54 +2370,16 @@ private: if (details::circular_less_than(newTailIndex, stopIndex)) { stopIndex = newTailIndex; } - if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) { + if (noexcept(new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) { while (currentTailIndex != stopIndex) { new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); } } else { - MOODYCAMEL_TRY { - while (currentTailIndex != stopIndex) { - new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); - ++currentTailIndex; - ++itemFirst; - } - } - MOODYCAMEL_CATCH (...) { - auto constructedStopIndex = currentTailIndex; - auto lastBlockEnqueued = this->tailBlock; - - if (!details::is_trivially_destructible::value) { - auto block = startBlock; - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - block = firstAllocatedBlock; - } - currentTailIndex = startTailIndex; - while (true) { - stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(constructedStopIndex, stopIndex)) { - stopIndex = constructedStopIndex; - } - while (currentTailIndex != stopIndex) { - (*block)[currentTailIndex++]->~T(); - } - if (block == lastBlockEnqueued) { - break; - } - block = block->next; - } - } - - currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { - currentTailIndex += static_cast(BLOCK_SIZE); - auto idxEntry = get_block_index_entry_for_index(currentTailIndex); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - rewind_block_index_tail(); - } - this->parent->add_blocks_to_free_list(firstAllocatedBlock); - this->tailBlock = startBlock; - MOODYCAMEL_RETHROW; + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!noexcept(new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; } } @@ -2724,7 +2428,7 @@ private: auto entry = localBlockIndex->index[indexIndex]; auto block = entry->value.load(std::memory_order_relaxed); - if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + if (noexcept(details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { while (index != endIndex) { auto& el = *((*block)[index]); *itemFirst++ = std::move(el); @@ -2733,45 +2437,16 @@ private: } } else { - MOODYCAMEL_TRY { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst = std::move(el); - ++itemFirst; - el.~T(); - ++index; - } - } - MOODYCAMEL_CATCH (...) { - do { - entry = localBlockIndex->index[indexIndex]; - block = entry->value.load(std::memory_order_relaxed); - while (index != endIndex) { - (*block)[index++]->~T(); - } - - if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - entry->value.store(nullptr, std::memory_order_relaxed); - this->parent->add_block_to_free_list(block); - } - indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); - - blockStartIndex = index; - endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - } while (index != firstIndex + actualCount); - - MOODYCAMEL_RETHROW; + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; } } if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif // Note that the set_many_empty above did a release, meaning that anybody who acquires the block // we're about to free can use it safely since our writes (and reads!) will have happened-before then. entry->value.store(nullptr, std::memory_order_relaxed); @@ -2855,9 +2530,6 @@ private: inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif index &= ~static_cast(BLOCK_SIZE - 1); localBlockIndex = blockIndex.load(std::memory_order_acquire); auto tail = localBlockIndex->tail.load(std::memory_order_acquire); @@ -2924,19 +2596,6 @@ private: details::ThreadExitListener threadExitListener; private: #endif - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - public: - ImplicitProducer* nextImplicitProducer; - private: -#endif - -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - mutable debug::DebugMutex mutex; -#endif -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif }; @@ -2974,9 +2633,6 @@ private: inline void add_block_to_free_list(Block* block) { -#ifdef MCDBGQ_TRACKMEM - block->owner = nullptr; -#endif freeList.add(block); } @@ -3015,112 +2671,6 @@ private: return nullptr; } - -#ifdef MCDBGQ_TRACKMEM - public: - struct MemStats { - size_t allocatedBlocks; - size_t usedBlocks; - size_t freeBlocks; - size_t ownedBlocksExplicit; - size_t ownedBlocksImplicit; - size_t implicitProducers; - size_t explicitProducers; - size_t elementsEnqueued; - size_t blockClassBytes; - size_t queueClassBytes; - size_t implicitBlockIndexBytes; - size_t explicitBlockIndexBytes; - - friend class ConcurrentQueue; - - private: - static MemStats getFor(ConcurrentQueue* q) - { - MemStats stats = { 0 }; - - stats.elementsEnqueued = q->size_approx(); - - auto block = q->freeList.head_unsafe(); - while (block != nullptr) { - ++stats.allocatedBlocks; - ++stats.freeBlocks; - block = block->freeListNext.load(std::memory_order_relaxed); - } - - for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - bool implicit = dynamic_cast(ptr) != nullptr; - stats.implicitProducers += implicit ? 1 : 0; - stats.explicitProducers += implicit ? 0 : 1; - - if (implicit) { - auto prod = static_cast(ptr); - stats.queueClassBytes += sizeof(ImplicitProducer); - auto head = prod->headIndex.load(std::memory_order_relaxed); - auto tail = prod->tailIndex.load(std::memory_order_relaxed); - auto hash = prod->blockIndex.load(std::memory_order_relaxed); - if (hash != nullptr) { - for (size_t i = 0; i != hash->capacity; ++i) { - if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { - ++stats.allocatedBlocks; - ++stats.ownedBlocksImplicit; - } - } - stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); - for (; hash != nullptr; hash = hash->prev) { - stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); - } - } - for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { - //auto block = prod->get_block_index_entry_for_index(head); - ++stats.usedBlocks; - } - } - else { - auto prod = static_cast(ptr); - stats.queueClassBytes += sizeof(ExplicitProducer); - auto tailBlock = prod->tailBlock; - bool wasNonEmpty = false; - if (tailBlock != nullptr) { - auto block = tailBlock; - do { - ++stats.allocatedBlocks; - if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { - ++stats.usedBlocks; - wasNonEmpty = wasNonEmpty || block != tailBlock; - } - ++stats.ownedBlocksExplicit; - block = block->next; - } while (block != tailBlock); - } - auto index = prod->blockIndex.load(std::memory_order_relaxed); - while (index != nullptr) { - stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); - index = static_cast(index->prev); - } - } - } - - auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); - stats.allocatedBlocks += freeOnInitialPool; - stats.freeBlocks += freeOnInitialPool; - - stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; - stats.queueClassBytes += sizeof(ConcurrentQueue); - - return stats; - } - }; - - // For debugging only. Not thread-safe. - MemStats getMemStats() - { - return MemStats::getFor(this); - } - private: - friend struct MemStats; -#endif - ////////////////////////////////// // Producer list manipulation @@ -3134,9 +2684,6 @@ private: ProducerBase* recycle_or_create_producer(bool isExplicit, bool& recycled) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif // Try to re-use one first for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { @@ -3168,21 +2715,6 @@ private: producer->next = prevTail; } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - if (producer->isExplicit) { - auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); - do { - static_cast(producer)->nextExplicitProducer = prevTailExplicit; - } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); - } - else { - auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); - do { - static_cast(producer)->nextImplicitProducer = prevTailImplicit; - } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); - } -#endif - return producer; } @@ -3208,19 +2740,19 @@ private: ImplicitProducerKVP() : value(nullptr) { } - ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + ImplicitProducerKVP(ImplicitProducerKVP&& other) noexcept { key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); value = other.value; } - inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) noexcept { swap(other); return *this; } - inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT + inline void swap(ImplicitProducerKVP& other) noexcept { if (this != &other) { details::swap_relaxed(key, other.key); @@ -3230,7 +2762,7 @@ private: }; template - friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) noexcept; struct ImplicitProducerHash { @@ -3301,10 +2833,6 @@ private: // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif - auto id = details::thread_id(); auto hashedId = details::hash_thread_id(id); @@ -3447,9 +2975,6 @@ private: details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener); // Remove from hash -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif auto hash = implicitProducerHash.load(std::memory_order_acquire); assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place auto id = details::thread_id(); @@ -3544,12 +3069,8 @@ private: std::atomic initialBlockPoolIndex; Block* initialBlockPool; size_t initialBlockPoolSize; - -#if !MCDBGQ_USEDEBUGFREELIST + FreeList freeList; -#else - debug::DebugFreeList freeList; -#endif std::atomic implicitProducerHash; std::atomic implicitProducerHashCount; // Number of slots logically used @@ -3559,15 +3080,6 @@ private: std::atomic nextExplicitConsumerId; std::atomic globalExplicitConsumerOffset; - -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugMutex implicitProdMutex; -#endif - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - std::atomic explicitProducers; - std::atomic implicitProducers; -#endif }; @@ -3606,23 +3118,23 @@ ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) } template -inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) noexcept { a.swap(b); } -inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT +inline void swap(ProducerToken& a, ProducerToken& b) noexcept { a.swap(b); } -inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT +inline void swap(ConsumerToken& a, ConsumerToken& b) noexcept { a.swap(b); } template -inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) noexcept { a.swap(b); } diff --git a/Source/ThirdParty/curl/curl.Build.cs b/Source/ThirdParty/curl/curl.Build.cs index 35943d9f1..ac77162a1 100644 --- a/Source/ThirdParty/curl/curl.Build.cs +++ b/Source/ThirdParty/curl/curl.Build.cs @@ -38,7 +38,7 @@ public class curl : DepsModule options.OutputFiles.Add("crypt32.lib"); break; case TargetPlatform.Linux: - options.Libraries.Add("curl"); + options.OutputFiles.Add(Path.Combine(depsRoot, "libcurl.a")); break; default: throw new InvalidPlatformException(options.Platform.Target); } diff --git a/Source/ThirdParty/detex/LICENSE b/Source/ThirdParty/detex/LICENSE new file mode 100644 index 000000000..8998ccac5 --- /dev/null +++ b/Source/ThirdParty/detex/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2015 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/Source/ThirdParty/detex/decompress-bc.cpp b/Source/ThirdParty/detex/decompress-bc.cpp new file mode 100644 index 000000000..82f89b54d --- /dev/null +++ b/Source/ThirdParty/detex/decompress-bc.cpp @@ -0,0 +1,241 @@ +/* + +Copyright (c) 2015 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +#include "detex.h" + +/* Decompress a 64-bit 4x4 pixel texture block compressed using the BC1 */ +/* format. */ +bool detexDecompressBlockBC1(const uint8_t * DETEX_RESTRICT bitstring, uint32_t mode_mask, +uint32_t flags, uint8_t * DETEX_RESTRICT pixel_buffer) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || !defined(__BYTE_ORDER__) + uint32_t colors = *(uint32_t *)&bitstring[0]; +#else + uint32_t colors = ((uint32_t)bitstring[0] << 24) | + ((uint32_t)bitstring[1] << 16) | + ((uint32_t)bitstring[2] << 8) | bitstring[3]; +#endif + // Decode the two 5-6-5 RGB colors. + int color_r[4], color_g[4], color_b[4]; + color_b[0] = (colors & 0x0000001F) << 3; + color_g[0] = (colors & 0x000007E0) >> (5 - 2); + color_r[0] = (colors & 0x0000F800) >> (11 - 3); + color_b[1] = (colors & 0x001F0000) >> (16 - 3); + color_g[1] = (colors & 0x07E00000) >> (21 - 2); + color_r[1] = (colors & 0xF8000000) >> (27 - 3); + if ((colors & 0xFFFF) > ((colors & 0xFFFF0000) >> 16)) { + color_r[2] = detexDivide0To767By3(2 * color_r[0] + color_r[1]); + color_g[2] = detexDivide0To767By3(2 * color_g[0] + color_g[1]); + color_b[2] = detexDivide0To767By3(2 * color_b[0] + color_b[1]); + color_r[3] = detexDivide0To767By3(color_r[0] + 2 * color_r[1]); + color_g[3] = detexDivide0To767By3(color_g[0] + 2 * color_g[1]); + color_b[3] = detexDivide0To767By3(color_b[0] + 2 * color_b[1]); + } + else { + color_r[2] = (color_r[0] + color_r[1]) / 2; + color_g[2] = (color_g[0] + color_g[1]) / 2; + color_b[2] = (color_b[0] + color_b[1]) / 2; + color_r[3] = color_g[3] = color_b[3] = 0; + } + uint32_t pixels = *(uint32_t *)&bitstring[4]; + for (int i = 0; i < 16; i++) { + int pixel = (pixels >> (i * 2)) & 0x3; + *(uint32_t *)(pixel_buffer + i * 4) = detexPack32RGB8Alpha0xFF( + color_r[pixel], color_g[pixel], color_b[pixel]); + } + return true; +} + +uint32_t detexGetModeBC1(const uint8_t *bitstring) { + uint32_t colors = *(uint32_t *)bitstring; + if ((colors & 0xFFFF) > ((colors & 0xFFFF0000) >> 16)) + return 0; + else + return 1; +} + +void detexSetModeBC1(uint8_t *bitstring, uint32_t mode, uint32_t flags, +uint32_t *colors) { + uint32_t colorbits = *(uint32_t *)bitstring; + uint32_t current_mode; + if ((colorbits & 0xFFFF) > ((colorbits & 0xFFFF0000) >> 16)) + current_mode = 0; + else + current_mode = 1; + if (current_mode != mode) { + colorbits = ((colorbits & 0xFFFF) << 16) | (colorbits >> 16); + *(uint32_t *)bitstring = colorbits; + } +} + +/* Decompress a 64-bit 4x4 pixel texture block compressed using the BC1A */ +/* format. */ +bool detexDecompressBlockBC1A(const uint8_t * DETEX_RESTRICT bitstring, uint32_t mode_mask, +uint32_t flags, uint8_t * DETEX_RESTRICT pixel_buffer) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || !defined(__BYTE_ORDER__) + uint32_t colors = *(uint32_t *)&bitstring[0]; +#else + uint32_t colors = ((uint32_t)bitstring[0] << 24) | + ((uint32_t)bitstring[1] << 16) | + ((uint32_t)bitstring[2] << 8) | bitstring[3]; +#endif + bool opaque = ((colors & 0xFFFF) > ((colors & 0xFFFF0000) >> 16)); + if (opaque && (flags & DETEX_DECOMPRESS_FLAG_NON_OPAQUE_ONLY)) + return false; + if (!opaque && (flags & DETEX_DECOMPRESS_FLAG_OPAQUE_ONLY)) + return false; + // Decode the two 5-6-5 RGB colors. + int color_r[4], color_g[4], color_b[4], color_a[4]; + color_b[0] = (colors & 0x0000001F) << 3; + color_g[0] = (colors & 0x000007E0) >> (5 - 2); + color_r[0] = (colors & 0x0000F800) >> (11 - 3); + color_b[1] = (colors & 0x001F0000) >> (16 - 3); + color_g[1] = (colors & 0x07E00000) >> (21 - 2); + color_r[1] = (colors & 0xF8000000) >> (27 - 3); + color_a[0] = color_a[1] = color_a[2] = color_a[3] = 0xFF; + if (opaque) { + color_r[2] = detexDivide0To767By3(2 * color_r[0] + color_r[1]); + color_g[2] = detexDivide0To767By3(2 * color_g[0] + color_g[1]); + color_b[2] = detexDivide0To767By3(2 * color_b[0] + color_b[1]); + color_r[3] = detexDivide0To767By3(color_r[0] + 2 * color_r[1]); + color_g[3] = detexDivide0To767By3(color_g[0] + 2 * color_g[1]); + color_b[3] = detexDivide0To767By3(color_b[0] + 2 * color_b[1]); + } + else { + color_r[2] = (color_r[0] + color_r[1]) / 2; + color_g[2] = (color_g[0] + color_g[1]) / 2; + color_b[2] = (color_b[0] + color_b[1]) / 2; + color_r[3] = color_g[3] = color_b[3] = color_a[3] = 0; + } + uint32_t pixels = *(uint32_t *)&bitstring[4]; + for (int i = 0; i < 16; i++) { + int pixel = (pixels >> (i * 2)) & 0x3; + *(uint32_t *)(pixel_buffer + i * 4) = detexPack32RGBA8( + color_r[pixel], color_g[pixel], color_b[pixel], + color_a[pixel]); + } + return true; +} + +/* Decompress a 64-bit 4x4 pixel texture block compressed using the BC2 */ +/* format. */ +bool detexDecompressBlockBC2(const uint8_t * DETEX_RESTRICT bitstring, uint32_t mode_mask, +uint32_t flags, uint8_t * DETEX_RESTRICT pixel_buffer) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || !defined(__BYTE_ORDER__) + uint32_t colors = *(uint32_t *)&bitstring[8]; +#else + uint32_t colors = ((uint32_t)bitstring[8] << 24) | + ((uint32_t)bitstring[9] << 16) | + ((uint32_t)bitstring[10] << 8) | bitstring[11]; +#endif + if ((colors & 0xFFFF) <= ((colors & 0xFFFF0000) >> 16) && + (flags & DETEX_DECOMPRESS_FLAG_ENCODE)) + // GeForce 6 and 7 series produce wrong result in this case. + return false; + int color_r[4], color_g[4], color_b[4]; + color_b[0] = (colors & 0x0000001F) << 3; + color_g[0] = (colors & 0x000007E0) >> (5 - 2); + color_r[0] = (colors & 0x0000F800) >> (11 - 3); + color_b[1] = (colors & 0x001F0000) >> (16 - 3); + color_g[1] = (colors & 0x07E00000) >> (21 - 2); + color_r[1] = (colors & 0xF8000000) >> (27 - 3); + color_r[2] = detexDivide0To767By3(2 * color_r[0] + color_r[1]); + color_g[2] = detexDivide0To767By3(2 * color_g[0] + color_g[1]); + color_b[2] = detexDivide0To767By3(2 * color_b[0] + color_b[1]); + color_r[3] = detexDivide0To767By3(color_r[0] + 2 * color_r[1]); + color_g[3] = detexDivide0To767By3(color_g[0] + 2 * color_g[1]); + color_b[3] = detexDivide0To767By3(color_b[0] + 2 * color_b[1]); + uint32_t pixels = *(uint32_t *)&bitstring[12]; + uint64_t alpha_pixels = *(uint64_t *)&bitstring[0]; + for (int i = 0; i < 16; i++) { + int pixel = (pixels >> (i * 2)) & 0x3; + int alpha = ((alpha_pixels >> (i * 4)) & 0xF) * 255 / 15; + *(uint32_t *)(pixel_buffer + i * 4) = detexPack32RGBA8( + color_r[pixel], color_g[pixel], color_b[pixel], alpha); + } + return true; +} + +/* Decompress a 64-bit 4x4 pixel texture block compressed using the BC3 */ +/* format. */ +bool detexDecompressBlockBC3(const uint8_t * DETEX_RESTRICT bitstring, uint32_t mode_mask, +uint32_t flags, uint8_t * DETEX_RESTRICT pixel_buffer) { + int alpha0 = bitstring[0]; + int alpha1 = bitstring[1]; + if (alpha0 > alpha1 && (flags & DETEX_DECOMPRESS_FLAG_OPAQUE_ONLY)) + return false; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || !defined(__BYTE_ORDER__) + uint32_t colors = *(uint32_t *)&bitstring[8]; +#else + uint32_t colors = ((uint32_t)bitstring[8] << 24) | + ((uint32_t)bitstring[9] << 16) | + ((uint32_t)bitstring[10] << 8) | bitstring[11]; +#endif + if ((colors & 0xFFFF) <= ((colors & 0xFFFF0000) >> 16) && + (flags & DETEX_DECOMPRESS_FLAG_ENCODE)) + // GeForce 6 and 7 series produce wrong result in this case. + return false; + int color_r[4], color_g[4], color_b[4]; + // color_x[] has a value between 0 and 248 with the lower three bits zero. + color_b[0] = (colors & 0x0000001F) << 3; + color_g[0] = (colors & 0x000007E0) >> (5 - 2); + color_r[0] = (colors & 0x0000F800) >> (11 - 3); + color_b[1] = (colors & 0x001F0000) >> (16 - 3); + color_g[1] = (colors & 0x07E00000) >> (21 - 2); + color_r[1] = (colors & 0xF8000000) >> (27 - 3); + color_r[2] = detexDivide0To767By3(2 * color_r[0] + color_r[1]); + color_g[2] = detexDivide0To767By3(2 * color_g[0] + color_g[1]); + color_b[2] = detexDivide0To767By3(2 * color_b[0] + color_b[1]); + color_r[3] = detexDivide0To767By3(color_r[0] + 2 * color_r[1]); + color_g[3] = detexDivide0To767By3(color_g[0] + 2 * color_g[1]); + color_b[3] = detexDivide0To767By3(color_b[0] + 2 * color_b[1]); + uint32_t pixels = *(uint32_t *)&bitstring[12]; + uint64_t alpha_bits = (uint32_t)bitstring[2] | + ((uint32_t)bitstring[3] << 8) | + ((uint64_t)*(uint32_t *)&bitstring[4] << 16); + for (int i = 0; i < 16; i++) { + int pixel = (pixels >> (i * 2)) & 0x3; + int code = (alpha_bits >> (i * 3)) & 0x7; + int alpha; + if (alpha0 > alpha1) + switch (code) { + case 0 : alpha = alpha0; break; + case 1 : alpha = alpha1; break; + case 2 : alpha = detexDivide0To1791By7(6 * alpha0 + 1 * alpha1); break; + case 3 : alpha = detexDivide0To1791By7(5 * alpha0 + 2 * alpha1); break; + case 4 : alpha = detexDivide0To1791By7(4 * alpha0 + 3 * alpha1); break; + case 5 : alpha = detexDivide0To1791By7(3 * alpha0 + 4 * alpha1); break; + case 6 : alpha = detexDivide0To1791By7(2 * alpha0 + 5 * alpha1); break; + case 7 : alpha = detexDivide0To1791By7(1 * alpha0 + 6 * alpha1); break; + } + else + switch (code) { + case 0 : alpha = alpha0; break; + case 1 : alpha = alpha1; break; + case 2 : alpha = detexDivide0To1279By5(4 * alpha0 + 1 * alpha1); break; + case 3 : alpha = detexDivide0To1279By5(3 * alpha0 + 2 * alpha1); break; + case 4 : alpha = detexDivide0To1279By5(2 * alpha0 + 3 * alpha1); break; + case 5 : alpha = detexDivide0To1279By5(1 * alpha0 + 4 * alpha1); break; + case 6 : alpha = 0; break; + case 7 : alpha = 0xFF; break; + } + *(uint32_t *)(pixel_buffer + i * 4) = detexPack32RGBA8( + color_r[pixel], color_g[pixel], color_b[pixel], alpha); + } + return true; +} + diff --git a/Source/ThirdParty/detex/detex.Build.cs b/Source/ThirdParty/detex/detex.Build.cs new file mode 100644 index 000000000..18e3414f8 --- /dev/null +++ b/Source/ThirdParty/detex/detex.Build.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using Flax.Build; + +/// +/// https://github.com/hglm/detex +/// +public class detex : ThirdPartyModule +{ + /// + public override void Init() + { + base.Init(); + + LicenseType = LicenseTypes.MIT; + LicenseFilePath = "LICENSE"; + + // Merge third-party modules into engine binary + BinaryModuleName = "FlaxEngine"; + } +} diff --git a/Source/ThirdParty/detex/detex.h b/Source/ThirdParty/detex/detex.h new file mode 100644 index 000000000..adbeac512 --- /dev/null +++ b/Source/ThirdParty/detex/detex.h @@ -0,0 +1,1194 @@ +/* + +Copyright (c) 2015 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +#ifndef __DETEX_H__ +#define __DETEX_H__ + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +#define __BEGIN_DECLS extern "C" { +#define __END_DECLS } +#else +#define __BEGIN_DECLS /* empty */ +#define __END_DECLS /* empty */ +#endif + +/* Generic helper definitions for shared library support. */ +#if defined _WIN32 || defined __CYGWIN__ + #define DETEX_HELPER_SHARED_IMPORT __declspec(dllimport) + #define DETEX_HELPER_SHARED_EXPORT __declspec(dllexport) + #define DETEX_HELPER_SHARED_LOCAL +#else + #if __GNUC__ >= 4 + #define DETEX_HELPER_SHARED_IMPORT __attribute__ ((visibility ("default"))) + #define DETEX_HELPER_SHARED_EXPORT __attribute__ ((visibility ("default"))) + #define DETEX_HELPER_SHARED_LOCAL __attribute__ ((visibility ("hidden"))) + #else + #define DETEX_HELPER_SHARED_IMPORT + #define DETEX_HELPER_SHARED_EXPORT + #define DETEX_HELPER_SHARED_LOCAL + #endif +#endif + +/* Now we use the generic helper definitions above to define DETEX_API and DETEX_LOCAL. */ +/* DETEX_API is used for the public API symbols. It either imports or exports the symbol */ +/* for shared/DLL libraries (or does nothing for static build). DETEX_LOCAL is used for */ +/* non-API symbols. */ + +#ifdef DETEX_SHARED + /* Defined if DETEX is compiled as a shared library. */ + #ifdef DETEX_SHARED_EXPORTS + /* Defined if we are building the detex shared library (instead of using it). */ + #define DETEX_API DETEX_HELPER_SHARED_EXPORT + #else + #define DETEX_API DETEX_HELPER_SHARED_IMPORT + #endif /* DETEX_SHARED_EXPORTS */ + #define DETEX_LOCAL DETEX_HELPER_SHARED_LOCAL +#else + /* DETEX_SHARED is not defined: this means detex is a static lib. */ + #define DETEX_API + #define DETEX_LOCAL +#endif /* DETEX_SHARED */ + +__BEGIN_DECLS + +#include +#include +#include + +#define DETEX_INLINE_ONLY __attribute__((always_inline)) inline +#define DETEX_RESTRICT __restrict + +/* Maximum uncompressed block size in bytes. */ +#define DETEX_MAX_BLOCK_SIZE 256 + +/* Detex library pixel formats. */ + +enum { + /* The format has 16-bit components. */ + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT = 0x1, + /* The format has 32-bit components. */ + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT = 0x2, + /* The format has an alpha component. */ + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT = 0x4, + /* The sequential component order is RGB. */ + DETEX_PIXEL_FORMAT_RGB_COMPONENT_ORDER_BIT = 0x0, + /* The sequential component order is BGR. */ + DETEX_PIXEL_FORMAT_BGR_COMPONENT_ORDER_BIT = 0x8, + /* The format has one component. */ + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS = 0x0, + /* The format has two components. */ + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS = 0x10, + /* The format has three components. */ + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS = 0x20, + /* The format has four components. */ + DETEX_PIXEL_FORMAT_FOUR_COMPONENTS_BITS = 0x30, + /* The format is stored as 8-bit pixels. */ + DETEX_PIXEL_FORMAT_8BIT_PIXEL_BITS = 0x000, + /* The format is stored as 16-bit pixels. */ + DETEX_PIXEL_FORMAT_16BIT_PIXEL_BITS = 0x100, + /* The format is stored as 24-bit pixels. */ + DETEX_PIXEL_FORMAT_24BIT_PIXEL_BITS = 0x200, + /* The format is stored as 32-bit pixels. */ + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS = 0x300, + /* The format is stored as 48-bit pixels. */ + DETEX_PIXEL_FORMAT_48BIT_PIXEL_BITS = 0x500, + /* The format is stored as 64-bit pixels. */ + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS = 0x700, + /* The format is stored as 96-bit pixels. */ + DETEX_PIXEL_FORMAT_96BIT_PIXEL_BITS = 0xB00, + /* The format is stored as 128-bit pixels. */ + DETEX_PIXEL_FORMAT_128BIT_PIXEL_BITS = 0xF00, + /* The format has signed integer components. */ + DETEX_PIXEL_FORMAT_SIGNED_BIT = 0x1000, + /* The format has (half-)float components. */ + DETEX_PIXEL_FORMAT_FLOAT_BIT = 0x2000, + /* The fomat is HDR (high dynamic range). */ + DETEX_PIXEL_FORMAT_HDR_BIT = 0x4000, + + DETEX_PIXEL_FORMAT_RGBA8 = ( + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_FOUR_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_BGRA8 = ( + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_BGR_COMPONENT_ORDER_BIT | + DETEX_PIXEL_FORMAT_FOUR_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_RGBX8 = ( + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_BGRX8 = ( + DETEX_PIXEL_FORMAT_BGR_COMPONENT_ORDER_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_RGB8 = ( + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_24BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_BGR8 = ( + DETEX_PIXEL_FORMAT_BGR_COMPONENT_ORDER_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_24BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_R8 = ( + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_8BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_SIGNED_R8 = ( + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_8BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_SIGNED_BIT + ), + DETEX_PIXEL_FORMAT_RG8 = ( + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_16BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_SIGNED_RG8 = ( + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_16BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_SIGNED_BIT + ), + DETEX_PIXEL_FORMAT_R16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_16BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_SIGNED_R16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_16BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_SIGNED_BIT + ), + DETEX_PIXEL_FORMAT_RG16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_SIGNED_RG16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_SIGNED_BIT + ), + DETEX_PIXEL_FORMAT_RGB16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_48BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_RGBX16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_RGBA16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_FOUR_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS + ), + DETEX_PIXEL_FORMAT_FLOAT_R16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_16BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_R16_HDR = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_16BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RG16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RG16_HDR = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGBX16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGBX16_HDR = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGBA16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_FOUR_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGBA16_HDR = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_FOUR_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGB16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_48BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGB16_HDR = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_48BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_BGRX16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_BGR_COMPONENT_ORDER_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_BGRX16_HDR = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_BGR_COMPONENT_ORDER_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_SIGNED_FLOAT_RGBX16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_SIGNED_BIT | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_SIGNED_FLOAT_BGRX16 = ( + DETEX_PIXEL_FORMAT_16BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_BGR_COMPONENT_ORDER_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_SIGNED_BIT | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_R32 = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_R32_HDR = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_32BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RG32 = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RG32_HDR = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_TWO_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_64BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGB32 = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_96BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGB32_HDR = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_96BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGBX32 = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_128BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGBX32_HDR = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_THREE_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_128BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGBA32 = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_FOUR_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_128BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT + ), + DETEX_PIXEL_FORMAT_FLOAT_RGBA32_HDR = ( + DETEX_PIXEL_FORMAT_32BIT_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_FOUR_COMPONENTS_BITS | + DETEX_PIXEL_FORMAT_128BIT_PIXEL_BITS | + DETEX_PIXEL_FORMAT_FLOAT_BIT | + DETEX_PIXEL_FORMAT_HDR_BIT + ), + DETEX_PIXEL_FORMAT_A8 = ( + DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT | + DETEX_PIXEL_FORMAT_ONE_COMPONENT_BITS | + DETEX_PIXEL_FORMAT_8BIT_PIXEL_BITS + ), +}; + +/* Mode mask flags. */ + +enum { + DETEX_MODE_MASK_ETC_INDIVIDUAL = 0x1, + DETEX_MODE_MASK_ETC_DIFFERENTIAL = 0x2, + DETEX_MODE_MASK_ETC_T = 0x4, + DETEX_MODE_MASK_ETC_H = 0x8, + DETEX_MODE_MASK_ETC_PLANAR = 0x10, + DETEX_MODE_MASK_ALL_MODES_ETC1 = 0x3, + DETEX_MODE_MASK_ALL_MODES_ETC2 = 0x1F, + DETEX_MODE_MASK_ALL_MODES_ETC2_PUNCHTHROUGH = 0X1E, + DETEX_MODE_MASK_ALL_MODES_BPTC = 0xFF, + DETEX_MODE_MASK_ALL_MODES_BPTC_FLOAT = 0x3FFF, + DETEX_MODE_MASK_ALL = 0XFFFFFFFF, +}; + +/* Decompression function flags. */ + +enum { + /* Function returns false (invalid block) when the compressed block */ + /* is in a format not allowed to be generated by an encoder. */ + DETEX_DECOMPRESS_FLAG_ENCODE = 0x1, + /* For compression formats that have opaque and non-opaque modes, */ + /* return false (invalid block) when the compressed block is encoded */ + /* using a non-opaque mode. */ + DETEX_DECOMPRESS_FLAG_OPAQUE_ONLY = 0x2, + /* For compression formats that have opaque and non-opaque modes, */ + /* return false (invalid block) when the compressed block is encoded */ + /* using an opaque mode. */ + DETEX_DECOMPRESS_FLAG_NON_OPAQUE_ONLY = 0x4, +}; + +/* Set mode function flags. */ + +enum { + /* The block is opaque (alpha is always 0xFF). */ + DETEX_SET_MODE_FLAG_OPAQUE = 0x2, + /* The block is non-opaque (alpha is not always 0xFF). */ + DETEX_SET_MODE_FLAG_NON_OPAQUE = 0x4, + /* The block has punchthrough alpha (alpha is either 0x00 or 0xFF). */ + DETEX_SET_MODE_FLAG_PUNCHTHROUGH = 0x8, + /* The block only consists of one or two different pixel colors. */ + DETEX_SET_MODE_FLAG_MAX_TWO_COLORS = 0x10, +}; + +/* + * Decompression functions for 8-bit RGB8/RGBA8 formats. The output pixel format + * is DETEX_PIXEL_FORMAT_RGBA8 or DETEX_PIXEL_FORMAT_RGBX8 (32-bit pixels with + * optional alpha component, red component in lowest-order byte. When the + * texture format does not have alpha, alpha is set to 0xFF. + */ + +/* Decompress a 64-bit 4x4 pixel texture block compressed using the ETC1 */ +/* format. */ +DETEX_API bool detexDecompressBlockETC1(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 64-bit 4x4 pixel texture block compressed using the ETC2 */ +/* format. */ +DETEX_API bool detexDecompressBlockETC2(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 64-bit 4x4 pixel texture block compressed using the */ +/* ETC2_PUNCHTROUGH format. */ +DETEX_API bool detexDecompressBlockETC2_PUNCHTHROUGH(const uint8_t *bitstring, + uint32_t mode_mask, uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 128-bit 4x4 pixel texture block compressed using the ETC2_EAC */ +/* format. */ +DETEX_API bool detexDecompressBlockETC2_EAC(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); + + +/* Decompress a 64-bit 4x4 pixel texture block compressed using the BC1 */ +/* format. */ +DETEX_API bool detexDecompressBlockBC1(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 64-bit 4x4 pixel texture block compressed using the BC1A */ +/* format. */ +DETEX_API bool detexDecompressBlockBC1A(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 64-bit 4x4 pixel texture block compressed using the BC2 */ +/* format. */ +DETEX_API bool detexDecompressBlockBC2(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 64-bit 4x4 pixel texture block compressed using the BC3 */ +/* format. */ +DETEX_API bool detexDecompressBlockBC3(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 128-bit 4x4 pixel texture block compressed using the BPTC */ +/* (BC7) format. */ +DETEX_API bool detexDecompressBlockBPTC(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); + +/* + * Decompression functions for 8-bit unsigned R and RG formats. The + * output format is DETEX_PIXEL_FORMAT_R8 or DETEX_PIXEL_FORMAT_RG8. + */ + +/* Decompress a 64-bit 4x4 pixel texture block compressed using the */ +/* unsigned RGTC1 (BC4) format. */ +DETEX_API bool detexDecompressBlockRGTC1(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 128-bit 4x4 pixel texture block compressed using the */ +/* unsigned RGTC2 (BC5) format. */ +DETEX_API bool detexDecompressBlockRGTC2(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); + +/* + * Decompression functions for 16-bit unsigned/signed R and RG formats. The + * output format is DETEX_PIXEL_FORMAT_R16, DETEX_PIXEL_FORMAT_SIGNED_R16, + * DETEX_PIXEL_FORMAT_RG16, or DETEX_PIXEL_FORMAT_SIGNED_RG16. + */ + +/* Decompress a 64-bit 4x4 pixel texture block compressed using the */ +/* signed RGTC1 (signed BC4) format. */ +DETEX_API bool detexDecompressBlockSIGNED_RGTC1(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 128-bit 4x4 pixel texture block compressed using the */ +/* signed RGTC2 (signed BC5) format. */ +DETEX_API bool detexDecompressBlockSIGNED_RGTC2(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 64-bit 4x4 pixel texture block compressed using the */ +/* ETC2_R11_EAC format. */ +DETEX_API bool detexDecompressBlockEAC_R11(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 64-bit 4x4 pixel texture block compressed using the */ +/* ETC2_SIGNED_R11_EAC format. */ +DETEX_API bool detexDecompressBlockEAC_SIGNED_R11(const uint8_t *bitstring, + uint32_t mode_mask, uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 128-bit 4x4 pixel texture block compressed using the */ +/* ETC2_RG11_EAC format. */ +DETEX_API bool detexDecompressBlockEAC_RG11(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 128-bit 4x4 pixel texture block compressed using the */ +/* ETC2_SIGNED_RG11_EAC format. */ +DETEX_API bool detexDecompressBlockEAC_SIGNED_RG11(const uint8_t *bitstring, + uint32_t mode_mask, uint32_t flags, uint8_t *pixel_buffer); + +/* + * Decompression functions for 16-bit half-float formats. The output format is + * DETEX_PIXEL_FORMAT_FLOAT_RGBX16 or DETEX_PIXEL_FORMAT_SIGNED_FLOAT_RGBX16. + */ + +/* Decompress a 128-bit 4x4 pixel texture block compressed using the */ +/* BPTC_FLOAT (BC6H) format. The output format is */ +/* DETEX_PIXEL_FORMAT_FLOAT_RGBX16. */ +DETEX_API bool detexDecompressBlockBPTC_FLOAT(const uint8_t *bitstring, uint32_t mode_mask, + uint32_t flags, uint8_t *pixel_buffer); +/* Decompress a 128-bit 4x4 pixel texture block compressed using the */ +/* BPTC_FLOAT (BC6H_FLOAT) format. The output format is */ +/* DETEX_PIXEL_FORMAT_SIGNED_FLOAT_RGBX16. */ +DETEX_API bool detexDecompressBlockBPTC_SIGNED_FLOAT(const uint8_t *bitstring, + uint32_t mode_mask, uint32_t flags, uint8_t *pixel_buffer); + + +/* + * Get mode functions. They return the internal compression format mode used + * inside the compressed block. For compressed formats that do not use a mode, + * there is no GetMode function. + */ + +DETEX_API uint32_t detexGetModeBC1(const uint8_t *bitstring); +DETEX_API uint32_t detexGetModeETC1(const uint8_t *bitstring); +DETEX_API uint32_t detexGetModeETC2(const uint8_t *bitstring); +DETEX_API uint32_t detexGetModeETC2_PUNCHTHROUGH(const uint8_t *bitstring); +DETEX_API uint32_t detexGetModeETC2_EAC(const uint8_t *bitstring); +DETEX_API uint32_t detexGetModeBPTC(const uint8_t *bitstring); +DETEX_API uint32_t detexGetModeBPTC_FLOAT(const uint8_t *bitstring); +DETEX_API uint32_t detexGetModeBPTC_SIGNED_FLOAT(const uint8_t *bitstring); + +/* + * Set mode functions. The set mode function modifies a compressed texture block + * so that the specified mode is set, making use of information about the block + * (whether it is opaque, non-opaque or punchthrough for formats with alpha, + * whether at most two different colors are used). For compressed formats + * that do not use a mode, there is no SetMode function. + */ + +DETEX_API void detexSetModeBC1(uint8_t *bitstring, uint32_t mode, uint32_t flags, + uint32_t *colors); +DETEX_API void detexSetModeETC1(uint8_t *bitstring, uint32_t mode, uint32_t flags, + uint32_t *colors); +DETEX_API void detexSetModeETC2(uint8_t *bitstring, uint32_t mode, uint32_t flags, + uint32_t *colors); +DETEX_API void detexSetModeETC2_PUNCHTHROUGH(uint8_t *bitstring, uint32_t mode, uint32_t flags, + uint32_t *colors); +DETEX_API void detexSetModeETC2_EAC(uint8_t *bitstring, uint32_t mode, uint32_t flags, + uint32_t *colors); +DETEX_API void detexSetModeBPTC(uint8_t *bitstring, uint32_t mode, uint32_t flags, + uint32_t *colors); +DETEX_API void detexSetModeBPTC_FLOAT(uint8_t *bitstring, uint32_t mode, uint32_t flags, + uint32_t *colors); + +/* Compressed texture format definitions for general texture decompression */ +/* functions. */ + +#define DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS(n) ((uint32_t)n << 24) + +enum { + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_UNCOMPRESSED = 0, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC1 = 1, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_DXT1 = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC1, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_S3TC = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC1, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC1A, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_DXT1A = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC1A, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC2, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_DXT3 = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC2, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC3, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_DXT5 = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC3, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_RGTC1, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC4_UNORM = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_RGTC1, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_SIGNED_RGTC1, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC4_SNORM = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_SIGNED_RGTC1, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_RGTC2, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC5_UNORM = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_RGTC2, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_SIGNED_RGTC2, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC5_SNORM = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_SIGNED_RGTC2, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC_FLOAT, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC6H_UF16 = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC_FLOAT, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC_SIGNED_FLOAT, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC6H_SF16 = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC_SIGNED_FLOAT, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC7 = DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ETC1, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ETC2, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ETC2_PUNCHTHROUGH, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ETC2_EAC, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_EAC_R11, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_EAC_SIGNED_R11, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_EAC_RG11, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_EAC_SIGNED_RG11, + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ASTC_4X4, +}; + +enum { + DETEX_TEXTURE_FORMAT_PIXEL_FORMAT_MASK = 0x0000FFFF, + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT = 0x00800000, + DETEX_TEXTURE_FORMAT_BC1 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC1) | + DETEX_PIXEL_FORMAT_RGBX8 + ), + DETEX_TEXTURE_FORMAT_BC1A = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC1A) | + DETEX_PIXEL_FORMAT_RGBA8 + ), + DETEX_TEXTURE_FORMAT_BC2 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC2) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_RGBA8 + ), + DETEX_TEXTURE_FORMAT_BC3 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BC3) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_RGBA8 + ), + DETEX_TEXTURE_FORMAT_RGTC1 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_RGTC1) | + DETEX_PIXEL_FORMAT_R8 + ), + DETEX_TEXTURE_FORMAT_SIGNED_RGTC1 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_SIGNED_RGTC1) | + DETEX_PIXEL_FORMAT_SIGNED_R16 + ), + DETEX_TEXTURE_FORMAT_RGTC2 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_RGTC2) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_RG8 + ), + DETEX_TEXTURE_FORMAT_SIGNED_RGTC2 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_SIGNED_RGTC2) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_SIGNED_RG16 + ), + DETEX_TEXTURE_FORMAT_BPTC_FLOAT = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC_FLOAT) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_FLOAT_RGBX16 + ), + DETEX_TEXTURE_FORMAT_BPTC_SIGNED_FLOAT = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC_SIGNED_FLOAT) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_SIGNED_FLOAT_RGBX16 + ), + DETEX_TEXTURE_FORMAT_BPTC = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_BPTC) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_RGBA8 + ), + DETEX_TEXTURE_FORMAT_ETC1 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ETC1) | + DETEX_PIXEL_FORMAT_RGBX8 + ), + DETEX_TEXTURE_FORMAT_ETC2 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ETC2) | + DETEX_PIXEL_FORMAT_RGBX8 + ), + DETEX_TEXTURE_FORMAT_ETC2_PUNCHTHROUGH = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ETC2_PUNCHTHROUGH) | + DETEX_PIXEL_FORMAT_RGBA8 + ), + DETEX_TEXTURE_FORMAT_ETC2_EAC = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ETC2_EAC) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_RGBA8 + ), + DETEX_TEXTURE_FORMAT_EAC_R11 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_EAC_R11) | + DETEX_PIXEL_FORMAT_R16 + ), + DETEX_TEXTURE_FORMAT_EAC_SIGNED_R11 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_EAC_SIGNED_R11) | + DETEX_PIXEL_FORMAT_SIGNED_R16 + ), + DETEX_TEXTURE_FORMAT_EAC_RG11 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_EAC_RG11) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_RG16 + ), + DETEX_TEXTURE_FORMAT_EAC_SIGNED_RG11 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_EAC_SIGNED_RG11) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_SIGNED_RG16 + ), + DETEX_TEXTURE_FORMAT_ASTC_4X4 = ( + DETEX_TEXTURE_FORMAT_COMPRESSED_FORMAT_BITS( + DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_ASTC_4X4 ) | + DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT | + DETEX_PIXEL_FORMAT_RGBA8 + ), +}; + +typedef struct { + uint32_t format; + uint8_t *data; + int width; + int height; + int width_in_blocks; + int height_in_blocks; +} detexTexture; + +/* + * General texture decompression functions (tiled or linear) with specified + * compression format. + */ + +/* + * General block decompression function. Block is decompressed using the given + * compressed format, and stored in the given pixel format. + */ +DETEX_API bool detexDecompressBlock(const uint8_t *bitstring, uint32_t texture_format, + uint32_t mode_mask, uint32_t flags, uint8_t *pixel_buffer, + uint32_t pixel_format); + +/* + * Decode texture function (tiled). Decode an entire compressed texture into an + * array of image buffer tiles (corresponding to compressed blocks), converting + * into the given pixel format. + */ +DETEX_API bool detexDecompressTextureTiled(const detexTexture *texture, uint8_t *pixel_buffer, + uint32_t pixel_format); + +/* + * Decode texture function (linear). Decode an entire texture into a single + * image buffer, with pixels stored row-by-row, converting into the given pixel + * format. + */ +DETEX_API bool detexDecompressTextureLinear(const detexTexture *texture, uint8_t *pixel_buffer, + uint32_t pixel_format); + + +/* + * Miscellaneous functions. + */ + +/* + * Convert pixels between different formats. The target pixel buffer must + * be allocated with sufficient size to the hold the result. Returns true if + * succesful. + */ +DETEX_API bool detexConvertPixels(uint8_t *source_pixel_buffer, uint32_t nu_pixels, + uint32_t source_pixel_format, uint8_t *target_pixel_buffer, + uint32_t target_pixel_format); + +/* Convert in-place, modifying the source pixel buffer only. If any conversion step changes the */ +/* pixel size, the function will not be succesful and return false. */ +DETEX_API bool detexConvertPixelsInPlace(uint8_t * DETEX_RESTRICT source_pixel_buffer, + uint32_t nu_pixels, uint32_t source_pixel_format, uint32_t target_pixel_format); + +/* Return the component bitfield masks for a pixel format (pixel size must be at most 64 bits). */ +/* Return true if succesful. */ +DETEX_API bool detexGetComponentMasks(uint32_t texture_format, uint64_t *red_mask, uint64_t *green_mask, + uint64_t *blue_mask, uint64_t *alpha_mask); + +/* Return a text description/identifier of the texture type. */ +DETEX_API const char *detexGetTextureFormatText(uint32_t texture_format); + +/* Return a alternative text description of the texture type. Returns empty string */ +/* when there is no alternative description. */ +DETEX_API const char *detexGetAlternativeTextureFormatText(uint32_t texture_format); + +/* Return OpenGL TexImage2D/KTX file parameters for a texture format. */ +DETEX_API bool detexGetOpenGLParameters(uint32_t texture_format, int *gl_internal_format, + uint32_t *gl_format, uint32_t *gl_type); + +/* Return DirectX 10 format for a texture format. */ +DETEX_API bool detexGetDX10Parameters(uint32_t texture_format, uint32_t *dx10_format); + +/* Return the error message for the last encountered error. */ +DETEX_API const char *detexGetErrorMessage(); + + +/* + * HDR-related functions. + */ + +/* Set HDR gamma curve parameters. */ +DETEX_API void detexSetHDRParameters(float gamma, float range_min, float range_max); + +/* Calculate the dynamic range of a pixel buffer. Valid for float and half-float formats. */ +/* Returns true if successful. */ +DETEX_API bool detexCalculateDynamicRange(uint8_t *pixel_buffer, int nu_pixels, uint32_t pixel_format, + float *range_min_out, float *range_max_out); + + +/* + * Texture file loading. + */ + +/* Load texture from KTX file with mip-maps. Returns true if successful. */ +/* nu_levels is a return parameter that returns the number of mipmap levels found. */ +/* textures_out is a return parameter for an array of detexTexture pointers that is allocated, */ +/* free with free(). textures_out[i] are allocated textures corresponding to each level, free */ +/* with free(). */ +DETEX_API bool detexLoadKTXFileWithMipmaps(const char *filename, int max_mipmaps, detexTexture ***textures_out, + int *nu_levels_out); + +/* Load texture from KTX file (first mip-map only). Returns true if successful. */ +/* The texture is allocated, free with free(). */ +DETEX_API bool detexLoadKTXFile(const char *filename, detexTexture **texture_out); + +/* Save textures to KTX file (multiple mip-maps levels). Return true if succesful. */ +DETEX_API bool detexSaveKTXFileWithMipmaps(detexTexture **textures, int nu_levels, const char *filename); + +/* Save texture to KTX file (single mip-map level). Returns true if succesful. */ +DETEX_API bool detexSaveKTXFile(detexTexture *texture, const char *filename); + +/* Load texture from DDS file with mip-maps. Returns true if successful. */ +/* nu_levels is a return parameter that returns the number of mipmap levels found. */ +/* textures_out is a return parameter for an array of detexTexture pointers that is allocated, */ +/* free with free(). textures_out[i] are allocated textures corresponding to each level, free */ +/* with free(). */ +DETEX_API bool detexLoadDDSFileWithMipmaps(const char *filename, int max_mipmaps, detexTexture ***textures_out, + int *nu_levels_out); + +/* Load texture from DDS file (first mip-map only). Returns true if successful. */ +/* The texture is allocated, free with free(). */ +DETEX_API bool detexLoadDDSFile(const char *filename, detexTexture **texture_out); + +/* Save textures to DDS file (multiple mip-maps levels). Return true if succesful. */ +DETEX_API bool detexSaveDDSFileWithMipmaps(detexTexture **textures, int nu_levels, const char *filename); + +/* Save texture to DDS file (single mip-map level). Returns true if succesful. */ +DETEX_API bool detexSaveDDSFile(detexTexture *texture, const char *filename); + +/* Load texture file (type autodetected from extension) with mipmaps. */ +DETEX_API bool detexLoadTextureFileWithMipmaps(const char *filename, int max_mipmaps, detexTexture ***textures_out, + int *nu_levels_out); + +/* Load texture file (type autodetected from extension). */ +DETEX_API bool detexLoadTextureFile(const char *filename, detexTexture **texture_out); + +/* Load texture from raw file (first mip-map only) given the format and dimensions */ +/* in texture. Returns true if successful. */ +/* The texture->data is allocated, free with free(). */ +DETEX_API bool detexLoadRawFile(const char *filename, detexTexture *texture); + +/* Save texture to raw file (first mip-map only) given the format and dimensions */ +/* in texture. Returns true if successful. */ +DETEX_API bool detexSaveRawFile(detexTexture *texture, const char *filename); + +/* Return pixel size in bytes for pixel format or texture format (decompressed). */ +static DETEX_INLINE_ONLY int detexGetPixelSize(uint32_t pixel_format) { + return 1 + ((pixel_format & 0xF00) >> 8); +} + +/* Return the number of components of a pixel format or texture format. */ +static DETEX_INLINE_ONLY int detexGetNumberOfComponents(uint32_t pixel_format) { + return 1 + ((pixel_format & 0x30) >> 4); +} + +/* Return the component size in bytes of a pixel format or texture format. */ +static DETEX_INLINE_ONLY int detexGetComponentSize(uint32_t pixel_format) { + return 1 << (pixel_format & 0x3); +} + +/* Return the approximate precision in bits of the components of a pixel format. */ +static DETEX_INLINE_ONLY uint32_t detexGetComponentPrecision(uint32_t pixel_format) { + return detexGetComponentSize(pixel_format) * 8 - + ((pixel_format & DETEX_PIXEL_FORMAT_FLOAT_BIT) != 0) * 5 * + (1 + (detexGetComponentSize(pixel_format) == 4)); +} + +/* Return the total size of a compressed texture. */ +static DETEX_INLINE_ONLY uint32_t detexTextureSize(uint32_t width_in_blocks, +uint32_t height_in_blocks, uint32_t pixel_format) { + return width_in_blocks * height_in_blocks * detexGetPixelSize(pixel_format) * 16; +} + +/* Return whether a pixel or texture format has an alpha component. */ +static DETEX_INLINE_ONLY uint32_t detexFormatHasAlpha(uint32_t pixel_format) { + return (pixel_format & DETEX_PIXEL_FORMAT_ALPHA_COMPONENT_BIT) != 0; +} + + +/* Return the compressed texture type index of a texture format. */ +static DETEX_INLINE_ONLY uint32_t detexGetCompressedFormat(uint32_t texture_format) { + return texture_format >> 24; +} + +/* Return the block size of a compressed texture format in bytes. */ +static DETEX_INLINE_ONLY uint32_t detexGetCompressedBlockSize(uint32_t texture_format) { + return 8 + ((texture_format & DETEX_TEXTURE_FORMAT_128BIT_BLOCK_BIT) >> 20); +} + +/* Return whether a texture format is compressed. */ +static DETEX_INLINE_ONLY uint32_t detexFormatIsCompressed(uint32_t texture_format) { + return detexGetCompressedFormat(texture_format) != DETEX_COMPRESSED_TEXTURE_FORMAT_INDEX_UNCOMPRESSED; +} + +/* Return the pixel format of a texture format. */ +static DETEX_INLINE_ONLY uint32_t detexGetPixelFormat(uint32_t texture_format) { + return texture_format & DETEX_TEXTURE_FORMAT_PIXEL_FORMAT_MASK; +} + + +DETEX_API extern const uint8_t detex_clamp0to255_table[767]; + +/* Clamp an integer value in the range -255 to 511 to the the range 0 to 255. */ +static DETEX_INLINE_ONLY uint8_t detexClamp0To255(int x) { + return detex_clamp0to255_table[x + 255]; +} + +/* Clamp a float point value to the range 0.0 to 1.0f. */ +static DETEX_INLINE_ONLY float detexClamp0To1(float f) { + if (f < 0.0f) + return 0.0f; + else if (f > 1.0f) + return 1.0f; + else + return f; +} + + +/* Integer division using look-up tables, used by BC1/2/3 and RGTC (BC4/5) */ +/* decompression. */ + +DETEX_API extern const uint8_t detex_division_by_3_table[768]; + +static DETEX_INLINE_ONLY uint32_t detexDivide0To767By3(uint32_t value) { + return detex_division_by_3_table[value]; +} + +DETEX_API extern const uint8_t detex_division_by_7_table[1792]; + +static DETEX_INLINE_ONLY uint32_t detexDivide0To1791By7(uint32_t value) { + return detex_division_by_7_table[value]; +} + +static DETEX_INLINE_ONLY int8_t detexSignInt32(int v) { + return (int8_t)((v >> 31) | - (- v >> 31)); +} + +static DETEX_INLINE_ONLY int detexDivideMinus895To895By7(int value) { + return (int8_t)detex_division_by_7_table[abs(value)] * detexSignInt32(value); +} + +DETEX_API extern const uint8_t detex_division_by_5_table[1280]; + +static DETEX_INLINE_ONLY uint32_t detexDivide0To1279By5(uint32_t value) { + return detex_division_by_5_table[value]; +} + +static DETEX_INLINE_ONLY int detexDivideMinus639To639By5(int value) { + return (int8_t)detex_division_by_5_table[abs(value)] * detexSignInt32(value); +} + + +/* + * Define some short functions for pixel packing/unpacking. The compiler will + * take care of optimization by inlining and removing unused functions. + * + * The pixel format used corresponds to formats with an RGB component order, + * including: + * + * DETEX_PIXEL_FORMAT_RGB8, DETEX_PIXEL_FORMAT_RGBA8 + * detexPack32RGB8Alpha0xFF, detexPack32R8, detexPack32G8, detexPack32B8, + * detexPixel32GetR8, detexPixel32GetG8, detexPixel32GetB8 + * DETEX_PIXEL_FORMAT_RGBA8 + * detexPack32RGBA8, detexPack32A8, detexPixel32GetA8 + * DETEX_PIXEL_FORMAT_RG16, DETEX_PIXEL_FORMAT_SIGNED_RG16, + * DETEX_PIXEL_FORMAT_FLOAT_RG16 + * detexPack32RG16, detexPack32R16, detexPack32G16, detexPack32RG16, + * detexPixel32GetR16, detexPixel32GetG16 + * DETEX_PIXEL_FORMAT_FLOAT_RGBX16, DETEX_PIXEL_FORMAT_SIGNED_FLOAT_RGBX16 + * detexPack64RGB16, detexPack64R16, detexPack64G16, detexPack64B16, + * detexPixel64GetR16, detexPixel64GetG16, detexPixel64GetB16 + */ + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || !defined(__BYTE_ORDER__) + +static DETEX_INLINE_ONLY uint32_t detexPack32RGBA8(int r, int g, int b, int a) { + return (uint32_t)r | ((uint32_t)g << 8) | ((uint32_t)b << 16) | + ((uint32_t)a << 24); +} + +static DETEX_INLINE_ONLY uint32_t detexPack32RGB8Alpha0xFF(int r, int g, int b) { + return detexPack32RGBA8(r, g, b, 0xFF); +} + +static DETEX_INLINE_ONLY uint32_t detexPack32R8(int r) { + return (uint32_t)r; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32G8(int g) { + return (uint32_t)g << 8; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32B8(int b) { + return (uint32_t)b << 16; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32A8(int a) { + return (uint32_t)a << 24; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32RG8(uint32_t r8, uint32_t g8) { + return r8 | (g8 << 8); +} + +static DETEX_INLINE_ONLY uint32_t detexPack32R16(uint32_t r16) { + return r16; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32G16(uint32_t g16) { + return g16 << 16; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32RG16(uint32_t r16, uint32_t g16) { + return r16 | (g16 << 16); +} + +static DETEX_INLINE_ONLY uint64_t detexPack64R16(uint32_t r16) { + return r16; +} + +static DETEX_INLINE_ONLY uint64_t detexPack64G16(uint32_t g16) { + return g16 << 16; +} + +static DETEX_INLINE_ONLY uint64_t detexPack64B16(uint32_t b16) { + return (uint64_t)b16 << 32; +} + +static DETEX_INLINE_ONLY uint64_t detexPack64A16(uint32_t a16) { + return (uint64_t)a16 << 48; +} + +static DETEX_INLINE_ONLY uint64_t detexPack64RGB16(uint16_t r16, uint16_t g16, uint16_t b16) { + return (uint64_t)r16 | ((uint64_t)g16 << 16) | ((uint64_t)b16 << 32); +} + +static DETEX_INLINE_ONLY uint64_t detexPack64RGBA16(uint16_t r16, uint16_t g16, uint16_t b16, uint16_t a16) { + return (uint64_t)r16 | ((uint64_t)g16 << 16) | ((uint64_t)b16 << 32) | ((uint64_t)a16 << 48); +} + +static DETEX_INLINE_ONLY uint32_t detexPixel32GetR8(uint32_t pixel) { + return pixel & 0xFF; +} + +static DETEX_INLINE_ONLY uint32_t detexPixel32GetG8(uint32_t pixel) { + return (pixel & 0xFF00) >> 8; +} + +static DETEX_INLINE_ONLY uint32_t detexPixel32GetB8(uint32_t pixel) { + return (pixel & 0xFF0000) >> 16; +} + +static DETEX_INLINE_ONLY uint32_t detexPixel32GetA8(uint32_t pixel) { + return (pixel & 0xFF000000) >> 24; +} + +static DETEX_INLINE_ONLY int detexPixel32GetSignedR8(uint32_t pixel) { + return (int8_t)(pixel & 0xFF); +} + +static DETEX_INLINE_ONLY int detexPixel32GetSignedG8(uint32_t pixel) { + return (int8_t)((pixel & 0xFF00) >> 8); +} + +static DETEX_INLINE_ONLY uint32_t detexPixel32GetR16(uint32_t pixel) { + return pixel & 0x0000FFFF; +} + +static DETEX_INLINE_ONLY uint32_t detexPixel32GetG16(uint32_t pixel) { + return (pixel & 0xFFFF0000) >> 16; +} + +static DETEX_INLINE_ONLY int detexPixel32GetSignedR16(uint32_t pixel) { + return (int16_t)(pixel & 0x0000FFFF); +} + +static DETEX_INLINE_ONLY int detexPixel32GetSignedG16(uint32_t pixel) { + return (int16_t)((pixel & 0xFFFF0000) >> 16); +} + +static DETEX_INLINE_ONLY uint64_t detexPixel64GetR16(uint64_t pixel) { + return pixel & 0xFFFF; +} + +static DETEX_INLINE_ONLY uint64_t detexPixel64GetG16(uint64_t pixel) { + return (pixel & 0xFFFF0000) >> 16; +} + +static DETEX_INLINE_ONLY uint64_t detexPixel64GetB16(uint64_t pixel) { + return (pixel & 0xFFFF00000000) >> 32; +} + +static DETEX_INLINE_ONLY uint64_t detexPixel64GetA16(uint64_t pixel) { + return (pixel & 0xFFFF000000000000) >> 48; +} + +#define DETEX_PIXEL32_ALPHA_BYTE_OFFSET 3 + +#else + +#error Big-endian byte order not supported. + +static DETEX_INLINE_ONLY uint32_t detexPack32RGBA8(int r, int g, int b, int a) { + return a | ((uint32_t)b << 8) | ((uint32_t)g << 16) | ((uint32_t)r << 24); +} + +static DETEX_INLINE_ONLY uint32_t detexPack32RGB8Alpha0xFF(int r, int g, int b) { + return pack_rgba(r, g, b, 0xFF); +} + +static DETEX_INLINE_ONLY uint32_t detexPack32R8(int r) { + return (uint32_t)r << 24; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32G8(int g) { + return (uint32_t)g << 16; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32B8(int b) { + return (uint32_t)b << 8; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32A8(int a) { + return a; +} + +static DETEX_INLINE_ONLY uint32_t detexPack32RG16(uint32_t r16, uint32_t g16) { + return g16 | (r16 << 16); +} + +static DETEX_INLINE_ONLY int detexPixel32GetR8(uint32_t pixel) { + return (pixel & 0xFF000000) >> 24; +} + +static DETEX_INLINE_ONLY int detexPixel32GetG8(uint32_t pixel) { + return (pixel & 0xFF0000) >> 16; +} + +static DETEX_INLINE_ONLY int detexPixel32GetB8(uint32_t pixel) { + return (pixel & 0xFF00) >> 8; +} + +static DETEX_INLINE_ONLY int detexPixel32GetA8(uint32_t pixel) { + return pixel & 0xFF; +} + +static DETEX_INLINE_ONLY uint32_t detexPixel32GetR16(uint32_t pixel) { + return ((pixel & 0xFF000000) >> 24) | ((pixel & 0x00FF0000) >> 8); +} + +static DETEX_INLINE_ONLY uint32_t detexPixel32GetG16(uint32_t pixel) { + return ((pixel & 0x0000FF00) >> 8) | ((pixel & 0x000000FF) << 8); +} + +#define DETEX_PIXEL32_ALPHA_BYTE_OFFSET 0 + +#endif + +__END_DECLS + +#endif + diff --git a/Source/ThirdParty/detex/division-tables.cpp b/Source/ThirdParty/detex/division-tables.cpp new file mode 100644 index 000000000..cb07c0c42 --- /dev/null +++ b/Source/ThirdParty/detex/division-tables.cpp @@ -0,0 +1,512 @@ +/* + +Copyright (c) 2015 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +#include "detex.h" + +// Integer division using look-up tables, used by BC1/2/3 and RGTC (BC4/5) +// decompression. + +const uint8_t detex_division_by_3_table[768] = { + 0, 0, 0, 1, 1, 1, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, + 5, 5, 6, 6, 6, 7, 7, 7, + 8, 8, 8, 9, 9, 9, 10, 10, + 10, 11, 11, 11, 12, 12, 12, 13, + 13, 13, 14, 14, 14, 15, 15, 15, + 16, 16, 16, 17, 17, 17, 18, 18, + 18, 19, 19, 19, 20, 20, 20, 21, + 21, 21, 22, 22, 22, 23, 23, 23, + 24, 24, 24, 25, 25, 25, 26, 26, + 26, 27, 27, 27, 28, 28, 28, 29, + 29, 29, 30, 30, 30, 31, 31, 31, + 32, 32, 32, 33, 33, 33, 34, 34, + 34, 35, 35, 35, 36, 36, 36, 37, + 37, 37, 38, 38, 38, 39, 39, 39, + 40, 40, 40, 41, 41, 41, 42, 42, + 42, 43, 43, 43, 44, 44, 44, 45, + 45, 45, 46, 46, 46, 47, 47, 47, + 48, 48, 48, 49, 49, 49, 50, 50, + 50, 51, 51, 51, 52, 52, 52, 53, + 53, 53, 54, 54, 54, 55, 55, 55, + 56, 56, 56, 57, 57, 57, 58, 58, + 58, 59, 59, 59, 60, 60, 60, 61, + 61, 61, 62, 62, 62, 63, 63, 63, + 64, 64, 64, 65, 65, 65, 66, 66, + 66, 67, 67, 67, 68, 68, 68, 69, + 69, 69, 70, 70, 70, 71, 71, 71, + 72, 72, 72, 73, 73, 73, 74, 74, + 74, 75, 75, 75, 76, 76, 76, 77, + 77, 77, 78, 78, 78, 79, 79, 79, + 80, 80, 80, 81, 81, 81, 82, 82, + 82, 83, 83, 83, 84, 84, 84, 85, + 85, 85, 86, 86, 86, 87, 87, 87, + 88, 88, 88, 89, 89, 89, 90, 90, + 90, 91, 91, 91, 92, 92, 92, 93, + 93, 93, 94, 94, 94, 95, 95, 95, + 96, 96, 96, 97, 97, 97, 98, 98, + 98, 99, 99, 99, 100, 100, 100, 101, + 101, 101, 102, 102, 102, 103, 103, 103, + 104, 104, 104, 105, 105, 105, 106, 106, + 106, 107, 107, 107, 108, 108, 108, 109, + 109, 109, 110, 110, 110, 111, 111, 111, + 112, 112, 112, 113, 113, 113, 114, 114, + 114, 115, 115, 115, 116, 116, 116, 117, + 117, 117, 118, 118, 118, 119, 119, 119, + 120, 120, 120, 121, 121, 121, 122, 122, + 122, 123, 123, 123, 124, 124, 124, 125, + 125, 125, 126, 126, 126, 127, 127, 127, + 128, 128, 128, 129, 129, 129, 130, 130, + 130, 131, 131, 131, 132, 132, 132, 133, + 133, 133, 134, 134, 134, 135, 135, 135, + 136, 136, 136, 137, 137, 137, 138, 138, + 138, 139, 139, 139, 140, 140, 140, 141, + 141, 141, 142, 142, 142, 143, 143, 143, + 144, 144, 144, 145, 145, 145, 146, 146, + 146, 147, 147, 147, 148, 148, 148, 149, + 149, 149, 150, 150, 150, 151, 151, 151, + 152, 152, 152, 153, 153, 153, 154, 154, + 154, 155, 155, 155, 156, 156, 156, 157, + 157, 157, 158, 158, 158, 159, 159, 159, + 160, 160, 160, 161, 161, 161, 162, 162, + 162, 163, 163, 163, 164, 164, 164, 165, + 165, 165, 166, 166, 166, 167, 167, 167, + 168, 168, 168, 169, 169, 169, 170, 170, + 170, 171, 171, 171, 172, 172, 172, 173, + 173, 173, 174, 174, 174, 175, 175, 175, + 176, 176, 176, 177, 177, 177, 178, 178, + 178, 179, 179, 179, 180, 180, 180, 181, + 181, 181, 182, 182, 182, 183, 183, 183, + 184, 184, 184, 185, 185, 185, 186, 186, + 186, 187, 187, 187, 188, 188, 188, 189, + 189, 189, 190, 190, 190, 191, 191, 191, + 192, 192, 192, 193, 193, 193, 194, 194, + 194, 195, 195, 195, 196, 196, 196, 197, + 197, 197, 198, 198, 198, 199, 199, 199, + 200, 200, 200, 201, 201, 201, 202, 202, + 202, 203, 203, 203, 204, 204, 204, 205, + 205, 205, 206, 206, 206, 207, 207, 207, + 208, 208, 208, 209, 209, 209, 210, 210, + 210, 211, 211, 211, 212, 212, 212, 213, + 213, 213, 214, 214, 214, 215, 215, 215, + 216, 216, 216, 217, 217, 217, 218, 218, + 218, 219, 219, 219, 220, 220, 220, 221, + 221, 221, 222, 222, 222, 223, 223, 223, + 224, 224, 224, 225, 225, 225, 226, 226, + 226, 227, 227, 227, 228, 228, 228, 229, + 229, 229, 230, 230, 230, 231, 231, 231, + 232, 232, 232, 233, 233, 233, 234, 234, + 234, 235, 235, 235, 236, 236, 236, 237, + 237, 237, 238, 238, 238, 239, 239, 239, + 240, 240, 240, 241, 241, 241, 242, 242, + 242, 243, 243, 243, 244, 244, 244, 245, + 245, 245, 246, 246, 246, 247, 247, 247, + 248, 248, 248, 249, 249, 249, 250, 250, + 250, 251, 251, 251, 252, 252, 252, 253, + 253, 253, 254, 254, 254, 255, 255, 255, +}; + +const uint8_t detex_division_by_7_table[1792] = { + 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 4, 4, 4, 4, + 4, 4, 4, 5, 5, 5, 5, 5, + 5, 5, 6, 6, 6, 6, 6, 6, + 6, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 9, + 9, 9, 9, 9, 9, 9, 10, 10, + 10, 10, 10, 10, 10, 11, 11, 11, + 11, 11, 11, 11, 12, 12, 12, 12, + 12, 12, 12, 13, 13, 13, 13, 13, + 13, 13, 14, 14, 14, 14, 14, 14, + 14, 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, 17, + 17, 17, 17, 17, 17, 17, 18, 18, + 18, 18, 18, 18, 18, 19, 19, 19, + 19, 19, 19, 19, 20, 20, 20, 20, + 20, 20, 20, 21, 21, 21, 21, 21, + 21, 21, 22, 22, 22, 22, 22, 22, + 22, 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, 25, + 25, 25, 25, 25, 25, 25, 26, 26, + 26, 26, 26, 26, 26, 27, 27, 27, + 27, 27, 27, 27, 28, 28, 28, 28, + 28, 28, 28, 29, 29, 29, 29, 29, + 29, 29, 30, 30, 30, 30, 30, 30, + 30, 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, 33, + 33, 33, 33, 33, 33, 33, 34, 34, + 34, 34, 34, 34, 34, 35, 35, 35, + 35, 35, 35, 35, 36, 36, 36, 36, + 36, 36, 36, 37, 37, 37, 37, 37, + 37, 37, 38, 38, 38, 38, 38, 38, + 38, 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, 41, + 41, 41, 41, 41, 41, 41, 42, 42, + 42, 42, 42, 42, 42, 43, 43, 43, + 43, 43, 43, 43, 44, 44, 44, 44, + 44, 44, 44, 45, 45, 45, 45, 45, + 45, 45, 46, 46, 46, 46, 46, 46, + 46, 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, 49, + 49, 49, 49, 49, 49, 49, 50, 50, + 50, 50, 50, 50, 50, 51, 51, 51, + 51, 51, 51, 51, 52, 52, 52, 52, + 52, 52, 52, 53, 53, 53, 53, 53, + 53, 53, 54, 54, 54, 54, 54, 54, + 54, 55, 55, 55, 55, 55, 55, 55, + 56, 56, 56, 56, 56, 56, 56, 57, + 57, 57, 57, 57, 57, 57, 58, 58, + 58, 58, 58, 58, 58, 59, 59, 59, + 59, 59, 59, 59, 60, 60, 60, 60, + 60, 60, 60, 61, 61, 61, 61, 61, + 61, 61, 62, 62, 62, 62, 62, 62, + 62, 63, 63, 63, 63, 63, 63, 63, + 64, 64, 64, 64, 64, 64, 64, 65, + 65, 65, 65, 65, 65, 65, 66, 66, + 66, 66, 66, 66, 66, 67, 67, 67, + 67, 67, 67, 67, 68, 68, 68, 68, + 68, 68, 68, 69, 69, 69, 69, 69, + 69, 69, 70, 70, 70, 70, 70, 70, + 70, 71, 71, 71, 71, 71, 71, 71, + 72, 72, 72, 72, 72, 72, 72, 73, + 73, 73, 73, 73, 73, 73, 74, 74, + 74, 74, 74, 74, 74, 75, 75, 75, + 75, 75, 75, 75, 76, 76, 76, 76, + 76, 76, 76, 77, 77, 77, 77, 77, + 77, 77, 78, 78, 78, 78, 78, 78, + 78, 79, 79, 79, 79, 79, 79, 79, + 80, 80, 80, 80, 80, 80, 80, 81, + 81, 81, 81, 81, 81, 81, 82, 82, + 82, 82, 82, 82, 82, 83, 83, 83, + 83, 83, 83, 83, 84, 84, 84, 84, + 84, 84, 84, 85, 85, 85, 85, 85, + 85, 85, 86, 86, 86, 86, 86, 86, + 86, 87, 87, 87, 87, 87, 87, 87, + 88, 88, 88, 88, 88, 88, 88, 89, + 89, 89, 89, 89, 89, 89, 90, 90, + 90, 90, 90, 90, 90, 91, 91, 91, + 91, 91, 91, 91, 92, 92, 92, 92, + 92, 92, 92, 93, 93, 93, 93, 93, + 93, 93, 94, 94, 94, 94, 94, 94, + 94, 95, 95, 95, 95, 95, 95, 95, + 96, 96, 96, 96, 96, 96, 96, 97, + 97, 97, 97, 97, 97, 97, 98, 98, + 98, 98, 98, 98, 98, 99, 99, 99, + 99, 99, 99, 99, 100, 100, 100, 100, + 100, 100, 100, 101, 101, 101, 101, 101, + 101, 101, 102, 102, 102, 102, 102, 102, + 102, 103, 103, 103, 103, 103, 103, 103, + 104, 104, 104, 104, 104, 104, 104, 105, + 105, 105, 105, 105, 105, 105, 106, 106, + 106, 106, 106, 106, 106, 107, 107, 107, + 107, 107, 107, 107, 108, 108, 108, 108, + 108, 108, 108, 109, 109, 109, 109, 109, + 109, 109, 110, 110, 110, 110, 110, 110, + 110, 111, 111, 111, 111, 111, 111, 111, + 112, 112, 112, 112, 112, 112, 112, 113, + 113, 113, 113, 113, 113, 113, 114, 114, + 114, 114, 114, 114, 114, 115, 115, 115, + 115, 115, 115, 115, 116, 116, 116, 116, + 116, 116, 116, 117, 117, 117, 117, 117, + 117, 117, 118, 118, 118, 118, 118, 118, + 118, 119, 119, 119, 119, 119, 119, 119, + 120, 120, 120, 120, 120, 120, 120, 121, + 121, 121, 121, 121, 121, 121, 122, 122, + 122, 122, 122, 122, 122, 123, 123, 123, + 123, 123, 123, 123, 124, 124, 124, 124, + 124, 124, 124, 125, 125, 125, 125, 125, + 125, 125, 126, 126, 126, 126, 126, 126, + 126, 127, 127, 127, 127, 127, 127, 127, + 128, 128, 128, 128, 128, 128, 128, 129, + 129, 129, 129, 129, 129, 129, 130, 130, + 130, 130, 130, 130, 130, 131, 131, 131, + 131, 131, 131, 131, 132, 132, 132, 132, + 132, 132, 132, 133, 133, 133, 133, 133, + 133, 133, 134, 134, 134, 134, 134, 134, + 134, 135, 135, 135, 135, 135, 135, 135, + 136, 136, 136, 136, 136, 136, 136, 137, + 137, 137, 137, 137, 137, 137, 138, 138, + 138, 138, 138, 138, 138, 139, 139, 139, + 139, 139, 139, 139, 140, 140, 140, 140, + 140, 140, 140, 141, 141, 141, 141, 141, + 141, 141, 142, 142, 142, 142, 142, 142, + 142, 143, 143, 143, 143, 143, 143, 143, + 144, 144, 144, 144, 144, 144, 144, 145, + 145, 145, 145, 145, 145, 145, 146, 146, + 146, 146, 146, 146, 146, 147, 147, 147, + 147, 147, 147, 147, 148, 148, 148, 148, + 148, 148, 148, 149, 149, 149, 149, 149, + 149, 149, 150, 150, 150, 150, 150, 150, + 150, 151, 151, 151, 151, 151, 151, 151, + 152, 152, 152, 152, 152, 152, 152, 153, + 153, 153, 153, 153, 153, 153, 154, 154, + 154, 154, 154, 154, 154, 155, 155, 155, + 155, 155, 155, 155, 156, 156, 156, 156, + 156, 156, 156, 157, 157, 157, 157, 157, + 157, 157, 158, 158, 158, 158, 158, 158, + 158, 159, 159, 159, 159, 159, 159, 159, + 160, 160, 160, 160, 160, 160, 160, 161, + 161, 161, 161, 161, 161, 161, 162, 162, + 162, 162, 162, 162, 162, 163, 163, 163, + 163, 163, 163, 163, 164, 164, 164, 164, + 164, 164, 164, 165, 165, 165, 165, 165, + 165, 165, 166, 166, 166, 166, 166, 166, + 166, 167, 167, 167, 167, 167, 167, 167, + 168, 168, 168, 168, 168, 168, 168, 169, + 169, 169, 169, 169, 169, 169, 170, 170, + 170, 170, 170, 170, 170, 171, 171, 171, + 171, 171, 171, 171, 172, 172, 172, 172, + 172, 172, 172, 173, 173, 173, 173, 173, + 173, 173, 174, 174, 174, 174, 174, 174, + 174, 175, 175, 175, 175, 175, 175, 175, + 176, 176, 176, 176, 176, 176, 176, 177, + 177, 177, 177, 177, 177, 177, 178, 178, + 178, 178, 178, 178, 178, 179, 179, 179, + 179, 179, 179, 179, 180, 180, 180, 180, + 180, 180, 180, 181, 181, 181, 181, 181, + 181, 181, 182, 182, 182, 182, 182, 182, + 182, 183, 183, 183, 183, 183, 183, 183, + 184, 184, 184, 184, 184, 184, 184, 185, + 185, 185, 185, 185, 185, 185, 186, 186, + 186, 186, 186, 186, 186, 187, 187, 187, + 187, 187, 187, 187, 188, 188, 188, 188, + 188, 188, 188, 189, 189, 189, 189, 189, + 189, 189, 190, 190, 190, 190, 190, 190, + 190, 191, 191, 191, 191, 191, 191, 191, + 192, 192, 192, 192, 192, 192, 192, 193, + 193, 193, 193, 193, 193, 193, 194, 194, + 194, 194, 194, 194, 194, 195, 195, 195, + 195, 195, 195, 195, 196, 196, 196, 196, + 196, 196, 196, 197, 197, 197, 197, 197, + 197, 197, 198, 198, 198, 198, 198, 198, + 198, 199, 199, 199, 199, 199, 199, 199, + 200, 200, 200, 200, 200, 200, 200, 201, + 201, 201, 201, 201, 201, 201, 202, 202, + 202, 202, 202, 202, 202, 203, 203, 203, + 203, 203, 203, 203, 204, 204, 204, 204, + 204, 204, 204, 205, 205, 205, 205, 205, + 205, 205, 206, 206, 206, 206, 206, 206, + 206, 207, 207, 207, 207, 207, 207, 207, + 208, 208, 208, 208, 208, 208, 208, 209, + 209, 209, 209, 209, 209, 209, 210, 210, + 210, 210, 210, 210, 210, 211, 211, 211, + 211, 211, 211, 211, 212, 212, 212, 212, + 212, 212, 212, 213, 213, 213, 213, 213, + 213, 213, 214, 214, 214, 214, 214, 214, + 214, 215, 215, 215, 215, 215, 215, 215, + 216, 216, 216, 216, 216, 216, 216, 217, + 217, 217, 217, 217, 217, 217, 218, 218, + 218, 218, 218, 218, 218, 219, 219, 219, + 219, 219, 219, 219, 220, 220, 220, 220, + 220, 220, 220, 221, 221, 221, 221, 221, + 221, 221, 222, 222, 222, 222, 222, 222, + 222, 223, 223, 223, 223, 223, 223, 223, + 224, 224, 224, 224, 224, 224, 224, 225, + 225, 225, 225, 225, 225, 225, 226, 226, + 226, 226, 226, 226, 226, 227, 227, 227, + 227, 227, 227, 227, 228, 228, 228, 228, + 228, 228, 228, 229, 229, 229, 229, 229, + 229, 229, 230, 230, 230, 230, 230, 230, + 230, 231, 231, 231, 231, 231, 231, 231, + 232, 232, 232, 232, 232, 232, 232, 233, + 233, 233, 233, 233, 233, 233, 234, 234, + 234, 234, 234, 234, 234, 235, 235, 235, + 235, 235, 235, 235, 236, 236, 236, 236, + 236, 236, 236, 237, 237, 237, 237, 237, + 237, 237, 238, 238, 238, 238, 238, 238, + 238, 239, 239, 239, 239, 239, 239, 239, + 240, 240, 240, 240, 240, 240, 240, 241, + 241, 241, 241, 241, 241, 241, 242, 242, + 242, 242, 242, 242, 242, 243, 243, 243, + 243, 243, 243, 243, 244, 244, 244, 244, + 244, 244, 244, 245, 245, 245, 245, 245, + 245, 245, 246, 246, 246, 246, 246, 246, + 246, 247, 247, 247, 247, 247, 247, 247, + 248, 248, 248, 248, 248, 248, 248, 249, + 249, 249, 249, 249, 249, 249, 250, 250, + 250, 250, 250, 250, 250, 251, 251, 251, + 251, 251, 251, 251, 252, 252, 252, 252, + 252, 252, 252, 253, 253, 253, 253, 253, + 253, 253, 254, 254, 254, 254, 254, 254, + 254, 255, 255, 255, 255, 255, 255, 255, +}; + +const uint8_t detex_division_by_5_table[1280] = { + 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 3, + 3, 3, 3, 3, 4, 4, 4, 4, + 4, 5, 5, 5, 5, 5, 6, 6, + 6, 6, 6, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 10, 10, 10, 10, 10, 11, + 11, 11, 11, 11, 12, 12, 12, 12, + 12, 13, 13, 13, 13, 13, 14, 14, + 14, 14, 14, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 17, 17, 17, + 17, 17, 18, 18, 18, 18, 18, 19, + 19, 19, 19, 19, 20, 20, 20, 20, + 20, 21, 21, 21, 21, 21, 22, 22, + 22, 22, 22, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 25, 25, 25, + 25, 25, 26, 26, 26, 26, 26, 27, + 27, 27, 27, 27, 28, 28, 28, 28, + 28, 29, 29, 29, 29, 29, 30, 30, + 30, 30, 30, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 33, 33, 33, + 33, 33, 34, 34, 34, 34, 34, 35, + 35, 35, 35, 35, 36, 36, 36, 36, + 36, 37, 37, 37, 37, 37, 38, 38, + 38, 38, 38, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 41, 41, 41, + 41, 41, 42, 42, 42, 42, 42, 43, + 43, 43, 43, 43, 44, 44, 44, 44, + 44, 45, 45, 45, 45, 45, 46, 46, + 46, 46, 46, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 49, 49, 49, + 49, 49, 50, 50, 50, 50, 50, 51, + 51, 51, 51, 51, 52, 52, 52, 52, + 52, 53, 53, 53, 53, 53, 54, 54, + 54, 54, 54, 55, 55, 55, 55, 55, + 56, 56, 56, 56, 56, 57, 57, 57, + 57, 57, 58, 58, 58, 58, 58, 59, + 59, 59, 59, 59, 60, 60, 60, 60, + 60, 61, 61, 61, 61, 61, 62, 62, + 62, 62, 62, 63, 63, 63, 63, 63, + 64, 64, 64, 64, 64, 65, 65, 65, + 65, 65, 66, 66, 66, 66, 66, 67, + 67, 67, 67, 67, 68, 68, 68, 68, + 68, 69, 69, 69, 69, 69, 70, 70, + 70, 70, 70, 71, 71, 71, 71, 71, + 72, 72, 72, 72, 72, 73, 73, 73, + 73, 73, 74, 74, 74, 74, 74, 75, + 75, 75, 75, 75, 76, 76, 76, 76, + 76, 77, 77, 77, 77, 77, 78, 78, + 78, 78, 78, 79, 79, 79, 79, 79, + 80, 80, 80, 80, 80, 81, 81, 81, + 81, 81, 82, 82, 82, 82, 82, 83, + 83, 83, 83, 83, 84, 84, 84, 84, + 84, 85, 85, 85, 85, 85, 86, 86, + 86, 86, 86, 87, 87, 87, 87, 87, + 88, 88, 88, 88, 88, 89, 89, 89, + 89, 89, 90, 90, 90, 90, 90, 91, + 91, 91, 91, 91, 92, 92, 92, 92, + 92, 93, 93, 93, 93, 93, 94, 94, + 94, 94, 94, 95, 95, 95, 95, 95, + 96, 96, 96, 96, 96, 97, 97, 97, + 97, 97, 98, 98, 98, 98, 98, 99, + 99, 99, 99, 99, 100, 100, 100, 100, + 100, 101, 101, 101, 101, 101, 102, 102, + 102, 102, 102, 103, 103, 103, 103, 103, + 104, 104, 104, 104, 104, 105, 105, 105, + 105, 105, 106, 106, 106, 106, 106, 107, + 107, 107, 107, 107, 108, 108, 108, 108, + 108, 109, 109, 109, 109, 109, 110, 110, + 110, 110, 110, 111, 111, 111, 111, 111, + 112, 112, 112, 112, 112, 113, 113, 113, + 113, 113, 114, 114, 114, 114, 114, 115, + 115, 115, 115, 115, 116, 116, 116, 116, + 116, 117, 117, 117, 117, 117, 118, 118, + 118, 118, 118, 119, 119, 119, 119, 119, + 120, 120, 120, 120, 120, 121, 121, 121, + 121, 121, 122, 122, 122, 122, 122, 123, + 123, 123, 123, 123, 124, 124, 124, 124, + 124, 125, 125, 125, 125, 125, 126, 126, + 126, 126, 126, 127, 127, 127, 127, 127, + 128, 128, 128, 128, 128, 129, 129, 129, + 129, 129, 130, 130, 130, 130, 130, 131, + 131, 131, 131, 131, 132, 132, 132, 132, + 132, 133, 133, 133, 133, 133, 134, 134, + 134, 134, 134, 135, 135, 135, 135, 135, + 136, 136, 136, 136, 136, 137, 137, 137, + 137, 137, 138, 138, 138, 138, 138, 139, + 139, 139, 139, 139, 140, 140, 140, 140, + 140, 141, 141, 141, 141, 141, 142, 142, + 142, 142, 142, 143, 143, 143, 143, 143, + 144, 144, 144, 144, 144, 145, 145, 145, + 145, 145, 146, 146, 146, 146, 146, 147, + 147, 147, 147, 147, 148, 148, 148, 148, + 148, 149, 149, 149, 149, 149, 150, 150, + 150, 150, 150, 151, 151, 151, 151, 151, + 152, 152, 152, 152, 152, 153, 153, 153, + 153, 153, 154, 154, 154, 154, 154, 155, + 155, 155, 155, 155, 156, 156, 156, 156, + 156, 157, 157, 157, 157, 157, 158, 158, + 158, 158, 158, 159, 159, 159, 159, 159, + 160, 160, 160, 160, 160, 161, 161, 161, + 161, 161, 162, 162, 162, 162, 162, 163, + 163, 163, 163, 163, 164, 164, 164, 164, + 164, 165, 165, 165, 165, 165, 166, 166, + 166, 166, 166, 167, 167, 167, 167, 167, + 168, 168, 168, 168, 168, 169, 169, 169, + 169, 169, 170, 170, 170, 170, 170, 171, + 171, 171, 171, 171, 172, 172, 172, 172, + 172, 173, 173, 173, 173, 173, 174, 174, + 174, 174, 174, 175, 175, 175, 175, 175, + 176, 176, 176, 176, 176, 177, 177, 177, + 177, 177, 178, 178, 178, 178, 178, 179, + 179, 179, 179, 179, 180, 180, 180, 180, + 180, 181, 181, 181, 181, 181, 182, 182, + 182, 182, 182, 183, 183, 183, 183, 183, + 184, 184, 184, 184, 184, 185, 185, 185, + 185, 185, 186, 186, 186, 186, 186, 187, + 187, 187, 187, 187, 188, 188, 188, 188, + 188, 189, 189, 189, 189, 189, 190, 190, + 190, 190, 190, 191, 191, 191, 191, 191, + 192, 192, 192, 192, 192, 193, 193, 193, + 193, 193, 194, 194, 194, 194, 194, 195, + 195, 195, 195, 195, 196, 196, 196, 196, + 196, 197, 197, 197, 197, 197, 198, 198, + 198, 198, 198, 199, 199, 199, 199, 199, + 200, 200, 200, 200, 200, 201, 201, 201, + 201, 201, 202, 202, 202, 202, 202, 203, + 203, 203, 203, 203, 204, 204, 204, 204, + 204, 205, 205, 205, 205, 205, 206, 206, + 206, 206, 206, 207, 207, 207, 207, 207, + 208, 208, 208, 208, 208, 209, 209, 209, + 209, 209, 210, 210, 210, 210, 210, 211, + 211, 211, 211, 211, 212, 212, 212, 212, + 212, 213, 213, 213, 213, 213, 214, 214, + 214, 214, 214, 215, 215, 215, 215, 215, + 216, 216, 216, 216, 216, 217, 217, 217, + 217, 217, 218, 218, 218, 218, 218, 219, + 219, 219, 219, 219, 220, 220, 220, 220, + 220, 221, 221, 221, 221, 221, 222, 222, + 222, 222, 222, 223, 223, 223, 223, 223, + 224, 224, 224, 224, 224, 225, 225, 225, + 225, 225, 226, 226, 226, 226, 226, 227, + 227, 227, 227, 227, 228, 228, 228, 228, + 228, 229, 229, 229, 229, 229, 230, 230, + 230, 230, 230, 231, 231, 231, 231, 231, + 232, 232, 232, 232, 232, 233, 233, 233, + 233, 233, 234, 234, 234, 234, 234, 235, + 235, 235, 235, 235, 236, 236, 236, 236, + 236, 237, 237, 237, 237, 237, 238, 238, + 238, 238, 238, 239, 239, 239, 239, 239, + 240, 240, 240, 240, 240, 241, 241, 241, + 241, 241, 242, 242, 242, 242, 242, 243, + 243, 243, 243, 243, 244, 244, 244, 244, + 244, 245, 245, 245, 245, 245, 246, 246, + 246, 246, 246, 247, 247, 247, 247, 247, + 248, 248, 248, 248, 248, 249, 249, 249, + 249, 249, 250, 250, 250, 250, 250, 251, + 251, 251, 251, 251, 252, 252, 252, 252, + 252, 253, 253, 253, 253, 253, 254, 254, + 254, 254, 254, 255, 255, 255, 255, 255, +}; + diff --git a/Source/ThirdParty/fmt/core.h b/Source/ThirdParty/fmt/core.h index e109ecbd4..0c1050381 100644 --- a/Source/ThirdParty/fmt/core.h +++ b/Source/ThirdParty/fmt/core.h @@ -11,6 +11,8 @@ // Custom configuration for Flax #include "Engine/Platform/Platform.h" #include "Engine/Platform/StringUtils.h" +#include "Engine/Core/Templates.h" +#include "Engine/Core/Memory/StlWrapper.h" #define FMT_USE_CONSTEXPR11 0 #define FMT_USE_USER_DEFINED_LITERALS 0 #define FMT_USE_WINDOWS_H 0 @@ -22,7 +24,7 @@ #define FMT_ASSERT(condition, message) \ if (!(condition)) \ { \ - Platform::Assert(message, __FILE__, __LINE__); \ + Platform::CheckFailed(message, __FILE__, __LINE__); \ } #else #define FMT_ASSERT(condition, message) ((void)0) @@ -38,10 +40,9 @@ namespace fmt { using back_inserter = std::back_inserter<_Container>; }; #else -#include namespace fmt { // std::back_insert_iterator impl to not include -template +template struct iterator { typedef _Category iterator_category; @@ -966,7 +967,7 @@ FMT_CONSTEXPR typename internal::result_of::type template FMT_CONSTEXPR typename internal::result_of::type visit(Visitor &&vis, const basic_format_arg &arg) { - return visit_format_arg(std::forward(vis), arg); + return visit_format_arg(::Forward(vis), arg); } // Parsing context consisting of a format string range being parsed and an @@ -1391,12 +1392,12 @@ class basic_format_args { struct format_args : basic_format_args { template format_args(Args &&... arg) - : basic_format_args(std::forward(arg)...) {} + : basic_format_args(::Forward(arg)...) {} }; struct wformat_args : basic_format_args { template wformat_args(Args &&... arg) - : basic_format_args(std::forward(arg)...) {} + : basic_format_args(::Forward(arg)...) {} }; #define FMT_ENABLE_IF_T(B, T) typename std::enable_if::type diff --git a/Source/ThirdParty/fmt/format-inl.h b/Source/ThirdParty/fmt/format-inl.h index c4e06f711..074fa83b5 100644 --- a/Source/ThirdParty/fmt/format-inl.h +++ b/Source/ThirdParty/fmt/format-inl.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include // for std::ptrdiff_t @@ -154,43 +156,6 @@ int safe_strerror( }; return dispatcher(error_code, buffer, buffer_size).run(); } - -void format_error_code(internal::buffer &out, int error_code, - string_view message) FMT_NOEXCEPT { - // Report error code making sure that the output fits into - // inline_buffer_size to avoid dynamic memory allocation and potential - // bad_alloc. - out.resize(0); - static const char SEP[] = ": "; - static const char ERROR_STR[] = "error "; - // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; - typedef internal::int_traits::main_type main_type; - main_type abs_value = static_cast(error_code); - if (internal::is_negative(error_code)) { - abs_value = 0 - abs_value; - ++error_code_size; - } - error_code_size += internal::to_unsigned(internal::count_digits(abs_value)); - writer w(out); - if (message.size() <= inline_buffer_size - error_code_size) { - w.write(message); - w.write(SEP); - } - w.write(ERROR_STR); - w.write(error_code); - FMT_ASSERT(out.size() <= inline_buffer_size, "invalid buffer size"); -} - -void report_error(FormatFunc func, int error_code, - string_view message) FMT_NOEXCEPT { - memory_buffer full_message; - func(full_message, error_code, message); - // Use Writer::data instead of Writer::c_str to avoid potential memory - // allocation. - std::fwrite(full_message.data(), full_message.size(), 1, stderr); - std::fputc('\n', stderr); -} } // namespace FMT_FUNC size_t internal::count_code_points(basic_string_view s) { diff --git a/Source/ThirdParty/fmt/format.h b/Source/ThirdParty/fmt/format.h index 0b35daf4e..14bd692c3 100644 --- a/Source/ThirdParty/fmt/format.h +++ b/Source/ThirdParty/fmt/format.h @@ -29,7 +29,6 @@ #define FMT_FORMAT_H_ #include -#include #include #ifdef __clang__ @@ -146,11 +145,6 @@ FMT_END_NAMESPACE # define FMT_USE_TRAILING_RETURN 0 #endif -#ifndef FMT_USE_GRISU -# define FMT_USE_GRISU 0 -//# define FMT_USE_GRISU std::numeric_limits::is_iec559 -#endif - // __builtin_clz is broken in clang with Microsoft CodeGen: // https://github.com/fmtlib/fmt/issues/519 #ifndef _MSC_VER @@ -167,7 +161,10 @@ FMT_END_NAMESPACE // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. #if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) -# include // _BitScanReverse, _BitScanReverse64 +extern "C" unsigned char _BitScanReverse(unsigned long* Index, unsigned long Mask); +# ifdef _WIN64 +extern "C" unsigned char _BitScanReverse64(unsigned long* Index, unsigned __int64 Mask); +#endif FMT_BEGIN_NAMESPACE namespace internal { @@ -250,26 +247,9 @@ struct function { struct result { typedef Result type; }; }; -struct dummy_int { - int data[2]; - operator int() const { return 0; } -}; -typedef std::numeric_limits fputil; - -// Dummy implementations of system functions called if the latter are not -// available. -inline dummy_int isinf(...) { return dummy_int(); } -inline dummy_int _finite(...) { return dummy_int(); } -inline dummy_int isnan(...) { return dummy_int(); } -inline dummy_int _isnan(...) { return dummy_int(); } - template typename Allocator::value_type *allocate(Allocator& alloc, std::size_t n) { -#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 - return std::allocator_traits::allocate(alloc, n); -#else return alloc.allocate(n); -#endif } // A helper function to suppress bogus "conditional expression is constant" @@ -277,40 +257,7 @@ typename Allocator::value_type *allocate(Allocator& alloc, std::size_t n) { template inline T const_check(T value) { return value; } } // namespace internal -FMT_END_NAMESPACE -namespace std { -// Standard permits specialization of std::numeric_limits. This specialization -// is used to resolve ambiguity between isinf and std::isinf in glibc: -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 -// and the same for isnan. -template <> -class numeric_limits : - public std::numeric_limits { - public: - // Portable version of isinf. - template - static bool isinfinity(T x) { - using namespace fmt::internal; - // The resolution "priority" is: - // isinf macro > std::isinf > ::isinf > fmt::internal::isinf - if (const_check(sizeof(isinf(x)) != sizeof(fmt::internal::dummy_int))) - return isinf(x) != 0; - return !_finite(static_cast(x)); - } - - // Portable version of isnan. - template - static bool isnotanumber(T x) { - using namespace fmt::internal; - if (const_check(sizeof(isnan(x)) != sizeof(fmt::internal::dummy_int))) - return isnan(x) != 0; - return _isnan(static_cast(x)) != 0; - } -}; -} // namespace std - -FMT_BEGIN_NAMESPACE template class basic_writer; @@ -346,22 +293,7 @@ class back_insert_range: typedef basic_writer> writer; typedef basic_writer> wwriter; -#if FMT_EXCEPTIONS -/** A formatting error such as invalid format string. */ -class format_error : public std::runtime_error { - public: - explicit format_error(const char *message) - : std::runtime_error(message) {} - -#if FMT_USE_STRING - explicit format_error(const std::string &message) - : std::runtime_error(message) {} -#endif -}; -#define FMT_THROW_FORMAT_ERROR(x) FMT_THROW(format_error(x)) -#else #define FMT_THROW_FORMAT_ERROR(x) FMT_THROW(x) -#endif namespace internal { @@ -447,7 +379,7 @@ enum { inline_buffer_size = 500 }; \endrst */ template > + typename Allocator = std_flax::allocator> class basic_memory_buffer: private Allocator, public internal::basic_buffer { private: T store_[SIZE]; @@ -475,7 +407,7 @@ class basic_memory_buffer: private Allocator, public internal::basic_buffer { // Move data from other to this buffer. void move(basic_memory_buffer &other) { Allocator &this_alloc = *this, &other_alloc = other; - this_alloc = std::move(other_alloc); + this_alloc = ::MoveTemp(other_alloc); T* data = other.data(); std::size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { @@ -588,143 +520,9 @@ inline typename std::enable_if< template inline Iterator &reserve(Iterator &it, std::size_t) { return it; } -template -class null_terminating_iterator; - -template -FMT_CONSTEXPR_DECL const Char *pointer_from(null_terminating_iterator it); - -// An output iterator that counts the number of objects written to it and -// discards them. -template -class counting_iterator { - private: - std::size_t count_; - mutable T blackhole_; - - public: - typedef std::output_iterator_tag iterator_category; - typedef T value_type; - typedef std::ptrdiff_t difference_type; - typedef T* pointer; - typedef T& reference; - typedef counting_iterator _Unchecked_type; // Mark iterator as checked. - - counting_iterator(): count_(0) {} - - std::size_t count() const { return count_; } - - counting_iterator& operator++() { - ++count_; - return *this; - } - - counting_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - T &operator*() const { return blackhole_; } -}; - -template -class truncating_iterator_base { - protected: - OutputIt out_; - std::size_t limit_; - std::size_t count_; - - truncating_iterator_base(OutputIt out, std::size_t limit) - : out_(out), limit_(limit), count_(0) {} - - public: - typedef std::output_iterator_tag iterator_category; - typedef void difference_type; - typedef void pointer; - typedef void reference; - typedef truncating_iterator_base _Unchecked_type; // Mark iterator as checked. - - OutputIt base() const { return out_; } - std::size_t count() const { return count_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template ::value_type>::type> -class truncating_iterator; - -template -class truncating_iterator: - public truncating_iterator_base { - typedef std::iterator_traits traits; - - mutable typename traits::value_type blackhole_; - - public: - typedef typename traits::value_type value_type; - - truncating_iterator(OutputIt out, std::size_t limit) - : truncating_iterator_base(out, limit) {} - - truncating_iterator& operator++() { - if (this->count_++ < this->limit_) - ++this->out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - value_type& operator*() const { - return this->count_ < this->limit_ ? *this->out_ : blackhole_; - } -}; - -template -class truncating_iterator: - public truncating_iterator_base { - public: - typedef typename OutputIt::container_type::value_type value_type; - - truncating_iterator(OutputIt out, std::size_t limit) - : truncating_iterator_base(out, limit) {} - - truncating_iterator& operator=(value_type val) { - if (this->count_++ < this->limit_) - this->out_ = val; - return *this; - } - - truncating_iterator& operator++() { return *this; } - truncating_iterator& operator++(int) { return *this; } - truncating_iterator& operator*() { return *this; } -}; - // Returns true if value is negative, false otherwise. -// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. template -FMT_CONSTEXPR typename std::enable_if< - std::numeric_limits::is_signed, bool>::type is_negative(T value) { - return value < 0; -} -template -FMT_CONSTEXPR typename std::enable_if< - !std::numeric_limits::is_signed, bool>::type is_negative(T) { - return false; -} - -template -struct int_traits { - // Smallest of uint32_t and uint64_t that is large enough to represent - // all values of T. - typedef typename std::conditional< - std::numeric_limits::digits <= 32, uint32_t, uint64_t>::type main_type; -}; +FMT_CONSTEXPR bool is_negative(T value) { return value < (T)0; } // Static data is placed in this class template to allow header-only // configuration. @@ -778,10 +576,19 @@ FMT_API size_t count_code_points(basic_string_view s); inline char8_t to_char8_t(char c) { return static_cast(c); } +template +struct iterator_traits { + typedef typename Iterator::value_type value_type; +}; +template +struct iterator_traits { + typedef T value_type; +}; + template struct needs_conversion: std::integral_constant::value_type, char>::value && + typename iterator_traits::value_type, char>::value && std::is_same::value> {}; template @@ -971,7 +778,7 @@ inline Iterator format_decimal( FMT_ASSERT(num_digits >= 0, "invalid digit count"); typedef typename ThousandsSep::char_type char_type; // Buffer should be large enough to hold all digits (<= digits10 + 1). - enum { max_size = std::numeric_limits::digits10 + 1 }; + enum { max_size = 19 + 1 }; FMT_ASSERT(ThousandsSep::size <= 1, "invalid separator"); char_type buffer[max_size + max_size / 3]; auto end = format_decimal(buffer, value, num_digits, sep); @@ -1002,60 +809,11 @@ inline It format_uint(It out, UInt value, int num_digits, bool upper = false) { // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1) // and null. - char buffer[std::numeric_limits::digits / BASE_BITS + 2]; + char buffer[64 / BASE_BITS + 2]; format_uint(buffer, value, num_digits, upper); return internal::copy_str(buffer, buffer + num_digits, out); } -#ifndef _WIN32 -# define FMT_USE_WINDOWS_H 0 -#elif !defined(FMT_USE_WINDOWS_H) -# define FMT_USE_WINDOWS_H 1 -#endif - -// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. -// All the functionality that relies on it will be disabled too. -#if FMT_USE_WINDOWS_H -// A converter from UTF-8 to UTF-16. -// It is only provided for Windows since other systems support UTF-8 natively. -class utf8_to_utf16 { - private: - wmemory_buffer buffer_; - - public: - FMT_API explicit utf8_to_utf16(string_view s); - operator wstring_view() const { return wstring_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const wchar_t *c_str() const { return &buffer_[0]; } - std::wstring str() const { return std::wstring(&buffer_[0], size()); } -}; - -// A converter from UTF-16 to UTF-8. -// It is only provided for Windows since other systems support UTF-8 natively. -class utf16_to_utf8 { - private: - memory_buffer buffer_; - - public: - utf16_to_utf8() {} - FMT_API explicit utf16_to_utf8(wstring_view s); - operator string_view() const { return string_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const char *c_str() const { return &buffer_[0]; } -#if FMT_USE_STRING - std::string str() const { return std::string(&buffer_[0], size()); } -#endif - - // Performs conversion returning a system error code instead of - // throwing exception on conversion error. This method may still throw - // in case of memory allocation error. - FMT_API int convert(wstring_view s); -}; - -FMT_API void format_windows_error(fmt::internal::buffer &out, int error_code, - fmt::string_view message) FMT_NOEXCEPT; -#endif - template struct null {}; } // namespace internal @@ -1444,7 +1202,7 @@ FMT_CONSTEXPR unsigned parse_nonnegative_int( } unsigned value = 0; // Convert to unsigned to prevent a warning. - unsigned max_int = (std::numeric_limits::max)(); + unsigned max_int = MAX_int32; unsigned big = max_int / 10; do { // Check for overflow. @@ -1639,7 +1397,7 @@ FMT_CONSTEXPR void set_dynamic_spec( T &value, basic_format_arg arg, ErrorHandler eh) { unsigned long long big_value = visit_format_arg(Handler(eh), arg); - if (big_value > to_unsigned((std::numeric_limits::max)())) + if (big_value > to_unsigned(MAX_int32)) eh.on_error("number is too big"); value = static_cast(big_value); } @@ -2045,7 +1803,7 @@ class format_string_checker { public: explicit FMT_CONSTEXPR format_string_checker( basic_string_view format_str, ErrorHandler eh) - : arg_id_((std::numeric_limits::max)()), context_(format_str, eh), + : arg_id_(MAX_uint32), context_(format_str, eh), parse_funcs_{&parse_format_specs...} {} FMT_CONSTEXPR void on_text(const Char *, const Char *) {} @@ -2271,7 +2029,7 @@ class basic_writer { // Writes a decimal integer. template void write_decimal(Int value) { - typedef typename internal::int_traits::main_type main_type; + typedef uint64_t main_type; main_type abs_value = static_cast(value); bool is_negative = internal::is_negative(value); if (is_negative) @@ -2286,7 +2044,7 @@ class basic_writer { // The handle_int_type_spec handler that writes an integer. template struct int_writer { - typedef typename internal::int_traits::main_type unsigned_type; + typedef uint64_t unsigned_type; basic_writer &writer; const Spec &spec; @@ -2645,20 +2403,15 @@ void basic_writer::write_double(T value, const format_specs &spec) { // Format NaN and ininity ourselves because sprintf's output is not consistent // across platforms. - if (internal::fputil::isnotanumber(value)) + if (::isnan(value)) return write_inf_or_nan(handler.upper ? "NAN" : "nan"); - if (internal::fputil::isinfinity(value)) + if (::isinf(value)) return write_inf_or_nan(handler.upper ? "INF" : "inf"); memory_buffer buffer; - bool use_grisu = FMT_USE_GRISU && sizeof(T) <= sizeof(double) && - spec.type != 'a' && spec.type != 'A' && - internal::grisu2_format(static_cast(value), buffer, spec); - if (!use_grisu) { - format_specs normalized_spec(spec); - normalized_spec.type = handler.type; - internal::sprintf_format(value, buffer, normalized_spec); - } + format_specs normalized_spec(spec); + normalized_spec.type = handler.type; + internal::sprintf_format(value, buffer, normalized_spec); size_t n = buffer.size(); align_spec as = spec; if (spec.align() == ALIGN_NUMERIC) { @@ -2684,7 +2437,7 @@ class format_int { private: // Buffer should be large enough to hold all digits (digits10 + 1), // a sign and a null character. - enum {BUFFER_SIZE = std::numeric_limits::digits10 + 3}; + enum {BUFFER_SIZE = 19 + 3}; mutable char buffer_[BUFFER_SIZE]; char *str_; @@ -2764,7 +2517,7 @@ class format_int { // write a terminating null character. template inline void format_decimal(char *&buffer, T value) { - typedef typename internal::int_traits::main_type main_type; + typedef uint64_t main_type; main_type abs_value = static_cast(value); if (internal::is_negative(value)) { *buffer++ = '-'; @@ -2861,69 +2614,6 @@ struct formatter< internal::dynamic_format_specs specs_; }; -// A formatter for types known only at run time such as variant alternatives. -// -// Usage: -// typedef std::variant variant; -// template <> -// struct formatter: dynamic_formatter<> { -// void format(buffer &buf, const variant &v, context &ctx) { -// visit([&](const auto &val) { format(buf, val, ctx); }, v); -// } -// }; -template -class dynamic_formatter { - private: - struct null_handler: internal::error_handler { - void on_align(alignment) {} - void on_plus() {} - void on_minus() {} - void on_space() {} - void on_hash() {} - }; - - public: - template - auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - // Checks are deferred to formatting time when the argument type is known. - internal::dynamic_specs_handler handler(specs_, ctx); - return parse_format_specs(ctx.begin(), ctx.end(), handler); - } - - template - auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { - handle_specs(ctx); - internal::specs_checker - checker(null_handler(), internal::get_type::value); - checker.on_align(specs_.align()); - if (specs_.flags == 0); // Do nothing. - else if (specs_.has(SIGN_FLAG)) - specs_.has(PLUS_FLAG) ? checker.on_plus() : checker.on_space(); - else if (specs_.has(MINUS_FLAG)) - checker.on_minus(); - else if (specs_.has(HASH_FLAG)) - checker.on_hash(); - if (specs_.precision != -1) - checker.end_precision(); - typedef output_range range; - visit_format_arg(arg_formatter(ctx, &specs_), - internal::make_arg(val)); - return ctx.out(); - } - - private: - template - void handle_specs(Context &ctx) { - internal::handle_dynamic_spec( - specs_.width_, specs_.width_ref, ctx); - internal::handle_dynamic_spec( - specs_.precision, specs_.precision_ref, ctx); - } - - internal::dynamic_format_specs specs_; -}; - template typename basic_format_context::format_arg basic_format_context::get_arg( @@ -3002,106 +2692,6 @@ typename Context::iterator vformat_to( return h.context.out(); } -// Casts ``p`` to ``const void*`` for pointer formatting. -// Example: -// auto s = format("{}", ptr(p)); -template -inline const void *ptr(const T *p) { return p; } - -template -struct arg_join { - It begin; - It end; - basic_string_view sep; - - arg_join(It begin, It end, basic_string_view sep) - : begin(begin), end(end), sep(sep) {} -}; - -template -struct formatter, Char>: - formatter::value_type, Char> { - template - auto format(const arg_join &value, FormatContext &ctx) - -> decltype(ctx.out()) { - typedef formatter::value_type, Char> base; - auto it = value.begin; - auto out = ctx.out(); - if (it != value.end) { - out = base::format(*it++, ctx); - while (it != value.end) { - out = internal::copy(value.sep.begin(), value.sep.end(), out); - ctx.advance_to(out); - out = base::format(*it++, ctx); - } - } - return out; - } -}; - -template -arg_join join(It begin, It end, string_view sep) { - return arg_join(begin, end, sep); -} - -template -arg_join join(It begin, It end, wstring_view sep) { - return arg_join(begin, end, sep); -} - -// The following causes ICE in gcc 4.4. -#if FMT_USE_TRAILING_RETURN && (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 405) -template -auto join(const Range &range, string_view sep) - -> arg_join { - return join(internal::begin(range), internal::end(range), sep); -} - -template -auto join(const Range &range, wstring_view sep) - -> arg_join { - return join(internal::begin(range), internal::end(range), sep); -} -#endif - -#if FMT_USE_STRING -/** - \rst - Converts *value* to ``std::string`` using the default format for type *T*. - It doesn't support user-defined types with custom formatters. - - **Example**:: - - #include - - std::string answer = fmt::to_string(42); - \endrst - */ -template -std::string to_string(const T &value) { - std::string str; - internal::container_buffer buf(str); - writer(buf).write(value); - return str; -} - -/** - Converts *value* to ``std::wstring`` using the default format for type *T*. - */ -template -std::wstring to_wstring(const T &value) { - std::wstring str; - internal::container_buffer buf(str); - wwriter(buf).write(value); - return str; -} - -template -std::basic_string to_string(const basic_memory_buffer &buf) { - return std::basic_string(buf.data(), buf.size()); -} -#endif - template typename buffer_context::type::iterator internal::vformat_to( internal::basic_buffer &buf, basic_string_view format_str, @@ -3132,69 +2722,20 @@ inline typename buffer_context::type::iterator format_to( basic_format_args(as)); } -namespace internal { - -// Detect the iterator category of *any* given type in a SFINAE-friendly way. -// Unfortunately, older implementations of std::iterator_traits are not safe -// for use in a SFINAE-context. - -// the gist of C++17's void_t magic -template -struct void_ { typedef void type; }; - -template -struct it_category : std::false_type {}; - -template -struct it_category { typedef std::random_access_iterator_tag type; }; - -template -struct it_category::type> { - typedef typename T::iterator_category type; -}; - -// Detect if *any* given type models the OutputIterator concept. -template -class is_output_iterator { - // Check for mutability because all iterator categories derived from - // std::input_iterator_tag *may* also meet the requirements of an - // OutputIterator, thereby falling into the category of 'mutable iterators' - // [iterator.requirements.general] clause 4. - // The compiler reveals this property only at the point of *actually - // dereferencing* the iterator! - template - static decltype(*(internal::declval())) test(std::input_iterator_tag); - template - static char& test(std::output_iterator_tag); - template - static const char& test(...); - - typedef decltype(test(typename it_category::type{})) type; - typedef typename std::remove_reference::type result; - public: - static const bool value = !std::is_const::value; -}; -} // internal - template -//using format_context_t = basic_format_context; struct format_context_t { typedef basic_format_context type; }; template -//using format_args_t = basic_format_args>; struct format_args_t { typedef basic_format_args< typename format_context_t::type> type; }; template -inline typename std::enable_if::value, - OutputIt>::type - vformat_to(OutputIt out, const String &format_str, +inline OutputIt vformat_to(OutputIt out, const String &format_str, typename format_args_t::type args) { typedef output_range range; - return vformat_to>(range(out), - to_string_view(format_str), args); + return vformat_to>(range(out), to_string_view(format_str), args); } /** @@ -3210,8 +2751,7 @@ inline typename std::enable_if::value, */ template inline FMT_ENABLE_IF_T( - internal::is_string::value && - internal::is_output_iterator::value, OutputIt) + internal::is_string::value, OutputIt) format_to(OutputIt out, const S &format_str, const Args &... args) { internal::check_format_string(format_str); typedef typename format_context_t::type context; @@ -3220,87 +2760,6 @@ inline FMT_ENABLE_IF_T( basic_format_args(as)); } -template -struct format_to_n_result { - /** Iterator past the end of the output range. */ - OutputIt out; - /** Total (not truncated) output size. */ - std::size_t size; -}; - -template -struct format_to_n_context : - format_context_t, Char> {}; - -template -struct format_to_n_args { - typedef basic_format_args< - typename format_to_n_context::type> type; -}; - -template -inline format_arg_store< - typename format_to_n_context::type, Args...> - make_format_to_n_args(const Args &... args) { - return format_arg_store< - typename format_to_n_context::type, Args...>(args...); -} - -template -inline typename std::enable_if< - internal::is_output_iterator::value, - format_to_n_result>::type vformat_to_n( - OutputIt out, std::size_t n, basic_string_view format_str, - typename format_to_n_args::type args) { - typedef internal::truncating_iterator It; - auto it = vformat_to(It(out, n), format_str, args); - return {it.base(), it.count()}; -} - -/** - \rst - Formats arguments, writes up to ``n`` characters of the result to the output - iterator ``out`` and returns the total output size and the iterator past the - end of the output range. - \endrst - */ -template -inline FMT_ENABLE_IF_T( - internal::is_string::value && - internal::is_output_iterator::value, - format_to_n_result) - format_to_n(OutputIt out, std::size_t n, const S &format_str, - const Args &... args) { - internal::check_format_string(format_str); - typedef FMT_CHAR(S) Char; - format_arg_store< - typename format_to_n_context::type, Args...> as(args...); - return vformat_to_n(out, n, to_string_view(format_str), - typename format_to_n_args::type(as)); -} - -#if FMT_USE_STRING -template -inline std::basic_string internal::vformat( - basic_string_view format_str, - basic_format_args::type> args) { - basic_memory_buffer buffer; - internal::vformat_to(buffer, format_str, args); - return fmt::to_string(buffer); -} -#endif - -/** - Returns the number of characters in the output of - ``format(format_str, args...)``. - */ -template -inline std::size_t formatted_size(string_view format_str, - const Args &... args) { - auto it = format_to(internal::counting_iterator(), format_str, args...); - return it.count(); -} - #if FMT_USE_USER_DEFINED_LITERALS namespace internal { @@ -3325,8 +2784,8 @@ struct udl_formatter { template auto operator()(Args &&... args) const - -> decltype(format(str, std::forward(args)...)) { - return format(str, std::forward(args)...); + -> decltype(format(str, ::Forward(args)...)) { + return format(str, ::Forward(args)...); } }; # endif // FMT_UDL_TEMPLATE @@ -3337,7 +2796,7 @@ struct udl_arg { template named_arg operator=(T &&value) const { - return {str, std::forward(value)}; + return {str, ::Forward(value)}; } }; @@ -3385,36 +2844,6 @@ operator"" _a(const wchar_t *s, std::size_t) { return {s}; } #endif // FMT_USE_USER_DEFINED_LITERALS FMT_END_NAMESPACE -#define FMT_STRING(s) [] { \ - typedef typename std::remove_cv::type>::type>::type ct; \ - struct str : fmt::compile_string { \ - typedef ct char_type; \ - FMT_CONSTEXPR operator fmt::basic_string_view() const { \ - return {s, sizeof(s) / sizeof(ct) - 1}; \ - } \ - }; \ - return str{}; \ - }() - -#if defined(FMT_STRING_ALIAS) && FMT_STRING_ALIAS -/** - \rst - Constructs a compile-time format string. This macro is disabled by default to - prevent potential name collisions. To enable it define ``FMT_STRING_ALIAS`` to - 1 before including ``fmt/format.h``. - - **Example**:: - - #define FMT_STRING_ALIAS 1 - #include - // A compile-time error because 'd' is an invalid specifier for strings. - std::string s = format(fmt("{:d}"), "foo"); - \endrst - */ -# define fmt(s) FMT_STRING(s) -#endif - #ifdef FMT_HEADER_ONLY # define FMT_FUNC inline # include "format-inl.h" diff --git a/Source/ThirdParty/fmt/ostream.h b/Source/ThirdParty/fmt/ostream.h index 2db98a5dc..d7c930ff5 100644 --- a/Source/ThirdParty/fmt/ostream.h +++ b/Source/ThirdParty/fmt/ostream.h @@ -78,7 +78,7 @@ void write(std::basic_ostream &os, basic_buffer &buf) { typedef std::make_unsigned::type UnsignedStreamSize; UnsignedStreamSize size = buf.size(); UnsignedStreamSize max_size = - internal::to_unsigned((std::numeric_limits::max)()); + internal::to_unsigned(MAX_int32); do { UnsignedStreamSize n = size <= max_size ? size : max_size; os.write(data, static_cast(n)); diff --git a/Source/ThirdParty/glib.h b/Source/ThirdParty/glib.h new file mode 100644 index 000000000..a8460bbe9 --- /dev/null +++ b/Source/ThirdParty/glib.h @@ -0,0 +1,36 @@ +// Wrapper for mono/mono/eglib/glib.h to mock the types for embedding + +#ifndef _GLIB_H_ +#define _GLIB_H_ + +#include +#include + +/* + * Basic data types + */ +typedef int gint; +typedef unsigned int guint; +typedef short gshort; +typedef unsigned short gushort; +typedef long glong; +typedef unsigned long gulong; +typedef void * gpointer; +typedef const void * gconstpointer; +typedef char gchar; +typedef unsigned char guchar; + +/* Types defined in terms of the stdint.h */ +typedef int8_t gint8; +typedef uint8_t guint8; +typedef int16_t gint16; +typedef uint16_t guint16; +typedef int32_t gint32; +typedef uint32_t guint32; +typedef int64_t gint64; +typedef uint64_t guint64; +typedef float gfloat; +typedef double gdouble; +typedef int32_t gboolean; + +#endif diff --git a/Source/ThirdParty/mono-2.0/mono.Build.cs b/Source/ThirdParty/mono-2.0/mono.Build.cs index 2399b47e8..9fa592f35 100644 --- a/Source/ThirdParty/mono-2.0/mono.Build.cs +++ b/Source/ThirdParty/mono-2.0/mono.Build.cs @@ -73,8 +73,6 @@ public class mono : DepsModule case TargetPlatform.Linux: options.PublicDefinitions.Add("USE_MONO_DYNAMIC_LIB"); options.DependencyFiles.Add(Path.Combine(depsRoot, "libmonosgen-2.0.so")); - options.DependencyFiles.Add(Path.Combine(depsRoot, "libmonosgen-2.0.so.1")); - options.DependencyFiles.Add(Path.Combine(depsRoot, "libmonosgen-2.0.so.1.0.0")); options.Libraries.Add(Path.Combine(depsRoot, "libmonosgen-2.0.so")); break; case TargetPlatform.PS4: diff --git a/Source/ThirdParty/pugixml/pugixml.cpp b/Source/ThirdParty/pugixml/pugixml.cpp index 5b77a271c..af52e1ea8 100644 --- a/Source/ThirdParty/pugixml/pugixml.cpp +++ b/Source/ThirdParty/pugixml/pugixml.cpp @@ -4799,6 +4799,16 @@ namespace pugi return xml_node(); } + PUGI__FN xml_node xml_node::child_or_append(const char_t* name_) + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return append_child(name_); + } + PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const { if (!_root) return xml_attribute(); @@ -4869,7 +4879,18 @@ namespace pugi return PUGIXML_TEXT(""); } - PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const + PUGI__FN bool xml_node::set_child_value(const char_t* rhs) + { + if (!_root) return false; + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->value && impl::is_text_node(i)) + return xml_node(i).set_value(rhs); + + return append_child(node_pcdata).set_value(rhs); + } + + PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const { return child(name_).child_value(); } diff --git a/Source/ThirdParty/pugixml/pugixml.hpp b/Source/ThirdParty/pugixml/pugixml.hpp index d937bff47..fd71dfb1a 100644 --- a/Source/ThirdParty/pugixml/pugixml.hpp +++ b/Source/ThirdParty/pugixml/pugixml.hpp @@ -452,12 +452,14 @@ namespace pugi // Get child, attribute or next/previous sibling with the specified name xml_node child(const char_t* name) const; + xml_node child_or_append(const char_t* name); xml_attribute attribute(const char_t* name) const; xml_node next_sibling(const char_t* name) const; xml_node previous_sibling(const char_t* name) const; // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA const char_t* child_value() const; + bool set_child_value(const char_t* rhs); // Get child value of child with specified name. Equivalent to child(name).child_value(). const char_t* child_value(const char_t* name) const; diff --git a/Source/ThirdParty/rapidjson/document.h b/Source/ThirdParty/rapidjson/document.h index b47115147..a6a6e4d9c 100644 --- a/Source/ThirdParty/rapidjson/document.h +++ b/Source/ThirdParty/rapidjson/document.h @@ -23,7 +23,6 @@ #include "memorystream.h" #include "encodedstream.h" #include // placement new -#include RAPIDJSON_DIAG_PUSH #ifdef _MSC_VER @@ -948,26 +947,6 @@ public: bool IsDouble() const { return (data_.f.flags & kDoubleFlag) != 0; } bool IsString() const { return (data_.f.flags & kStringFlag) != 0; } - // Checks whether a number can be losslessly converted to a double. - bool IsLosslessDouble() const { - if (!IsNumber()) return false; - if (IsUint64()) { - uint64_t u = GetUint64(); - volatile double d = static_cast(u); - return (d >= 0.0) - && (d < static_cast(std::numeric_limits::max())) - && (u == static_cast(d)); - } - if (IsInt64()) { - int64_t i = GetInt64(); - volatile double d = static_cast(i); - return (d >= static_cast(std::numeric_limits::min())) - && (d < static_cast(std::numeric_limits::max())) - && (i == static_cast(d)); - } - return true; // double, int, uint are always lossless - } - // Checks whether a number is a float (possible lossy). bool IsFloat() const { if ((data_.f.flags & kDoubleFlag) == 0) @@ -975,16 +954,6 @@ public: double d = GetDouble(); return d >= -3.4028234e38 && d <= 3.4028234e38; } - // Checks whether a number can be losslessly converted to a float. - bool IsLosslessFloat() const { - if (!IsNumber()) return false; - double a = GetDouble(); - if (a < static_cast(-std::numeric_limits::max()) - || a > static_cast(std::numeric_limits::max())) - return false; - double b = static_cast(static_cast(a)); - return a >= b && a <= b; // Prevent -Wfloat-equal - } //@} diff --git a/Source/ThirdParty/rapidjson/internal/biginteger.h b/Source/ThirdParty/rapidjson/internal/biginteger.h index 9d3e88c99..5e7e61688 100644 --- a/Source/ThirdParty/rapidjson/internal/biginteger.h +++ b/Source/ThirdParty/rapidjson/internal/biginteger.h @@ -18,7 +18,7 @@ #include "../rapidjson.h" #if defined(_MSC_VER) && defined(_M_AMD64) -#include // for _umul128 +#include // for _umul128 #pragma intrinsic(_umul128) #endif diff --git a/Source/ThirdParty/rapidjson/internal/diyfp.h b/Source/ThirdParty/rapidjson/internal/diyfp.h index c9fefdc61..617b07968 100644 --- a/Source/ThirdParty/rapidjson/internal/diyfp.h +++ b/Source/ThirdParty/rapidjson/internal/diyfp.h @@ -22,7 +22,7 @@ #include "../rapidjson.h" #if defined(_MSC_VER) && defined(_M_AMD64) -#include +#include #pragma intrinsic(_BitScanReverse64) #pragma intrinsic(_umul128) #endif diff --git a/Source/ThirdParty/rapidjson/reader.h b/Source/ThirdParty/rapidjson/reader.h index 19f8849b1..a61a94ec5 100644 --- a/Source/ThirdParty/rapidjson/reader.h +++ b/Source/ThirdParty/rapidjson/reader.h @@ -23,7 +23,6 @@ #include "internal/meta.h" #include "internal/stack.h" #include "internal/strtod.h" -#include #if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) #include @@ -1187,10 +1186,10 @@ private: else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) { useNanOrInf = true; if (RAPIDJSON_LIKELY(Consume(s, 'N') && Consume(s, 'a') && Consume(s, 'N'))) { - d = std::numeric_limits::quiet_NaN(); + d = NAN; } else if (RAPIDJSON_LIKELY(Consume(s, 'I') && Consume(s, 'n') && Consume(s, 'f'))) { - d = (minus ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()); + d = (minus ? -INFINITY : INFINITY); if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n') && Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); diff --git a/Source/ThirdParty/stb/stb_dxt.h b/Source/ThirdParty/stb/stb_dxt.h new file mode 100644 index 000000000..04666de99 --- /dev/null +++ b/Source/ThirdParty/stb/stb_dxt.h @@ -0,0 +1,753 @@ +// stb_dxt.h - v1.10 - DXT1/DXT5 compressor - public domain +// original by fabian "ryg" giesen - ported to C by stb +// use '#define STB_DXT_IMPLEMENTATION' before including to create the implementation +// +// USAGE: +// call stb_compress_dxt_block() for every block (you must pad) +// source should be a 4x4 block of RGBA data in row-major order; +// Alpha channel is not stored if you specify alpha=0 (but you +// must supply some constant alpha in the alpha channel). +// You can turn on dithering and "high quality" using mode. +// +// version history: +// v1.10 - (i.c) various small quality improvements +// v1.09 - (stb) update documentation re: surprising alpha channel requirement +// v1.08 - (stb) fix bug in dxt-with-alpha block +// v1.07 - (stb) bc4; allow not using libc; add STB_DXT_STATIC +// v1.06 - (stb) fix to known-broken 1.05 +// v1.05 - (stb) support bc5/3dc (Arvids Kokins), use extern "C" in C++ (Pavel Krajcevski) +// v1.04 - (ryg) default to no rounding bias for lerped colors (as per S3TC/DX10 spec); +// single color match fix (allow for inexact color interpolation); +// optimal DXT5 index finder; "high quality" mode that runs multiple refinement steps. +// v1.03 - (stb) endianness support +// v1.02 - (stb) fix alpha encoding bug +// v1.01 - (stb) fix bug converting to RGB that messed up quality, thanks ryg & cbloom +// v1.00 - (stb) first release +// +// contributors: +// Rich Geldreich (more accurate index selection) +// Kevin Schmidt (#defines for "freestanding" compilation) +// github:ppiastucki (BC4 support) +// Ignacio Castano - improve DXT endpoint quantization +// +// LICENSE +// +// See end of file for license information. + +#ifndef STB_INCLUDE_STB_DXT_H +#define STB_INCLUDE_STB_DXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_DXT_STATIC +#define STBDDEF static +#else +#define STBDDEF extern +#endif + +// compression mode (bitflags) +#define STB_DXT_NORMAL 0 +#define STB_DXT_DITHER 1 // use dithering. dubious win. never use for normal maps and the like! +#define STB_DXT_HIGHQUAL 2 // high quality mode, does two refinement steps instead of 1. ~30-40% slower. + +STBDDEF void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src_rgba_four_bytes_per_pixel, int alpha, int mode); +STBDDEF void stb_compress_bc4_block(unsigned char *dest, const unsigned char *src_r_one_byte_per_pixel); +STBDDEF void stb_compress_bc5_block(unsigned char *dest, const unsigned char *src_rg_two_byte_per_pixel); + +#define STB_COMPRESS_DXT_BLOCK + +#ifdef __cplusplus +} +#endif +#endif // STB_INCLUDE_STB_DXT_H + +#ifdef STB_DXT_IMPLEMENTATION + +// configuration options for DXT encoder. set them in the project/makefile or just define +// them at the top. + +// STB_DXT_USE_ROUNDING_BIAS +// use a rounding bias during color interpolation. this is closer to what "ideal" +// interpolation would do but doesn't match the S3TC/DX10 spec. old versions (pre-1.03) +// implicitly had this turned on. +// +// in case you're targeting a specific type of hardware (e.g. console programmers): +// NVidia and Intel GPUs (as of 2010) as well as DX9 ref use DXT decoders that are closer +// to STB_DXT_USE_ROUNDING_BIAS. AMD/ATI, S3 and DX10 ref are closer to rounding with no bias. +// you also see "(a*5 + b*3) / 8" on some old GPU designs. +// #define STB_DXT_USE_ROUNDING_BIAS + +#include + +#if !defined(STBD_ABS) || !defined(STBI_FABS) +#include +#endif + +#ifndef STBD_ABS +#define STBD_ABS(i) abs(i) +#endif + +#ifndef STBD_FABS +#define STBD_FABS(x) fabs(x) +#endif + +#ifndef STBD_MEMSET +#include +#define STBD_MEMSET memset +#endif + +static unsigned char stb__Expand5[32]; +static unsigned char stb__Expand6[64]; +static unsigned char stb__OMatch5[256][2]; +static unsigned char stb__OMatch6[256][2]; +static unsigned char stb__QuantRBTab[256+16]; +static unsigned char stb__QuantGTab[256+16]; + +static int stb__Mul8Bit(int a, int b) +{ + int t = a*b + 128; + return (t + (t >> 8)) >> 8; +} + +static void stb__From16Bit(unsigned char *out, unsigned short v) +{ + int rv = (v & 0xf800) >> 11; + int gv = (v & 0x07e0) >> 5; + int bv = (v & 0x001f) >> 0; + + out[0] = stb__Expand5[rv]; + out[1] = stb__Expand6[gv]; + out[2] = stb__Expand5[bv]; + out[3] = 0; +} + +static unsigned short stb__As16Bit(int r, int g, int b) +{ + return (unsigned short)((stb__Mul8Bit(r,31) << 11) + (stb__Mul8Bit(g,63) << 5) + stb__Mul8Bit(b,31)); +} + +// linear interpolation at 1/3 point between a and b, using desired rounding type +static int stb__Lerp13(int a, int b) +{ +#ifdef STB_DXT_USE_ROUNDING_BIAS + // with rounding bias + return a + stb__Mul8Bit(b-a, 0x55); +#else + // without rounding bias + // replace "/ 3" by "* 0xaaab) >> 17" if your compiler sucks or you really need every ounce of speed. + return (2*a + b) / 3; +#endif +} + +// lerp RGB color +static void stb__Lerp13RGB(unsigned char *out, unsigned char *p1, unsigned char *p2) +{ + out[0] = (unsigned char)stb__Lerp13(p1[0], p2[0]); + out[1] = (unsigned char)stb__Lerp13(p1[1], p2[1]); + out[2] = (unsigned char)stb__Lerp13(p1[2], p2[2]); +} + +/****************************************************************************/ + +// compute table to reproduce constant colors as accurately as possible +static void stb__PrepareOptTable(unsigned char *Table,const unsigned char *expand,int size) +{ + int i,mn,mx; + for (i=0;i<256;i++) { + int bestErr = 256; + for (mn=0;mn> 4)]; + ep1[0] = bp[ 0] - dp[ 0]; + dp[ 4] = quant[bp[ 4] + ((7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]) >> 4)]; + ep1[1] = bp[ 4] - dp[ 4]; + dp[ 8] = quant[bp[ 8] + ((7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]) >> 4)]; + ep1[2] = bp[ 8] - dp[ 8]; + dp[12] = quant[bp[12] + ((7*ep1[2] + 5*ep2[3] + ep2[2]) >> 4)]; + ep1[3] = bp[12] - dp[12]; + bp += 16; + dp += 16; + et = ep1, ep1 = ep2, ep2 = et; // swap + } + } +} + +// The color matching function +static unsigned int stb__MatchColorsBlock(unsigned char *block, unsigned char *color,int dither) +{ + unsigned int mask = 0; + int dirr = color[0*4+0] - color[1*4+0]; + int dirg = color[0*4+1] - color[1*4+1]; + int dirb = color[0*4+2] - color[1*4+2]; + int dots[16]; + int stops[4]; + int i; + int c0Point, halfPoint, c3Point; + + for(i=0;i<16;i++) + dots[i] = block[i*4+0]*dirr + block[i*4+1]*dirg + block[i*4+2]*dirb; + + for(i=0;i<4;i++) + stops[i] = color[i*4+0]*dirr + color[i*4+1]*dirg + color[i*4+2]*dirb; + + // think of the colors as arranged on a line; project point onto that line, then choose + // next color out of available ones. we compute the crossover points for "best color in top + // half"/"best in bottom half" and then the same inside that subinterval. + // + // relying on this 1d approximation isn't always optimal in terms of euclidean distance, + // but it's very close and a lot faster. + // http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html + + c0Point = (stops[1] + stops[3]); + halfPoint = (stops[3] + stops[2]); + c3Point = (stops[2] + stops[0]); + + if(!dither) { + // the version without dithering is straightforward + for (i=15;i>=0;i--) { + int dot = dots[i]*2; + mask <<= 2; + + if(dot < halfPoint) + mask |= (dot < c0Point) ? 1 : 3; + else + mask |= (dot < c3Point) ? 2 : 0; + } + } else { + // with floyd-steinberg dithering + int err[8],*ep1 = err,*ep2 = err+4; + int *dp = dots, y; + + c0Point <<= 3; + halfPoint <<= 3; + c3Point <<= 3; + for(i=0;i<8;i++) + err[i] = 0; + + for(y=0;y<4;y++) + { + int dot,lmask,step; + + dot = (dp[0] << 4) + (3*ep2[1] + 5*ep2[0]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[0] = dp[0] - stops[step]; + lmask = step; + + dot = (dp[1] << 4) + (7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[1] = dp[1] - stops[step]; + lmask |= step<<2; + + dot = (dp[2] << 4) + (7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[2] = dp[2] - stops[step]; + lmask |= step<<4; + + dot = (dp[3] << 4) + (7*ep1[2] + 5*ep2[3] + ep2[2]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[3] = dp[3] - stops[step]; + lmask |= step<<6; + + dp += 4; + mask |= lmask << (y*8); + { int *et = ep1; ep1 = ep2; ep2 = et; } // swap + } + } + + return mask; +} + +// The color optimization function. (Clever code, part 1) +static void stb__OptimizeColorsBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16) +{ + int mind = 0x7fffffff,maxd = -0x7fffffff; + unsigned char *minp, *maxp; + double magn; + int v_r,v_g,v_b; + static const int nIterPower = 4; + float covf[6],vfr,vfg,vfb; + + // determine color distribution + int cov[6]; + int mu[3],min[3],max[3]; + int ch,i,iter; + + for(ch=0;ch<3;ch++) + { + const unsigned char *bp = ((const unsigned char *) block) + ch; + int muv,minv,maxv; + + muv = minv = maxv = bp[0]; + for(i=4;i<64;i+=4) + { + muv += bp[i]; + if (bp[i] < minv) minv = bp[i]; + else if (bp[i] > maxv) maxv = bp[i]; + } + + mu[ch] = (muv + 8) >> 4; + min[ch] = minv; + max[ch] = maxv; + } + + // determine covariance matrix + for (i=0;i<6;i++) + cov[i] = 0; + + for (i=0;i<16;i++) + { + int r = block[i*4+0] - mu[0]; + int g = block[i*4+1] - mu[1]; + int b = block[i*4+2] - mu[2]; + + cov[0] += r*r; + cov[1] += r*g; + cov[2] += r*b; + cov[3] += g*g; + cov[4] += g*b; + cov[5] += b*b; + } + + // convert covariance matrix to float, find principal axis via power iter + for(i=0;i<6;i++) + covf[i] = cov[i] / 255.0f; + + vfr = (float) (max[0] - min[0]); + vfg = (float) (max[1] - min[1]); + vfb = (float) (max[2] - min[2]); + + for(iter=0;iter magn) magn = STBD_FABS(vfg); + if (STBD_FABS(vfb) > magn) magn = STBD_FABS(vfb); + + if(magn < 4.0f) { // too small, default to luminance + v_r = 299; // JPEG YCbCr luma coefs, scaled by 1000. + v_g = 587; + v_b = 114; + } else { + magn = 512.0 / magn; + v_r = (int) (vfr * magn); + v_g = (int) (vfg * magn); + v_b = (int) (vfb * magn); + } + + // Pick colors at extreme points + for(i=0;i<16;i++) + { + int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b; + + if (dot < mind) { + mind = dot; + minp = block+i*4; + } + + if (dot > maxd) { + maxd = dot; + maxp = block+i*4; + } + } + + *pmax16 = stb__As16Bit(maxp[0],maxp[1],maxp[2]); + *pmin16 = stb__As16Bit(minp[0],minp[1],minp[2]); +} + +static const float midpoints5[32] = { + 0.015686f, 0.047059f, 0.078431f, 0.111765f, 0.145098f, 0.176471f, 0.207843f, 0.241176f, 0.274510f, 0.305882f, 0.337255f, 0.370588f, 0.403922f, 0.435294f, 0.466667f, 0.5f, + 0.533333f, 0.564706f, 0.596078f, 0.629412f, 0.662745f, 0.694118f, 0.725490f, 0.758824f, 0.792157f, 0.823529f, 0.854902f, 0.888235f, 0.921569f, 0.952941f, 0.984314f, 1.0f +}; + +static const float midpoints6[64] = { + 0.007843f, 0.023529f, 0.039216f, 0.054902f, 0.070588f, 0.086275f, 0.101961f, 0.117647f, 0.133333f, 0.149020f, 0.164706f, 0.180392f, 0.196078f, 0.211765f, 0.227451f, 0.245098f, + 0.262745f, 0.278431f, 0.294118f, 0.309804f, 0.325490f, 0.341176f, 0.356863f, 0.372549f, 0.388235f, 0.403922f, 0.419608f, 0.435294f, 0.450980f, 0.466667f, 0.482353f, 0.500000f, + 0.517647f, 0.533333f, 0.549020f, 0.564706f, 0.580392f, 0.596078f, 0.611765f, 0.627451f, 0.643137f, 0.658824f, 0.674510f, 0.690196f, 0.705882f, 0.721569f, 0.737255f, 0.754902f, + 0.772549f, 0.788235f, 0.803922f, 0.819608f, 0.835294f, 0.850980f, 0.866667f, 0.882353f, 0.898039f, 0.913725f, 0.929412f, 0.945098f, 0.960784f, 0.976471f, 0.992157f, 1.0f +}; + +static unsigned short stb__Quantize5(float x) +{ + unsigned short q; + x = x < 0 ? 0 : x > 1 ? 1 : x; // saturate + q = (unsigned short)(x * 31); + q += (x > midpoints5[q]); + return q; +} + +static unsigned short stb__Quantize6(float x) +{ + unsigned short q; + x = x < 0 ? 0 : x > 1 ? 1 : x; // saturate + q = (unsigned short)(x * 63); + q += (x > midpoints6[q]); + return q; +} + +// The refinement function. (Clever code, part 2) +// Tries to optimize colors to suit block contents better. +// (By solving a least squares system via normal equations+Cramer's rule) +static int stb__RefineBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16, unsigned int mask) +{ + static const int w1Tab[4] = { 3,0,2,1 }; + static const int prods[4] = { 0x090000,0x000900,0x040102,0x010402 }; + // ^some magic to save a lot of multiplies in the accumulating loop... + // (precomputed products of weights for least squares system, accumulated inside one 32-bit register) + + float f; + unsigned short oldMin, oldMax, min16, max16; + int i, akku = 0, xx,xy,yy; + int At1_r,At1_g,At1_b; + int At2_r,At2_g,At2_b; + unsigned int cm = mask; + + oldMin = *pmin16; + oldMax = *pmax16; + + if((mask ^ (mask<<2)) < 4) // all pixels have the same index? + { + // yes, linear system would be singular; solve using optimal + // single-color match on average color + int r = 8, g = 8, b = 8; + for (i=0;i<16;++i) { + r += block[i*4+0]; + g += block[i*4+1]; + b += block[i*4+2]; + } + + r >>= 4; g >>= 4; b >>= 4; + + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else { + At1_r = At1_g = At1_b = 0; + At2_r = At2_g = At2_b = 0; + for (i=0;i<16;++i,cm>>=2) { + int step = cm&3; + int w1 = w1Tab[step]; + int r = block[i*4+0]; + int g = block[i*4+1]; + int b = block[i*4+2]; + + akku += prods[step]; + At1_r += w1*r; + At1_g += w1*g; + At1_b += w1*b; + At2_r += r; + At2_g += g; + At2_b += b; + } + + At2_r = 3*At2_r - At1_r; + At2_g = 3*At2_g - At1_g; + At2_b = 3*At2_b - At1_b; + + // extract solutions and decide solvability + xx = akku >> 16; + yy = (akku >> 8) & 0xff; + xy = (akku >> 0) & 0xff; + + f = 3.0f / 255.0f / (xx*yy - xy*xy); + + max16 = stb__Quantize5((At1_r*yy - At2_r * xy) * f) << 11; + max16 |= stb__Quantize6((At1_g*yy - At2_g * xy) * f) << 5; + max16 |= stb__Quantize5((At1_b*yy - At2_b * xy) * f) << 0; + + min16 = stb__Quantize5((At2_r*xx - At1_r * xy) * f) << 11; + min16 |= stb__Quantize6((At2_g*xx - At1_g * xy) * f) << 5; + min16 |= stb__Quantize5((At2_b*xx - At1_b * xy) * f) << 0; + } + + *pmin16 = min16; + *pmax16 = max16; + return oldMin != min16 || oldMax != max16; +} + +// Color block compression +static void stb__CompressColorBlock(unsigned char *dest, unsigned char *block, int mode) +{ + unsigned int mask; + int i; + int dither; + int refinecount; + unsigned short max16, min16; + unsigned char dblock[16*4],color[4*4]; + + dither = mode & STB_DXT_DITHER; + refinecount = (mode & STB_DXT_HIGHQUAL) ? 2 : 1; + + // check if block is constant + for (i=1;i<16;i++) + if (((unsigned int *) block)[i] != ((unsigned int *) block)[0]) + break; + + if(i == 16) { // constant color + int r = block[0], g = block[1], b = block[2]; + mask = 0xaaaaaaaa; + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else { + // first step: compute dithered version for PCA if desired + if(dither) + stb__DitherBlock(dblock,block); + + // second step: pca+map along principal axis + stb__OptimizeColorsBlock(dither ? dblock : block,&max16,&min16); + if (max16 != min16) { + stb__EvalColors(color,max16,min16); + mask = stb__MatchColorsBlock(block,color,dither); + } else + mask = 0; + + // third step: refine (multiple times if requested) + for (i=0;i> 8); + dest[2] = (unsigned char) (min16); + dest[3] = (unsigned char) (min16 >> 8); + dest[4] = (unsigned char) (mask); + dest[5] = (unsigned char) (mask >> 8); + dest[6] = (unsigned char) (mask >> 16); + dest[7] = (unsigned char) (mask >> 24); +} + +// Alpha block compression (this is easy for a change) +static void stb__CompressAlphaBlock(unsigned char *dest,unsigned char *src, int stride) +{ + int i,dist,bias,dist4,dist2,bits,mask; + + // find min/max color + int mn,mx; + mn = mx = src[0]; + + for (i=1;i<16;i++) + { + if (src[i*stride] < mn) mn = src[i*stride]; + else if (src[i*stride] > mx) mx = src[i*stride]; + } + + // encode them + dest[0] = (unsigned char)mx; + dest[1] = (unsigned char)mn; + dest += 2; + + // determine bias and emit color indices + // given the choice of mx/mn, these indices are optimal: + // http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/ + dist = mx-mn; + dist4 = dist*4; + dist2 = dist*2; + bias = (dist < 8) ? (dist - 1) : (dist/2 + 2); + bias -= mn * 7; + bits = 0,mask=0; + + for (i=0;i<16;i++) { + int a = src[i*stride]*7 + bias; + int ind,t; + + // select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max). + t = (a >= dist4) ? -1 : 0; ind = t & 4; a -= dist4 & t; + t = (a >= dist2) ? -1 : 0; ind += t & 2; a -= dist2 & t; + ind += (a >= dist); + + // turn linear scale into DXT index (0/1 are extremal pts) + ind = -ind & 7; + ind ^= (2 > ind); + + // write index + mask |= ind << bits; + if((bits += 3) >= 8) { + *dest++ = (unsigned char)mask; + mask >>= 8; + bits -= 8; + } + } +} + +static void stb__InitDXT() +{ + int i; + for(i=0;i<32;i++) + stb__Expand5[i] = (unsigned char)((i<<3)|(i>>2)); + + for(i=0;i<64;i++) + stb__Expand6[i] = (unsigned char)((i<<2)|(i>>4)); + + for(i=0;i<256+16;i++) + { + int v = i-8 < 0 ? 0 : i-8 > 255 ? 255 : i-8; + stb__QuantRBTab[i] = stb__Expand5[stb__Mul8Bit(v,31)]; + stb__QuantGTab[i] = stb__Expand6[stb__Mul8Bit(v,63)]; + } + + stb__PrepareOptTable(&stb__OMatch5[0][0],stb__Expand5,32); + stb__PrepareOptTable(&stb__OMatch6[0][0],stb__Expand6,64); +} + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode) +{ + unsigned char data[16][4]; + static int init=1; + if (init) { + stb__InitDXT(); + init=0; + } + + if (alpha) { + int i; + stb__CompressAlphaBlock(dest,(unsigned char*) src+3, 4); + dest += 8; + // make a new copy of the data in which alpha is opaque, + // because code uses a fast test for color constancy + memcpy(data, src, 4*16); + for (i=0; i < 16; ++i) + data[i][3] = 255; + src = &data[0][0]; + } + + stb__CompressColorBlock(dest,(unsigned char*) src,mode); +} + +void stb_compress_bc4_block(unsigned char *dest, const unsigned char *src) +{ + stb__CompressAlphaBlock(dest,(unsigned char*) src, 1); +} + +void stb_compress_bc5_block(unsigned char *dest, const unsigned char *src) +{ + stb__CompressAlphaBlock(dest,(unsigned char*) src,2); + stb__CompressAlphaBlock(dest + 8,(unsigned char*) src+1,2); +} +#endif // STB_DXT_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF 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/tracy/LICENSE b/Source/ThirdParty/tracy/LICENSE new file mode 100644 index 000000000..c2a76e56c --- /dev/null +++ b/Source/ThirdParty/tracy/LICENSE @@ -0,0 +1,27 @@ +Tracy Profiler (https://github.com/wolfpld/tracy) is licensed under the +3-clause BSD license. + +Copyright (c) 2017-2021, Bartosz Taudul +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Source/ThirdParty/tracy/Tracy.h b/Source/ThirdParty/tracy/Tracy.h new file mode 100644 index 000000000..cb115740f --- /dev/null +++ b/Source/ThirdParty/tracy/Tracy.h @@ -0,0 +1,252 @@ +#ifndef __TRACY_HPP__ +#define __TRACY_HPP__ + +#include "common/TracySystem.hpp" + +#ifndef TRACY_ENABLE + +#define ZoneNamed(x,y) +#define ZoneNamedN(x,y,z) +#define ZoneNamedC(x,y,z) +#define ZoneNamedNC(x,y,z,w) + +#define ZoneTransient(x,y) +#define ZoneTransientN(x,y,z) + +#define ZoneScoped +#define ZoneScopedN(x) +#define ZoneScopedC(x) +#define ZoneScopedNC(x,y) + +#define ZoneText(x,y) +#define ZoneTextV(x,y,z) +#define ZoneName(x,y) +#define ZoneNameV(x,y,z) +#define ZoneColor(x) +#define ZoneColorV(x,y) +#define ZoneValue(x) +#define ZoneValueV(x,y) + +#define FrameMark +#define FrameMarkNamed(x) + +#define TracyPlot(x,y) +#define TracyPlotConfig(x,y) + +#define TracyMessage(x,y) +#define TracyMessageL(x) +#define TracyMessageC(x,y,z) +#define TracyMessageLC(x,y) +#define TracyAppInfo(x,y) + +#define TracyAlloc(x,y) +#define TracyFree(x) +#define TracySecureAlloc(x,y) +#define TracySecureFree(x) + +#define TracyAllocN(x,y,z) +#define TracyFreeN(x,y) +#define TracySecureAllocN(x,y,z) +#define TracySecureFreeN(x,y) + +#define ZoneNamedS(x,y,z) +#define ZoneNamedNS(x,y,z,w) +#define ZoneNamedCS(x,y,z,w) +#define ZoneNamedNCS(x,y,z,w,a) + +#define ZoneTransientS(x,y,z) +#define ZoneTransientNS(x,y,z,w) + +#define ZoneScopedS(x) +#define ZoneScopedNS(x,y) +#define ZoneScopedCS(x,y) +#define ZoneScopedNCS(x,y,z) + +#define TracyAllocS(x,y,z) +#define TracyFreeS(x,y) +#define TracySecureAllocS(x,y,z) +#define TracySecureFreeS(x,y) + +#define TracyAllocNS(x,y,z,w) +#define TracyFreeNS(x,y,z) +#define TracySecureAllocNS(x,y,z,w) +#define TracySecureFreeNS(x,y,z) + +#define TracyMessageS(x,y,z) +#define TracyMessageLS(x,y) +#define TracyMessageCS(x,y,z,w) +#define TracyMessageLCS(x,y,z) + +#define TracyParameterRegister(x) +#define TracyParameterSetup(x,y,z,w) + +#else + +#include + +#include "client/TracyCallstack.h" + +namespace tracy +{ +class TRACY_API Profiler +{ +public: + static void SendFrameMark( const char* name ); + static void PlotData( const char* name, int64_t val ); + static void PlotData( const char* name, float val ); + static void PlotData( const char* name, double val ); + static void ConfigurePlot( const char* name, PlotFormatType type ); + static void Message( const char* txt, size_t size, int callstack ); + static void Message( const char* txt, int callstack ); + static void MessageColor( const char* txt, size_t size, uint32_t color, int callstack ); + static void MessageColor( const char* txt, uint32_t color, int callstack ); + static void MessageAppInfo( const char* txt, size_t size ); + static void MemAlloc( const void* ptr, size_t size, bool secure ); + static void MemFree( const void* ptr, bool secure ); + static void MemAllocCallstack( const void* ptr, size_t size, int depth, bool secure ); + static void MemFreeCallstack( const void* ptr, int depth, bool secure ); + static void MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name ); + static void MemFreeNamed( const void* ptr, bool secure, const char* name ); + static void MemAllocCallstackNamed( const void* ptr, size_t size, int depth, bool secure, const char* name ); + static void MemFreeCallstackNamed( const void* ptr, int depth, bool secure, const char* name ); + static void ParameterRegister( ParameterCallback cb ); + static void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val ); +}; +} + +#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK +# define ZoneNamed( varname, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), TRACY_CALLSTACK, active ); +# define ZoneNamedN( varname, name, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), TRACY_CALLSTACK, active ); +# define ZoneNamedC( varname, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), TRACY_CALLSTACK, active ); +# define ZoneNamedNC( varname, name, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), TRACY_CALLSTACK, active ); + +# define ZoneTransient( varname, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), nullptr, 0, TRACY_CALLSTACK, active ); +# define ZoneTransientN( varname, name, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name, strlen( name ), TRACY_CALLSTACK, active ); +#else +# define ZoneNamed( varname, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), active ); +# define ZoneNamedN( varname, name, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), active ); +# define ZoneNamedC( varname, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), active ); +# define ZoneNamedNC( varname, name, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), active ); + +# define ZoneTransient( varname, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), nullptr, 0, active ); +# define ZoneTransientN( varname, name, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name, strlen( name ), active ); +#endif + +#define ZoneScoped ZoneNamed( ___tracy_scoped_zone, true ) +#define ZoneScopedN( name ) ZoneNamedN( ___tracy_scoped_zone, name, true ) +#define ZoneScopedC( color ) ZoneNamedC( ___tracy_scoped_zone, color, true ) +#define ZoneScopedNC( name, color ) ZoneNamedNC( ___tracy_scoped_zone, name, color, true ) + +#define ZoneText( txt, size ) ___tracy_scoped_zone.Text( txt, size ); +#define ZoneTextV( varname, txt, size ) varname.Text( txt, size ); +#define ZoneName( txt, size ) ___tracy_scoped_zone.Name( txt, size ); +#define ZoneNameV( varname, txt, size ) varname.Name( txt, size ); +#define ZoneColor( color ) ___tracy_scoped_zone.Color( color ); +#define ZoneColorV( varname, color ) varname.Color( color ); +#define ZoneValue( value ) ___tracy_scoped_zone.Value( value ); +#define ZoneValueV( varname, value ) varname.Value( value ); + +#define FrameMark tracy::Profiler::SendFrameMark( nullptr ); +#define FrameMarkNamed( name ) tracy::Profiler::SendFrameMark( name ); + +#define TracyPlot( name, val ) tracy::Profiler::PlotData( name, val ); +#define TracyPlotConfig( name, type ) tracy::Profiler::ConfigurePlot( name, type ); + +#define TracyAppInfo( txt, size ) tracy::Profiler::MessageAppInfo( txt, size ); + +#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK +# define TracyMessage( txt, size ) tracy::Profiler::Message( txt, size, TRACY_CALLSTACK ); +# define TracyMessageL( txt ) tracy::Profiler::Message( txt, TRACY_CALLSTACK ); +# define TracyMessageC( txt, size, color ) tracy::Profiler::MessageColor( txt, size, color, TRACY_CALLSTACK ); +# define TracyMessageLC( txt, color ) tracy::Profiler::MessageColor( txt, color, TRACY_CALLSTACK ); + +# define TracyAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK, false ); +# define TracyFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK, false ); +# define TracySecureAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK, true ); +# define TracySecureFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK, true ); + +# define TracyAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, false, name ); +# define TracyFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, false, name ); +# define TracySecureAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, true, name ); +# define TracySecureFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, true, name ); +#else +# define TracyMessage( txt, size ) tracy::Profiler::Message( txt, size, 0 ); +# define TracyMessageL( txt ) tracy::Profiler::Message( txt, 0 ); +# define TracyMessageC( txt, size, color ) tracy::Profiler::MessageColor( txt, size, color, 0 ); +# define TracyMessageLC( txt, color ) tracy::Profiler::MessageColor( txt, color, 0 ); + +# define TracyAlloc( ptr, size ) tracy::Profiler::MemAlloc( ptr, size, false ); +# define TracyFree( ptr ) tracy::Profiler::MemFree( ptr, false ); +# define TracySecureAlloc( ptr, size ) tracy::Profiler::MemAlloc( ptr, size, true ); +# define TracySecureFree( ptr ) tracy::Profiler::MemFree( ptr, true ); + +# define TracyAllocN( ptr, size, name ) tracy::Profiler::MemAllocNamed( ptr, size, false, name ); +# define TracyFreeN( ptr, name ) tracy::Profiler::MemFreeNamed( ptr, false, name ); +# define TracySecureAllocN( ptr, size, name ) tracy::Profiler::MemAllocNamed( ptr, size, true, name ); +# define TracySecureFreeN( ptr, name ) tracy::Profiler::MemFreeNamed( ptr, true, name ); +#endif + +#ifdef TRACY_HAS_CALLSTACK +# define ZoneNamedS( varname, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), depth, active ); +# define ZoneNamedNS( varname, name, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), depth, active ); +# define ZoneNamedCS( varname, color, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), depth, active ); +# define ZoneNamedNCS( varname, name, color, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), depth, active ); + +# define ZoneTransientS( varname, depth, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), nullptr, 0, depth, active ); +# define ZoneTransientNS( varname, name, depth, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name, strlen( name ), depth, active ); + +# define ZoneScopedS( depth ) ZoneNamedS( ___tracy_scoped_zone, depth, true ) +# define ZoneScopedNS( name, depth ) ZoneNamedNS( ___tracy_scoped_zone, name, depth, true ) +# define ZoneScopedCS( color, depth ) ZoneNamedCS( ___tracy_scoped_zone, color, depth, true ) +# define ZoneScopedNCS( name, color, depth ) ZoneNamedNCS( ___tracy_scoped_zone, name, color, depth, true ) + +# define TracyAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth, false ); +# define TracyFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth, false ); +# define TracySecureAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth, true ); +# define TracySecureFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth, true ); + +# define TracyAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, false, name ); +# define TracyFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, false, name ); +# define TracySecureAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, true, name ); +# define TracySecureFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, true, name ); + +# define TracyMessageS( txt, size, depth ) tracy::Profiler::Message( txt, size, depth ); +# define TracyMessageLS( txt, depth ) tracy::Profiler::Message( txt, depth ); +# define TracyMessageCS( txt, size, color, depth ) tracy::Profiler::MessageColor( txt, size, color, depth ); +# define TracyMessageLCS( txt, color, depth ) tracy::Profiler::MessageColor( txt, color, depth ); +#else +# define ZoneNamedS( varname, depth, active ) ZoneNamed( varname, active ) +# define ZoneNamedNS( varname, name, depth, active ) ZoneNamedN( varname, name, active ) +# define ZoneNamedCS( varname, color, depth, active ) ZoneNamedC( varname, color, active ) +# define ZoneNamedNCS( varname, name, color, depth, active ) ZoneNamedNC( varname, name, color, active ) + +# define ZoneTransientS( varname, depth, active ) ZoneTransient( varname, active ) +# define ZoneTransientNS( varname, name, depth, active ) ZoneTransientN( varname, name, active ) + +# define ZoneScopedS( depth ) ZoneScoped +# define ZoneScopedNS( name, depth ) ZoneScopedN( name ) +# define ZoneScopedCS( color, depth ) ZoneScopedC( color ) +# define ZoneScopedNCS( name, color, depth ) ZoneScopedNC( name, color ) + +# define TracyAllocS( ptr, size, depth ) TracyAlloc( ptr, size ) +# define TracyFreeS( ptr, depth ) TracyFree( ptr ) +# define TracySecureAllocS( ptr, size, depth ) TracySecureAlloc( ptr, size ) +# define TracySecureFreeS( ptr, depth ) TracySecureFree( ptr ) + +# define TracyAllocNS( ptr, size, depth, name ) TracyAlloc( ptr, size, name ) +# define TracyFreeNS( ptr, depth, name ) TracyFree( ptr, name ) +# define TracySecureAllocNS( ptr, size, depth, name ) TracySecureAlloc( ptr, size, name ) +# define TracySecureFreeNS( ptr, depth, name ) TracySecureFree( ptr, name ) + +# define TracyMessageS( txt, size, depth ) TracyMessage( txt, size ) +# define TracyMessageLS( txt, depth ) TracyMessageL( txt ) +# define TracyMessageCS( txt, size, color, depth ) TracyMessageC( txt, size, color ) +# define TracyMessageLCS( txt, color, depth ) TracyMessageLC( txt, color ) +#endif + +#define TracyParameterRegister( cb ) tracy::Profiler::ParameterRegister( cb ); +#define TracyParameterSetup( idx, name, isBool, val ) tracy::Profiler::ParameterSetup( idx, name, isBool, val ); + +#endif + +#endif diff --git a/Source/ThirdParty/tracy/TracyClient.cpp b/Source/ThirdParty/tracy/TracyClient.cpp new file mode 100644 index 000000000..dd56765c1 --- /dev/null +++ b/Source/ThirdParty/tracy/TracyClient.cpp @@ -0,0 +1,53 @@ +// +// Tracy profiler +// ---------------- +// +// For fast integration, compile and +// link with this source file (and none +// other) in your executable (or in the +// main DLL / shared object on multi-DLL +// projects). +// + +// Define TRACY_ENABLE to enable profiler. + +#include "common/TracySystem.cpp" + +#ifdef TRACY_ENABLE + +#ifdef _MSC_VER +# pragma warning(push, 0) +#endif + +#include +#include "client/TracyProfiler.cpp" +#include "client/TracyCallstack.cpp" +#include "client/TracySysTime.cpp" +#include "client/TracySysTrace.cpp" +#include "common/TracySocket.cpp" +#include "client/tracy_rpmalloc.cpp" + +#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 +# include "libbacktrace/alloc.cpp" +# include "libbacktrace/dwarf.cpp" +# include "libbacktrace/fileline.cpp" +# include "libbacktrace/mmapio.cpp" +# include "libbacktrace/posix.cpp" +# include "libbacktrace/sort.cpp" +# include "libbacktrace/state.cpp" +# if TRACY_HAS_CALLSTACK == 4 +# include "libbacktrace/macho.cpp" +# else +# include "libbacktrace/elf.cpp" +# endif +#endif + +#ifdef _MSC_VER +# pragma comment(lib, "ws2_32.lib") +# pragma comment(lib, "dbghelp.lib") +# pragma comment(lib, "advapi32.lib") +# pragma comment(lib, "user32.lib") +# pragma warning(pop) +#endif + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyArmCpuTable.hpp b/Source/ThirdParty/tracy/client/TracyArmCpuTable.hpp new file mode 100644 index 000000000..ff7d976c8 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyArmCpuTable.hpp @@ -0,0 +1,349 @@ +namespace tracy +{ + +#if defined __linux__ && defined __ARM_ARCH + +static const char* DecodeArmImplementer( uint32_t v ) +{ + static char buf[16]; + switch( v ) + { + case 0x41: return "ARM"; + case 0x42: return "Broadcom"; + case 0x43: return "Cavium"; + case 0x44: return "DEC"; + case 0x46: return "Fujitsu"; + case 0x48: return "HiSilicon"; + case 0x49: return "Infineon"; + case 0x4d: return "Motorola"; + case 0x4e: return "Nvidia"; + case 0x50: return "Applied Micro"; + case 0x51: return "Qualcomm"; + case 0x53: return "Samsung"; + case 0x54: return "Texas Instruments"; + case 0x56: return "Marvell"; + case 0x61: return "Apple"; + case 0x66: return "Faraday"; + case 0x68: return "HXT"; + case 0x69: return "Intel"; + case 0xc0: return "Ampere Computing"; + default: break; + } + sprintf( buf, "0x%x", v ); + return buf; +} + +static const char* DecodeArmPart( uint32_t impl, uint32_t part ) +{ + static char buf[16]; + switch( impl ) + { + case 0x41: + switch( part ) + { + case 0x810: return "810"; + case 0x920: return "920"; + case 0x922: return "922"; + case 0x926: return "926"; + case 0x940: return "940"; + case 0x946: return "946"; + case 0x966: return "966"; + case 0xa20: return "1020"; + case 0xa22: return "1022"; + case 0xa26: return "1026"; + case 0xb02: return "11 MPCore"; + case 0xb36: return "1136"; + case 0xb56: return "1156"; + case 0xb76: return "1176"; + case 0xc05: return " Cortex-A5"; + case 0xc07: return " Cortex-A7"; + case 0xc08: return " Cortex-A8"; + case 0xc09: return " Cortex-A9"; + case 0xc0c: return " Cortex-A12"; + case 0xc0d: return " Rockchip RK3288"; + case 0xc0f: return " Cortex-A15"; + case 0xc0e: return " Cortex-A17"; + case 0xc14: return " Cortex-R4"; + case 0xc15: return " Cortex-R5"; + case 0xc17: return " Cortex-R7"; + case 0xc18: return " Cortex-R8"; + case 0xc20: return " Cortex-M0"; + case 0xc21: return " Cortex-M1"; + case 0xc23: return " Cortex-M3"; + case 0xc24: return " Cortex-M4"; + case 0xc27: return " Cortex-M7"; + case 0xc60: return " Cortex-M0+"; + case 0xd00: return " AArch64 simulator"; + case 0xd01: return " Cortex-A32"; + case 0xd02: return " Cortex-A34"; + case 0xd03: return " Cortex-A53"; + case 0xd04: return " Cortex-A35"; + case 0xd05: return " Cortex-A55"; + case 0xd06: return " Cortex-A65"; + case 0xd07: return " Cortex-A57"; + case 0xd08: return " Cortex-A72"; + case 0xd09: return " Cortex-A73"; + case 0xd0a: return " Cortex-A75"; + case 0xd0b: return " Cortex-A76"; + case 0xd0c: return " Neoverse N1"; + case 0xd0d: return " Cortex-A77"; + case 0xd0e: return " Cortex-A76AE"; + case 0xd0f: return " AEMv8"; + case 0xd13: return " Cortex-R52"; + case 0xd20: return " Cortex-M23"; + case 0xd21: return " Cortex-M33"; + case 0xd40: return " Zeus"; + case 0xd41: return " Cortex-A78"; + case 0xd43: return " Cortex-A65AE"; + case 0xd44: return " Cortex-X1"; + case 0xd4a: return " Neoverse E1"; + default: break; + } + case 0x42: + switch( part ) + { + case 0xf: return " Brahma B15"; + case 0x100: return " Brahma B53"; + case 0x516: return " ThunderX2"; + default: break; + } + case 0x43: + switch( part ) + { + case 0xa0: return " ThunderX"; + case 0xa1: return " ThunderX 88XX"; + case 0xa2: return " ThunderX 81XX"; + case 0xa3: return " ThunderX 83XX"; + case 0xaf: return " ThunderX2 99xx"; + case 0xb0: return " OcteonTX2"; + case 0xb1: return " OcteonTX2 T98"; + case 0xb2: return " OcteonTX2 T96"; + case 0xb3: return " OcteonTX2 F95"; + case 0xb4: return " OcteonTX2 F95N"; + case 0xb5: return " OcteonTX2 F95MM"; + case 0xb8: return " ThunderX3 T110"; + default: break; + } + case 0x44: + switch( part ) + { + case 0xa10: return " SA110"; + case 0xa11: return " SA1100"; + default: break; + } + case 0x46: + switch( part ) + { + case 0x1: return " A64FX"; + default: break; + } + case 0x48: + switch( part ) + { + case 0xd01: return " TSV100"; + case 0xd40: return " Kirin 980"; + default: break; + } + case 0x4e: + switch( part ) + { + case 0x0: return " Denver"; + case 0x3: return " Denver 2"; + case 0x4: return " Carmel"; + default: break; + } + case 0x50: + switch( part ) + { + case 0x0: return " X-Gene"; + default: break; + } + case 0x51: + switch( part ) + { + case 0xf: return " Scorpion"; + case 0x2d: return " Scorpion"; + case 0x4d: return " Krait"; + case 0x6f: return " Krait"; + case 0x200: return " Kryo"; + case 0x201: return " Kryo Silver (Snapdragon 821)"; + case 0x205: return " Kryo Gold"; + case 0x211: return " Kryo Silver (Snapdragon 820)"; + case 0x800: return " Kryo 260 / 280 Gold"; + case 0x801: return " Kryo 260 / 280 Silver"; + case 0x802: return " Kryo 385 Gold"; + case 0x803: return " Kryo 385 Silver"; + case 0x804: return " Kryo 485 Gold"; + case 0xc00: return " Falkor"; + case 0xc01: return " Saphira"; + default: break; + } + case 0x53: + switch( part ) + { + case 0x1: return " Exynos M1/M2"; + case 0x2: return " Exynos M3"; + default: break; + } + case 0x56: + switch( part ) + { + case 0x131: return " Feroceon 88FR131"; + case 0x581: return " PJ4 / PJ4B"; + case 0x584: return " PJ4B-MP / PJ4C"; + default: break; + } + case 0x61: + switch( part ) + { + case 0x1: return " Cyclone"; + case 0x2: return " Typhoon"; + case 0x3: return " Typhoon/Capri"; + case 0x4: return " Twister"; + case 0x5: return " Twister/Elba/Malta"; + case 0x6: return " Hurricane"; + case 0x7: return " Hurricane/Myst"; + default: break; + } + case 0x66: + switch( part ) + { + case 0x526: return " FA526"; + case 0x626: return " FA626"; + default: break; + } + case 0x68: + switch( part ) + { + case 0x0: return " Phecda"; + default: break; + } + default: break; + } + sprintf( buf, " 0x%x", part ); + return buf; +} + +#elif defined __APPLE__ && TARGET_OS_IPHONE == 1 + +static const char* DecodeIosDevice( const char* id ) +{ + static const char* DeviceTable[] = { + "i386", "32-bit simulator", + "x86_64", "64-bit simulator", + "iPhone1,1", "iPhone", + "iPhone1,2", "iPhone 3G", + "iPhone2,1", "iPhone 3GS", + "iPhone3,1", "iPhone 4 (GSM)", + "iPhone3,2", "iPhone 4 (GSM)", + "iPhone3,3", "iPhone 4 (CDMA)", + "iPhone4,1", "iPhone 4S", + "iPhone5,1", "iPhone 5 (A1428)", + "iPhone5,2", "iPhone 5 (A1429)", + "iPhone5,3", "iPhone 5c (A1456/A1532)", + "iPhone5,4", "iPhone 5c (A1507/A1516/1526/A1529)", + "iPhone6,1", "iPhone 5s (A1433/A1533)", + "iPhone6,2", "iPhone 5s (A1457/A1518/A1528/A1530)", + "iPhone7,1", "iPhone 6 Plus", + "iPhone7,2", "iPhone 6", + "iPhone8,1", "iPhone 6S", + "iPhone8,2", "iPhone 6S Plus", + "iPhone8,4", "iPhone SE", + "iPhone9,1", "iPhone 7 (CDMA)", + "iPhone9,2", "iPhone 7 Plus (CDMA)", + "iPhone9,3", "iPhone 7 (GSM)", + "iPhone9,4", "iPhone 7 Plus (GSM)", + "iPhone10,1", "iPhone 8 (CDMA)", + "iPhone10,2", "iPhone 8 Plus (CDMA)", + "iPhone10,3", "iPhone X (CDMA)", + "iPhone10,4", "iPhone 8 (GSM)", + "iPhone10,5", "iPhone 8 Plus (GSM)", + "iPhone10,6", "iPhone X (GSM)", + "iPhone11,2", "iPhone XS", + "iPhone11,4", "iPhone XS Max", + "iPhone11,6", "iPhone XS Max China", + "iPhone11,8", "iPhone XR", + "iPhone12,1", "iPhone 11", + "iPhone12,3", "iPhone 11 Pro", + "iPhone12,5", "iPhone 11 Pro Max", + "iPhone12,8", "iPhone SE 2nd Gen", + "iPad1,1", "iPad (A1219/A1337)", + "iPad2,1", "iPad 2 (A1395)", + "iPad2,2", "iPad 2 (A1396)", + "iPad2,3", "iPad 2 (A1397)", + "iPad2,4", "iPad 2 (A1395)", + "iPad2,5", "iPad Mini (A1432)", + "iPad2,6", "iPad Mini (A1454)", + "iPad2,7", "iPad Mini (A1455)", + "iPad3,1", "iPad 3 (A1416)", + "iPad3,2", "iPad 3 (A1403)", + "iPad3,3", "iPad 3 (A1430)", + "iPad3,4", "iPad 4 (A1458)", + "iPad3,5", "iPad 4 (A1459)", + "iPad3,6", "iPad 4 (A1460)", + "iPad4,1", "iPad Air (A1474)", + "iPad4,2", "iPad Air (A1475)", + "iPad4,3", "iPad Air (A1476)", + "iPad4,4", "iPad Mini 2 (A1489)", + "iPad4,5", "iPad Mini 2 (A1490)", + "iPad4,6", "iPad Mini 2 (A1491)", + "iPad4,7", "iPad Mini 3 (A1599)", + "iPad4,8", "iPad Mini 3 (A1600)", + "iPad4,9", "iPad Mini 3 (A1601)", + "iPad5,1", "iPad Mini 4 (A1538)", + "iPad5,2", "iPad Mini 4 (A1550)", + "iPad5,3", "iPad Air 2 (A1566)", + "iPad5,4", "iPad Air 2 (A1567)", + "iPad6,3", "iPad Pro 9.7\" (A1673)", + "iPad6,4", "iPad Pro 9.7\" (A1674)", + "iPad6,5", "iPad Pro 9.7\" (A1675)", + "iPad6,7", "iPad Pro 12.9\" (A1584)", + "iPad6,8", "iPad Pro 12.9\" (A1652)", + "iPad6,11", "iPad 5th gen (A1822)", + "iPad6,12", "iPad 5th gen (A1823)", + "iPad7,1", "iPad Pro 12.9\" 2nd gen (A1670)", + "iPad7,2", "iPad Pro 12.9\" 2nd gen (A1671/A1821)", + "iPad7,3", "iPad Pro 10.5\" (A1701)", + "iPad7,4", "iPad Pro 10.5\" (A1709)", + "iPad7,5", "iPad 6th gen (A1893)", + "iPad7,6", "iPad 6th gen (A1954)", + "iPad7,11", "iPad 7th gen 10.2\" (Wifi)", + "iPad7,12", "iPad 7th gen 10.2\" (Wifi+Cellular)", + "iPad8,1", "iPad Pro 11\" (A1980)", + "iPad8,2", "iPad Pro 11\" (A1980)", + "iPad8,3", "iPad Pro 11\" (A1934/A1979/A2013)", + "iPad8,4", "iPad Pro 11\" (A1934/A1979/A2013)", + "iPad8,5", "iPad Pro 12.9\" 3rd gen (A1876)", + "iPad8,6", "iPad Pro 12.9\" 3rd gen (A1876)", + "iPad8,7", "iPad Pro 12.9\" 3rd gen (A1895/A1983/A2014)", + "iPad8,8", "iPad Pro 12.9\" 3rd gen (A1895/A1983/A2014)", + "iPad8,9", "iPad Pro 11\" 2nd gen (Wifi)", + "iPad8,10", "iPad Pro 11\" 2nd gen (Wifi+Cellular)", + "iPad8,11", "iPad Pro 12.9\" 4th gen (Wifi)", + "iPad8,12", "iPad Pro 12.9\" 4th gen (Wifi+Cellular)", + "iPad11,1", "iPad Mini 5th gen (A2133)", + "iPad11,2", "iPad Mini 5th gen (A2124/A2125/A2126)", + "iPad11,3", "iPad Air 3rd gen (A2152)", + "iPad11,4", "iPad Air 3rd gen (A2123/A2153/A2154)", + "iPod1,1", "iPod Touch", + "iPod2,1", "iPod Touch 2nd gen", + "iPod3,1", "iPod Touch 3rd gen", + "iPod4,1", "iPod Touch 4th gen", + "iPod5,1", "iPod Touch 5th gen", + "iPod7,1", "iPod Touch 6th gen", + "iPod9,1", "iPod Touch 7th gen", + nullptr + }; + + auto ptr = DeviceTable; + while( *ptr ) + { + if( strcmp( ptr[0], id ) == 0 ) return ptr[1]; + ptr += 2; + } + return id; +} + +#endif + +} diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.cpp b/Source/ThirdParty/tracy/client/TracyCallstack.cpp new file mode 100644 index 000000000..10698cb19 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyCallstack.cpp @@ -0,0 +1,768 @@ +#include +#include +#include +#include "TracyCallstack.hpp" +#include "TracyFastVector.hpp" +#include "../common/TracyAlloc.hpp" + +#ifdef TRACY_HAS_CALLSTACK + +#if TRACY_HAS_CALLSTACK == 1 +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +# include +# ifdef _MSC_VER +# pragma warning( push ) +# pragma warning( disable : 4091 ) +# endif +# include +# ifdef _MSC_VER +# pragma warning( pop ) +# endif +#elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 +# include "../libbacktrace/backtrace.hpp" +# include +# include +#elif TRACY_HAS_CALLSTACK == 5 +# include +# include +#endif + +#ifdef TRACY_DBGHELP_LOCK +# include "TracyProfiler.hpp" + +# define DBGHELP_INIT TracyConcat( TRACY_DBGHELP_LOCK, Init() ) +# define DBGHELP_LOCK TracyConcat( TRACY_DBGHELP_LOCK, Lock() ); +# define DBGHELP_UNLOCK TracyConcat( TRACY_DBGHELP_LOCK, Unlock() ); + +extern "C" +{ + void DBGHELP_INIT; + void DBGHELP_LOCK; + void DBGHELP_UNLOCK; +}; +#endif + +namespace tracy +{ + +static inline char* CopyString( const char* src, size_t sz ) +{ + assert( strlen( src ) == sz ); + auto dst = (char*)tracy_malloc( sz + 1 ); + memcpy( dst, src, sz ); + dst[sz] = '\0'; + return dst; +} + +static inline char* CopyString( const char* src ) +{ + const auto sz = strlen( src ); + auto dst = (char*)tracy_malloc( sz + 1 ); + memcpy( dst, src, sz ); + dst[sz] = '\0'; + return dst; +} + + +#if TRACY_HAS_CALLSTACK == 1 + +enum { MaxCbTrace = 16 }; +enum { MaxNameSize = 8*1024 }; + +int cb_num; +CallstackEntry cb_data[MaxCbTrace]; + +extern "C" +{ + typedef unsigned long (__stdcall *t_RtlWalkFrameChain)( void**, unsigned long, unsigned long ); + t_RtlWalkFrameChain RtlWalkFrameChain = 0; +} + +#if defined __MINGW32__ && API_VERSION_NUMBER < 12 +extern "C" { +// Actual required API_VERSION_NUMBER is unknown because it is undocumented. These functions are not present in at least v11. +DWORD IMAGEAPI SymAddrIncludeInlineTrace(HANDLE hProcess, DWORD64 Address); +BOOL IMAGEAPI SymQueryInlineTrace(HANDLE hProcess, DWORD64 StartAddress, DWORD StartContext, DWORD64 StartRetAddress, + DWORD64 CurAddress, LPDWORD CurContext, LPDWORD CurFrameIndex); +BOOL IMAGEAPI SymFromInlineContext(HANDLE hProcess, DWORD64 Address, ULONG InlineContext, PDWORD64 Displacement, + PSYMBOL_INFO Symbol); +BOOL IMAGEAPI SymGetLineFromInlineContext(HANDLE hProcess, DWORD64 qwAddr, ULONG InlineContext, + DWORD64 qwModuleBaseAddress, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64); +}; +#endif + +#ifndef __CYGWIN__ +struct ModuleCache +{ + uint64_t start; + uint64_t end; + char* name; +}; + +static FastVector* s_modCache; +#endif + +void InitCallstack() +{ + RtlWalkFrameChain = (t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" ); + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_INIT; + DBGHELP_LOCK; +#endif + + //SymInitialize( GetCurrentProcess(), "C:\\Flax\\FlaxEngine\\Binaries\\Editor\\Win64\\Debug;C:\\Flax\\FlaxEngine\\Cache\\Projects", true ); + SymInitialize( GetCurrentProcess(), nullptr, true ); + SymSetOptions( SYMOPT_LOAD_LINES ); + +#ifndef __CYGWIN__ + HMODULE mod[1024]; + DWORD needed; + HANDLE proc = GetCurrentProcess(); + + s_modCache = (FastVector*)tracy_malloc( sizeof( FastVector ) ); + new(s_modCache) FastVector( 512 ); + + if( EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) + { + const auto sz = needed / sizeof( HMODULE ); + for( size_t i=0; i 0 ) + { + auto ptr = name + res; + while( ptr > name && *ptr != '\\' && *ptr != '/' ) ptr--; + if( ptr > name ) ptr++; + const auto namelen = name + res - ptr; + auto cache = s_modCache->push_next(); + cache->start = base; + cache->end = base + info.SizeOfImage; + cache->name = (char*)tracy_malloc( namelen+3 ); + cache->name[0] = '['; + memcpy( cache->name+1, ptr, namelen ); + cache->name[namelen+1] = ']'; + cache->name[namelen+2] = '\0'; + } + } + } + } +#endif + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif +} + +TRACY_API uintptr_t* CallTrace( int depth ) +{ + auto trace = (uintptr_t*)tracy_malloc( ( 1 + depth ) * sizeof( uintptr_t ) ); + const auto num = RtlWalkFrameChain( (void**)( trace + 1 ), depth, 0 ); + *trace = num; + return trace; +} + +const char* DecodeCallstackPtrFast( uint64_t ptr ) +{ + static char ret[MaxNameSize]; + const auto proc = GetCurrentProcess(); + + char buf[sizeof( SYMBOL_INFO ) + MaxNameSize]; + auto si = (SYMBOL_INFO*)buf; + si->SizeOfStruct = sizeof( SYMBOL_INFO ); + si->MaxNameLen = MaxNameSize; + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif + if( SymFromAddr( proc, ptr, nullptr, si ) == 0 ) + { + *ret = '\0'; + } + else + { + memcpy( ret, si->Name, si->NameLen ); + ret[si->NameLen] = '\0'; + } +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + return ret; +} + +static const char* GetModuleName( uint64_t addr ) +{ + if( ( addr & 0x8000000000000000 ) != 0 ) return "[kernel]"; + +#ifndef __CYGWIN__ + for( auto& v : *s_modCache ) + { + if( addr >= v.start && addr < v.end ) + { + return v.name; + } + } + + HMODULE mod[1024]; + DWORD needed; + HANDLE proc = GetCurrentProcess(); + + if( EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) + { + const auto sz = needed / sizeof( HMODULE ); + for( size_t i=0; i= base && addr < base + info.SizeOfImage ) + { + char name[1024]; + const auto res = GetModuleFileNameA( mod[i], name, 1021 ); + if( res > 0 ) + { + auto ptr = name + res; + while( ptr > name && *ptr != '\\' && *ptr != '/' ) ptr--; + if( ptr > name ) ptr++; + const auto namelen = name + res - ptr; + auto cache = s_modCache->push_next(); + cache->start = base; + cache->end = base + info.SizeOfImage; + cache->name = (char*)tracy_malloc( namelen+3 ); + cache->name[0] = '['; + memcpy( cache->name+1, ptr, namelen ); + cache->name[namelen+1] = ']'; + cache->name[namelen+2] = '\0'; + return cache->name; + } + } + } + } + } +#endif + + return "[unknown]"; +} + +CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) +{ + CallstackSymbolData sym; + IMAGEHLP_LINE64 line; + DWORD displacement = 0; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif + const auto res = SymGetLineFromAddr64( GetCurrentProcess(), ptr, &displacement, &line ); +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + if( res == 0 ) + { + sym.file = "[unknown]"; + sym.line = 0; + } + else + { + sym.file = line.FileName; + sym.line = line.LineNumber; + } + sym.needFree = false; + return sym; +} + +CallstackSymbolData DecodeCodeAddress( uint64_t ptr ) +{ + CallstackSymbolData sym; + const auto proc = GetCurrentProcess(); + bool done = false; + + IMAGEHLP_LINE64 line; + DWORD displacement = 0; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif +#ifndef __CYGWIN__ + DWORD inlineNum = SymAddrIncludeInlineTrace( proc, ptr ); + DWORD ctx = 0; + DWORD idx; + BOOL doInline = FALSE; + if( inlineNum != 0 ) doInline = SymQueryInlineTrace( proc, ptr, 0, ptr, ptr, &ctx, &idx ); + if( doInline ) + { + if( SymGetLineFromInlineContext( proc, ptr, ctx, 0, &displacement, &line ) != 0 ) + { + sym.file = line.FileName; + sym.line = line.LineNumber; + done = true; + } + } +#endif + if( !done ) + { + if( SymGetLineFromAddr64( proc, ptr, &displacement, &line ) == 0 ) + { + sym.file = "[unknown]"; + sym.line = 0; + } + else + { + sym.file = line.FileName; + sym.line = line.LineNumber; + } + } +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + sym.needFree = false; + return sym; +} + +CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) +{ + int write; + const auto proc = GetCurrentProcess(); +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif +#ifndef __CYGWIN__ + DWORD inlineNum = SymAddrIncludeInlineTrace( proc, ptr ); + if( inlineNum > MaxCbTrace - 1 ) inlineNum = MaxCbTrace - 1; + DWORD ctx = 0; + DWORD idx; + BOOL doInline = FALSE; + if( inlineNum != 0 ) doInline = SymQueryInlineTrace( proc, ptr, 0, ptr, ptr, &ctx, &idx ); + if( doInline ) + { + write = inlineNum; + cb_num = 1 + inlineNum; + } + else +#endif + { + write = 0; + cb_num = 1; + } + + char buf[sizeof( SYMBOL_INFO ) + MaxNameSize]; + auto si = (SYMBOL_INFO*)buf; + si->SizeOfStruct = sizeof( SYMBOL_INFO ); + si->MaxNameLen = MaxNameSize; + + const auto moduleName = GetModuleName( ptr ); + const auto symValid = SymFromAddr( proc, ptr, nullptr, si ) != 0; + + IMAGEHLP_LINE64 line; + DWORD displacement = 0; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + { + const char* filename; + if( SymGetLineFromAddr64( proc, ptr, &displacement, &line ) == 0 ) + { + filename = "[unknown]"; + cb_data[write].line = 0; + } + else + { + filename = line.FileName; + cb_data[write].line = line.LineNumber; + } + + cb_data[write].name = symValid ? CopyString( si->Name, si->NameLen ) : CopyString( moduleName ); + cb_data[write].file = CopyString( filename ); + if( symValid ) + { + cb_data[write].symLen = si->Size; + cb_data[write].symAddr = si->Address; + } + else + { + cb_data[write].symLen = 0; + cb_data[write].symAddr = 0; + } + } + +#ifndef __CYGWIN__ + if( doInline ) + { + for( DWORD i=0; iName, si->NameLen ) : CopyString( moduleName ); + cb.file = CopyString( filename ); + if( symInlineValid ) + { + cb.symLen = si->Size; + cb.symAddr = si->Address; + } + else + { + cb.symLen = 0; + cb.symAddr = 0; + } + + ctx++; + } + } +#endif +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + + return { cb_data, uint8_t( cb_num ), moduleName }; +} + +#elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 + +enum { MaxCbTrace = 16 }; + +struct backtrace_state* cb_bts; +int cb_num; +CallstackEntry cb_data[MaxCbTrace]; +int cb_fixup; + +void InitCallstack() +{ + cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr ); +} + +static int FastCallstackDataCb( void* data, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function ) +{ + if( function ) + { + strcpy( (char*)data, function ); + } + else + { + const char* symname = nullptr; + auto vptr = (void*)pc; + Dl_info dlinfo; + if( dladdr( vptr, &dlinfo ) ) + { + symname = dlinfo.dli_sname; + } + if( symname ) + { + strcpy( (char*)data, symname ); + } + else + { + *(char*)data = '\0'; + } + } + return 1; +} + +static void FastCallstackErrorCb( void* data, const char* /*msg*/, int /*errnum*/ ) +{ + *(char*)data = '\0'; +} + +const char* DecodeCallstackPtrFast( uint64_t ptr ) +{ + static char ret[1024]; + backtrace_pcinfo( cb_bts, ptr, FastCallstackDataCb, FastCallstackErrorCb, ret ); + return ret; +} + +static int SymbolAddressDataCb( void* data, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function ) +{ + auto& sym = *(CallstackSymbolData*)data; + if( !fn ) + { + sym.file = "[unknown]"; + sym.line = 0; + sym.needFree = false; + } + else + { + sym.file = CopyString( fn ); + sym.line = lineno; + sym.needFree = true; + } + + return 1; +} + +static void SymbolAddressErrorCb( void* data, const char* /*msg*/, int /*errnum*/ ) +{ + auto& sym = *(CallstackSymbolData*)data; + sym.file = "[unknown]"; + sym.line = 0; + sym.needFree = false; +} + +CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) +{ + CallstackSymbolData sym; + backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym ); + return sym; +} + +CallstackSymbolData DecodeCodeAddress( uint64_t ptr ) +{ + return DecodeSymbolAddress( ptr ); +} + +static int CallstackDataCb( void* /*data*/, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function ) +{ + enum { DemangleBufLen = 64*1024 }; + char demangled[DemangleBufLen]; + + cb_data[cb_num].symLen = 0; + cb_data[cb_num].symAddr = (uint64_t)lowaddr; + + if( !fn && !function ) + { + const char* symname = nullptr; + auto vptr = (void*)pc; + ptrdiff_t symoff = 0; + + Dl_info dlinfo; + if( dladdr( vptr, &dlinfo ) ) + { + symname = dlinfo.dli_sname; + symoff = (char*)pc - (char*)dlinfo.dli_saddr; + + if( symname && symname[0] == '_' ) + { + size_t len = DemangleBufLen; + int status; + abi::__cxa_demangle( symname, demangled, &len, &status ); + if( status == 0 ) + { + symname = demangled; + } + } + } + + if( !symname ) symname = "[unknown]"; + + if( symoff == 0 ) + { + cb_data[cb_num].name = CopyString( symname ); + } + else + { + char buf[32]; + const auto offlen = sprintf( buf, " + %td", symoff ); + const auto namelen = strlen( symname ); + auto name = (char*)tracy_malloc( namelen + offlen + 1 ); + memcpy( name, symname, namelen ); + memcpy( name + namelen, buf, offlen ); + name[namelen + offlen] = '\0'; + cb_data[cb_num].name = name; + } + + cb_data[cb_num].file = CopyString( "[unknown]" ); + cb_data[cb_num].line = 0; + } + else + { + if( !fn ) fn = "[unknown]"; + if( !function ) + { + function = "[unknown]"; + } + else + { + if( function[0] == '_' ) + { + size_t len = DemangleBufLen; + int status; + abi::__cxa_demangle( function, demangled, &len, &status ); + if( status == 0 ) + { + function = demangled; + } + } + } + + cb_data[cb_num].name = CopyString( function ); + cb_data[cb_num].file = CopyString( fn ); + cb_data[cb_num].line = lineno; + } + + if( ++cb_num >= MaxCbTrace ) + { + return 1; + } + else + { + return 0; + } +} + +static void CallstackErrorCb( void* /*data*/, const char* /*msg*/, int /*errnum*/ ) +{ + for( int i=0; i 0 ); + + backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr ); + + const char* symloc = nullptr; + Dl_info dlinfo; + if( dladdr( (void*)ptr, &dlinfo ) ) symloc = dlinfo.dli_fname; + + return { cb_data, uint8_t( cb_num ), symloc ? symloc : "[unknown]" }; +} + +#elif TRACY_HAS_CALLSTACK == 5 + +void InitCallstack() +{ +} + +const char* DecodeCallstackPtrFast( uint64_t ptr ) +{ + static char ret[1024]; + auto vptr = (void*)ptr; + const char* symname = nullptr; + Dl_info dlinfo; + if( dladdr( vptr, &dlinfo ) && dlinfo.dli_sname ) + { + symname = dlinfo.dli_sname; + } + if( symname ) + { + strcpy( ret, symname ); + } + else + { + *ret = '\0'; + } + return ret; +} + +CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) +{ + const char* symloc = nullptr; + Dl_info dlinfo; + if( dladdr( (void*)ptr, &dlinfo ) ) symloc = dlinfo.dli_fname; + if( !symloc ) symloc = "[unknown]"; + return CallstackSymbolData { symloc, 0, false }; +} + +CallstackSymbolData DecodeCodeAddress( uint64_t ptr ) +{ + return DecodeSymbolAddress( ptr ); +} + +CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) +{ + static CallstackEntry cb; + cb.line = 0; + + char* demangled = nullptr; + const char* symname = nullptr; + const char* symloc = nullptr; + auto vptr = (void*)ptr; + ptrdiff_t symoff = 0; + void* symaddr = nullptr; + + Dl_info dlinfo; + if( dladdr( vptr, &dlinfo ) ) + { + symloc = dlinfo.dli_fname; + symname = dlinfo.dli_sname; + symoff = (char*)ptr - (char*)dlinfo.dli_saddr; + symaddr = dlinfo.dli_saddr; + + if( symname && symname[0] == '_' ) + { + size_t len = 0; + int status; + demangled = abi::__cxa_demangle( symname, nullptr, &len, &status ); + if( status == 0 ) + { + symname = demangled; + } + } + } + + if( !symname ) symname = "[unknown]"; + if( !symloc ) symloc = "[unknown]"; + + if( symoff == 0 ) + { + cb.name = CopyString( symname ); + } + else + { + char buf[32]; + const auto offlen = sprintf( buf, " + %td", symoff ); + const auto namelen = strlen( symname ); + auto name = (char*)tracy_malloc( namelen + offlen + 1 ); + memcpy( name, symname, namelen ); + memcpy( name + namelen, buf, offlen ); + name[namelen + offlen] = '\0'; + cb.name = name; + } + + cb.file = CopyString( "[unknown]" ); + cb.symLen = 0; + cb.symAddr = (uint64_t)symaddr; + + if( demangled ) free( demangled ); + + return { &cb, 1, symloc }; +} + +#endif + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.h b/Source/ThirdParty/tracy/client/TracyCallstack.h new file mode 100644 index 000000000..87d8ce721 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyCallstack.h @@ -0,0 +1,28 @@ +#ifndef __TRACYCALLSTACK_H__ +#define __TRACYCALLSTACK_H__ + +#if !defined _WIN32 && !defined __CYGWIN__ +# include +#endif + +#if defined _WIN32 || defined __CYGWIN__ +# define TRACY_HAS_CALLSTACK 1 +#elif defined __ANDROID__ +# if !defined __arm__ || __ANDROID_API__ >= 21 +# define TRACY_HAS_CALLSTACK 2 +# else +# define TRACY_HAS_CALLSTACK 5 +# endif +#elif defined __linux +# if defined _GNU_SOURCE && defined __GLIBC__ +# define TRACY_HAS_CALLSTACK 3 +# else +# define TRACY_HAS_CALLSTACK 2 +# endif +#elif defined __APPLE__ +# define TRACY_HAS_CALLSTACK 4 +#elif defined BSD +# define TRACY_HAS_CALLSTACK 6 +#endif + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.hpp b/Source/ThirdParty/tracy/client/TracyCallstack.hpp new file mode 100644 index 000000000..923eccc04 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyCallstack.hpp @@ -0,0 +1,114 @@ +#ifndef __TRACYCALLSTACK_HPP__ +#define __TRACYCALLSTACK_HPP__ + +#include "TracyCallstack.h" + +#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 5 +# include +#elif TRACY_HAS_CALLSTACK >= 3 +# include +#endif + + +#ifdef TRACY_HAS_CALLSTACK + +#include +#include + +#include "../common/TracyAlloc.hpp" + +namespace tracy +{ + +struct CallstackSymbolData +{ + const char* file; + uint32_t line; + bool needFree; +}; + +struct CallstackEntry +{ + const char* name; + const char* file; + uint32_t line; + uint32_t symLen; + uint64_t symAddr; +}; + +struct CallstackEntryData +{ + const CallstackEntry* data; + uint8_t size; + const char* imageName; +}; + +CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ); +CallstackSymbolData DecodeCodeAddress( uint64_t ptr ); +const char* DecodeCallstackPtrFast( uint64_t ptr ); +CallstackEntryData DecodeCallstackPtr( uint64_t ptr ); +void InitCallstack(); + +#if TRACY_HAS_CALLSTACK == 1 + +TRACY_API uintptr_t* CallTrace( int depth ); + +static tracy_force_inline void* Callstack( int depth ) +{ + assert( depth >= 1 && depth < 63 ); + return CallTrace( depth ); +} + +#elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 5 + +struct BacktraceState +{ + void** current; + void** end; +}; + +static _Unwind_Reason_Code tracy_unwind_callback( struct _Unwind_Context* ctx, void* arg ) +{ + auto state = (BacktraceState*)arg; + uintptr_t pc = _Unwind_GetIP( ctx ); + if( pc ) + { + if( state->current == state->end ) return _URC_END_OF_STACK; + *state->current++ = (void*)pc; + } + return _URC_NO_REASON; +} + +static tracy_force_inline void* Callstack( int depth ) +{ + assert( depth >= 1 && depth < 63 ); + + auto trace = (uintptr_t*)tracy_malloc( ( 1 + depth ) * sizeof( uintptr_t ) ); + BacktraceState state = { (void**)(trace+1), (void**)(trace+1+depth) }; + _Unwind_Backtrace( tracy_unwind_callback, &state ); + + *trace = (uintptr_t*)state.current - trace + 1; + + return trace; +} + +#elif TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 + +static tracy_force_inline void* Callstack( int depth ) +{ + assert( depth >= 1 ); + + auto trace = (uintptr_t*)tracy_malloc( ( 1 + (size_t)depth ) * sizeof( uintptr_t ) ); + const auto num = (size_t)backtrace( (void**)(trace+1), depth ); + *trace = num; + + return trace; +} + +#endif + +} + +#endif + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyFastVector.hpp b/Source/ThirdParty/tracy/client/TracyFastVector.hpp new file mode 100644 index 000000000..fc4108016 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyFastVector.hpp @@ -0,0 +1,117 @@ +#ifndef __TRACYFASTVECTOR_HPP__ +#define __TRACYFASTVECTOR_HPP__ + +#include +#include + +#include "../common/TracyAlloc.hpp" + +namespace tracy +{ + +template +class FastVector +{ +public: + using iterator = T*; + using const_iterator = const T*; + + FastVector( size_t capacity ) + : m_ptr( (T*)tracy_malloc( sizeof( T ) * capacity ) ) + , m_write( m_ptr ) + , m_end( m_ptr + capacity ) + { + assert( capacity != 0 ); + } + + FastVector( const FastVector& ) = delete; + FastVector( FastVector&& ) = delete; + + ~FastVector() + { + tracy_free( m_ptr ); + } + + FastVector& operator=( const FastVector& ) = delete; + FastVector& operator=( FastVector&& ) = delete; + + bool empty() const { return m_ptr == m_write; } + size_t size() const { return m_write - m_ptr; } + + T* data() { return m_ptr; } + const T* data() const { return m_ptr; }; + + T* begin() { return m_ptr; } + const T* begin() const { return m_ptr; } + T* end() { return m_write; } + const T* end() const { return m_write; } + + T& front() { assert( !empty() ); return m_ptr[0]; } + const T& front() const { assert( !empty() ); return m_ptr[0]; } + + T& back() { assert( !empty() ); return m_write[-1]; } + const T& back() const { assert( !empty() ); return m_write[-1]; } + + T& operator[]( size_t idx ) { return m_ptr[idx]; } + const T& operator[]( size_t idx ) const { return m_ptr[idx]; } + + T* push_next() + { + if( m_write == m_end ) AllocMore(); + return m_write++; + } + + T* prepare_next() + { + if( m_write == m_end ) AllocMore(); + return m_write; + } + + void commit_next() + { + m_write++; + } + + void clear() + { + m_write = m_ptr; + } + + void swap( FastVector& vec ) + { + const auto ptr1 = m_ptr; + const auto ptr2 = vec.m_ptr; + const auto write1 = m_write; + const auto write2 = vec.m_write; + const auto end1 = m_end; + const auto end2 = vec.m_end; + + m_ptr = ptr2; + vec.m_ptr = ptr1; + m_write = write2; + vec.m_write = write1; + m_end = end2; + vec.m_end = end1; + } + +private: + tracy_no_inline void AllocMore() + { + const auto cap = size_t( m_end - m_ptr ) * 2; + const auto size = size_t( m_write - m_ptr ); + T* ptr = (T*)tracy_malloc( sizeof( T ) * cap ); + memcpy( ptr, m_ptr, size * sizeof( T ) ); + tracy_free( m_ptr ); + m_ptr = ptr; + m_write = m_ptr + size; + m_end = m_ptr + cap; + } + + T* m_ptr; + T* m_write; + T* m_end; +}; + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyLock.hpp b/Source/ThirdParty/tracy/client/TracyLock.hpp new file mode 100644 index 000000000..e513cdc5d --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyLock.hpp @@ -0,0 +1,548 @@ +#ifndef __TRACYLOCK_HPP__ +#define __TRACYLOCK_HPP__ + +#include +#include + +#include "../common/TracySystem.hpp" +#include "../common/TracyAlign.hpp" +#include "TracyProfiler.hpp" + +namespace tracy +{ + +class LockableCtx +{ +public: + tracy_force_inline LockableCtx( const SourceLocationData* srcloc ) + : m_id( GetLockCounter().fetch_add( 1, std::memory_order_relaxed ) ) +#ifdef TRACY_ON_DEMAND + , m_lockCount( 0 ) + , m_active( false ) +#endif + { + assert( m_id != std::numeric_limits::max() ); + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockAnnounce ); + MemWrite( &item->lockAnnounce.id, m_id ); + MemWrite( &item->lockAnnounce.time, Profiler::GetTime() ); + MemWrite( &item->lockAnnounce.lckloc, (uint64_t)srcloc ); + MemWrite( &item->lockAnnounce.type, LockType::Lockable ); +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + Profiler::QueueSerialFinish(); + } + + LockableCtx( const LockableCtx& ) = delete; + LockableCtx& operator=( const LockableCtx& ) = delete; + + tracy_force_inline ~LockableCtx() + { + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockTerminate ); + MemWrite( &item->lockTerminate.id, m_id ); + MemWrite( &item->lockTerminate.time, Profiler::GetTime() ); +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + Profiler::QueueSerialFinish(); + } + + tracy_force_inline bool BeforeLock() + { +#ifdef TRACY_ON_DEMAND + bool queue = false; + const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = GetProfiler().IsConnected(); + if( active != connected ) m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return false; +#endif + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockWait ); + MemWrite( &item->lockWait.thread, GetThreadHandle() ); + MemWrite( &item->lockWait.id, m_id ); + MemWrite( &item->lockWait.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + return true; + } + + tracy_force_inline void AfterLock() + { + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockObtain ); + MemWrite( &item->lockObtain.thread, GetThreadHandle() ); + MemWrite( &item->lockObtain.id, m_id ); + MemWrite( &item->lockObtain.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + + tracy_force_inline void AfterUnlock() + { +#ifdef TRACY_ON_DEMAND + m_lockCount.fetch_sub( 1, std::memory_order_relaxed ); + if( !m_active.load( std::memory_order_relaxed ) ) return; + if( !GetProfiler().IsConnected() ) + { + m_active.store( false, std::memory_order_relaxed ); + return; + } +#endif + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockRelease ); + MemWrite( &item->lockRelease.thread, GetThreadHandle() ); + MemWrite( &item->lockRelease.id, m_id ); + MemWrite( &item->lockRelease.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + + tracy_force_inline void AfterTryLock( bool acquired ) + { +#ifdef TRACY_ON_DEMAND + if( !acquired ) return; + + bool queue = false; + const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = GetProfiler().IsConnected(); + if( active != connected ) m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return; +#endif + + if( acquired ) + { + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockObtain ); + MemWrite( &item->lockObtain.thread, GetThreadHandle() ); + MemWrite( &item->lockObtain.id, m_id ); + MemWrite( &item->lockObtain.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + } + + tracy_force_inline void Mark( const SourceLocationData* srcloc ) + { +#ifdef TRACY_ON_DEMAND + const auto active = m_active.load( std::memory_order_relaxed ); + if( !active ) return; + const auto connected = GetProfiler().IsConnected(); + if( !connected ) + { + if( active ) m_active.store( false, std::memory_order_relaxed ); + return; + } +#endif + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockMark ); + MemWrite( &item->lockMark.thread, GetThreadHandle() ); + MemWrite( &item->lockMark.id, m_id ); + MemWrite( &item->lockMark.srcloc, (uint64_t)srcloc ); + Profiler::QueueSerialFinish(); + } + + tracy_force_inline void CustomName( const char* name, size_t size ) + { + assert( size < std::numeric_limits::max() ); + auto ptr = (char*)tracy_malloc( size ); + memcpy( ptr, name, size ); + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockName ); + MemWrite( &item->lockNameFat.id, m_id ); + MemWrite( &item->lockNameFat.name, (uint64_t)ptr ); + MemWrite( &item->lockNameFat.size, (uint16_t)size ); +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + Profiler::QueueSerialFinish(); + } + +private: + uint32_t m_id; + +#ifdef TRACY_ON_DEMAND + std::atomic m_lockCount; + std::atomic m_active; +#endif +}; + +template +class Lockable +{ +public: + tracy_force_inline Lockable( const SourceLocationData* srcloc ) + : m_ctx( srcloc ) + { + } + + Lockable( const Lockable& ) = delete; + Lockable& operator=( const Lockable& ) = delete; + + tracy_force_inline void lock() + { + const auto runAfter = m_ctx.BeforeLock(); + m_lockable.lock(); + if( runAfter ) m_ctx.AfterLock(); + } + + tracy_force_inline void unlock() + { + m_lockable.unlock(); + m_ctx.AfterUnlock(); + } + + tracy_force_inline bool try_lock() + { + const auto acquired = m_lockable.try_lock(); + m_ctx.AfterTryLock( acquired ); + return acquired; + } + + tracy_force_inline void Mark( const SourceLocationData* srcloc ) + { + m_ctx.Mark( srcloc ); + } + + tracy_force_inline void CustomName( const char* name, size_t size ) + { + m_ctx.CustomName( name, size ); + } + +private: + T m_lockable; + LockableCtx m_ctx; +}; + + +class SharedLockableCtx +{ +public: + tracy_force_inline SharedLockableCtx( const SourceLocationData* srcloc ) + : m_id( GetLockCounter().fetch_add( 1, std::memory_order_relaxed ) ) +#ifdef TRACY_ON_DEMAND + , m_lockCount( 0 ) + , m_active( false ) +#endif + { + assert( m_id != std::numeric_limits::max() ); + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockAnnounce ); + MemWrite( &item->lockAnnounce.id, m_id ); + MemWrite( &item->lockAnnounce.time, Profiler::GetTime() ); + MemWrite( &item->lockAnnounce.lckloc, (uint64_t)srcloc ); + MemWrite( &item->lockAnnounce.type, LockType::SharedLockable ); +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + Profiler::QueueSerialFinish(); + } + + SharedLockableCtx( const SharedLockableCtx& ) = delete; + SharedLockableCtx& operator=( const SharedLockableCtx& ) = delete; + + tracy_force_inline ~SharedLockableCtx() + { + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockTerminate ); + MemWrite( &item->lockTerminate.id, m_id ); + MemWrite( &item->lockTerminate.time, Profiler::GetTime() ); +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + Profiler::QueueSerialFinish(); + } + + tracy_force_inline bool BeforeLock() + { +#ifdef TRACY_ON_DEMAND + bool queue = false; + const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = GetProfiler().IsConnected(); + if( active != connected ) m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return false; +#endif + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockWait ); + MemWrite( &item->lockWait.thread, GetThreadHandle() ); + MemWrite( &item->lockWait.id, m_id ); + MemWrite( &item->lockWait.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + return true; + } + + tracy_force_inline void AfterLock() + { + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockObtain ); + MemWrite( &item->lockObtain.thread, GetThreadHandle() ); + MemWrite( &item->lockObtain.id, m_id ); + MemWrite( &item->lockObtain.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + + tracy_force_inline void AfterUnlock() + { +#ifdef TRACY_ON_DEMAND + m_lockCount.fetch_sub( 1, std::memory_order_relaxed ); + if( !m_active.load( std::memory_order_relaxed ) ) return; + if( !GetProfiler().IsConnected() ) + { + m_active.store( false, std::memory_order_relaxed ); + return; + } +#endif + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockRelease ); + MemWrite( &item->lockRelease.thread, GetThreadHandle() ); + MemWrite( &item->lockRelease.id, m_id ); + MemWrite( &item->lockRelease.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + + tracy_force_inline void AfterTryLock( bool acquired ) + { +#ifdef TRACY_ON_DEMAND + if( !acquired ) return; + + bool queue = false; + const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = GetProfiler().IsConnected(); + if( active != connected ) m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return; +#endif + + if( acquired ) + { + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockObtain ); + MemWrite( &item->lockObtain.thread, GetThreadHandle() ); + MemWrite( &item->lockObtain.id, m_id ); + MemWrite( &item->lockObtain.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + } + + tracy_force_inline bool BeforeLockShared() + { +#ifdef TRACY_ON_DEMAND + bool queue = false; + const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = GetProfiler().IsConnected(); + if( active != connected ) m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return false; +#endif + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockSharedWait ); + MemWrite( &item->lockWait.thread, GetThreadHandle() ); + MemWrite( &item->lockWait.id, m_id ); + MemWrite( &item->lockWait.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + return true; + } + + tracy_force_inline void AfterLockShared() + { + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockSharedObtain ); + MemWrite( &item->lockObtain.thread, GetThreadHandle() ); + MemWrite( &item->lockObtain.id, m_id ); + MemWrite( &item->lockObtain.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + + tracy_force_inline void AfterUnlockShared() + { +#ifdef TRACY_ON_DEMAND + m_lockCount.fetch_sub( 1, std::memory_order_relaxed ); + if( !m_active.load( std::memory_order_relaxed ) ) return; + if( !GetProfiler().IsConnected() ) + { + m_active.store( false, std::memory_order_relaxed ); + return; + } +#endif + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockSharedRelease ); + MemWrite( &item->lockRelease.thread, GetThreadHandle() ); + MemWrite( &item->lockRelease.id, m_id ); + MemWrite( &item->lockRelease.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + + tracy_force_inline void AfterTryLockShared( bool acquired ) + { +#ifdef TRACY_ON_DEMAND + if( !acquired ) return; + + bool queue = false; + const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = GetProfiler().IsConnected(); + if( active != connected ) m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return; +#endif + + if( acquired ) + { + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockSharedObtain ); + MemWrite( &item->lockObtain.thread, GetThreadHandle() ); + MemWrite( &item->lockObtain.id, m_id ); + MemWrite( &item->lockObtain.time, Profiler::GetTime() ); + Profiler::QueueSerialFinish(); + } + } + + tracy_force_inline void Mark( const SourceLocationData* srcloc ) + { +#ifdef TRACY_ON_DEMAND + const auto active = m_active.load( std::memory_order_relaxed ); + if( !active ) return; + const auto connected = GetProfiler().IsConnected(); + if( !connected ) + { + if( active ) m_active.store( false, std::memory_order_relaxed ); + return; + } +#endif + + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockMark ); + MemWrite( &item->lockMark.thread, GetThreadHandle() ); + MemWrite( &item->lockMark.id, m_id ); + MemWrite( &item->lockMark.srcloc, (uint64_t)srcloc ); + Profiler::QueueSerialFinish(); + } + + tracy_force_inline void CustomName( const char* name, size_t size ) + { + assert( size < std::numeric_limits::max() ); + auto ptr = (char*)tracy_malloc( size ); + memcpy( ptr, name, size ); + auto item = Profiler::QueueSerial(); + MemWrite( &item->hdr.type, QueueType::LockName ); + MemWrite( &item->lockNameFat.id, m_id ); + MemWrite( &item->lockNameFat.name, (uint64_t)ptr ); + MemWrite( &item->lockNameFat.size, (uint16_t)size ); +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + Profiler::QueueSerialFinish(); + } + +private: + uint32_t m_id; + +#ifdef TRACY_ON_DEMAND + std::atomic m_lockCount; + std::atomic m_active; +#endif +}; + +template +class SharedLockable +{ +public: + tracy_force_inline SharedLockable( const SourceLocationData* srcloc ) + : m_ctx( srcloc ) + { + } + + SharedLockable( const SharedLockable& ) = delete; + SharedLockable& operator=( const SharedLockable& ) = delete; + + tracy_force_inline void lock() + { + const auto runAfter = m_ctx.BeforeLock(); + m_lockable.lock(); + if( runAfter ) m_ctx.AfterLock(); + } + + tracy_force_inline void unlock() + { + m_lockable.unlock(); + m_ctx.AfterUnlock(); + } + + tracy_force_inline bool try_lock() + { + const auto acquired = m_lockable.try_lock(); + m_ctx.AfterTryLock( acquired ); + return acquired; + } + + tracy_force_inline void lock_shared() + { + const auto runAfter = m_ctx.BeforeLockShared(); + m_lockable.lock_shared(); + if( runAfter ) m_ctx.AfterLockShared(); + } + + tracy_force_inline void unlock_shared() + { + m_lockable.unlock_shared(); + m_ctx.AfterUnlockShared(); + } + + tracy_force_inline bool try_lock_shared() + { + const auto acquired = m_lockable.try_lock_shared(); + m_ctx.AfterTryLockShared( acquired ); + return acquired; + } + + tracy_force_inline void Mark( const SourceLocationData* srcloc ) + { + m_ctx.Mark( srcloc ); + } + + tracy_force_inline void CustomName( const char* name, size_t size ) + { + m_ctx.CustomName( name, size ); + } + +private: + T m_lockable; + SharedLockableCtx m_ctx; +}; + + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.cpp b/Source/ThirdParty/tracy/client/TracyProfiler.cpp new file mode 100644 index 000000000..b8783a0eb --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyProfiler.cpp @@ -0,0 +1,3573 @@ +#ifdef TRACY_ENABLE + +#ifdef _WIN32 +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +# include +# include +# include +# include +#else +# include +# include +#endif + +#ifdef __CYGWIN__ +# include +# include +# include +#endif + +#ifdef _GNU_SOURCE +# include +#endif + +#ifdef __linux__ +# include +# include +# include +# include +# include +#endif + +#if defined __APPLE__ || defined BSD +# include +# include +#endif + +#if defined __APPLE__ +# include "TargetConditionals.h" +# include +#endif + +#ifdef __ANDROID__ +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/TracyAlign.hpp" +#include "../common/TracySocket.hpp" +#include "../common/TracySystem.hpp" +#include "tracy_rpmalloc.hpp" +#include "TracyCallstack.hpp" +#include "TracyScoped.hpp" +#include "TracyProfiler.hpp" +#include "TracyThread.hpp" +#include "TracyArmCpuTable.hpp" +#include "TracySysTrace.hpp" + +#ifdef TRACY_PORT +# ifndef TRACY_DATA_PORT +# define TRACY_DATA_PORT TRACY_PORT +# endif +# ifndef TRACY_BROADCAST_PORT +# define TRACY_BROADCAST_PORT TRACY_PORT +# endif +#endif + +#ifdef __APPLE__ +# define TRACY_DELAYED_INIT +#else +# ifdef __GNUC__ +# define init_order( val ) __attribute__ ((init_priority(val))) +# else +# define init_order(x) +# endif +#endif + +#if defined _WIN32 || defined __CYGWIN__ +# include +extern "C" typedef LONG (WINAPI *t_RtlGetVersion)( PRTL_OSVERSIONINFOW ); +extern "C" typedef BOOL (WINAPI *t_GetLogicalProcessorInformationEx)( LOGICAL_PROCESSOR_RELATIONSHIP, PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD ); +#else +# include +# include +#endif +#if defined __linux__ +# include +# include +#endif + +#if !defined _WIN32 && !defined __CYGWIN__ && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) +# include +#endif + +#if !( ( ( defined _WIN32 || defined __CYGWIN__ ) && _WIN32_WINNT >= _WIN32_WINNT_VISTA ) || defined __linux__ ) +# include +#endif + +namespace tracy +{ + +namespace +{ +# if ( defined _WIN32 || defined __CYGWIN__ ) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + BOOL CALLBACK InitOnceCallback( PINIT_ONCE /*initOnce*/, PVOID /*Parameter*/, PVOID* /*Context*/) + { + rpmalloc_initialize(); + return TRUE; + } + INIT_ONCE InitOnce = INIT_ONCE_STATIC_INIT; +# elif defined __linux__ + void InitOnceCallback() + { + rpmalloc_initialize(); + } + pthread_once_t once_control = PTHREAD_ONCE_INIT; +# else + void InitOnceCallback() + { + rpmalloc_initialize(); + } + std::once_flag once_flag; +# endif +} + +struct RPMallocInit +{ + RPMallocInit() + { +# if ( defined _WIN32 || defined __CYGWIN__ ) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + InitOnceExecuteOnce( &InitOnce, InitOnceCallback, nullptr, nullptr ); +# elif defined __linux__ + pthread_once( &once_control, InitOnceCallback ); +# else + std::call_once( once_flag, InitOnceCallback ); +# endif + rpmalloc_thread_initialize(); + } +}; + +#ifndef TRACY_DELAYED_INIT + +struct InitTimeWrapper +{ + int64_t val; +}; + +struct ProducerWrapper +{ + tracy::moodycamel::ConcurrentQueue::ExplicitProducer* ptr; +}; + +struct ThreadHandleWrapper +{ + uint64_t val; +}; +#endif + + +#if defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 +static inline void CpuId( uint32_t* regs, uint32_t leaf ) +{ + memset(regs, 0, sizeof(uint32_t) * 4); +#if defined _WIN32 || defined __CYGWIN__ + __cpuidex( (int*)regs, leaf, 0 ); +#else + __get_cpuid( leaf, regs, regs+1, regs+2, regs+3 ); +#endif +} + +static void InitFailure( const char* msg ) +{ +#if defined _WIN32 || defined __CYGWIN__ + bool hasConsole = false; + bool reopen = false; + const auto attached = AttachConsole( ATTACH_PARENT_PROCESS ); + if( attached ) + { + hasConsole = true; + reopen = true; + } + else + { + const auto err = GetLastError(); + if( err == ERROR_ACCESS_DENIED ) + { + hasConsole = true; + } + } + if( hasConsole ) + { + fprintf( stderr, "Tracy Profiler initialization failure: %s\n", msg ); + if( reopen ) + { + freopen( "CONOUT$", "w", stderr ); + fprintf( stderr, "Tracy Profiler initialization failure: %s\n", msg ); + } + } + else + { + MessageBoxA( nullptr, msg, "Tracy Profiler initialization failure", MB_ICONSTOP ); + } +#else + fprintf( stderr, "Tracy Profiler initialization failure: %s\n", msg ); +#endif + exit( 0 ); +} + +static int64_t SetupHwTimer() +{ +#if !defined TRACY_TIMER_QPC && !defined TRACY_TIMER_FALLBACK + uint32_t regs[4]; + CpuId( regs, 1 ); + if( !( regs[3] & ( 1 << 4 ) ) ) InitFailure( "CPU doesn't support RDTSC instruction." ); + CpuId( regs, 0x80000007 ); + if( !( regs[3] & ( 1 << 8 ) ) ) + { + const char* noCheck = getenv( "TRACY_NO_INVARIANT_CHECK" ); + if( !noCheck || noCheck[0] != '1' ) + { +#if defined _WIN32 || defined __CYGWIN__ + InitFailure( "CPU doesn't support invariant TSC.\nDefine TRACY_NO_INVARIANT_CHECK=1 to ignore this error, *if you know what you are doing*.\nAlternatively you may rebuild the application with the TRACY_TIMER_QPC or TRACY_TIMER_FALLBACK define to use lower resolution timer." ); +#else + InitFailure( "CPU doesn't support invariant TSC.\nDefine TRACY_NO_INVARIANT_CHECK=1 to ignore this error, *if you know what you are doing*.\nAlternatively you may rebuild the application with the TRACY_TIMER_FALLBACK define to use lower resolution timer." ); +#endif + } + } +#endif + + return Profiler::GetTime(); +} +#else +static int64_t SetupHwTimer() +{ + return Profiler::GetTime(); +} +#endif + +static const char* GetProcessName() +{ + const char* processName = "unknown"; +#ifdef _WIN32 + static char buf[_MAX_PATH]; + GetModuleFileNameA( nullptr, buf, _MAX_PATH ); + const char* ptr = buf; + while( *ptr != '\0' ) ptr++; + while( ptr > buf && *ptr != '\\' && *ptr != '/' ) ptr--; + if( ptr > buf ) ptr++; + processName = ptr; +#elif defined __ANDROID__ +# if __ANDROID_API__ >= 21 + auto buf = getprogname(); + if( buf ) processName = buf; +# endif +#elif defined _GNU_SOURCE || defined __CYGWIN__ + if( program_invocation_short_name ) processName = program_invocation_short_name; +#elif defined __APPLE__ || defined BSD + auto buf = getprogname(); + if( buf ) processName = buf; +#endif + return processName; +} + +static const char* GetProcessExecutablePath() +{ +#ifdef _WIN32 + static char buf[_MAX_PATH]; + GetModuleFileNameA( nullptr, buf, _MAX_PATH ); + return buf; +#elif defined __ANDROID__ + return nullptr; +#elif defined _GNU_SOURCE || defined __CYGWIN__ + return program_invocation_name; +#elif defined __APPLE__ + static char buf[1024]; + uint32_t size = 1024; + _NSGetExecutablePath( buf, &size ); + return buf; +#elif defined __DragonFly__ + static char buf[1024]; + readlink( "/proc/curproc/file", buf, 1024 ); + return buf; +#elif defined __FreeBSD__ + static char buf[1024]; + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + size_t cb = 1024; + sysctl( mib, 4, buf, &cb, nullptr, 0 ); + return buf; +#elif defined __NetBSD__ + static char buf[1024]; + readlink( "/proc/curproc/exe", buf, 1024 ); + return buf; +#else + return nullptr; +#endif +} + +#if defined __linux__ && defined __ARM_ARCH +static uint32_t GetHex( char*& ptr, int skip ) +{ + uint32_t ret; + ptr += skip; + char* end; + if( ptr[0] == '0' && ptr[1] == 'x' ) + { + ptr += 2; + ret = strtol( ptr, &end, 16 ); + } + else + { + ret = strtol( ptr, &end, 10 ); + } + ptr = end; + return ret; +} +#endif + +static const char* GetHostInfo() +{ + static char buf[1024]; + auto ptr = buf; +#if defined _WIN32 || defined __CYGWIN__ + t_RtlGetVersion RtlGetVersion = (t_RtlGetVersion)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlGetVersion" ); + if( !RtlGetVersion ) + { +# ifdef __CYGWIN__ + ptr += sprintf( ptr, "OS: Windows (Cygwin)\n" ); +# elif defined __MINGW32__ + ptr += sprintf( ptr, "OS: Windows (MingW)\n" ); +# else + ptr += sprintf( ptr, "OS: Windows\n" ); +# endif + } + else + { + RTL_OSVERSIONINFOW ver = { sizeof( RTL_OSVERSIONINFOW ) }; + RtlGetVersion( &ver ); + +# ifdef __CYGWIN__ + ptr += sprintf( ptr, "OS: Windows %i.%i.%i (Cygwin)\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber ); +# elif defined __MINGW32__ + ptr += sprintf( ptr, "OS: Windows %i.%i.%i (MingW)\n", (int)ver.dwMajorVersion, (int)ver.dwMinorVersion, (int)ver.dwBuildNumber ); +# else + ptr += sprintf( ptr, "OS: Windows %i.%i.%i\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber ); +# endif + } +#elif defined __linux__ + struct utsname utsName; + uname( &utsName ); +# if defined __ANDROID__ + ptr += sprintf( ptr, "OS: Linux %s (Android)\n", utsName.release ); +# else + ptr += sprintf( ptr, "OS: Linux %s\n", utsName.release ); +# endif +#elif defined __APPLE__ +# if TARGET_OS_IPHONE == 1 + ptr += sprintf( ptr, "OS: Darwin (iOS)\n" ); +# elif TARGET_OS_MAC == 1 + ptr += sprintf( ptr, "OS: Darwin (OSX)\n" ); +# else + ptr += sprintf( ptr, "OS: Darwin (unknown)\n" ); +# endif +#elif defined __DragonFly__ + ptr += sprintf( ptr, "OS: BSD (DragonFly)\n" ); +#elif defined __FreeBSD__ + ptr += sprintf( ptr, "OS: BSD (FreeBSD)\n" ); +#elif defined __NetBSD__ + ptr += sprintf( ptr, "OS: BSD (NetBSD)\n" ); +#elif defined __OpenBSD__ + ptr += sprintf( ptr, "OS: BSD (OpenBSD)\n" ); +#else + ptr += sprintf( ptr, "OS: unknown\n" ); +#endif + +#if defined _MSC_VER +# if defined __clang__ + ptr += sprintf( ptr, "Compiler: MSVC clang-cl %i.%i.%i\n", __clang_major__, __clang_minor__, __clang_patchlevel__ ); +# else + ptr += sprintf( ptr, "Compiler: MSVC %i\n", _MSC_VER ); +# endif +#elif defined __clang__ + ptr += sprintf( ptr, "Compiler: clang %i.%i.%i\n", __clang_major__, __clang_minor__, __clang_patchlevel__ ); +#elif defined __GNUC__ + ptr += sprintf( ptr, "Compiler: gcc %i.%i\n", __GNUC__, __GNUC_MINOR__ ); +#else + ptr += sprintf( ptr, "Compiler: unknown\n" ); +#endif + +#if defined _WIN32 || defined __CYGWIN__ +# ifndef __CYGWIN__ + InitWinSock(); +# endif + char hostname[512]; + gethostname( hostname, 512 ); + + DWORD userSz = UNLEN+1; + char user[UNLEN+1]; + GetUserNameA( user, &userSz ); + + ptr += sprintf( ptr, "User: %s@%s\n", user, hostname ); +#else + char hostname[_POSIX_HOST_NAME_MAX]{}; + char user[_POSIX_LOGIN_NAME_MAX]{}; + + gethostname( hostname, _POSIX_HOST_NAME_MAX ); +# if defined __ANDROID__ + const auto login = getlogin(); + if( login ) + { + strcpy( user, login ); + } + else + { + memcpy( user, "(?)", 4 ); + } +# else + getlogin_r( user, _POSIX_LOGIN_NAME_MAX ); +# endif + + ptr += sprintf( ptr, "User: %s@%s\n", user, hostname ); +#endif + +#if defined __i386 || defined _M_IX86 + ptr += sprintf( ptr, "Arch: x86\n" ); +#elif defined __x86_64__ || defined _M_X64 + ptr += sprintf( ptr, "Arch: x64\n" ); +#elif defined __aarch64__ + ptr += sprintf( ptr, "Arch: ARM64\n" ); +#elif defined __ARM_ARCH + ptr += sprintf( ptr, "Arch: ARM\n" ); +#else + ptr += sprintf( ptr, "Arch: unknown\n" ); +#endif + +#if defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 + uint32_t regs[4]; + char cpuModel[4*4*3]; + auto modelPtr = cpuModel; + for( uint32_t i=0x80000002; i<0x80000005; ++i ) + { + CpuId( regs, i ); + memcpy( modelPtr, regs, sizeof( regs ) ); modelPtr += sizeof( regs ); + } + + ptr += sprintf( ptr, "CPU: %s\n", cpuModel ); +#elif defined __linux__ && defined __ARM_ARCH + bool cpuFound = false; + FILE* fcpuinfo = fopen( "/proc/cpuinfo", "rb" ); + if( fcpuinfo ) + { + enum { BufSize = 4*1024 }; + char buf[BufSize]; + const auto sz = fread( buf, 1, BufSize, fcpuinfo ); + fclose( fcpuinfo ); + const auto end = buf + sz; + auto cptr = buf; + + uint32_t impl = 0; + uint32_t var = 0; + uint32_t part = 0; + uint32_t rev = 0; + + while( end - cptr > 20 ) + { + while( end - cptr > 20 && memcmp( cptr, "CPU ", 4 ) != 0 ) + { + cptr += 4; + while( end - cptr > 20 && *cptr != '\n' ) cptr++; + cptr++; + } + if( end - cptr <= 20 ) break; + cptr += 4; + if( memcmp( cptr, "implementer\t: ", 14 ) == 0 ) + { + if( impl != 0 ) break; + impl = GetHex( cptr, 14 ); + } + else if( memcmp( cptr, "variant\t: ", 10 ) == 0 ) var = GetHex( cptr, 10 ); + else if( memcmp( cptr, "part\t: ", 7 ) == 0 ) part = GetHex( cptr, 7 ); + else if( memcmp( cptr, "revision\t: ", 11 ) == 0 ) rev = GetHex( cptr, 11 ); + while( *cptr != '\n' && *cptr != '\0' ) cptr++; + cptr++; + } + + if( impl != 0 || var != 0 || part != 0 || rev != 0 ) + { + cpuFound = true; + ptr += sprintf( ptr, "CPU: %s%s r%ip%i\n", DecodeArmImplementer( impl ), DecodeArmPart( impl, part ), var, rev ); + } + } + if( !cpuFound ) + { + ptr += sprintf( ptr, "CPU: unknown\n" ); + } +#elif defined __APPLE__ && TARGET_OS_IPHONE == 1 + { + size_t sz; + sysctlbyname( "hw.machine", nullptr, &sz, nullptr, 0 ); + auto str = (char*)tracy_malloc( sz ); + sysctlbyname( "hw.machine", str, &sz, nullptr, 0 ); + ptr += sprintf( ptr, "Device: %s\n", DecodeIosDevice( str ) ); + tracy_free( str ); + } +#else + ptr += sprintf( ptr, "CPU: unknown\n" ); +#endif + + ptr += sprintf( ptr, "CPU cores: %i\n", std::thread::hardware_concurrency() ); + +#if defined _WIN32 || defined __CYGWIN__ + MEMORYSTATUSEX statex; + statex.dwLength = sizeof( statex ); + GlobalMemoryStatusEx( &statex ); +# ifdef _MSC_VER + ptr += sprintf( ptr, "RAM: %I64u MB\n", statex.ullTotalPhys / 1024 / 1024 ); +# else + ptr += sprintf( ptr, "RAM: %llu MB\n", statex.ullTotalPhys / 1024 / 1024 ); +# endif +#elif defined __linux__ + struct sysinfo sysInfo; + sysinfo( &sysInfo ); + ptr += sprintf( ptr, "RAM: %lu MB\n", sysInfo.totalram / 1024 / 1024 ); +#elif defined __APPLE__ + size_t memSize; + size_t sz = sizeof( memSize ); + sysctlbyname( "hw.memsize", &memSize, &sz, nullptr, 0 ); + ptr += sprintf( ptr, "RAM: %zu MB\n", memSize / 1024 / 1024 ); +#elif defined BSD + size_t memSize; + size_t sz = sizeof( memSize ); + sysctlbyname( "hw.physmem", &memSize, &sz, nullptr, 0 ); + ptr += sprintf( ptr, "RAM: %zu MB\n", memSize / 1024 / 1024 ); +#else + ptr += sprintf( ptr, "RAM: unknown\n" ); +#endif + + return buf; +} + +static uint64_t GetPid() +{ +#if defined _WIN32 || defined __CYGWIN__ + return uint64_t( GetCurrentProcessId() ); +#else + return uint64_t( getpid() ); +#endif +} + +void Profiler::AckServerQuery() +{ + QueueItem item; + MemWrite( &item.hdr.type, QueueType::AckServerQueryNoop ); + NeedDataSize( QueueDataSize[(int)QueueType::AckServerQueryNoop] ); + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::AckServerQueryNoop] ); +} + +void Profiler::AckSourceCodeNotAvailable() +{ + QueueItem item; + MemWrite( &item.hdr.type, QueueType::AckSourceCodeNotAvailable ); + NeedDataSize( QueueDataSize[(int)QueueType::AckSourceCodeNotAvailable] ); + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::AckSourceCodeNotAvailable] ); +} + +static BroadcastMessage& GetBroadcastMessage( const char* procname, size_t pnsz, int& len, int port ) +{ + static BroadcastMessage msg; + + msg.broadcastVersion = BroadcastVersion; + msg.protocolVersion = ProtocolVersion; + msg.listenPort = port; + + memcpy( msg.programName, procname, pnsz ); + memset( msg.programName + pnsz, 0, WelcomeMessageProgramNameSize - pnsz ); + + len = int( offsetof( BroadcastMessage, programName ) + pnsz + 1 ); + return msg; +} + +#if defined _WIN32 || defined __CYGWIN__ +static DWORD s_profilerThreadId = 0; +static char s_crashText[1024]; + +LONG WINAPI CrashFilter( PEXCEPTION_POINTERS pExp ) +{ + if( !GetProfiler().IsConnected() ) return EXCEPTION_CONTINUE_SEARCH; + + const unsigned ec = pExp->ExceptionRecord->ExceptionCode; + auto msgPtr = s_crashText; + switch( ec ) + { + case EXCEPTION_ACCESS_VIOLATION: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_ACCESS_VIOLATION (0x%x). ", ec ); + switch( pExp->ExceptionRecord->ExceptionInformation[0] ) + { + case 0: + msgPtr += sprintf( msgPtr, "Read violation at address 0x%" PRIxPTR ".", pExp->ExceptionRecord->ExceptionInformation[1] ); + break; + case 1: + msgPtr += sprintf( msgPtr, "Write violation at address 0x%" PRIxPTR ".", pExp->ExceptionRecord->ExceptionInformation[1] ); + break; + case 8: + msgPtr += sprintf( msgPtr, "DEP violation at address 0x%" PRIxPTR ".", pExp->ExceptionRecord->ExceptionInformation[1] ); + break; + default: + break; + } + break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_ARRAY_BOUNDS_EXCEEDED (0x%x). ", ec ); + break; + case EXCEPTION_DATATYPE_MISALIGNMENT: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_DATATYPE_MISALIGNMENT (0x%x). ", ec ); + break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_FLT_DIVIDE_BY_ZERO (0x%x). ", ec ); + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_ILLEGAL_INSTRUCTION (0x%x). ", ec ); + break; + case EXCEPTION_IN_PAGE_ERROR: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_IN_PAGE_ERROR (0x%x). ", ec ); + break; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_INT_DIVIDE_BY_ZERO (0x%x). ", ec ); + break; + case EXCEPTION_PRIV_INSTRUCTION: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_PRIV_INSTRUCTION (0x%x). ", ec ); + break; + case EXCEPTION_STACK_OVERFLOW: + msgPtr += sprintf( msgPtr, "Exception EXCEPTION_STACK_OVERFLOW (0x%x). ", ec ); + break; + default: + return EXCEPTION_CONTINUE_SEARCH; + } + + { + GetProfiler().SendCallstack( 60, "KiUserExceptionDispatcher" ); + + TracyLfqPrepare( QueueType::CrashReport ); + item->crashReport.time = Profiler::GetTime(); + item->crashReport.text = (uint64_t)s_crashText; + TracyLfqCommit; + } + + HANDLE h = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); + if( h == INVALID_HANDLE_VALUE ) return EXCEPTION_CONTINUE_SEARCH; + + THREADENTRY32 te = { sizeof( te ) }; + if( !Thread32First( h, &te ) ) + { + CloseHandle( h ); + return EXCEPTION_CONTINUE_SEARCH; + } + + const auto pid = GetCurrentProcessId(); + const auto tid = GetCurrentThreadId(); + + do + { + if( te.th32OwnerProcessID == pid && te.th32ThreadID != tid && te.th32ThreadID != s_profilerThreadId ) + { + HANDLE th = OpenThread( THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID ); + if( th != INVALID_HANDLE_VALUE ) + { + SuspendThread( th ); + CloseHandle( th ); + } + } + } + while( Thread32Next( h, &te ) ); + CloseHandle( h ); + + { + TracyLfqPrepare( QueueType::Crash ); + TracyLfqCommit; + } + + std::this_thread::sleep_for( std::chrono::milliseconds( 500 ) ); + GetProfiler().RequestShutdown(); + while( !GetProfiler().HasShutdownFinished() ) { std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); }; + + TerminateProcess( GetCurrentProcess(), 1 ); + + return EXCEPTION_CONTINUE_SEARCH; +} +#endif + +#ifdef __linux__ +static long s_profilerTid = 0; +static char s_crashText[1024]; +static std::atomic s_alreadyCrashed( false ); + +static void ThreadFreezer( int /*signal*/ ) +{ + for(;;) sleep( 1000 ); +} + +static inline void HexPrint( char*& ptr, uint64_t val ) +{ + if( val == 0 ) + { + *ptr++ = '0'; + return; + } + + static const char HexTable[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + char buf[16]; + auto bptr = buf; + + do + { + *bptr++ = HexTable[val%16]; + val /= 16; + } + while( val > 0 ); + + do + { + *ptr++ = *--bptr; + } + while( bptr != buf ); +} + +static void CrashHandler( int signal, siginfo_t* info, void* /*ucontext*/ ) +{ + bool expected = false; + if( !s_alreadyCrashed.compare_exchange_strong( expected, true ) ) ThreadFreezer( signal ); + + struct sigaction act = {}; + act.sa_handler = SIG_DFL; + sigaction( SIGABRT, &act, nullptr ); + + auto msgPtr = s_crashText; + switch( signal ) + { + case SIGILL: + strcpy( msgPtr, "Illegal Instruction.\n" ); + while( *msgPtr ) msgPtr++; + switch( info->si_code ) + { + case ILL_ILLOPC: + strcpy( msgPtr, "Illegal opcode.\n" ); + break; + case ILL_ILLOPN: + strcpy( msgPtr, "Illegal operand.\n" ); + break; + case ILL_ILLADR: + strcpy( msgPtr, "Illegal addressing mode.\n" ); + break; + case ILL_ILLTRP: + strcpy( msgPtr, "Illegal trap.\n" ); + break; + case ILL_PRVOPC: + strcpy( msgPtr, "Privileged opcode.\n" ); + break; + case ILL_PRVREG: + strcpy( msgPtr, "Privileged register.\n" ); + break; + case ILL_COPROC: + strcpy( msgPtr, "Coprocessor error.\n" ); + break; + case ILL_BADSTK: + strcpy( msgPtr, "Internal stack error.\n" ); + break; + default: + break; + } + break; + case SIGFPE: + strcpy( msgPtr, "Floating-point exception.\n" ); + while( *msgPtr ) msgPtr++; + switch( info->si_code ) + { + case FPE_INTDIV: + strcpy( msgPtr, "Integer divide by zero.\n" ); + break; + case FPE_INTOVF: + strcpy( msgPtr, "Integer overflow.\n" ); + break; + case FPE_FLTDIV: + strcpy( msgPtr, "Floating-point divide by zero.\n" ); + break; + case FPE_FLTOVF: + strcpy( msgPtr, "Floating-point overflow.\n" ); + break; + case FPE_FLTUND: + strcpy( msgPtr, "Floating-point underflow.\n" ); + break; + case FPE_FLTRES: + strcpy( msgPtr, "Floating-point inexact result.\n" ); + break; + case FPE_FLTINV: + strcpy( msgPtr, "Floating-point invalid operation.\n" ); + break; + case FPE_FLTSUB: + strcpy( msgPtr, "Subscript out of range.\n" ); + break; + default: + break; + } + break; + case SIGSEGV: + strcpy( msgPtr, "Invalid memory reference.\n" ); + while( *msgPtr ) msgPtr++; + switch( info->si_code ) + { + case SEGV_MAPERR: + strcpy( msgPtr, "Address not mapped to object.\n" ); + break; + case SEGV_ACCERR: + strcpy( msgPtr, "Invalid permissions for mapped object.\n" ); + break; +# ifdef SEGV_BNDERR + case SEGV_BNDERR: + strcpy( msgPtr, "Failed address bound checks.\n" ); + break; +# endif +# ifdef SEGV_PKUERR + case SEGV_PKUERR: + strcpy( msgPtr, "Access was denied by memory protection keys.\n" ); + break; +# endif + default: + break; + } + break; + case SIGPIPE: + strcpy( msgPtr, "Broken pipe.\n" ); + while( *msgPtr ) msgPtr++; + break; + case SIGBUS: + strcpy( msgPtr, "Bus error.\n" ); + while( *msgPtr ) msgPtr++; + switch( info->si_code ) + { + case BUS_ADRALN: + strcpy( msgPtr, "Invalid address alignment.\n" ); + break; + case BUS_ADRERR: + strcpy( msgPtr, "Nonexistent physical address.\n" ); + break; + case BUS_OBJERR: + strcpy( msgPtr, "Object-specific hardware error.\n" ); + break; +# ifdef BUS_MCEERR_AR + case BUS_MCEERR_AR: + strcpy( msgPtr, "Hardware memory error consumed on a machine check; action required.\n" ); + break; +# endif +# ifdef BUS_MCEERR_AO + case BUS_MCEERR_AO: + strcpy( msgPtr, "Hardware memory error detected in process but not consumed; action optional.\n" ); + break; +# endif + default: + break; + } + break; + case SIGABRT: + strcpy( msgPtr, "Abort signal from abort().\n" ); + break; + default: + abort(); + } + while( *msgPtr ) msgPtr++; + + if( signal != SIGPIPE ) + { + strcpy( msgPtr, "Fault address: 0x" ); + while( *msgPtr ) msgPtr++; + HexPrint( msgPtr, uint64_t( info->si_addr ) ); + *msgPtr++ = '\n'; + } + + { + GetProfiler().SendCallstack( 60, "__kernel_rt_sigreturn" ); + + TracyLfqPrepare( QueueType::CrashReport ); + item->crashReport.time = Profiler::GetTime(); + item->crashReport.text = (uint64_t)s_crashText; + TracyLfqCommit; + } + + DIR* dp = opendir( "/proc/self/task" ); + if( !dp ) abort(); + + const auto selfTid = syscall( SYS_gettid ); + + struct dirent* ep; + while( ( ep = readdir( dp ) ) != nullptr ) + { + if( ep->d_name[0] == '.' ) continue; + int tid = atoi( ep->d_name ); + if( tid != selfTid && tid != s_profilerTid ) + { + syscall( SYS_tkill, tid, SIGPWR ); + } + } + closedir( dp ); + + { + TracyLfqPrepare( QueueType::Crash ); + TracyLfqCommit; + } + + std::this_thread::sleep_for( std::chrono::milliseconds( 500 ) ); + GetProfiler().RequestShutdown(); + while( !GetProfiler().HasShutdownFinished() ) { std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); }; + + abort(); +} +#endif + + +enum { QueuePrealloc = 256 * 1024 }; + +static Profiler* s_instance = nullptr; +static Thread* s_thread; + +#ifdef TRACY_HAS_SYSTEM_TRACING +static Thread* s_sysTraceThread = nullptr; +#endif + +TRACY_API bool ProfilerAvailable() { return s_instance != nullptr; } + +TRACY_API int64_t GetFrequencyQpc() +{ +#if defined _WIN32 || defined __CYGWIN__ + LARGE_INTEGER t; + QueryPerformanceFrequency( &t ); + return t.QuadPart; +#else + return 0; +#endif +} + +#ifdef TRACY_DELAYED_INIT +struct ThreadNameData; +TRACY_API moodycamel::ConcurrentQueue& GetQueue(); +TRACY_API void InitRPMallocThread(); + +void InitRPMallocThread() +{ + RPMallocInit rpinit; + rpmalloc_thread_initialize(); +} + +struct ProfilerData +{ + int64_t initTime = SetupHwTimer(); + RPMallocInit rpmalloc_init; + moodycamel::ConcurrentQueue queue; + Profiler profiler; + std::atomic lockCounter { 0 }; + std::atomic gpuCtxCounter { 0 }; + std::atomic threadNameData { nullptr }; +}; + +struct ProducerWrapper +{ + ProducerWrapper( ProfilerData& data ) : detail( data.queue ), ptr( data.queue.get_explicit_producer( detail ) ) {} + moodycamel::ProducerToken detail; + tracy::moodycamel::ConcurrentQueue::ExplicitProducer* ptr; +}; + +struct ProfilerThreadData +{ + ProfilerThreadData( ProfilerData& data ) : token( data ), gpuCtx( { nullptr } ) {} + RPMallocInit rpmalloc_init; + ProducerWrapper token; + GpuCtxWrapper gpuCtx; +# ifdef TRACY_ON_DEMAND + LuaZoneState luaZoneState; +# endif +}; + +# ifdef TRACY_MANUAL_LIFETIME +ProfilerData* s_profilerData = nullptr; +TRACY_API void StartupProfiler() +{ + s_profilerData = new ProfilerData; + s_profilerData->profiler.SpawnWorkerThreads(); +} +static ProfilerData& GetProfilerData() +{ + assert(s_profilerData); + return *s_profilerData; +} +TRACY_API void ShutdownProfiler() +{ + delete s_profilerData; + s_profilerData = nullptr; + rpmalloc_finalize(); +} +# else +static std::atomic profilerDataLock { 0 }; +static std::atomic profilerData { nullptr }; + +static ProfilerData& GetProfilerData() +{ + auto ptr = profilerData.load( std::memory_order_acquire ); + if( !ptr ) + { + int expected = 0; + while( !profilerDataLock.compare_exchange_strong( expected, 1, std::memory_order_release, std::memory_order_relaxed ) ) { expected = 0; } + ptr = profilerData.load( std::memory_order_acquire ); + if( !ptr ) + { + ptr = (ProfilerData*)malloc( sizeof( ProfilerData ) ); + new (ptr) ProfilerData(); + profilerData.store( ptr, std::memory_order_release ); + } + profilerDataLock.store( 0, std::memory_order_release ); + } + return *ptr; +} +# endif + +static ProfilerThreadData& GetProfilerThreadData() +{ + thread_local ProfilerThreadData data( GetProfilerData() ); + return data; +} + +TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* GetToken() { return GetProfilerThreadData().token.ptr; } +TRACY_API Profiler& GetProfiler() { return GetProfilerData().profiler; } +TRACY_API moodycamel::ConcurrentQueue& GetQueue() { return GetProfilerData().queue; } +TRACY_API int64_t GetInitTime() { return GetProfilerData().initTime; } +TRACY_API std::atomic& GetLockCounter() { return GetProfilerData().lockCounter; } +TRACY_API std::atomic& GetGpuCtxCounter() { return GetProfilerData().gpuCtxCounter; } +TRACY_API GpuCtxWrapper& GetGpuCtx() { return GetProfilerThreadData().gpuCtx; } +TRACY_API uint64_t GetThreadHandle() { return detail::GetThreadHandleImpl(); } +std::atomic& GetThreadNameData() { return GetProfilerData().threadNameData; } + +# ifdef TRACY_ON_DEMAND +TRACY_API LuaZoneState& GetLuaZoneState() { return GetProfilerThreadData().luaZoneState; } +# endif + +# ifndef TRACY_MANUAL_LIFETIME +namespace +{ + const auto& __profiler_init = GetProfiler(); +} +# endif + +#else +TRACY_API void InitRPMallocThread() +{ + rpmalloc_thread_initialize(); +} + +// MSVC static initialization order solution. gcc/clang uses init_order() to avoid all this. + +// 1a. But s_queue is needed for initialization of variables in point 2. +extern moodycamel::ConcurrentQueue s_queue; + +thread_local RPMallocInit init_order(106) s_rpmalloc_thread_init; + +// 2. If these variables would be in the .CRT$XCB section, they would be initialized only in main thread. +thread_local moodycamel::ProducerToken init_order(107) s_token_detail( s_queue ); +thread_local ProducerWrapper init_order(108) s_token { s_queue.get_explicit_producer( s_token_detail ) }; +thread_local ThreadHandleWrapper init_order(104) s_threadHandle { detail::GetThreadHandleImpl() }; + +# ifdef _MSC_VER +// 1. Initialize these static variables before all other variables. +# pragma warning( disable : 4075 ) +# pragma init_seg( ".CRT$XCB" ) +# endif + +static InitTimeWrapper init_order(101) s_initTime { SetupHwTimer() }; +static RPMallocInit init_order(102) s_rpmalloc_init; +moodycamel::ConcurrentQueue init_order(103) s_queue( QueuePrealloc ); +std::atomic init_order(104) s_lockCounter( 0 ); +std::atomic init_order(104) s_gpuCtxCounter( 0 ); + +thread_local GpuCtxWrapper init_order(104) s_gpuCtx { nullptr }; + +struct ThreadNameData; +static std::atomic init_order(104) s_threadNameDataInstance( nullptr ); +std::atomic& s_threadNameData = s_threadNameDataInstance; + +# ifdef TRACY_ON_DEMAND +thread_local LuaZoneState init_order(104) s_luaZoneState { 0, false }; +# endif + +static Profiler init_order(105) s_profiler; + +TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* GetToken() { return s_token.ptr; } +TRACY_API Profiler& GetProfiler() { return s_profiler; } +TRACY_API moodycamel::ConcurrentQueue& GetQueue() { return s_queue; } +TRACY_API int64_t GetInitTime() { return s_initTime.val; } +TRACY_API std::atomic& GetLockCounter() { return s_lockCounter; } +TRACY_API std::atomic& GetGpuCtxCounter() { return s_gpuCtxCounter; } +TRACY_API GpuCtxWrapper& GetGpuCtx() { return s_gpuCtx; } +# ifdef __CYGWIN__ +// Hackfix for cygwin reporting memory frees without matching allocations. WTF? +TRACY_API uint64_t GetThreadHandle() { return detail::GetThreadHandleImpl(); } +# else +TRACY_API uint64_t GetThreadHandle() { return s_threadHandle.val; } +# endif + +std::atomic& GetThreadNameData() { return s_threadNameData; } + +# ifdef TRACY_ON_DEMAND +TRACY_API LuaZoneState& GetLuaZoneState() { return s_luaZoneState; } +# endif +#endif + +Profiler::Profiler() + : m_timeBegin( 0 ) + , m_mainThread( detail::GetThreadHandleImpl() ) + , m_epoch( std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count() ) + , m_shutdown( false ) + , m_shutdownManual( false ) + , m_shutdownFinished( false ) + , m_sock( nullptr ) + , m_broadcast( nullptr ) + , m_noExit( false ) + , m_userPort( 0 ) + , m_zoneId( 1 ) + , m_samplingPeriod( 0 ) + , m_stream( LZ4_createStream() ) + , m_buffer( (char*)tracy_malloc( TargetFrameSize*3 ) ) + , m_bufferOffset( 0 ) + , m_bufferStart( 0 ) + , m_lz4Buf( (char*)tracy_malloc( LZ4Size + sizeof( lz4sz_t ) ) ) + , m_serialQueue( 1024*1024 ) + , m_serialDequeue( 1024*1024 ) + , m_frameCount( 0 ) + , m_isConnected( false ) +#ifdef TRACY_ON_DEMAND + , m_connectionId( 0 ) + , m_deferredQueue( 64*1024 ) +#endif + , m_paramCallback( nullptr ) + , m_queryData( nullptr ) +{ + assert( !s_instance ); + s_instance = this; + +#ifndef TRACY_DELAYED_INIT +# ifdef _MSC_VER + // 3. But these variables need to be initialized in main thread within the .CRT$XCB section. Do it here. + s_token_detail = moodycamel::ProducerToken( s_queue ); + s_token = ProducerWrapper { s_queue.get_explicit_producer( s_token_detail ) }; + s_threadHandle = ThreadHandleWrapper { m_mainThread }; +# endif +#endif + + CalibrateTimer(); + CalibrateDelay(); + ReportTopology(); + +#ifndef TRACY_NO_EXIT + const char* noExitEnv = getenv( "TRACY_NO_EXIT" ); + if( noExitEnv && noExitEnv[0] == '1' ) + { + m_noExit = true; + } +#endif + + const char* userPort = getenv( "TRACY_PORT" ); + if( userPort ) + { + m_userPort = atoi( userPort ); + } + +#if !defined(TRACY_DELAYED_INIT) || !defined(TRACY_MANUAL_LIFETIME) + SpawnWorkerThreads(); +#endif +} + +void Profiler::SpawnWorkerThreads() +{ + s_thread = (Thread*)tracy_malloc( sizeof( Thread ) ); + new(s_thread) Thread( LaunchWorker, this ); + +#ifdef TRACY_HAS_SYSTEM_TRACING + if( SysTraceStart( m_samplingPeriod ) ) + { + s_sysTraceThread = (Thread*)tracy_malloc( sizeof( Thread ) ); + new(s_sysTraceThread) Thread( SysTraceWorker, nullptr ); + std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) ); + } +#endif + +#if defined _WIN32 || defined __CYGWIN__ + s_profilerThreadId = GetThreadId( s_thread->Handle() ); + AddVectoredExceptionHandler( 1, CrashFilter ); +#endif + +#ifdef __linux__ + struct sigaction threadFreezer = {}; + threadFreezer.sa_handler = ThreadFreezer; + sigaction( SIGPWR, &threadFreezer, nullptr ); + + struct sigaction crashHandler = {}; + crashHandler.sa_sigaction = CrashHandler; + crashHandler.sa_flags = SA_SIGINFO; + sigaction( SIGILL, &crashHandler, nullptr ); + sigaction( SIGFPE, &crashHandler, nullptr ); + sigaction( SIGSEGV, &crashHandler, nullptr ); + sigaction( SIGPIPE, &crashHandler, nullptr ); + sigaction( SIGBUS, &crashHandler, nullptr ); + sigaction( SIGABRT, &crashHandler, nullptr ); +#endif + +#ifdef TRACY_HAS_CALLSTACK + InitCallstack(); +#endif + + m_timeBegin.store( GetTime(), std::memory_order_relaxed ); +} + +Profiler::~Profiler() +{ + m_shutdown.store( true, std::memory_order_relaxed ); + +#ifdef TRACY_HAS_SYSTEM_TRACING + if( s_sysTraceThread ) + { + SysTraceStop(); + s_sysTraceThread->~Thread(); + tracy_free( s_sysTraceThread ); + } +#endif + + s_thread->~Thread(); + tracy_free( s_thread ); + + tracy_free( m_lz4Buf ); + tracy_free( m_buffer ); + LZ4_freeStream( (LZ4_stream_t*)m_stream ); + + if( m_sock ) + { + m_sock->~Socket(); + tracy_free( m_sock ); + } + + if( m_broadcast ) + { + m_broadcast->~UdpBroadcast(); + tracy_free( m_broadcast ); + } + + assert( s_instance ); + s_instance = nullptr; +} + +bool Profiler::ShouldExit() +{ + return s_instance->m_shutdown.load( std::memory_order_relaxed ); +} + +void Profiler::Worker() +{ +#ifdef __linux__ + s_profilerTid = syscall( SYS_gettid ); +#endif + + ThreadExitHandler threadExitHandler; + + SetThreadName( "Tracy Profiler" ); + +#ifdef TRACY_DATA_PORT + const bool dataPortSearch = false; + auto dataPort = m_userPort != 0 ? m_userPort : TRACY_DATA_PORT; +#else + const bool dataPortSearch = m_userPort == 0; + auto dataPort = m_userPort != 0 ? m_userPort : 8086; +#endif +#ifdef TRACY_BROADCAST_PORT + const auto broadcastPort = TRACY_BROADCAST_PORT; +#else + const auto broadcastPort = 8086; +#endif + + while( m_timeBegin.load( std::memory_order_relaxed ) == 0 ) std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + + rpmalloc_thread_initialize(); + + m_exectime = 0; + const auto execname = GetProcessExecutablePath(); + if( execname ) + { + struct stat st; + if( stat( execname, &st ) == 0 ) + { + m_exectime = (uint64_t)st.st_mtime; + } + } + + const auto procname = GetProcessName(); + const auto pnsz = std::min( strlen( procname ), WelcomeMessageProgramNameSize - 1 ); + + const auto hostinfo = GetHostInfo(); + const auto hisz = std::min( strlen( hostinfo ), WelcomeMessageHostInfoSize - 1 ); + + const uint64_t pid = GetPid(); + +#ifdef TRACY_ON_DEMAND + uint8_t onDemand = 1; +#else + uint8_t onDemand = 0; +#endif + +#ifdef __APPLE__ + uint8_t isApple = 1; +#else + uint8_t isApple = 0; +#endif + +#if defined __i386 || defined _M_IX86 + uint8_t cpuArch = CpuArchX86; +#elif defined __x86_64__ || defined _M_X64 + uint8_t cpuArch = CpuArchX64; +#elif defined __aarch64__ + uint8_t cpuArch = CpuArchArm64; +#elif defined __ARM_ARCH + uint8_t cpuArch = CpuArchArm32; +#else + uint8_t cpuArch = CpuArchUnknown; +#endif + +#ifdef TRACY_NO_CODE_TRANSFER + uint8_t codeTransfer = 0; +#else + uint8_t codeTransfer = 1; +#endif + +#if defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 + uint32_t regs[4]; + char manufacturer[12]; + CpuId( regs, 0 ); + memcpy( manufacturer, regs+1, 4 ); + memcpy( manufacturer+4, regs+3, 4 ); + memcpy( manufacturer+8, regs+2, 4 ); + + CpuId( regs, 1 ); + uint32_t cpuId = ( regs[0] & 0xFFF ) | ( ( regs[0] & 0xFFF0000 ) >> 4 ); +#else + const char manufacturer[12] = {}; + uint32_t cpuId = 0; +#endif + + WelcomeMessage welcome; + MemWrite( &welcome.timerMul, m_timerMul ); + MemWrite( &welcome.initBegin, GetInitTime() ); + MemWrite( &welcome.initEnd, m_timeBegin.load( std::memory_order_relaxed ) ); + MemWrite( &welcome.delay, m_delay ); + MemWrite( &welcome.resolution, m_resolution ); + MemWrite( &welcome.epoch, m_epoch ); + MemWrite( &welcome.exectime, m_exectime ); + MemWrite( &welcome.pid, pid ); + MemWrite( &welcome.samplingPeriod, m_samplingPeriod ); + MemWrite( &welcome.onDemand, onDemand ); + MemWrite( &welcome.isApple, isApple ); + MemWrite( &welcome.cpuArch, cpuArch ); + MemWrite( &welcome.codeTransfer, codeTransfer ); + memcpy( welcome.cpuManufacturer, manufacturer, 12 ); + MemWrite( &welcome.cpuId, cpuId ); + memcpy( welcome.programName, procname, pnsz ); + memset( welcome.programName + pnsz, 0, WelcomeMessageProgramNameSize - pnsz ); + memcpy( welcome.hostInfo, hostinfo, hisz ); + memset( welcome.hostInfo + hisz, 0, WelcomeMessageHostInfoSize - hisz ); + + moodycamel::ConsumerToken token( GetQueue() ); + + ListenSocket listen; + bool isListening = false; + if( !dataPortSearch ) + { + isListening = listen.Listen( dataPort, 4 ); + } + else + { + for( uint32_t i=0; i<20; i++ ) + { + if( listen.Listen( dataPort+i, 4 ) ) + { + dataPort += i; + isListening = true; + break; + } + } + } + if( !isListening ) + { + for(;;) + { + if( ShouldExit() ) + { + m_shutdownFinished.store( true, std::memory_order_relaxed ); + return; + } + + ClearQueues( token ); + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + } + +#ifndef TRACY_NO_BROADCAST + m_broadcast = (UdpBroadcast*)tracy_malloc( sizeof( UdpBroadcast ) ); + new(m_broadcast) UdpBroadcast(); +# ifdef TRACY_ONLY_LOCALHOST + const char* addr = "127.255.255.255"; +# else + const char* addr = "255.255.255.255"; +# endif + if( !m_broadcast->Open( addr, broadcastPort ) ) + { + m_broadcast->~UdpBroadcast(); + tracy_free( m_broadcast ); + m_broadcast = nullptr; + } +#endif + + int broadcastLen = 0; + auto& broadcastMsg = GetBroadcastMessage( procname, pnsz, broadcastLen, dataPort ); + uint64_t lastBroadcast = 0; + + // Connections loop. + // Each iteration of the loop handles whole connection. Multiple iterations will only + // happen in the on-demand mode or when handshake fails. + for(;;) + { + // Wait for incoming connection + for(;;) + { +#ifndef TRACY_NO_EXIT + if( !m_noExit && ShouldExit() ) + { + if( m_broadcast ) + { + broadcastMsg.activeTime = -1; + m_broadcast->Send( broadcastPort, &broadcastMsg, broadcastLen ); + } + m_shutdownFinished.store( true, std::memory_order_relaxed ); + return; + } +#endif + m_sock = listen.Accept(); + if( m_sock ) break; +#ifndef TRACY_ON_DEMAND + ProcessSysTime(); +#endif + + if( m_broadcast ) + { + const auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + if( t - lastBroadcast > 3000000000 ) // 3s + { + lastBroadcast = t; + const auto ts = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); + broadcastMsg.activeTime = int32_t( ts - m_epoch ); + assert( broadcastMsg.activeTime >= 0 ); + m_broadcast->Send( broadcastPort, &broadcastMsg, broadcastLen ); + } + } + } + + if( m_broadcast ) + { + lastBroadcast = 0; + broadcastMsg.activeTime = -1; + m_broadcast->Send( broadcastPort, &broadcastMsg, broadcastLen ); + } + + // Handshake + { + char shibboleth[HandshakeShibbolethSize]; + auto res = m_sock->ReadRaw( shibboleth, HandshakeShibbolethSize, 2000 ); + if( !res || memcmp( shibboleth, HandshakeShibboleth, HandshakeShibbolethSize ) != 0 ) + { + m_sock->~Socket(); + tracy_free( m_sock ); + m_sock = nullptr; + continue; + } + + uint32_t protocolVersion; + res = m_sock->ReadRaw( &protocolVersion, sizeof( protocolVersion ), 2000 ); + if( !res ) + { + m_sock->~Socket(); + tracy_free( m_sock ); + m_sock = nullptr; + continue; + } + + if( protocolVersion != ProtocolVersion ) + { + HandshakeStatus status = HandshakeProtocolMismatch; + m_sock->Send( &status, sizeof( status ) ); + m_sock->~Socket(); + tracy_free( m_sock ); + m_sock = nullptr; + continue; + } + } + +#ifdef TRACY_ON_DEMAND + const auto currentTime = GetTime(); + ClearQueues( token ); + m_connectionId.fetch_add( 1, std::memory_order_release ); +#endif + m_isConnected.store( true, std::memory_order_release ); + + HandshakeStatus handshake = HandshakeWelcome; + m_sock->Send( &handshake, sizeof( handshake ) ); + + LZ4_resetStream( (LZ4_stream_t*)m_stream ); + m_sock->Send( &welcome, sizeof( welcome ) ); + + m_threadCtx = 0; + m_refTimeSerial = 0; + m_refTimeCtx = 0; + m_refTimeGpu = 0; + +#ifdef TRACY_ON_DEMAND + OnDemandPayloadMessage onDemand; + onDemand.frames = m_frameCount.load( std::memory_order_relaxed ); + onDemand.currentTime = currentTime; + + m_sock->Send( &onDemand, sizeof( onDemand ) ); + + m_deferredLock.lock(); + for( auto& item : m_deferredQueue ) + { + uint64_t ptr; + uint16_t size; + const auto idx = MemRead( &item.hdr.idx ); + switch( (QueueType)idx ) + { + case QueueType::MessageAppInfo: + ptr = MemRead( &item.messageFat.text ); + size = MemRead( &item.messageFat.size ); + SendSingleString( (const char*)ptr, size ); + break; + case QueueType::LockName: + ptr = MemRead( &item.lockNameFat.name ); + size = MemRead( &item.lockNameFat.size ); + SendSingleString( (const char*)ptr, size ); + break; + case QueueType::GpuContextName: + ptr = MemRead( &item.gpuContextNameFat.ptr ); + size = MemRead( &item.gpuContextNameFat.size ); + SendSingleString( (const char*)ptr, size ); + break; + default: + break; + } + AppendData( &item, QueueDataSize[idx] ); + } + m_deferredLock.unlock(); +#endif + + // Main communications loop + int keepAlive = 0; + for(;;) + { + ProcessSysTime(); + const auto status = Dequeue( token ); + const auto serialStatus = DequeueSerial(); + if( status == DequeueStatus::ConnectionLost || serialStatus == DequeueStatus::ConnectionLost ) + { + break; + } + else if( status == DequeueStatus::QueueEmpty && serialStatus == DequeueStatus::QueueEmpty ) + { + if( ShouldExit() ) break; + if( m_bufferOffset != m_bufferStart ) + { + if( !CommitData() ) break; + } + if( keepAlive == 500 ) + { + QueueItem ka; + ka.hdr.type = QueueType::KeepAlive; + AppendData( &ka, QueueDataSize[ka.hdr.idx] ); + if( !CommitData() ) break; + + keepAlive = 0; + } + else + { + keepAlive++; + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + } + else + { + keepAlive = 0; + } + + bool connActive = true; + while( m_sock->HasData() && connActive ) + { + connActive = HandleServerQuery(); + } + if( !connActive ) break; + } + if( ShouldExit() ) break; + + m_isConnected.store( false, std::memory_order_release ); +#ifdef TRACY_ON_DEMAND + m_bufferOffset = 0; + m_bufferStart = 0; +#endif + + m_sock->~Socket(); + tracy_free( m_sock ); + m_sock = nullptr; + +#ifndef TRACY_ON_DEMAND + // Client is no longer available here. Accept incoming connections, but reject handshake. + for(;;) + { + if( ShouldExit() ) + { + m_shutdownFinished.store( true, std::memory_order_relaxed ); + return; + } + + ClearQueues( token ); + + m_sock = listen.Accept(); + if( m_sock ) + { + char shibboleth[HandshakeShibbolethSize]; + auto res = m_sock->ReadRaw( shibboleth, HandshakeShibbolethSize, 1000 ); + if( !res || memcmp( shibboleth, HandshakeShibboleth, HandshakeShibbolethSize ) != 0 ) + { + m_sock->~Socket(); + tracy_free( m_sock ); + m_sock = nullptr; + continue; + } + + uint32_t protocolVersion; + res = m_sock->ReadRaw( &protocolVersion, sizeof( protocolVersion ), 1000 ); + if( !res ) + { + m_sock->~Socket(); + tracy_free( m_sock ); + m_sock = nullptr; + continue; + } + + HandshakeStatus status = HandshakeNotAvailable; + m_sock->Send( &status, sizeof( status ) ); + m_sock->~Socket(); + tracy_free( m_sock ); + } + } +#endif + } + // End of connections loop + + // Client is exiting. Send items remaining in queues. + for(;;) + { + const auto status = Dequeue( token ); + const auto serialStatus = DequeueSerial(); + if( status == DequeueStatus::ConnectionLost || serialStatus == DequeueStatus::ConnectionLost ) + { + m_shutdownFinished.store( true, std::memory_order_relaxed ); + return; + } + else if( status == DequeueStatus::QueueEmpty && serialStatus == DequeueStatus::QueueEmpty ) + { + if( m_bufferOffset != m_bufferStart ) CommitData(); + break; + } + + while( m_sock->HasData() ) + { + if( !HandleServerQuery() ) + { + m_shutdownFinished.store( true, std::memory_order_relaxed ); + return; + } + } + } + + // Send client termination notice to the server + QueueItem terminate; + MemWrite( &terminate.hdr.type, QueueType::Terminate ); + if( !SendData( (const char*)&terminate, 1 ) ) + { + m_shutdownFinished.store( true, std::memory_order_relaxed ); + return; + } + // Handle remaining server queries + for(;;) + { + if( m_sock->HasData() ) + { + while( m_sock->HasData() ) + { + if( !HandleServerQuery() ) + { + m_shutdownFinished.store( true, std::memory_order_relaxed ); + return; + } + } + while( Dequeue( token ) == DequeueStatus::DataDequeued ) {} + while( DequeueSerial() == DequeueStatus::DataDequeued ) {} + if( m_bufferOffset != m_bufferStart ) + { + if( !CommitData() ) + { + m_shutdownFinished.store( true, std::memory_order_relaxed ); + return; + } + } + } + else + { + if( m_bufferOffset != m_bufferStart ) CommitData(); + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + } +} + +static void FreeAssociatedMemory( const QueueItem& item ) +{ + if( item.hdr.idx >= (int)QueueType::Terminate ) return; + + uint64_t ptr; + switch( item.hdr.type ) + { + case QueueType::ZoneText: + case QueueType::ZoneName: + ptr = MemRead( &item.zoneTextFat.text ); + tracy_free( (void*)ptr ); + break; + case QueueType::MessageColor: + case QueueType::MessageColorCallstack: + ptr = MemRead( &item.messageColorFat.text ); + tracy_free( (void*)ptr ); + break; + case QueueType::Message: + case QueueType::MessageCallstack: +#ifndef TRACY_ON_DEMAND + case QueueType::MessageAppInfo: +#endif + ptr = MemRead( &item.messageFat.text ); + tracy_free( (void*)ptr ); + break; + case QueueType::ZoneBeginAllocSrcLoc: + case QueueType::ZoneBeginAllocSrcLocCallstack: + ptr = MemRead( &item.zoneBegin.srcloc ); + tracy_free( (void*)ptr ); + break; + case QueueType::GpuZoneBeginAllocSrcLoc: + case QueueType::GpuZoneBeginAllocSrcLocCallstack: + case QueueType::GpuZoneBeginAllocSrcLocSerial: + case QueueType::GpuZoneBeginAllocSrcLocCallstackSerial: + ptr = MemRead( &item.gpuZoneBegin.srcloc ); + tracy_free( (void*)ptr ); + break; + case QueueType::CallstackSerial: + case QueueType::Callstack: + ptr = MemRead( &item.callstackFat.ptr ); + tracy_free( (void*)ptr ); + break; + case QueueType::CallstackAlloc: + ptr = MemRead( &item.callstackAllocFat.nativePtr ); + tracy_free( (void*)ptr ); + ptr = MemRead( &item.callstackAllocFat.ptr ); + tracy_free( (void*)ptr ); + break; + case QueueType::CallstackSample: + ptr = MemRead( &item.callstackSampleFat.ptr ); + tracy_free( (void*)ptr ); + break; + case QueueType::FrameImage: + ptr = MemRead( &item.frameImageFat.image ); + tracy_free( (void*)ptr ); + break; +#ifndef TRACY_ON_DEMAND + case QueueType::LockName: + ptr = MemRead( &item.lockNameFat.name ); + tracy_free( (void*)ptr ); + break; + case QueueType::GpuContextName: + ptr = MemRead( &item.gpuContextNameFat.ptr ); + tracy_free( (void*)ptr ); + break; +#endif +#ifdef TRACY_ON_DEMAND + case QueueType::MessageAppInfo: + case QueueType::GpuContextName: + // Don't free memory associated with deferred messages. + break; +#endif + default: + break; + } +} + +void Profiler::ClearQueues( moodycamel::ConsumerToken& token ) +{ + for(;;) + { + const auto sz = GetQueue().try_dequeue_bulk_single( token, [](const uint64_t&){}, []( QueueItem* item, size_t sz ) { assert( sz > 0 ); while( sz-- > 0 ) FreeAssociatedMemory( *item++ ); } ); + if( sz == 0 ) break; + } + + ClearSerial(); +} + +void Profiler::ClearSerial() +{ + bool lockHeld = true; + while( !m_serialLock.try_lock() ) + { + if( m_shutdownManual.load( std::memory_order_relaxed ) ) + { + lockHeld = false; + break; + } + } + for( auto& v : m_serialQueue ) FreeAssociatedMemory( v ); + m_serialQueue.clear(); + if( lockHeld ) + { + m_serialLock.unlock(); + } + + for( auto& v : m_serialDequeue ) FreeAssociatedMemory( v ); + m_serialDequeue.clear(); +} + +Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) +{ + bool connectionLost = false; + const auto sz = GetQueue().try_dequeue_bulk_single( token, + [this, &connectionLost] ( const uint64_t& threadId ) + { + if( threadId != m_threadCtx ) + { + QueueItem item; + MemWrite( &item.hdr.type, QueueType::ThreadContext ); + MemWrite( &item.threadCtx.thread, threadId ); + if( !AppendData( &item, QueueDataSize[(int)QueueType::ThreadContext] ) ) connectionLost = true; + m_threadCtx = threadId; + m_refTimeThread = 0; + } + }, + [this, &connectionLost] ( QueueItem* item, size_t sz ) + { + if( connectionLost ) return; + assert( sz > 0 ); + int64_t refThread = m_refTimeThread; + int64_t refCtx = m_refTimeCtx; + int64_t refGpu = m_refTimeGpu; + while( sz-- > 0 ) + { + uint64_t ptr; + uint16_t size; + auto idx = MemRead( &item->hdr.idx ); + if( idx < (int)QueueType::Terminate ) + { + switch( (QueueType)idx ) + { + case QueueType::ZoneText: + case QueueType::ZoneName: + ptr = MemRead( &item->zoneTextFat.text ); + size = MemRead( &item->zoneTextFat.size ); + SendSingleString( (const char*)ptr, size ); + tracy_free( (void*)ptr ); + break; + case QueueType::Message: + case QueueType::MessageCallstack: + ptr = MemRead( &item->messageFat.text ); + size = MemRead( &item->messageFat.size ); + SendSingleString( (const char*)ptr, size ); + tracy_free( (void*)ptr ); + break; + case QueueType::MessageColor: + case QueueType::MessageColorCallstack: + ptr = MemRead( &item->messageColorFat.text ); + size = MemRead( &item->messageColorFat.size ); + SendSingleString( (const char*)ptr, size ); + tracy_free( (void*)ptr ); + break; + case QueueType::MessageAppInfo: + ptr = MemRead( &item->messageFat.text ); + size = MemRead( &item->messageFat.size ); + SendSingleString( (const char*)ptr, size ); +#ifndef TRACY_ON_DEMAND + tracy_free( (void*)ptr ); +#endif + break; + case QueueType::ZoneBeginAllocSrcLoc: + case QueueType::ZoneBeginAllocSrcLocCallstack: + { + int64_t t = MemRead( &item->zoneBegin.time ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->zoneBegin.time, dt ); + ptr = MemRead( &item->zoneBegin.srcloc ); + SendSourceLocationPayload( ptr ); + tracy_free( (void*)ptr ); + break; + } + case QueueType::Callstack: + ptr = MemRead( &item->callstackFat.ptr ); + SendCallstackPayload( ptr ); + tracy_free( (void*)ptr ); + break; + case QueueType::CallstackAlloc: + ptr = MemRead( &item->callstackAllocFat.nativePtr ); + if( ptr != 0 ) + { + CutCallstack( (void*)ptr, "lua_pcall" ); + SendCallstackPayload( ptr ); + tracy_free( (void*)ptr ); + } + ptr = MemRead( &item->callstackAllocFat.ptr ); + SendCallstackAlloc( ptr ); + tracy_free( (void*)ptr ); + break; + case QueueType::CallstackSample: + { + ptr = MemRead( &item->callstackSampleFat.ptr ); + SendCallstackPayload64( ptr ); + tracy_free( (void*)ptr ); + int64_t t = MemRead( &item->callstackSampleFat.time ); + int64_t dt = t - refCtx; + refCtx = t; + MemWrite( &item->callstackSampleFat.time, dt ); + break; + } + case QueueType::FrameImage: + { + ptr = MemRead( &item->frameImageFat.image ); + const auto w = MemRead( &item->frameImageFat.w ); + const auto h = MemRead( &item->frameImageFat.h ); + const auto csz = size_t( w * h / 2 ); + SendLongString( ptr, (const char*)ptr, csz, QueueType::FrameImageData ); + tracy_free( (void*)ptr ); + break; + } + case QueueType::ZoneBegin: + case QueueType::ZoneBeginCallstack: + { + int64_t t = MemRead( &item->zoneBegin.time ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->zoneBegin.time, dt ); + break; + } + case QueueType::ZoneEnd: + { + int64_t t = MemRead( &item->zoneEnd.time ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->zoneEnd.time, dt ); + break; + } + case QueueType::GpuZoneBegin: + case QueueType::GpuZoneBeginCallstack: + { + int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->gpuZoneBegin.cpuTime, dt ); + break; + } + case QueueType::GpuZoneBeginAllocSrcLoc: + case QueueType::GpuZoneBeginAllocSrcLocCallstack: + { + int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->gpuZoneBegin.cpuTime, dt ); + ptr = MemRead( &item->gpuZoneBegin.srcloc ); + SendSourceLocationPayload( ptr ); + tracy_free( (void*)ptr ); + break; + } + case QueueType::GpuZoneEnd: + { + int64_t t = MemRead( &item->gpuZoneEnd.cpuTime ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->gpuZoneEnd.cpuTime, dt ); + break; + } + case QueueType::GpuContextName: + ptr = MemRead( &item->gpuContextNameFat.ptr ); + size = MemRead( &item->gpuContextNameFat.size ); + SendSingleString( (const char*)ptr, size ); +#ifndef TRACY_ON_DEMAND + tracy_free( (void*)ptr ); +#endif + break; + case QueueType::PlotData: + { + int64_t t = MemRead( &item->plotData.time ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->plotData.time, dt ); + break; + } + case QueueType::ContextSwitch: + { + int64_t t = MemRead( &item->contextSwitch.time ); + int64_t dt = t - refCtx; + refCtx = t; + MemWrite( &item->contextSwitch.time, dt ); + break; + } + case QueueType::ThreadWakeup: + { + int64_t t = MemRead( &item->threadWakeup.time ); + int64_t dt = t - refCtx; + refCtx = t; + MemWrite( &item->threadWakeup.time, dt ); + break; + } + case QueueType::GpuTime: + { + int64_t t = MemRead( &item->gpuTime.gpuTime ); + int64_t dt = t - refGpu; + refGpu = t; + MemWrite( &item->gpuTime.gpuTime, dt ); + break; + } + default: + assert( false ); + break; + } + } + if( !AppendData( item++, QueueDataSize[idx] ) ) + { + connectionLost = true; + m_refTimeThread = refThread; + m_refTimeCtx = refCtx; + m_refTimeGpu = refGpu; + return; + } + } + m_refTimeThread = refThread; + m_refTimeCtx = refCtx; + m_refTimeGpu = refGpu; + } + ); + if( connectionLost ) return DequeueStatus::ConnectionLost; + return sz > 0 ? DequeueStatus::DataDequeued : DequeueStatus::QueueEmpty; +} + +Profiler::DequeueStatus Profiler::DequeueContextSwitches( tracy::moodycamel::ConsumerToken& token, int64_t& timeStop ) +{ + const auto sz = GetQueue().try_dequeue_bulk_single( token, [] ( const uint64_t& ) {}, + [this, &timeStop] ( QueueItem* item, size_t sz ) + { + assert( sz > 0 ); + int64_t refCtx = m_refTimeCtx; + while( sz-- > 0 ) + { + FreeAssociatedMemory( *item ); + if( timeStop < 0 ) return; + const auto idx = MemRead( &item->hdr.idx ); + if( idx == (uint8_t)QueueType::ContextSwitch ) + { + const auto csTime = MemRead( &item->contextSwitch.time ); + if( csTime > timeStop ) + { + timeStop = -1; + m_refTimeCtx = refCtx; + return; + } + int64_t dt = csTime - refCtx; + refCtx = csTime; + MemWrite( &item->contextSwitch.time, dt ); + if( !AppendData( item, QueueDataSize[(int)QueueType::ContextSwitch] ) ) + { + timeStop = -2; + m_refTimeCtx = refCtx; + return; + } + } + else if( idx == (uint8_t)QueueType::ThreadWakeup ) + { + const auto csTime = MemRead( &item->threadWakeup.time ); + if( csTime > timeStop ) + { + timeStop = -1; + m_refTimeCtx = refCtx; + return; + } + int64_t dt = csTime - refCtx; + refCtx = csTime; + MemWrite( &item->threadWakeup.time, dt ); + if( !AppendData( item, QueueDataSize[(int)QueueType::ThreadWakeup] ) ) + { + timeStop = -2; + m_refTimeCtx = refCtx; + return; + } + } + item++; + } + m_refTimeCtx = refCtx; + } + ); + + if( timeStop == -2 ) return DequeueStatus::ConnectionLost; + return ( timeStop == -1 || sz > 0 ) ? DequeueStatus::DataDequeued : DequeueStatus::QueueEmpty; +} + +Profiler::DequeueStatus Profiler::DequeueSerial() +{ + { + bool lockHeld = true; + while( !m_serialLock.try_lock() ) + { + if( m_shutdownManual.load( std::memory_order_relaxed ) ) + { + lockHeld = false; + break; + } + } + if( !m_serialQueue.empty() ) m_serialQueue.swap( m_serialDequeue ); + if( lockHeld ) + { + m_serialLock.unlock(); + } + } + + const auto sz = m_serialDequeue.size(); + if( sz > 0 ) + { + int64_t refSerial = m_refTimeSerial; + int64_t refGpu = m_refTimeGpu; + auto item = m_serialDequeue.data(); + auto end = item + sz; + while( item != end ) + { + uint64_t ptr; + auto idx = MemRead( &item->hdr.idx ); + if( idx < (int)QueueType::Terminate ) + { + switch( (QueueType)idx ) + { + case QueueType::CallstackSerial: + ptr = MemRead( &item->callstackFat.ptr ); + SendCallstackPayload( ptr ); + tracy_free( (void*)ptr ); + break; + case QueueType::LockWait: + case QueueType::LockSharedWait: + { + int64_t t = MemRead( &item->lockWait.time ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->lockWait.time, dt ); + break; + } + case QueueType::LockObtain: + case QueueType::LockSharedObtain: + { + int64_t t = MemRead( &item->lockObtain.time ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->lockObtain.time, dt ); + break; + } + case QueueType::LockRelease: + case QueueType::LockSharedRelease: + { + int64_t t = MemRead( &item->lockRelease.time ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->lockRelease.time, dt ); + break; + } + case QueueType::LockName: + { + ptr = MemRead( &item->lockNameFat.name ); + uint16_t size = MemRead( &item->lockNameFat.size ); + SendSingleString( (const char*)ptr, size ); +#ifndef TRACY_ON_DEMAND + tracy_free( (void*)ptr ); +#endif + break; + } + case QueueType::MemAlloc: + case QueueType::MemAllocNamed: + case QueueType::MemAllocCallstack: + case QueueType::MemAllocCallstackNamed: + { + int64_t t = MemRead( &item->memAlloc.time ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->memAlloc.time, dt ); + break; + } + case QueueType::MemFree: + case QueueType::MemFreeNamed: + case QueueType::MemFreeCallstack: + case QueueType::MemFreeCallstackNamed: + { + int64_t t = MemRead( &item->memFree.time ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->memFree.time, dt ); + break; + } + case QueueType::GpuZoneBeginSerial: + case QueueType::GpuZoneBeginCallstackSerial: + { + int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->gpuZoneBegin.cpuTime, dt ); + break; + } + case QueueType::GpuZoneBeginAllocSrcLocSerial: + case QueueType::GpuZoneBeginAllocSrcLocCallstackSerial: + { + int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->gpuZoneBegin.cpuTime, dt ); + ptr = MemRead( &item->gpuZoneBegin.srcloc ); + SendSourceLocationPayload( ptr ); + tracy_free( (void*)ptr ); + break; + } + case QueueType::GpuZoneEndSerial: + { + int64_t t = MemRead( &item->gpuZoneEnd.cpuTime ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->gpuZoneEnd.cpuTime, dt ); + break; + } + case QueueType::GpuTime: + { + int64_t t = MemRead( &item->gpuTime.gpuTime ); + int64_t dt = t - refGpu; + refGpu = t; + MemWrite( &item->gpuTime.gpuTime, dt ); + break; + } + case QueueType::GpuContextName: + { + ptr = MemRead( &item->gpuContextNameFat.ptr ); + uint16_t size = MemRead( &item->gpuContextNameFat.size ); + SendSingleString( (const char*)ptr, size ); +#ifndef TRACY_ON_DEMAND + tracy_free( (void*)ptr ); +#endif + break; + } + default: + assert( false ); + break; + } + } + if( !AppendData( item, QueueDataSize[idx] ) ) return DequeueStatus::ConnectionLost; + item++; + } + m_refTimeSerial = refSerial; + m_refTimeGpu = refGpu; + m_serialDequeue.clear(); + } + else + { + return DequeueStatus::QueueEmpty; + } + return DequeueStatus::DataDequeued; +} + +bool Profiler::CommitData() +{ + bool ret = SendData( m_buffer + m_bufferStart, m_bufferOffset - m_bufferStart ); + if( m_bufferOffset > TargetFrameSize * 2 ) m_bufferOffset = 0; + m_bufferStart = m_bufferOffset; + return ret; +} + +bool Profiler::SendData( const char* data, size_t len ) +{ + const lz4sz_t lz4sz = LZ4_compress_fast_continue( (LZ4_stream_t*)m_stream, data, m_lz4Buf + sizeof( lz4sz_t ), (int)len, LZ4Size, 1 ); + memcpy( m_lz4Buf, &lz4sz, sizeof( lz4sz ) ); + return m_sock->Send( m_lz4Buf, lz4sz + sizeof( lz4sz_t ) ) != -1; +} + +void Profiler::SendString( uint64_t str, const char* ptr, size_t len, QueueType type ) +{ + assert( type == QueueType::StringData || + type == QueueType::ThreadName || + type == QueueType::PlotName || + type == QueueType::FrameName || + type == QueueType::ExternalName || + type == QueueType::ExternalThreadName ); + + QueueItem item; + MemWrite( &item.hdr.type, type ); + MemWrite( &item.stringTransfer.ptr, str ); + + assert( len <= std::numeric_limits::max() ); + auto l16 = uint16_t( len ); + + NeedDataSize( QueueDataSize[(int)type] + sizeof( l16 ) + l16 ); + + AppendDataUnsafe( &item, QueueDataSize[(int)type] ); + AppendDataUnsafe( &l16, sizeof( l16 ) ); + AppendDataUnsafe( ptr, l16 ); +} + +void Profiler::SendSingleString( const char* ptr, size_t len ) +{ + QueueItem item; + MemWrite( &item.hdr.type, QueueType::SingleStringData ); + + assert( len <= std::numeric_limits::max() ); + auto l16 = uint16_t( len ); + + NeedDataSize( QueueDataSize[(int)QueueType::SingleStringData] + sizeof( l16 ) + l16 ); + + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::SingleStringData] ); + AppendDataUnsafe( &l16, sizeof( l16 ) ); + AppendDataUnsafe( ptr, l16 ); +} + +void Profiler::SendSecondString( const char* ptr, size_t len ) +{ + QueueItem item; + MemWrite( &item.hdr.type, QueueType::SecondStringData ); + + assert( len <= std::numeric_limits::max() ); + auto l16 = uint16_t( len ); + + NeedDataSize( QueueDataSize[(int)QueueType::SecondStringData] + sizeof( l16 ) + l16 ); + + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::SecondStringData] ); + AppendDataUnsafe( &l16, sizeof( l16 ) ); + AppendDataUnsafe( ptr, l16 ); +} + +void Profiler::SendLongString( uint64_t str, const char* ptr, size_t len, QueueType type ) +{ + assert( type == QueueType::FrameImageData || + type == QueueType::SymbolCode || + type == QueueType::SourceCode ); + + QueueItem item; + MemWrite( &item.hdr.type, type ); + MemWrite( &item.stringTransfer.ptr, str ); + + assert( len <= std::numeric_limits::max() ); + assert( QueueDataSize[(int)type] + sizeof( uint32_t ) + len <= TargetFrameSize ); + auto l32 = uint32_t( len ); + + NeedDataSize( QueueDataSize[(int)type] + sizeof( l32 ) + l32 ); + + AppendDataUnsafe( &item, QueueDataSize[(int)type] ); + AppendDataUnsafe( &l32, sizeof( l32 ) ); + AppendDataUnsafe( ptr, l32 ); +} + +void Profiler::SendSourceLocation( uint64_t ptr ) +{ + auto srcloc = (const SourceLocationData*)ptr; + QueueItem item; + MemWrite( &item.hdr.type, QueueType::SourceLocation ); + MemWrite( &item.srcloc.name, (uint64_t)srcloc->name ); + MemWrite( &item.srcloc.file, (uint64_t)srcloc->file ); + MemWrite( &item.srcloc.function, (uint64_t)srcloc->function ); + MemWrite( &item.srcloc.line, srcloc->line ); + MemWrite( &item.srcloc.r, uint8_t( ( srcloc->color ) & 0xFF ) ); + MemWrite( &item.srcloc.g, uint8_t( ( srcloc->color >> 8 ) & 0xFF ) ); + MemWrite( &item.srcloc.b, uint8_t( ( srcloc->color >> 16 ) & 0xFF ) ); + AppendData( &item, QueueDataSize[(int)QueueType::SourceLocation] ); +} + +void Profiler::SendSourceLocationPayload( uint64_t _ptr ) +{ + auto ptr = (const char*)_ptr; + + QueueItem item; + MemWrite( &item.hdr.type, QueueType::SourceLocationPayload ); + MemWrite( &item.stringTransfer.ptr, _ptr ); + + uint16_t len; + memcpy( &len, ptr, sizeof( len ) ); + assert( len > 2 ); + len -= 2; + ptr += 2; + + NeedDataSize( QueueDataSize[(int)QueueType::SourceLocationPayload] + sizeof( len ) + len ); + + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::SourceLocationPayload] ); + AppendDataUnsafe( &len, sizeof( len ) ); + AppendDataUnsafe( ptr, len ); +} + +void Profiler::SendCallstackPayload( uint64_t _ptr ) +{ + auto ptr = (uintptr_t*)_ptr; + + QueueItem item; + MemWrite( &item.hdr.type, QueueType::CallstackPayload ); + MemWrite( &item.stringTransfer.ptr, _ptr ); + + const auto sz = *ptr++; + const auto len = sz * sizeof( uint64_t ); + const auto l16 = uint16_t( len ); + + NeedDataSize( QueueDataSize[(int)QueueType::CallstackPayload] + sizeof( l16 ) + l16 ); + + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::CallstackPayload] ); + AppendDataUnsafe( &l16, sizeof( l16 ) ); + + if( compile_time_condition::value ) + { + AppendDataUnsafe( ptr, sizeof( uint64_t ) * sz ); + } + else + { + for( uintptr_t i=0; iRead( &payload, sizeof( payload ), 10 ) ) return false; + + uint8_t type; + uint64_t ptr; + uint32_t extra; + memcpy( &type, &payload.type, sizeof( payload.type ) ); + memcpy( &ptr, &payload.ptr, sizeof( payload.ptr ) ); + memcpy( &extra, &payload.extra, sizeof( payload.extra ) ); + + switch( type ) + { + case ServerQueryString: + SendString( ptr, (const char*)ptr, QueueType::StringData ); + break; + case ServerQueryThreadString: + if( ptr == m_mainThread ) + { + SendString( ptr, "Main thread", 11, QueueType::ThreadName ); + } + else + { + SendString( ptr, GetThreadName( ptr ), QueueType::ThreadName ); + } + break; + case ServerQuerySourceLocation: + SendSourceLocation( ptr ); + break; + case ServerQueryPlotName: + SendString( ptr, (const char*)ptr, QueueType::PlotName ); + break; + case ServerQueryTerminate: + return false; + case ServerQueryCallstackFrame: + SendCallstackFrame( ptr ); + break; + case ServerQueryFrameName: + SendString( ptr, (const char*)ptr, QueueType::FrameName ); + break; + case ServerQueryDisconnect: + HandleDisconnect(); + return false; +#ifdef TRACY_HAS_SYSTEM_TRACING + case ServerQueryExternalName: + SysTraceSendExternalName( ptr ); + break; +#endif + case ServerQueryParameter: + HandleParameter( ptr ); + break; + case ServerQuerySymbol: + HandleSymbolQuery( ptr ); + break; +#ifndef TRACY_NO_CODE_TRANSFER + case ServerQuerySymbolCode: + HandleSymbolCodeQuery( ptr, extra ); + break; +#endif + case ServerQueryCodeLocation: + SendCodeLocation( ptr ); + break; + case ServerQuerySourceCode: + HandleSourceCodeQuery(); + break; + case ServerQueryDataTransfer: + assert( !m_queryData ); + m_queryDataPtr = m_queryData = (char*)tracy_malloc( ptr + 11 ); + AckServerQuery(); + break; + case ServerQueryDataTransferPart: + memcpy( m_queryDataPtr, &ptr, 8 ); + memcpy( m_queryDataPtr+8, &extra, 4 ); + m_queryDataPtr += 12; + AckServerQuery(); + break; + default: + assert( false ); + break; + } + + return true; +} + +void Profiler::HandleDisconnect() +{ + moodycamel::ConsumerToken token( GetQueue() ); + +#ifdef TRACY_HAS_SYSTEM_TRACING + if( s_sysTraceThread ) + { + auto timestamp = GetTime(); + for(;;) + { + const auto status = DequeueContextSwitches( token, timestamp ); + if( status == DequeueStatus::ConnectionLost ) + { + return; + } + else if( status == DequeueStatus::QueueEmpty ) + { + if( m_bufferOffset != m_bufferStart ) + { + if( !CommitData() ) return; + } + } + if( timestamp < 0 ) + { + if( m_bufferOffset != m_bufferStart ) + { + if( !CommitData() ) return; + } + break; + } + ClearSerial(); + if( m_sock->HasData() ) + { + while( m_sock->HasData() ) + { + if( !HandleServerQuery() ) return; + } + if( m_bufferOffset != m_bufferStart ) + { + if( !CommitData() ) return; + } + } + else + { + if( m_bufferOffset != m_bufferStart ) + { + if( !CommitData() ) return; + } + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + } + } +#endif + + QueueItem terminate; + MemWrite( &terminate.hdr.type, QueueType::Terminate ); + if( !SendData( (const char*)&terminate, 1 ) ) return; + for(;;) + { + ClearQueues( token ); + if( m_sock->HasData() ) + { + while( m_sock->HasData() ) + { + if( !HandleServerQuery() ) return; + } + if( m_bufferOffset != m_bufferStart ) + { + if( !CommitData() ) return; + } + } + else + { + if( m_bufferOffset != m_bufferStart ) + { + if( !CommitData() ) return; + } + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + } +} + +void Profiler::CalibrateTimer() +{ +#ifdef TRACY_HW_TIMER + std::atomic_signal_fence( std::memory_order_acq_rel ); + const auto t0 = std::chrono::high_resolution_clock::now(); + const auto r0 = GetTime(); + std::atomic_signal_fence( std::memory_order_acq_rel ); + std::this_thread::sleep_for( std::chrono::milliseconds( 200 ) ); + std::atomic_signal_fence( std::memory_order_acq_rel ); + const auto t1 = std::chrono::high_resolution_clock::now(); + const auto r1 = GetTime(); + std::atomic_signal_fence( std::memory_order_acq_rel ); + + const auto dt = std::chrono::duration_cast( t1 - t0 ).count(); + const auto dr = r1 - r0; + + m_timerMul = double( dt ) / double( dr ); +#else + m_timerMul = 1.; +#endif +} + +void Profiler::CalibrateDelay() +{ + constexpr int Iterations = 50000; + + auto mindiff = std::numeric_limits::max(); + for( int i=0; i 0 && dti < mindiff ) mindiff = dti; + } + m_resolution = mindiff; + +#ifdef TRACY_DELAYED_INIT + m_delay = m_resolution; +#else + constexpr int Events = Iterations * 2; // start + end + static_assert( Events < QueuePrealloc, "Delay calibration loop will allocate memory in queue" ); + + static const tracy::SourceLocationData __tracy_source_location { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; + const auto t0 = GetTime(); + for( int i=0; izoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, (uint64_t)&__tracy_source_location ); + TracyLfqCommit; + } + { + TracyLfqPrepare( QueueType::ZoneEnd ); + MemWrite( &item->zoneEnd.time, GetTime() ); + TracyLfqCommit; + } + } + const auto t1 = GetTime(); + const auto dt = t1 - t0; + m_delay = dt / Events; + + moodycamel::ConsumerToken token( GetQueue() ); + int left = Events; + while( left != 0 ) + { + const auto sz = GetQueue().try_dequeue_bulk_single( token, [](const uint64_t&){}, [](QueueItem* item, size_t sz){} ); + assert( sz > 0 ); + left -= (int)sz; + } + assert( GetQueue().size_approx() == 0 ); +#endif +} + +void Profiler::ReportTopology() +{ +#ifndef TRACY_DELAYED_INIT + struct CpuData + { + uint32_t package; + uint32_t core; + uint32_t thread; + }; + +#if defined _WIN32 || defined __CYGWIN__ + t_GetLogicalProcessorInformationEx _GetLogicalProcessorInformationEx = (t_GetLogicalProcessorInformationEx)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "GetLogicalProcessorInformationEx" ); + if( !_GetLogicalProcessorInformationEx ) return; + + DWORD psz = 0; + _GetLogicalProcessorInformationEx( RelationProcessorPackage, nullptr, &psz ); + auto packageInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( psz ); + auto res = _GetLogicalProcessorInformationEx( RelationProcessorPackage, packageInfo, &psz ); + assert( res ); + + DWORD csz = 0; + _GetLogicalProcessorInformationEx( RelationProcessorCore, nullptr, &csz ); + auto coreInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( csz ); + res = _GetLogicalProcessorInformationEx( RelationProcessorCore, coreInfo, &csz ); + assert( res ); + + SYSTEM_INFO sysinfo; + GetSystemInfo( &sysinfo ); + const uint32_t numcpus = sysinfo.dwNumberOfProcessors; + + auto cpuData = (CpuData*)tracy_malloc( sizeof( CpuData ) * numcpus ); + for( uint32_t i=0; iRelationship == RelationProcessorPackage ); + // FIXME account for GroupCount + auto mask = ptr->Processor.GroupMask[0].Mask; + int core = 0; + while( mask != 0 ) + { + if( mask & 1 ) cpuData[core].package = idx; + core++; + mask >>= 1; + } + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); + idx++; + } + + idx = 0; + ptr = coreInfo; + while( (char*)ptr < ((char*)coreInfo) + csz ) + { + assert( ptr->Relationship == RelationProcessorCore ); + // FIXME account for GroupCount + auto mask = ptr->Processor.GroupMask[0].Mask; + int core = 0; + while( mask != 0 ) + { + if( mask & 1 ) cpuData[core].core = idx; + core++; + mask >>= 1; + } + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); + idx++; + } + + for( uint32_t i=0; icpuTopology.package, data.package ); + MemWrite( &item->cpuTopology.core, data.core ); + MemWrite( &item->cpuTopology.thread, data.thread ); + +#ifdef TRACY_ON_DEMAND + DeferItem( *item ); +#endif + + TracyLfqCommit; + } + + tracy_free( cpuData ); + tracy_free( coreInfo ); + tracy_free( packageInfo ); +#elif defined __linux__ + const int numcpus = std::thread::hardware_concurrency(); + auto cpuData = (CpuData*)tracy_malloc( sizeof( CpuData ) * numcpus ); + memset( cpuData, 0, sizeof( CpuData ) * numcpus ); + + const char* basePath = "/sys/devices/system/cpu/cpu"; + for( int i=0; icpuTopology.package, data.package ); + MemWrite( &item->cpuTopology.core, data.core ); + MemWrite( &item->cpuTopology.thread, data.thread ); + +#ifdef TRACY_ON_DEMAND + DeferItem( *item ); +#endif + + TracyLfqCommit; + } + + tracy_free( cpuData ); +#endif +#endif +} + +void Profiler::SendFrameMark( const char* name ) +{ + if( !name ) GetProfiler().m_frameCount.fetch_add( 1, std::memory_order_relaxed ); +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + TracyLfqPrepare( QueueType::FrameMarkMsg ); + MemWrite( &item->frameMark.time, GetTime() ); + MemWrite( &item->frameMark.name, uint64_t( name ) ); + TracyLfqCommit; +} + +void Profiler::SendFrameMark( const char* name, QueueType type ) +{ + assert( type == QueueType::FrameMarkMsgStart || type == QueueType::FrameMarkMsgEnd ); +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + auto item = QueueSerial(); + MemWrite( &item->hdr.type, type ); + MemWrite( &item->frameMark.time, GetTime() ); + MemWrite( &item->frameMark.name, uint64_t( name ) ); + QueueSerialFinish(); +} + +void Profiler::PlotData( const char* name, int64_t val ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + TracyLfqPrepare( QueueType::PlotData ); + MemWrite( &item->plotData.name, (uint64_t)name ); + MemWrite( &item->plotData.time, GetTime() ); + MemWrite( &item->plotData.type, PlotDataType::Int ); + MemWrite( &item->plotData.data.i, val ); + TracyLfqCommit; +} + +void Profiler::PlotData( const char* name, float val ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + TracyLfqPrepare( QueueType::PlotData ); + MemWrite( &item->plotData.name, (uint64_t)name ); + MemWrite( &item->plotData.time, GetTime() ); + MemWrite( &item->plotData.type, PlotDataType::Float ); + MemWrite( &item->plotData.data.f, val ); + TracyLfqCommit; +} + +void Profiler::PlotData( const char* name, double val ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + TracyLfqPrepare( QueueType::PlotData ); + MemWrite( &item->plotData.name, (uint64_t)name ); + MemWrite( &item->plotData.time, GetTime() ); + MemWrite( &item->plotData.type, PlotDataType::Double ); + MemWrite( &item->plotData.data.d, val ); + TracyLfqCommit; +} + +void Profiler::ConfigurePlot( const char* name, PlotFormatType type ) +{ + TracyLfqPrepare( QueueType::PlotConfig ); + MemWrite( &item->plotConfig.name, (uint64_t)name ); + MemWrite( &item->plotConfig.type, (uint8_t)type ); + +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + + TracyLfqCommit; +} + + void Profiler::Message( const char* txt, size_t size, int callstack ) +{ + assert( size < std::numeric_limits::max() ); +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + if( callstack != 0 ) + { + InitRPMallocThread(); + tracy::GetProfiler().SendCallstack( callstack ); + } + + TracyLfqPrepare( callstack == 0 ? QueueType::Message : QueueType::MessageCallstack ); + auto ptr = (char*)tracy_malloc( size ); + memcpy( ptr, txt, size ); + MemWrite( &item->messageFat.time, GetTime() ); + MemWrite( &item->messageFat.text, (uint64_t)ptr ); + MemWrite( &item->messageFat.size, (uint16_t)size ); + TracyLfqCommit; +} + +void Profiler::Message( const char* txt, int callstack ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + if( callstack != 0 ) + { + InitRPMallocThread(); + tracy::GetProfiler().SendCallstack( callstack ); + } + + TracyLfqPrepare( callstack == 0 ? QueueType::MessageLiteral : QueueType::MessageLiteralCallstack ); + MemWrite( &item->messageLiteral.time, GetTime() ); + MemWrite( &item->messageLiteral.text, (uint64_t)txt ); + TracyLfqCommit; +} + +void Profiler::MessageColor( const char* txt, size_t size, uint32_t color, int callstack ) +{ + assert( size < std::numeric_limits::max() ); +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + if( callstack != 0 ) + { + InitRPMallocThread(); + tracy::GetProfiler().SendCallstack( callstack ); + } + + TracyLfqPrepare( callstack == 0 ? QueueType::MessageColor : QueueType::MessageColorCallstack ); + auto ptr = (char*)tracy_malloc( size ); + memcpy( ptr, txt, size ); + MemWrite( &item->messageColorFat.time, GetTime() ); + MemWrite( &item->messageColorFat.text, (uint64_t)ptr ); + MemWrite( &item->messageColorFat.r, uint8_t( ( color ) & 0xFF ) ); + MemWrite( &item->messageColorFat.g, uint8_t( ( color >> 8 ) & 0xFF ) ); + MemWrite( &item->messageColorFat.b, uint8_t( ( color >> 16 ) & 0xFF ) ); + MemWrite( &item->messageColorFat.size, (uint16_t)size ); + TracyLfqCommit; +} + +void Profiler::MessageColor( const char* txt, uint32_t color, int callstack ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + if( callstack != 0 ) + { + InitRPMallocThread(); + tracy::GetProfiler().SendCallstack( callstack ); + } + + TracyLfqPrepare( callstack == 0 ? QueueType::MessageLiteralColor : QueueType::MessageLiteralColorCallstack ); + MemWrite( &item->messageColorLiteral.time, GetTime() ); + MemWrite( &item->messageColorLiteral.text, (uint64_t)txt ); + MemWrite( &item->messageColorLiteral.r, uint8_t( ( color ) & 0xFF ) ); + MemWrite( &item->messageColorLiteral.g, uint8_t( ( color >> 8 ) & 0xFF ) ); + MemWrite( &item->messageColorLiteral.b, uint8_t( ( color >> 16 ) & 0xFF ) ); + TracyLfqCommit; +} + +void Profiler::MessageAppInfo( const char* txt, size_t size ) +{ + assert( size < std::numeric_limits::max() ); + InitRPMallocThread(); + auto ptr = (char*)tracy_malloc( size ); + memcpy( ptr, txt, size ); + TracyLfqPrepare( QueueType::MessageAppInfo ); + MemWrite( &item->messageFat.time, GetTime() ); + MemWrite( &item->messageFat.text, (uint64_t)ptr ); + MemWrite( &item->messageFat.size, (uint16_t)size ); + +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + + TracyLfqCommit; +} + +void Profiler::MemAlloc(const void* ptr, size_t size, bool secure) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + const auto thread = GetThreadHandle(); + + GetProfiler().m_serialLock.lock(); + SendMemAlloc( QueueType::MemAlloc, thread, ptr, size ); + GetProfiler().m_serialLock.unlock(); +} + +void Profiler::MemFree( const void* ptr, bool secure ) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + const auto thread = GetThreadHandle(); + + GetProfiler().m_serialLock.lock(); + SendMemFree( QueueType::MemFree, thread, ptr ); + GetProfiler().m_serialLock.unlock(); +} + +void Profiler::MemAllocCallstack( const void* ptr, size_t size, int depth, bool secure ) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_HAS_CALLSTACK + auto& profiler = GetProfiler(); +# ifdef TRACY_ON_DEMAND + if( !profiler.IsConnected() ) return; +# endif + const auto thread = GetThreadHandle(); + + InitRPMallocThread(); + auto callstack = Callstack( depth ); + + profiler.m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemAlloc( QueueType::MemAllocCallstack, thread, ptr, size ); + profiler.m_serialLock.unlock(); +#else + MemAlloc( ptr, size, secure ); +#endif +} + +void Profiler::MemFreeCallstack( const void* ptr, int depth, bool secure ) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_HAS_CALLSTACK + auto& profiler = GetProfiler(); +# ifdef TRACY_ON_DEMAND + if( !profiler.IsConnected() ) return; +# endif + const auto thread = GetThreadHandle(); + + InitRPMallocThread(); + auto callstack = Callstack( depth ); + + profiler.m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemFree( QueueType::MemFreeCallstack, thread, ptr ); + profiler.m_serialLock.unlock(); +#else + MemFree( ptr, secure ); +#endif +} + +void Profiler::MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name ) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + const auto thread = GetThreadHandle(); + + GetProfiler().m_serialLock.lock(); + SendMemName( name ); + SendMemAlloc( QueueType::MemAllocNamed, thread, ptr, size ); + GetProfiler().m_serialLock.unlock(); +} + +void Profiler::MemFreeNamed( const void* ptr, bool secure, const char* name ) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + const auto thread = GetThreadHandle(); + + GetProfiler().m_serialLock.lock(); + SendMemName( name ); + SendMemFree( QueueType::MemFreeNamed, thread, ptr ); + GetProfiler().m_serialLock.unlock(); +} + +void Profiler::MemAllocCallstackNamed( const void* ptr, size_t size, int depth, bool secure, const char* name ) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_HAS_CALLSTACK + auto& profiler = GetProfiler(); +# ifdef TRACY_ON_DEMAND + if( !profiler.IsConnected() ) return; +# endif + const auto thread = GetThreadHandle(); + + InitRPMallocThread(); + auto callstack = Callstack( depth ); + + profiler.m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemName( name ); + SendMemAlloc( QueueType::MemAllocCallstackNamed, thread, ptr, size ); + profiler.m_serialLock.unlock(); +#else + MemAlloc( ptr, size, secure ); +#endif +} + +void Profiler::MemFreeCallstackNamed( const void* ptr, int depth, bool secure, const char* name ) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_HAS_CALLSTACK + auto& profiler = GetProfiler(); +# ifdef TRACY_ON_DEMAND + if( !profiler.IsConnected() ) return; +# endif + const auto thread = GetThreadHandle(); + + InitRPMallocThread(); + auto callstack = Callstack( depth ); + + profiler.m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemName( name ); + SendMemFree( QueueType::MemFreeCallstackNamed, thread, ptr ); + profiler.m_serialLock.unlock(); +#else + MemFree( ptr, secure ); +#endif +} + +void Profiler::SendCallstack( int depth ) +{ +#ifdef TRACY_HAS_CALLSTACK + auto ptr = Callstack( depth ); + TracyLfqPrepare( QueueType::Callstack ); + MemWrite( &item->callstackFat.ptr, (uint64_t)ptr ); + TracyLfqCommit; +#endif +} + +void Profiler::ParameterRegister( ParameterCallback cb ) { GetProfiler().m_paramCallback = cb; } +void Profiler::ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val ) +{ + TracyLfqPrepare( QueueType::ParamSetup ); + tracy::MemWrite( &item->paramSetup.idx, idx ); + tracy::MemWrite( &item->paramSetup.name, (uint64_t)name ); + tracy::MemWrite( &item->paramSetup.isBool, (uint8_t)isBool ); + tracy::MemWrite( &item->paramSetup.val, val ); + +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + + TracyLfqCommit; +} + +void Profiler::SendCallstack( int depth, const char* skipBefore ) +{ +#ifdef TRACY_HAS_CALLSTACK + TracyLfqPrepare( QueueType::Callstack ); + auto ptr = Callstack( depth ); + CutCallstack( ptr, skipBefore ); + MemWrite( &item->callstackFat.ptr, (uint64_t)ptr ); + TracyLfqCommit; +#endif +} + +void Profiler::CutCallstack( void* callstack, const char* skipBefore ) +{ +#ifdef TRACY_HAS_CALLSTACK + auto data = (uintptr_t*)callstack; + const auto sz = *data++; + uintptr_t i; + for( i=0; i 100000000 ) // 100 ms + { + auto sysTime = m_sysTime.Get(); + if( sysTime >= 0 ) + { + m_sysTimeLast = t; + + TracyLfqPrepare( QueueType::SysTimeReport ); + MemWrite( &item->sysTime.time, GetTime() ); + MemWrite( &item->sysTime.sysTime, sysTime ); + TracyLfqCommit; + } + } +} +#endif + +void Profiler::HandleParameter( uint64_t payload ) +{ + assert( m_paramCallback ); + const auto idx = uint32_t( payload >> 32 ); + const auto val = int32_t( payload & 0xFFFFFFFF ); + m_paramCallback( idx, val ); + AckServerQuery(); +} + +#ifdef __ANDROID__ +// Implementation helpers of EnsureReadable(address). +// This is so far only needed on Android, where it is common for libraries to be mapped +// with only executable, not readable, permissions. Typical example (line from /proc/self/maps): +/* +746b63b000-746b6dc000 --xp 00042000 07:48 35 /apex/com.android.runtime/lib64/bionic/libc.so +*/ +// See https://github.com/wolfpld/tracy/issues/125 . +// To work around this, we parse /proc/self/maps and we use mprotect to set read permissions +// on any mappings that contain symbols addresses hit by HandleSymbolCodeQuery. + +namespace { +// Holds some information about a single memory mapping. +struct MappingInfo { + // Start of address range. Inclusive. + uintptr_t start_address; + // End of address range. Exclusive, so the mapping is the half-open interval + // [start, end) and its length in bytes is `end - start`. As in /proc/self/maps. + uintptr_t end_address; + // Read/Write/Executable permissions. + bool perm_r, perm_w, perm_x; +}; +} // anonymous namespace + +// Internal implementation helper for LookUpMapping(address). +// +// Parses /proc/self/maps returning a vector. +// /proc/self/maps is assumed to be sorted by ascending address, so the resulting +// vector is sorted by ascending address too. +static std::vector ParseMappings() +{ + std::vector result; + FILE* file = fopen( "/proc/self/maps", "r" ); + if( !file ) return result; + char line[1024]; + while( fgets( line, sizeof( line ), file ) ) + { + uintptr_t start_addr; + uintptr_t end_addr; + if( sscanf( line, "%lx-%lx", &start_addr, &end_addr ) != 2 ) continue; + char* first_space = strchr( line, ' ' ); + if( !first_space ) continue; + char* perm = first_space + 1; + char* second_space = strchr( perm, ' ' ); + if( !second_space || second_space - perm != 4 ) continue; + result.emplace_back(); + auto& mapping = result.back(); + mapping.start_address = start_addr; + mapping.end_address = end_addr; + mapping.perm_r = perm[0] == 'r'; + mapping.perm_w = perm[1] == 'w'; + mapping.perm_x = perm[2] == 'x'; + } + fclose( file ); + return result; +} + +// Internal implementation helper for LookUpMapping(address). +// +// Takes as input an `address` and a known vector `mappings`, assumed to be +// sorted by increasing addresses, as /proc/self/maps seems to be. +// Returns a pointer to the MappingInfo describing the mapping that this +// address belongs to, or nullptr if the address isn't in `mappings`. +static MappingInfo* LookUpMapping(std::vector& mappings, uintptr_t address) +{ + // Comparison function for std::lower_bound. Returns true if all addresses in `m1` + // are lower than `addr`. + auto Compare = []( const MappingInfo& m1, uintptr_t addr ) { + // '<=' because the address ranges are half-open intervals, [start, end). + return m1.end_address <= addr; + }; + auto iter = std::lower_bound( mappings.begin(), mappings.end(), address, Compare ); + if( iter == mappings.end() || iter->start_address > address) { + return nullptr; + } + return &*iter; +} + +// Internal implementation helper for EnsureReadable(address). +// +// Takes as input an `address` and returns a pointer to a MappingInfo +// describing the mapping that this address belongs to, or nullptr if +// the address isn't in any known mapping. +// +// This function is stateful and not reentrant (assumes to be called from +// only one thread). It holds a vector of mappings parsed from /proc/self/maps. +// +// Attempts to react to mappings changes by re-parsing /proc/self/maps. +static MappingInfo* LookUpMapping(uintptr_t address) +{ + // Static state managed by this function. Not constant, we mutate that state as + // we turn some mappings readable. Initially parsed once here, updated as needed below. + static std::vector s_mappings = ParseMappings(); + MappingInfo* mapping = LookUpMapping( s_mappings, address ); + if( mapping ) return mapping; + + // This address isn't in any known mapping. Try parsing again, maybe + // mappings changed. + s_mappings = ParseMappings(); + return LookUpMapping( s_mappings, address ); +} + +// Internal implementation helper for EnsureReadable(address). +// +// Attempts to make the specified `mapping` readable if it isn't already. +// Returns true if and only if the mapping is readable. +static bool EnsureReadable( MappingInfo& mapping ) +{ + if( mapping.perm_r ) + { + // The mapping is already readable. + return true; + } + int prot = PROT_READ; + if( mapping.perm_w ) prot |= PROT_WRITE; + if( mapping.perm_x ) prot |= PROT_EXEC; + if( mprotect( reinterpret_cast( mapping.start_address ), + mapping.end_address - mapping.start_address, prot ) == -1 ) + { + // Failed to make the mapping readable. Shouldn't happen, hasn't + // been observed yet. If it happened in practice, we should consider + // adding a bool to MappingInfo to track this to avoid retrying mprotect + // everytime on such mappings. + return false; + } + // The mapping is now readable. Update `mapping` so the next call will be fast. + mapping.perm_r = true; + return true; +} + +// Attempts to set the read permission on the entire mapping containing the +// specified address. Returns true if and only if the mapping is now readable. +static bool EnsureReadable( uintptr_t address ) +{ + MappingInfo* mapping = LookUpMapping(address); + return mapping && EnsureReadable( *mapping ); +} + +#endif // defined __ANDROID__ + +void Profiler::HandleSymbolQuery( uint64_t symbol ) +{ +#ifdef TRACY_HAS_CALLSTACK +#ifdef __ANDROID__ + // On Android it's common for code to be in mappings that are only executable + // but not readable. + if( !EnsureReadable( symbol ) ) + { + return; + } +#endif + const auto sym = DecodeSymbolAddress( symbol ); + + SendSingleString( sym.file ); + + QueueItem item; + MemWrite( &item.hdr.type, QueueType::SymbolInformation ); + MemWrite( &item.symbolInformation.line, sym.line ); + MemWrite( &item.symbolInformation.symAddr, symbol ); + + AppendData( &item, QueueDataSize[(int)QueueType::SymbolInformation] ); + + if( sym.needFree ) tracy_free( (void*)sym.file ); +#endif +} + +void Profiler::HandleSymbolCodeQuery( uint64_t symbol, uint32_t size ) +{ +#ifdef __ANDROID__ + // On Android it's common for code to be in mappings that are only executable + // but not readable. + if( !EnsureReadable( symbol ) ) + { + return; + } +#endif + SendLongString( symbol, (const char*)symbol, size, QueueType::SymbolCode ); +} + +void Profiler::HandleSourceCodeQuery() +{ + assert( m_exectime != 0 ); + assert( m_queryData ); + + struct stat st; + if( stat( m_queryData, &st ) == 0 && (uint64_t)st.st_mtime < m_exectime && st.st_size < ( TargetFrameSize - 16 ) ) + { + FILE* f = fopen( m_queryData, "rb" ); + tracy_free( m_queryData ); + if( f ) + { + auto ptr = (char*)tracy_malloc( st.st_size ); + auto rd = fread( ptr, 1, st.st_size, f ); + fclose( f ); + if( rd == (size_t)st.st_size ) + { + SendLongString( (uint64_t)ptr, ptr, rd, QueueType::SourceCode ); + } + else + { + AckSourceCodeNotAvailable(); + } + tracy_free( ptr ); + } + else + { + AckSourceCodeNotAvailable(); + } + } + else + { + tracy_free( m_queryData ); + AckSourceCodeNotAvailable(); + } + m_queryData = nullptr; +} + +void Profiler::SendCodeLocation( uint64_t ptr ) +{ +#ifdef TRACY_HAS_CALLSTACK + const auto sym = DecodeCodeAddress( ptr ); + + SendSingleString( sym.file ); + + QueueItem item; + MemWrite( &item.hdr.type, QueueType::CodeInformation ); + MemWrite( &item.codeInformation.ptr, ptr ); + MemWrite( &item.codeInformation.line, sym.line ); + + AppendData( &item, QueueDataSize[(int)QueueType::CodeInformation] ); + + if( sym.needFree ) tracy_free( (void*)sym.file ); +#endif +} + +#if ( defined _WIN32 || defined __CYGWIN__ ) && defined TRACY_TIMER_QPC +int64_t Profiler::GetTimeQpc() +{ + LARGE_INTEGER t; + QueryPerformanceCounter( &t ); + return t.QuadPart; +} +#endif + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.hpp b/Source/ThirdParty/tracy/client/TracyProfiler.hpp new file mode 100644 index 000000000..434ca60cf --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyProfiler.hpp @@ -0,0 +1,452 @@ +#ifndef __TRACYPROFILER_HPP__ +#define __TRACYPROFILER_HPP__ + +#include +#include +#include +#include +#include + +#include "tracy_concurrentqueue.h" +#include "TracyCallstack.hpp" +#include "TracySysTime.hpp" +#include "TracyFastVector.hpp" +#include "../common/TracyQueue.hpp" +#include "../common/TracyAlign.hpp" +#include "../common/TracyAlloc.hpp" +#include "../common/TracyMutex.hpp" +#include "../common/TracyProtocol.hpp" + +#if defined _WIN32 || defined __CYGWIN__ +# include +#endif +#ifdef __APPLE__ +# include +# include +#endif + +#if !defined TRACY_TIMER_FALLBACK && ( defined _WIN32 || defined __CYGWIN__ || ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) || ( defined TARGET_OS_IOS && TARGET_OS_IOS == 1 ) ) +# define TRACY_HW_TIMER +#endif + +#if !defined TRACY_HW_TIMER +# include +#endif + +namespace tracy +{ +#if defined(TRACY_DELAYED_INIT) && defined(TRACY_MANUAL_LIFETIME) +TRACY_API void StartupProfiler(); +TRACY_API void ShutdownProfiler(); +#endif + +class GpuCtx; +class Profiler; +class Socket; +class UdpBroadcast; + +struct GpuCtxWrapper +{ + GpuCtx* ptr; +}; + +TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* GetToken(); +TRACY_API Profiler& GetProfiler(); +TRACY_API std::atomic& GetLockCounter(); +TRACY_API std::atomic& GetGpuCtxCounter(); +TRACY_API GpuCtxWrapper& GetGpuCtx(); +TRACY_API uint64_t GetThreadHandle(); +TRACY_API void InitRPMallocThread(); +TRACY_API bool ProfilerAvailable(); +TRACY_API int64_t GetFrequencyQpc(); + +#ifdef TRACY_ON_DEMAND +struct LuaZoneState +{ + uint32_t counter; + bool active; +}; +#endif + + +#define TracyLfqPrepare( _type ) \ + moodycamel::ConcurrentQueueDefaultTraits::index_t __magic; \ + auto __token = GetToken(); \ + auto& __tail = __token->get_tail_index(); \ + auto item = __token->enqueue_begin( __magic ); \ + MemWrite( &item->hdr.type, _type ); + +#define TracyLfqCommit \ + __tail.store( __magic + 1, std::memory_order_release ); + +#define TracyLfqPrepareC( _type ) \ + tracy::moodycamel::ConcurrentQueueDefaultTraits::index_t __magic; \ + auto __token = tracy::GetToken(); \ + auto& __tail = __token->get_tail_index(); \ + auto item = __token->enqueue_begin( __magic ); \ + tracy::MemWrite( &item->hdr.type, _type ); + +#define TracyLfqCommitC \ + __tail.store( __magic + 1, std::memory_order_release ); + + +class TRACY_API Profiler +{ + struct FrameImageQueueItem + { + void* image; + uint32_t frame; + uint16_t w; + uint16_t h; + uint8_t offset; + bool flip; + }; + +public: + Profiler(); + ~Profiler(); + + void SpawnWorkerThreads(); + + static tracy_force_inline int64_t GetTime() + { +#ifdef TRACY_HW_TIMER +# if defined TARGET_OS_IOS && TARGET_OS_IOS == 1 + return mach_absolute_time(); +# elif defined _WIN32 || defined __CYGWIN__ +# ifdef TRACY_TIMER_QPC + return GetTimeQpc(); +# else + return int64_t( __rdtsc() ); +# endif +# elif defined __i386 || defined _M_IX86 + uint32_t eax, edx; + asm volatile ( "rdtsc" : "=a" (eax), "=d" (edx) ); + return ( uint64_t( edx ) << 32 ) + uint64_t( eax ); +# elif defined __x86_64__ || defined _M_X64 + uint64_t rax, rdx; + asm volatile ( "rdtsc" : "=a" (rax), "=d" (rdx) ); + return (int64_t)(( rdx << 32 ) + rax); +# else +# error "TRACY_HW_TIMER detection logic needs fixing" +# endif +#else +# if defined __linux__ && defined CLOCK_MONOTONIC_RAW + struct timespec ts; + clock_gettime( CLOCK_MONOTONIC_RAW, &ts ); + return int64_t( ts.tv_sec ) * 1000000000ll + int64_t( ts.tv_nsec ); +# else + return std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); +# endif +#endif + } + + tracy_force_inline uint32_t GetNextZoneId() + { + return m_zoneId.fetch_add( 1, std::memory_order_relaxed ); + } + + static tracy_force_inline QueueItem* QueueSerial() + { + auto& p = GetProfiler(); + p.m_serialLock.lock(); + return p.m_serialQueue.prepare_next(); + } + + static tracy_force_inline QueueItem* QueueSerialCallstack( void* ptr ) + { + auto& p = GetProfiler(); + p.m_serialLock.lock(); + p.SendCallstackSerial( ptr ); + return p.m_serialQueue.prepare_next(); + } + + static tracy_force_inline void QueueSerialFinish() + { + auto& p = GetProfiler(); + p.m_serialQueue.commit_next(); + p.m_serialLock.unlock(); + } + + static void SendFrameMark( const char* name ); + static void SendFrameMark( const char* name, QueueType type ); + static void PlotData( const char* name, int64_t val ); + static void PlotData( const char* name, float val ); + static void PlotData( const char* name, double val ); + static void ConfigurePlot( const char* name, PlotFormatType type ); + static void Message( const char* txt, size_t size, int callstack ); + static void Message( const char* txt, int callstack ); + static void MessageColor( const char* txt, size_t size, uint32_t color, int callstack ); + static void MessageColor( const char* txt, uint32_t color, int callstack ); + static void MessageAppInfo( const char* txt, size_t size ); + static void MemAlloc( const void* ptr, size_t size, bool secure ); + static void MemFree( const void* ptr, bool secure ); + static void MemAllocCallstack( const void* ptr, size_t size, int depth, bool secure ); + static void MemFreeCallstack( const void* ptr, int depth, bool secure ); + static void MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name ); + static void MemFreeNamed( const void* ptr, bool secure, const char* name ); + static void MemAllocCallstackNamed( const void* ptr, size_t size, int depth, bool secure, const char* name ); + static void MemFreeCallstackNamed( const void* ptr, int depth, bool secure, const char* name ); + static void SendCallstack( int depth ); + static void ParameterRegister( ParameterCallback cb ); + static void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val ); + + void SendCallstack( int depth, const char* skipBefore ); + static void CutCallstack( void* callstack, const char* skipBefore ); + + static bool ShouldExit(); + + tracy_force_inline bool IsConnected() const + { + return m_isConnected.load( std::memory_order_acquire ); + } + +#ifdef TRACY_ON_DEMAND + tracy_force_inline uint64_t ConnectionId() const + { + return m_connectionId.load( std::memory_order_acquire ); + } + + tracy_force_inline void DeferItem( const QueueItem& item ) + { + m_deferredLock.lock(); + auto dst = m_deferredQueue.push_next(); + memcpy( dst, &item, sizeof( item ) ); + m_deferredLock.unlock(); + } +#endif + + void RequestShutdown() { m_shutdown.store( true, std::memory_order_relaxed ); m_shutdownManual.store( true, std::memory_order_relaxed ); } + bool HasShutdownFinished() const { return m_shutdownFinished.load( std::memory_order_relaxed ); } + + void SendString( uint64_t str, const char* ptr, QueueType type ) { SendString( str, ptr, strlen( ptr ), type ); } + void SendString( uint64_t str, const char* ptr, size_t len, QueueType type ); + void SendSingleString( const char* ptr ) { SendSingleString( ptr, strlen( ptr ) ); } + void SendSingleString( const char* ptr, size_t len ); + void SendSecondString( const char* ptr ) { SendSecondString( ptr, strlen( ptr ) ); } + void SendSecondString( const char* ptr, size_t len ); + + + // Allocated source location data layout: + // 2b payload size + // 4b color + // 4b source line + // fsz function name + // 1b null terminator + // ssz source file name + // 1b null terminator + // nsz zone name (optional) + + static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, const char* function ) + { + return AllocSourceLocation( line, source, function, nullptr, 0 ); + } + + static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, const char* function, const char* name, size_t nameSz ) + { + return AllocSourceLocation( line, source, strlen(source), function, strlen(function), name, nameSz ); + } + + static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz ) + { + return AllocSourceLocation( line, source, sourceSz, function, functionSz, nullptr, 0 ); + } + + static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz ) + { + const auto sz32 = uint32_t( 2 + 4 + 4 + functionSz + 1 + sourceSz + 1 + nameSz ); + assert( sz32 <= std::numeric_limits::max() ); + const auto sz = uint16_t( sz32 ); + auto ptr = (char*)tracy_malloc( sz ); + memcpy( ptr, &sz, 2 ); + memset( ptr + 2, 0, 4 ); + memcpy( ptr + 6, &line, 4 ); + memcpy( ptr + 10, function, functionSz ); + ptr[10 + functionSz] = '\0'; + memcpy( ptr + 10 + functionSz + 1, source, sourceSz ); + ptr[10 + functionSz + 1 + sourceSz] = '\0'; + if( nameSz != 0 ) + { + memcpy( ptr + 10 + functionSz + 1 + sourceSz + 1, name, nameSz ); + } + return uint64_t( ptr ); + } + +private: + enum class DequeueStatus { DataDequeued, ConnectionLost, QueueEmpty }; + + static void LaunchWorker( void* ptr ) { ((Profiler*)ptr)->Worker(); } + void Worker(); + + void ClearQueues( tracy::moodycamel::ConsumerToken& token ); + void ClearSerial(); + DequeueStatus Dequeue( tracy::moodycamel::ConsumerToken& token ); + DequeueStatus DequeueContextSwitches( tracy::moodycamel::ConsumerToken& token, int64_t& timeStop ); + DequeueStatus DequeueSerial(); + bool CommitData(); + + tracy_force_inline bool AppendData( const void* data, size_t len ) + { + const auto ret = NeedDataSize( len ); + AppendDataUnsafe( data, len ); + return ret; + } + + tracy_force_inline bool NeedDataSize( size_t len ) + { + assert( len <= TargetFrameSize ); + bool ret = true; + if( m_bufferOffset - m_bufferStart + (int)len > TargetFrameSize ) + { + ret = CommitData(); + } + return ret; + } + + tracy_force_inline void AppendDataUnsafe( const void* data, size_t len ) + { + memcpy( m_buffer + m_bufferOffset, data, len ); + m_bufferOffset += int( len ); + } + + bool SendData( const char* data, size_t len ); + void SendLongString( uint64_t ptr, const char* str, size_t len, QueueType type ); + void SendSourceLocation( uint64_t ptr ); + void SendSourceLocationPayload( uint64_t ptr ); + void SendCallstackPayload( uint64_t ptr ); + void SendCallstackPayload64( uint64_t ptr ); + void SendCallstackAlloc( uint64_t ptr ); + void SendCallstackFrame( uint64_t ptr ); + void SendCodeLocation( uint64_t ptr ); + + bool HandleServerQuery(); + void HandleDisconnect(); + void HandleParameter( uint64_t payload ); + void HandleSymbolQuery( uint64_t symbol ); + void HandleSymbolCodeQuery( uint64_t symbol, uint32_t size ); + void HandleSourceCodeQuery(); + + void AckServerQuery(); + void AckSourceCodeNotAvailable(); + + void CalibrateTimer(); + void CalibrateDelay(); + void ReportTopology(); + + static tracy_force_inline void SendCallstackSerial( void* ptr ) + { +#ifdef TRACY_HAS_CALLSTACK + auto item = GetProfiler().m_serialQueue.prepare_next(); + MemWrite( &item->hdr.type, QueueType::CallstackSerial ); + MemWrite( &item->callstackFat.ptr, (uint64_t)ptr ); + GetProfiler().m_serialQueue.commit_next(); +#endif + } + + static tracy_force_inline void SendMemAlloc( QueueType type, const uint64_t thread, const void* ptr, size_t size ) + { + assert( type == QueueType::MemAlloc || type == QueueType::MemAllocCallstack || type == QueueType::MemAllocNamed || type == QueueType::MemAllocCallstackNamed ); + + auto item = GetProfiler().m_serialQueue.prepare_next(); + MemWrite( &item->hdr.type, type ); + MemWrite( &item->memAlloc.time, GetTime() ); + MemWrite( &item->memAlloc.thread, thread ); + MemWrite( &item->memAlloc.ptr, (uint64_t)ptr ); + if( compile_time_condition::value ) + { + memcpy( &item->memAlloc.size, &size, 4 ); + memset( &item->memAlloc.size + 4, 0, 2 ); + } + else + { + assert( sizeof( size ) == 8 ); + memcpy( &item->memAlloc.size, &size, 4 ); + memcpy( ((char*)&item->memAlloc.size)+4, ((char*)&size)+4, 2 ); + } + GetProfiler().m_serialQueue.commit_next(); + } + + static tracy_force_inline void SendMemFree( QueueType type, const uint64_t thread, const void* ptr ) + { + assert( type == QueueType::MemFree || type == QueueType::MemFreeCallstack || type == QueueType::MemFreeNamed || type == QueueType::MemFreeCallstackNamed ); + + auto item = GetProfiler().m_serialQueue.prepare_next(); + MemWrite( &item->hdr.type, type ); + MemWrite( &item->memFree.time, GetTime() ); + MemWrite( &item->memFree.thread, thread ); + MemWrite( &item->memFree.ptr, (uint64_t)ptr ); + GetProfiler().m_serialQueue.commit_next(); + } + + static tracy_force_inline void SendMemName( const char* name ) + { + assert( name ); + auto item = GetProfiler().m_serialQueue.prepare_next(); + MemWrite( &item->hdr.type, QueueType::MemNamePayload ); + MemWrite( &item->memName.name, (uint64_t)name ); + GetProfiler().m_serialQueue.commit_next(); + } + +#if ( defined _WIN32 || defined __CYGWIN__ ) && defined TRACY_TIMER_QPC + static int64_t GetTimeQpc(); +#endif + + double m_timerMul; + uint64_t m_resolution; + uint64_t m_delay; + std::atomic m_timeBegin; + uint64_t m_mainThread; + uint64_t m_epoch, m_exectime; + std::atomic m_shutdown; + std::atomic m_shutdownManual; + std::atomic m_shutdownFinished; + Socket* m_sock; + UdpBroadcast* m_broadcast; + bool m_noExit; + uint32_t m_userPort; + std::atomic m_zoneId; + int64_t m_samplingPeriod; + + uint64_t m_threadCtx; + int64_t m_refTimeThread; + int64_t m_refTimeSerial; + int64_t m_refTimeCtx; + int64_t m_refTimeGpu; + + void* m_stream; // LZ4_stream_t* + char* m_buffer; + int m_bufferOffset; + int m_bufferStart; + + char* m_lz4Buf; + + FastVector m_serialQueue, m_serialDequeue; + TracyMutex m_serialLock; + + std::atomic m_frameCount; + std::atomic m_isConnected; +#ifdef TRACY_ON_DEMAND + std::atomic m_connectionId; + + TracyMutex m_deferredLock; + FastVector m_deferredQueue; +#endif + +#ifdef TRACY_HAS_SYSTIME + void ProcessSysTime(); + + SysTime m_sysTime; + uint64_t m_sysTimeLast = 0; +#else + void ProcessSysTime() {} +#endif + + ParameterCallback m_paramCallback; + + char* m_queryData; + char* m_queryDataPtr; +}; + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyRingBuffer.hpp b/Source/ThirdParty/tracy/client/TracyRingBuffer.hpp new file mode 100644 index 000000000..29d935596 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyRingBuffer.hpp @@ -0,0 +1,116 @@ +namespace tracy +{ + +template +class RingBuffer +{ +public: + RingBuffer( int fd ) + : m_fd( fd ) + { + const auto pageSize = uint32_t( getpagesize() ); + assert( Size >= pageSize ); + assert( __builtin_popcount( Size ) == 1 ); + m_mapSize = Size + pageSize; + auto mapAddr = mmap( nullptr, m_mapSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); + if( !mapAddr ) + { + m_fd = 0; + close( fd ); + return; + } + m_metadata = (perf_event_mmap_page*)mapAddr; + assert( m_metadata->data_offset == pageSize ); + m_buffer = ((char*)mapAddr) + pageSize; + } + + ~RingBuffer() + { + if( m_metadata ) munmap( m_metadata, m_mapSize ); + if( m_fd ) close( m_fd ); + } + + RingBuffer( const RingBuffer& ) = delete; + RingBuffer& operator=( const RingBuffer& ) = delete; + + RingBuffer( RingBuffer&& other ) + { + memcpy( (char*)&other, (char*)this, sizeof( RingBuffer ) ); + m_metadata = nullptr; + m_fd = 0; + } + + RingBuffer& operator=( RingBuffer&& other ) + { + memcpy( (char*)&other, (char*)this, sizeof( RingBuffer ) ); + m_metadata = nullptr; + m_fd = 0; + return *this; + } + + bool IsValid() const { return m_metadata != nullptr; } + + void Enable() + { + ioctl( m_fd, PERF_EVENT_IOC_ENABLE, 0 ); + } + + bool HasData() const + { + const auto head = LoadHead(); + return head > m_metadata->data_tail; + } + + void Read( void* dst, uint64_t offset, uint64_t cnt ) + { + auto src = ( m_metadata->data_tail + offset ) % Size; + if( src + cnt <= Size ) + { + memcpy( dst, m_buffer + src, cnt ); + } + else + { + const auto s0 = Size - src; + memcpy( dst, m_buffer + src, s0 ); + memcpy( (char*)dst + s0, m_buffer, cnt - s0 ); + } + } + + void Advance( uint64_t cnt ) + { + StoreTail( m_metadata->data_tail + cnt ); + } + + bool CheckTscCaps() const + { + return m_metadata->cap_user_time_zero; + } + + int64_t ConvertTimeToTsc( int64_t timestamp ) const + { + assert( m_metadata->cap_user_time_zero ); + const auto time = timestamp - m_metadata->time_zero; + const auto quot = time / m_metadata->time_mult; + const auto rem = time % m_metadata->time_mult; + return ( quot << m_metadata->time_shift ) + ( rem << m_metadata->time_shift ) / m_metadata->time_mult; + } + +private: + uint64_t LoadHead() const + { + return std::atomic_load_explicit( (const volatile std::atomic*)&m_metadata->data_head, std::memory_order_acquire ); + } + + void StoreTail( uint64_t tail ) + { + std::atomic_store_explicit( (volatile std::atomic*)&m_metadata->data_tail, tail, std::memory_order_release ); + } + + perf_event_mmap_page* m_metadata; + char* m_buffer; + + size_t m_mapSize; + int m_fd; +}; + +} diff --git a/Source/ThirdParty/tracy/client/TracyScoped.hpp b/Source/ThirdParty/tracy/client/TracyScoped.hpp new file mode 100644 index 000000000..fa6a52808 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyScoped.hpp @@ -0,0 +1,174 @@ +#ifndef __TRACYSCOPED_HPP__ +#define __TRACYSCOPED_HPP__ + +#include +#include +#include + +#include "../common/TracySystem.hpp" +#include "../common/TracyAlign.hpp" +#include "../common/TracyAlloc.hpp" +#include "TracyProfiler.hpp" + +namespace tracy +{ +inline ScopedZone::ScopedZone( const SourceLocationData* srcloc, bool is_active ) +#ifdef TRACY_ON_DEMAND + : m_active( is_active && GetProfiler().IsConnected() ) +#else + : m_active( is_active ) +#endif +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + m_connectionId = GetProfiler().ConnectionId(); +#endif + TracyLfqPrepare( QueueType::ZoneBegin ); + MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); + TracyLfqCommit; +} + +inline ScopedZone::ScopedZone( const SourceLocationData* srcloc, int depth, bool is_active ) +#ifdef TRACY_ON_DEMAND + : m_active( is_active && GetProfiler().IsConnected() ) +#else + : m_active( is_active ) +#endif +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + m_connectionId = GetProfiler().ConnectionId(); +#endif + GetProfiler().SendCallstack( depth ); + + TracyLfqPrepare( QueueType::ZoneBeginCallstack ); + MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); + TracyLfqCommit; +} + +inline ScopedZone::ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, bool is_active ) +#ifdef TRACY_ON_DEMAND + : m_active( is_active && GetProfiler().IsConnected() ) +#else + : m_active( is_active ) +#endif +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + m_connectionId = GetProfiler().ConnectionId(); +#endif + TracyLfqPrepare( QueueType::ZoneBeginAllocSrcLoc ); + const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz ); + MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, srcloc ); + TracyLfqCommit; +} + +inline ScopedZone::ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, int depth, bool is_active ) +#ifdef TRACY_ON_DEMAND + : m_active( is_active && GetProfiler().IsConnected() ) +#else + : m_active( is_active ) +#endif +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + m_connectionId = GetProfiler().ConnectionId(); +#endif + GetProfiler().SendCallstack( depth ); + + TracyLfqPrepare( QueueType::ZoneBeginAllocSrcLocCallstack ); + const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz ); + MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, srcloc ); + TracyLfqCommit; +} + +inline ScopedZone::~ScopedZone() +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif + TracyLfqPrepare( QueueType::ZoneEnd ); + MemWrite( &item->zoneEnd.time, Profiler::GetTime() ); + TracyLfqCommit; +} + +inline void ScopedZone::Text( const char* txt, size_t size ) +{ + assert( size < std::numeric_limits::max() ); + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif + auto ptr = (char*)tracy_malloc( size ); + memcpy( ptr, txt, size ); + TracyLfqPrepare( QueueType::ZoneText ); + MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyLfqCommit; +} + +inline void ScopedZone::Name( const char* txt, size_t size ) +{ + assert( size < std::numeric_limits::max() ); + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif + auto ptr = (char*)tracy_malloc( size ); + memcpy( ptr, txt, size ); + TracyLfqPrepare( QueueType::ZoneName ); + MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyLfqCommit; +} + +inline void ScopedZone::Name( const Char* txt, size_t size ) +{ + assert( size < std::numeric_limits::max() ); + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif + auto ptr = (char*)tracy_malloc( size ); + for( int i = 0; i < size; i++) + ptr[i] = (char)txt[i]; + TracyLfqPrepare( QueueType::ZoneName ); + MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyLfqCommit; +} + +inline void ScopedZone::Color( uint32_t color ) +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif + TracyLfqPrepare( QueueType::ZoneColor ); + MemWrite( &item->zoneColor.r, uint8_t( ( color ) & 0xFF ) ); + MemWrite( &item->zoneColor.g, uint8_t( ( color >> 8 ) & 0xFF ) ); + MemWrite( &item->zoneColor.b, uint8_t( ( color >> 16 ) & 0xFF ) ); + TracyLfqCommit; +} + +inline void ScopedZone::Value( uint64_t value ) +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif + TracyLfqPrepare( QueueType::ZoneValue ); + MemWrite( &item->zoneValue.value, value ); + TracyLfqCommit; +} + +inline bool ScopedZone::IsActive() const { return m_active; } + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/TracySysTime.cpp b/Source/ThirdParty/tracy/client/TracySysTime.cpp new file mode 100644 index 000000000..e5903467d --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracySysTime.cpp @@ -0,0 +1,108 @@ +#include "TracySysTime.hpp" + +#ifdef TRACY_HAS_SYSTIME + +# if defined _WIN32 || defined __CYGWIN__ +# include +# elif defined __linux__ +# include +# include +# elif defined __APPLE__ +# include +# include +# elif defined BSD +# include +# include +# endif + +namespace tracy +{ + +# if defined _WIN32 || defined __CYGWIN__ + +static inline uint64_t ConvertTime( const FILETIME& t ) +{ + return ( uint64_t( t.dwHighDateTime ) << 32 ) | uint64_t( t.dwLowDateTime ); +} + +void SysTime::ReadTimes() +{ + FILETIME idleTime; + FILETIME kernelTime; + FILETIME userTime; + + GetSystemTimes( &idleTime, &kernelTime, &userTime ); + + idle = ConvertTime( idleTime ); + const auto kernel = ConvertTime( kernelTime ); + const auto user = ConvertTime( userTime ); + used = kernel + user; +} + +# elif defined __linux__ + +void SysTime::ReadTimes() +{ + uint64_t user, nice, system; + FILE* f = fopen( "/proc/stat", "r" ); + if( f ) + { + int read = fscanf( f, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64" %" PRIu64, &user, &nice, &system, &idle ); + fclose( f ); + if (read == 4) + { + used = user + nice + system; + } + } +} + +# elif defined __APPLE__ + +void SysTime::ReadTimes() +{ + host_cpu_load_info_data_t info; + mach_msg_type_number_t cnt = HOST_CPU_LOAD_INFO_COUNT; + host_statistics( mach_host_self(), HOST_CPU_LOAD_INFO, reinterpret_cast( &info ), &cnt ); + used = info.cpu_ticks[CPU_STATE_USER] + info.cpu_ticks[CPU_STATE_NICE] + info.cpu_ticks[CPU_STATE_SYSTEM]; + idle = info.cpu_ticks[CPU_STATE_IDLE]; +} + +# elif defined BSD + +void SysTime::ReadTimes() +{ + u_long data[5]; + size_t sz = sizeof( data ); + sysctlbyname( "kern.cp_time", &data, &sz, nullptr, 0 ); + used = data[0] + data[1] + data[2] + data[3]; + idle = data[4]; +} + +#endif + +SysTime::SysTime() +{ + ReadTimes(); +} + +float SysTime::Get() +{ + const auto oldUsed = used; + const auto oldIdle = idle; + + ReadTimes(); + + const auto diffIdle = idle - oldIdle; + const auto diffUsed = used - oldUsed; + +#if defined _WIN32 || defined __CYGWIN__ + return diffUsed == 0 ? -1 : ( diffUsed - diffIdle ) * 100.f / diffUsed; +#elif defined __linux__ || defined __APPLE__ || defined BSD + const auto total = diffUsed + diffIdle; + return total == 0 ? -1 : diffUsed * 100.f / total; +#endif +} + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/TracySysTime.hpp b/Source/ThirdParty/tracy/client/TracySysTime.hpp new file mode 100644 index 000000000..fc6ba321a --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracySysTime.hpp @@ -0,0 +1,36 @@ +#ifndef __TRACYSYSTIME_HPP__ +#define __TRACYSYSTIME_HPP__ + +#if defined _WIN32 || defined __CYGWIN__ || defined __linux__ || defined __APPLE__ +# define TRACY_HAS_SYSTIME +#else +# include +#endif + +#ifdef BSD +# define TRACY_HAS_SYSTIME +#endif + +#ifdef TRACY_HAS_SYSTIME + +#include + +namespace tracy +{ + +class SysTime +{ +public: + SysTime(); + float Get(); + + void ReadTimes(); + +private: + uint64_t idle, used; +}; + +} +#endif + +#endif diff --git a/Source/ThirdParty/tracy/client/TracySysTrace.cpp b/Source/ThirdParty/tracy/client/TracySysTrace.cpp new file mode 100644 index 000000000..972779770 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracySysTrace.cpp @@ -0,0 +1,1326 @@ +#include "TracySysTrace.hpp" + +#ifdef TRACY_HAS_SYSTEM_TRACING + +# if defined _WIN32 || defined __CYGWIN__ + +# ifndef NOMINMAX +# define NOMINMAX +# endif + +# define INITGUID +# include +# include +# include +# include +# include +# include +# include +# include + +# include "../common/TracyAlloc.hpp" +# include "../common/TracySystem.hpp" +# include "TracyProfiler.hpp" +# include "TracyThread.hpp" + +namespace tracy +{ + +static const GUID PerfInfoGuid = { 0xce1dbfb4, 0x137e, 0x4da6, { 0x87, 0xb0, 0x3f, 0x59, 0xaa, 0x10, 0x2c, 0xbc } }; +static const GUID DxgKrnlGuid = { 0x802ec45a, 0x1e99, 0x4b83, { 0x99, 0x20, 0x87, 0xc9, 0x82, 0x77, 0xba, 0x9d } }; + + +static TRACEHANDLE s_traceHandle; +static TRACEHANDLE s_traceHandle2; +static EVENT_TRACE_PROPERTIES* s_prop; +static DWORD s_pid; + +static EVENT_TRACE_PROPERTIES* s_propVsync; +static TRACEHANDLE s_traceHandleVsync; +static TRACEHANDLE s_traceHandleVsync2; +Thread* s_threadVsync = nullptr; + +struct CSwitch +{ + uint32_t newThreadId; + uint32_t oldThreadId; + int8_t newThreadPriority; + int8_t oldThreadPriority; + uint8_t previousCState; + int8_t spareByte; + int8_t oldThreadWaitReason; + int8_t oldThreadWaitMode; + int8_t oldThreadState; + int8_t oldThreadWaitIdealProcessor; + uint32_t newThreadWaitTime; + uint32_t reserved; +}; + +struct ReadyThread +{ + uint32_t threadId; + int8_t adjustReason; + int8_t adjustIncrement; + int8_t flag; + int8_t reserverd; +}; + +struct ThreadTrace +{ + uint32_t processId; + uint32_t threadId; + uint32_t stackBase; + uint32_t stackLimit; + uint32_t userStackBase; + uint32_t userStackLimit; + uint32_t startAddr; + uint32_t win32StartAddr; + uint32_t tebBase; + uint32_t subProcessTag; +}; + +struct StackWalkEvent +{ + uint64_t eventTimeStamp; + uint32_t stackProcess; + uint32_t stackThread; + uint64_t stack[192]; +}; + +struct VSyncInfo +{ + void* dxgAdapter; + uint32_t vidPnTargetId; + uint64_t scannedPhysicalAddress; + uint32_t vidPnSourceId; + uint32_t frameNumber; + int64_t frameQpcTime; + void* hFlipDevice; + uint32_t flipType; + uint64_t flipFenceId; +}; + +#ifdef __CYGWIN__ +extern "C" typedef DWORD (WINAPI *t_GetProcessIdOfThread)( HANDLE ); +extern "C" typedef DWORD (WINAPI *t_GetProcessImageFileNameA)( HANDLE, LPSTR, DWORD ); +extern "C" ULONG WMIAPI TraceSetInformation(TRACEHANDLE SessionHandle, TRACE_INFO_CLASS InformationClass, PVOID TraceInformation, ULONG InformationLength); +t_GetProcessIdOfThread GetProcessIdOfThread = (t_GetProcessIdOfThread)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "GetProcessIdOfThread" ); +t_GetProcessImageFileNameA GetProcessImageFileNameA = (t_GetProcessImageFileNameA)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "K32GetProcessImageFileNameA" ); +#endif + +extern "C" typedef NTSTATUS (WINAPI *t_NtQueryInformationThread)( HANDLE, THREADINFOCLASS, PVOID, ULONG, PULONG ); +extern "C" typedef BOOL (WINAPI *t_EnumProcessModules)( HANDLE, HMODULE*, DWORD, LPDWORD ); +extern "C" typedef BOOL (WINAPI *t_GetModuleInformation)( HANDLE, HMODULE, LPMODULEINFO, DWORD ); +extern "C" typedef DWORD (WINAPI *t_GetModuleBaseNameA)( HANDLE, HMODULE, LPSTR, DWORD ); +extern "C" typedef HRESULT (WINAPI *t_GetThreadDescription)( HANDLE, PWSTR* ); + +t_NtQueryInformationThread NtQueryInformationThread = (t_NtQueryInformationThread)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "NtQueryInformationThread" ); +t_EnumProcessModules _EnumProcessModules = (t_EnumProcessModules)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "K32EnumProcessModules" ); +t_GetModuleInformation _GetModuleInformation = (t_GetModuleInformation)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "K32GetModuleInformation" ); +t_GetModuleBaseNameA _GetModuleBaseNameA = (t_GetModuleBaseNameA)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "K32GetModuleBaseNameA" ); + +static t_GetThreadDescription _GetThreadDescription = 0; + + +void WINAPI EventRecordCallback( PEVENT_RECORD record ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + + const auto& hdr = record->EventHeader; + switch( hdr.ProviderId.Data1 ) + { + case 0x3d6fa8d1: // Thread Guid + if( hdr.EventDescriptor.Opcode == 36 ) + { + const auto cswitch = (const CSwitch*)record->UserData; + + TracyLfqPrepare( QueueType::ContextSwitch ); + MemWrite( &item->contextSwitch.time, hdr.TimeStamp.QuadPart ); + memcpy( &item->contextSwitch.oldThread, &cswitch->oldThreadId, sizeof( cswitch->oldThreadId ) ); + memcpy( &item->contextSwitch.newThread, &cswitch->newThreadId, sizeof( cswitch->newThreadId ) ); + memset( ((char*)&item->contextSwitch.oldThread)+4, 0, 4 ); + memset( ((char*)&item->contextSwitch.newThread)+4, 0, 4 ); + MemWrite( &item->contextSwitch.cpu, record->BufferContext.ProcessorNumber ); + MemWrite( &item->contextSwitch.reason, cswitch->oldThreadWaitReason ); + MemWrite( &item->contextSwitch.state, cswitch->oldThreadState ); + TracyLfqCommit; + } + else if( hdr.EventDescriptor.Opcode == 50 ) + { + const auto rt = (const ReadyThread*)record->UserData; + + TracyLfqPrepare( QueueType::ThreadWakeup ); + MemWrite( &item->threadWakeup.time, hdr.TimeStamp.QuadPart ); + memcpy( &item->threadWakeup.thread, &rt->threadId, sizeof( rt->threadId ) ); + memset( ((char*)&item->threadWakeup.thread)+4, 0, 4 ); + TracyLfqCommit; + } + else if( hdr.EventDescriptor.Opcode == 1 || hdr.EventDescriptor.Opcode == 3 ) + { + const auto tt = (const ThreadTrace*)record->UserData; + + uint64_t tid = tt->threadId; + if( tid == 0 ) return; + uint64_t pid = tt->processId; + TracyLfqPrepare( QueueType::TidToPid ); + MemWrite( &item->tidToPid.tid, tid ); + MemWrite( &item->tidToPid.pid, pid ); + TracyLfqCommit; + } + break; + case 0xdef2fe46: // StackWalk Guid + if( hdr.EventDescriptor.Opcode == 32 ) + { + const auto sw = (const StackWalkEvent*)record->UserData; + if( sw->stackProcess == s_pid && ( sw->stack[0] & 0x8000000000000000 ) == 0 ) + { + const uint64_t sz = ( record->UserDataLength - 16 ) / 8; + if( sz > 0 ) + { + auto trace = (uint64_t*)tracy_malloc( ( 1 + sz ) * sizeof( uint64_t ) ); + memcpy( trace, &sz, sizeof( uint64_t ) ); + memcpy( trace+1, sw->stack, sizeof( uint64_t ) * sz ); + TracyLfqPrepare( QueueType::CallstackSample ); + MemWrite( &item->callstackSampleFat.time, sw->eventTimeStamp ); + MemWrite( &item->callstackSampleFat.thread, (uint64_t)sw->stackThread ); + MemWrite( &item->callstackSampleFat.ptr, (uint64_t)trace ); + TracyLfqCommit; + } + } + } + break; + default: + break; + } +} + +static constexpr const char* VsyncName[] = { + "[0] Vsync", + "[1] Vsync", + "[2] Vsync", + "[3] Vsync", + "[4] Vsync", + "[5] Vsync", + "[6] Vsync", + "[7] Vsync", + "Vsync" +}; + +static uint32_t VsyncTarget[8] = {}; + +void WINAPI EventRecordCallbackVsync( PEVENT_RECORD record ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + + const auto& hdr = record->EventHeader; + assert( hdr.ProviderId.Data1 == 0x802EC45A ); + assert( hdr.EventDescriptor.Id == 0x0011 ); + + const auto vs = (const VSyncInfo*)record->UserData; + + int idx = 0; + do + { + if( VsyncTarget[idx] == 0 ) + { + VsyncTarget[idx] = vs->vidPnTargetId; + break; + } + else if( VsyncTarget[idx] == vs->vidPnTargetId ) + { + break; + } + } + while( ++idx < 8 ); + + TracyLfqPrepare( QueueType::FrameMarkMsg ); + MemWrite( &item->frameMark.time, hdr.TimeStamp.QuadPart ); + MemWrite( &item->frameMark.name, uint64_t( VsyncName[idx] ) ); + TracyLfqCommit; +} + +static void SetupVsync() +{ +#if _WIN32_WINNT >= _WIN32_WINNT_WINBLUE + const auto psz = sizeof( EVENT_TRACE_PROPERTIES ) + MAX_PATH; + s_propVsync = (EVENT_TRACE_PROPERTIES*)tracy_malloc( psz ); + memset( s_propVsync, 0, sizeof( EVENT_TRACE_PROPERTIES ) ); + s_propVsync->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + s_propVsync->Wnode.BufferSize = psz; +#ifdef TRACY_TIMER_QPC + s_propVsync->Wnode.ClientContext = 1; +#else + s_propVsync->Wnode.ClientContext = 3; +#endif + s_propVsync->LoggerNameOffset = sizeof( EVENT_TRACE_PROPERTIES ); + strcpy( ((char*)s_propVsync) + sizeof( EVENT_TRACE_PROPERTIES ), "TracyVsync" ); + + auto backup = tracy_malloc( psz ); + memcpy( backup, s_propVsync, psz ); + + const auto controlStatus = ControlTraceA( 0, "TracyVsync", s_propVsync, EVENT_TRACE_CONTROL_STOP ); + if( controlStatus != ERROR_SUCCESS && controlStatus != ERROR_WMI_INSTANCE_NOT_FOUND ) + { + tracy_free( backup ); + tracy_free( s_propVsync ); + return; + } + + memcpy( s_propVsync, backup, psz ); + tracy_free( backup ); + + const auto startStatus = StartTraceA( &s_traceHandleVsync, "TracyVsync", s_propVsync ); + if( startStatus != ERROR_SUCCESS ) + { + tracy_free( s_propVsync ); + return; + } + + EVENT_FILTER_EVENT_ID fe = {}; + fe.FilterIn = TRUE; + fe.Count = 1; + fe.Events[0] = 0x0011; // VSyncDPC_Info + + EVENT_FILTER_DESCRIPTOR desc = {}; + desc.Ptr = (ULONGLONG)&fe; + desc.Size = sizeof( fe ); + desc.Type = EVENT_FILTER_TYPE_EVENT_ID; + + ENABLE_TRACE_PARAMETERS params = {}; + params.Version = ENABLE_TRACE_PARAMETERS_VERSION_2; + params.EnableProperty = EVENT_ENABLE_PROPERTY_IGNORE_KEYWORD_0; + params.SourceId = s_propVsync->Wnode.Guid; + params.EnableFilterDesc = &desc; + params.FilterDescCount = 1; + + uint64_t mask = 0x4000000000000001; // Microsoft_Windows_DxgKrnl_Performance | Base + if( EnableTraceEx2( s_traceHandleVsync, &DxgKrnlGuid, EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, mask, mask, 0, ¶ms ) != ERROR_SUCCESS ) + { + tracy_free( s_propVsync ); + return; + } + + char loggerName[MAX_PATH]; + strcpy( loggerName, "TracyVsync" ); + + EVENT_TRACE_LOGFILEA log = {}; + log.LoggerName = loggerName; + log.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; + log.EventRecordCallback = EventRecordCallbackVsync; + + s_traceHandleVsync2 = OpenTraceA( &log ); + if( s_traceHandleVsync2 == (TRACEHANDLE)INVALID_HANDLE_VALUE ) + { + CloseTrace( s_traceHandleVsync ); + tracy_free( s_propVsync ); + return; + } + + s_threadVsync = (Thread*)tracy_malloc( sizeof( Thread ) ); + new(s_threadVsync) Thread( [] (void*) { + ThreadExitHandler threadExitHandler; + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); + SetThreadName( "Tracy Vsync" ); + ProcessTrace( &s_traceHandleVsync2, 1, nullptr, nullptr ); + }, nullptr ); +#endif +} + +bool SysTraceStart( int64_t& samplingPeriod ) +{ + if( !_GetThreadDescription ) _GetThreadDescription = (t_GetThreadDescription)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "GetThreadDescription" ); + + s_pid = GetCurrentProcessId(); + +#if defined _WIN64 + constexpr bool isOs64Bit = true; +#else + BOOL _iswow64; + IsWow64Process( GetCurrentProcess(), &_iswow64 ); + const bool isOs64Bit = _iswow64; +#endif + + TOKEN_PRIVILEGES priv = {}; + priv.PrivilegeCount = 1; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if( LookupPrivilegeValue( nullptr, SE_SYSTEM_PROFILE_NAME, &priv.Privileges[0].Luid ) == 0 ) return false; + + HANDLE pt; + if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &pt ) == 0 ) return false; + const auto adjust = AdjustTokenPrivileges( pt, FALSE, &priv, 0, nullptr, nullptr ); + CloseHandle( pt ); + if( adjust == 0 ) return false; + const auto status = GetLastError(); + if( status != ERROR_SUCCESS ) return false; + + if( isOs64Bit ) + { + TRACE_PROFILE_INTERVAL interval = {}; + interval.Interval = 1250; // 8 kHz + const auto intervalStatus = TraceSetInformation( 0, TraceSampledProfileIntervalInfo, &interval, sizeof( interval ) ); + if( intervalStatus != ERROR_SUCCESS ) return false; + samplingPeriod = 125*1000; + } + + const auto psz = sizeof( EVENT_TRACE_PROPERTIES ) + sizeof( KERNEL_LOGGER_NAME ); + s_prop = (EVENT_TRACE_PROPERTIES*)tracy_malloc( psz ); + memset( s_prop, 0, sizeof( EVENT_TRACE_PROPERTIES ) ); + ULONG flags = 0; +#ifndef TRACY_NO_CONTEXT_SWITCH + flags = EVENT_TRACE_FLAG_CSWITCH | EVENT_TRACE_FLAG_DISPATCHER | EVENT_TRACE_FLAG_THREAD; +#endif +#ifndef TRACY_NO_SAMPLING + if( isOs64Bit ) flags |= EVENT_TRACE_FLAG_PROFILE; +#endif + s_prop->EnableFlags = flags; + s_prop->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + s_prop->Wnode.BufferSize = psz; + s_prop->Wnode.Flags = WNODE_FLAG_TRACED_GUID; +#ifdef TRACY_TIMER_QPC + s_prop->Wnode.ClientContext = 1; +#else + s_prop->Wnode.ClientContext = 3; +#endif + s_prop->Wnode.Guid = SystemTraceControlGuid; + s_prop->BufferSize = 1024; + s_prop->MinimumBuffers = std::thread::hardware_concurrency() * 4; + s_prop->MaximumBuffers = std::thread::hardware_concurrency() * 6; + s_prop->LoggerNameOffset = sizeof( EVENT_TRACE_PROPERTIES ); + memcpy( ((char*)s_prop) + sizeof( EVENT_TRACE_PROPERTIES ), KERNEL_LOGGER_NAME, sizeof( KERNEL_LOGGER_NAME ) ); + + auto backup = tracy_malloc( psz ); + memcpy( backup, s_prop, psz ); + + const auto controlStatus = ControlTrace( 0, KERNEL_LOGGER_NAME, s_prop, EVENT_TRACE_CONTROL_STOP ); + if( controlStatus != ERROR_SUCCESS && controlStatus != ERROR_WMI_INSTANCE_NOT_FOUND ) + { + tracy_free( backup ); + tracy_free( s_prop ); + return false; + } + + memcpy( s_prop, backup, psz ); + tracy_free( backup ); + + const auto startStatus = StartTrace( &s_traceHandle, KERNEL_LOGGER_NAME, s_prop ); + if( startStatus != ERROR_SUCCESS ) + { + tracy_free( s_prop ); + return false; + } + + if( isOs64Bit ) + { + CLASSIC_EVENT_ID stackId; + stackId.EventGuid = PerfInfoGuid; + stackId.Type = 46; + const auto stackStatus = TraceSetInformation( s_traceHandle, TraceStackTracingInfo, &stackId, sizeof( stackId ) ); + if( stackStatus != ERROR_SUCCESS ) + { + tracy_free( s_prop ); + return false; + } + } + +#ifdef UNICODE + WCHAR KernelLoggerName[sizeof( KERNEL_LOGGER_NAME )]; +#else + char KernelLoggerName[sizeof( KERNEL_LOGGER_NAME )]; +#endif + memcpy( KernelLoggerName, KERNEL_LOGGER_NAME, sizeof( KERNEL_LOGGER_NAME ) ); + EVENT_TRACE_LOGFILE log = {}; + log.LoggerName = KernelLoggerName; + log.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; + log.EventRecordCallback = EventRecordCallback; + + s_traceHandle2 = OpenTrace( &log ); + if( s_traceHandle2 == (TRACEHANDLE)INVALID_HANDLE_VALUE ) + { + CloseTrace( s_traceHandle ); + tracy_free( s_prop ); + return false; + } + +#ifndef TRACY_NO_VSYNC_CAPTURE + SetupVsync(); +#endif + + return true; +} + +void SysTraceStop() +{ + if( s_threadVsync ) + { + CloseTrace( s_traceHandleVsync2 ); + CloseTrace( s_traceHandleVsync ); + s_threadVsync->~Thread(); + tracy_free( s_threadVsync ); + } + + CloseTrace( s_traceHandle2 ); + CloseTrace( s_traceHandle ); +} + +void SysTraceWorker( void* ptr ) +{ + ThreadExitHandler threadExitHandler; + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); + SetThreadName( "Tracy SysTrace" ); + ProcessTrace( &s_traceHandle2, 1, 0, 0 ); + ControlTrace( 0, KERNEL_LOGGER_NAME, s_prop, EVENT_TRACE_CONTROL_STOP ); + tracy_free( s_prop ); +} + +void SysTraceSendExternalName( uint64_t thread ) +{ + bool threadSent = false; + auto hnd = OpenThread( THREAD_QUERY_INFORMATION, FALSE, DWORD( thread ) ); + if( hnd == 0 ) + { + hnd = OpenThread( THREAD_QUERY_LIMITED_INFORMATION, FALSE, DWORD( thread ) ); + } + if( hnd != 0 ) + { + PWSTR tmp; + _GetThreadDescription( hnd, &tmp ); + char buf[256]; + if( tmp ) + { + auto ret = wcstombs( buf, tmp, 256 ); + if( ret != 0 ) + { + GetProfiler().SendString( thread, buf, ret, QueueType::ExternalThreadName ); + threadSent = true; + } + } + const auto pid = GetProcessIdOfThread( hnd ); + if( !threadSent && NtQueryInformationThread && _EnumProcessModules && _GetModuleInformation && _GetModuleBaseNameA ) + { + void* ptr; + ULONG retlen; + auto status = NtQueryInformationThread( hnd, (THREADINFOCLASS)9 /*ThreadQuerySetWin32StartAddress*/, &ptr, sizeof( &ptr ), &retlen ); + if( status == 0 ) + { + const auto phnd = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid ); + if( phnd != INVALID_HANDLE_VALUE ) + { + HMODULE modules[1024]; + DWORD needed; + if( _EnumProcessModules( phnd, modules, 1024 * sizeof( HMODULE ), &needed ) != 0 ) + { + const auto sz = std::min( DWORD( needed / sizeof( HMODULE ) ), DWORD( 1024 ) ); + for( DWORD i=0; i= (uint64_t)info.lpBaseOfDll && (uint64_t)ptr <= (uint64_t)info.lpBaseOfDll + (uint64_t)info.SizeOfImage ) + { + char buf2[1024]; + const auto modlen = _GetModuleBaseNameA( phnd, modules[i], buf2, 1024 ); + if( modlen != 0 ) + { + GetProfiler().SendString( thread, buf2, modlen, QueueType::ExternalThreadName ); + threadSent = true; + } + } + } + } + } + CloseHandle( phnd ); + } + } + } + CloseHandle( hnd ); + if( !threadSent ) + { + GetProfiler().SendString( thread, "???", 3, QueueType::ExternalThreadName ); + threadSent = true; + } + if( pid != 0 ) + { + { + uint64_t _pid = pid; + TracyLfqPrepare( QueueType::TidToPid ); + MemWrite( &item->tidToPid.tid, thread ); + MemWrite( &item->tidToPid.pid, _pid ); + TracyLfqCommit; + } + if( pid == 4 ) + { + GetProfiler().SendString( thread, "System", 6, QueueType::ExternalName ); + return; + } + else + { + const auto phnd = OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid ); + if( phnd != INVALID_HANDLE_VALUE ) + { + char buf2[1024]; + const auto sz = GetProcessImageFileNameA( phnd, buf2, 1024 ); + CloseHandle( phnd ); + if( sz != 0 ) + { + auto ptr = buf2 + sz - 1; + while( ptr > buf2 && *ptr != '\\' ) ptr--; + if( *ptr == '\\' ) ptr++; + GetProfiler().SendString( thread, ptr, QueueType::ExternalName ); + return; + } + } + } + } + } + + if( !threadSent ) + { + GetProfiler().SendString( thread, "???", 3, QueueType::ExternalThreadName ); + } + GetProfiler().SendString( thread, "???", 3, QueueType::ExternalName ); +} + +} + +# elif defined __linux__ + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "TracyProfiler.hpp" +# include "TracyRingBuffer.hpp" +# include "TracyThread.hpp" + +# ifdef __ANDROID__ +# include "TracySysTracePayload.hpp" +# endif + +namespace tracy +{ + +static const char BasePath[] = "/sys/kernel/debug/tracing/"; +static const char TracingOn[] = "tracing_on"; +static const char CurrentTracer[] = "current_tracer"; +static const char TraceOptions[] = "trace_options"; +static const char TraceClock[] = "trace_clock"; +static const char SchedSwitch[] = "events/sched/sched_switch/enable"; +static const char SchedWakeup[] = "events/sched/sched_wakeup/enable"; +static const char BufferSizeKb[] = "buffer_size_kb"; +static const char TracePipe[] = "trace_pipe"; + +static std::atomic traceActive { false }; +static Thread* s_threadSampling = nullptr; +static int s_numCpus = 0; + +static constexpr size_t RingBufSize = 64*1024; +static RingBuffer* s_ring = nullptr; + +static int perf_event_open( struct perf_event_attr* hw_event, pid_t pid, int cpu, int group_fd, unsigned long flags ) +{ + return syscall( __NR_perf_event_open, hw_event, pid, cpu, group_fd, flags ); +} + +static void SetupSampling( int64_t& samplingPeriod ) +{ +#ifndef CLOCK_MONOTONIC_RAW + return; +#endif + + samplingPeriod = 100*1000; + + s_numCpus = (int)std::thread::hardware_concurrency(); + s_ring = (RingBuffer*)tracy_malloc( sizeof( RingBuffer ) * s_numCpus ); + + perf_event_attr pe = {}; + + pe.type = PERF_TYPE_SOFTWARE; + pe.size = sizeof( perf_event_attr ); + pe.config = PERF_COUNT_SW_CPU_CLOCK; + + pe.sample_freq = 10000; + pe.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_CALLCHAIN; +#if LINUX_VERSION_CODE >= KERNEL_VERSION( 4, 8, 0 ) + pe.sample_max_stack = 127; +#endif + pe.exclude_callchain_kernel = 1; + + pe.disabled = 1; + pe.freq = 1; +#if !defined TRACY_HW_TIMER || !( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) + pe.use_clockid = 1; + pe.clockid = CLOCK_MONOTONIC_RAW; +#endif + + for( int i=0; i(); + tracy_free( s_ring ); + return; + } + new( s_ring+i ) RingBuffer( fd ); + } + + s_threadSampling = (Thread*)tracy_malloc( sizeof( Thread ) ); + new(s_threadSampling) Thread( [] (void*) { + ThreadExitHandler threadExitHandler; + SetThreadName( "Tracy Sampling" ); + sched_param sp = { 5 }; + pthread_setschedparam( pthread_self(), SCHED_FIFO, &sp ); + uint32_t currentPid = (uint32_t)getpid(); +#if defined TRACY_HW_TIMER && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) + for( int i=0; i(); + tracy_free( s_ring ); + const char* err = "Tracy Profiler: sampling is disabled due to non-native scheduler clock. Are you running under a VM?"; + Profiler::MessageAppInfo( err, strlen( err ) ); + return; + } + } +#endif + for( int i=0; i> 63; + const auto m2 = test >> 47; + if( m1 == m2 ) break; + } + while( --cnt > 0 ); + for( uint64_t j=1; j> 63; + const auto m2 = test >> 47; + if( m1 != m2 ) trace[j] = 0; + } + + // skip kernel frames + uint64_t j; + for( j=0; j= 0 ) break; + } + if( j == cnt ) + { + tracy_free( trace ); + } + else + { + if( j > 0 ) + { + cnt -= j; + memmove( trace+1, trace+1+j, sizeof( uint64_t ) * cnt ); + } + memcpy( trace, &cnt, sizeof( uint64_t ) ); + +#if defined TRACY_HW_TIMER && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) + t0 = s_ring[i].ConvertTimeToTsc( t0 ); +#endif + + TracyLfqPrepare( QueueType::CallstackSample ); + MemWrite( &item->callstackSampleFat.time, t0 ); + MemWrite( &item->callstackSampleFat.thread, (uint64_t)tid ); + MemWrite( &item->callstackSampleFat.ptr, (uint64_t)trace ); + TracyLfqCommit; + } + } + } + s_ring[i].Advance( hdr.size ); + } + if( !traceActive.load( std::memory_order_relaxed) ) break; + if( !hadData ) + { + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + } + + for( int i=0; i(); + tracy_free( s_ring ); + }, nullptr ); +} + +#ifdef __ANDROID__ +static bool TraceWrite( const char* path, size_t psz, const char* val, size_t vsz ) +{ + // Explanation for "su root sh -c": there are 2 flavors of "su" in circulation + // on Android. The default Android su has the following syntax to run a command + // as root: + // su root 'command' + // and 'command' is exec'd not passed to a shell, so if shell interpretation is + // wanted, one needs to do: + // su root sh -c 'command' + // Besides that default Android 'su' command, some Android devices use a different + // su with a command-line interface closer to the familiar util-linux su found + // on Linux distributions. Fortunately, both the util-linux su and the one + // in https://github.com/topjohnwu/Magisk seem to be happy with the above + // `su root sh -c 'command'` command line syntax. + char tmp[256]; + sprintf( tmp, "su root sh -c 'echo \"%s\" > %s%s'", val, BasePath, path ); + return system( tmp ) == 0; +} +#else +static bool TraceWrite( const char* path, size_t psz, const char* val, size_t vsz ) +{ + char tmp[256]; + memcpy( tmp, BasePath, sizeof( BasePath ) - 1 ); + memcpy( tmp + sizeof( BasePath ) - 1, path, psz ); + + int fd = open( tmp, O_WRONLY ); + if( fd < 0 ) return false; + + for(;;) + { + ssize_t cnt = write( fd, val, vsz ); + if( cnt == (ssize_t)vsz ) + { + close( fd ); + return true; + } + if( cnt < 0 ) + { + close( fd ); + return false; + } + vsz -= cnt; + val += cnt; + } +} +#endif + +#ifdef __ANDROID__ +void SysTraceInjectPayload() +{ + int pipefd[2]; + if( pipe( pipefd ) == 0 ) + { + const auto pid = fork(); + if( pid == 0 ) + { + // child + close( pipefd[1] ); + if( dup2( pipefd[0], STDIN_FILENO ) >= 0 ) + { + close( pipefd[0] ); + execlp( "su", "su", "root", "sh", "-c", "cat > /data/tracy_systrace", (char*)nullptr ); + exit( 1 ); + } + } + else if( pid > 0 ) + { + // parent + close( pipefd[0] ); + +#ifdef __aarch64__ + write( pipefd[1], tracy_systrace_aarch64_data, tracy_systrace_aarch64_size ); +#else + write( pipefd[1], tracy_systrace_armv7_data, tracy_systrace_armv7_size ); +#endif + close( pipefd[1] ); + waitpid( pid, nullptr, 0 ); + + system( "su root sh -c 'chmod 700 /data/tracy_systrace'" ); + } + } +} +#endif + +bool SysTraceStart( int64_t& samplingPeriod ) +{ +#ifndef CLOCK_MONOTONIC_RAW + return false; +#endif + + if( !TraceWrite( TracingOn, sizeof( TracingOn ), "0", 2 ) ) return false; + if( !TraceWrite( CurrentTracer, sizeof( CurrentTracer ), "nop", 4 ) ) return false; + TraceWrite( TraceOptions, sizeof( TraceOptions ), "norecord-cmd", 13 ); + TraceWrite( TraceOptions, sizeof( TraceOptions ), "norecord-tgid", 14 ); + TraceWrite( TraceOptions, sizeof( TraceOptions ), "noirq-info", 11 ); + TraceWrite( TraceOptions, sizeof( TraceOptions ), "noannotate", 11 ); +#if defined TRACY_HW_TIMER && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) + if( !TraceWrite( TraceClock, sizeof( TraceClock ), "x86-tsc", 8 ) ) return false; +#else + if( !TraceWrite( TraceClock, sizeof( TraceClock ), "mono_raw", 9 ) ) return false; +#endif + if( !TraceWrite( SchedSwitch, sizeof( SchedSwitch ), "1", 2 ) ) return false; + if( !TraceWrite( SchedWakeup, sizeof( SchedWakeup ), "1", 2 ) ) return false; + if( !TraceWrite( BufferSizeKb, sizeof( BufferSizeKb ), "4096", 5 ) ) return false; + +#if defined __ANDROID__ && ( defined __aarch64__ || defined __ARM_ARCH ) + SysTraceInjectPayload(); +#endif + + if( !TraceWrite( TracingOn, sizeof( TracingOn ), "1", 2 ) ) return false; + traceActive.store( true, std::memory_order_relaxed ); + + SetupSampling( samplingPeriod ); + + return true; +} + +void SysTraceStop() +{ + TraceWrite( TracingOn, sizeof( TracingOn ), "0", 2 ); + traceActive.store( false, std::memory_order_relaxed ); + if( s_threadSampling ) + { + s_threadSampling->~Thread(); + tracy_free( s_threadSampling ); + } +} + +static uint64_t ReadNumber( const char*& data ) +{ + auto ptr = data; + assert( *ptr >= '0' && *ptr <= '9' ); + uint64_t val = *ptr++ - '0'; + for(;;) + { + const uint8_t v = uint8_t( *ptr - '0' ); + if( v > 9 ) break; + val = val * 10 + v; + ptr++; + } + data = ptr; + return val; +} + +static uint8_t ReadState( char state ) +{ + switch( state ) + { + case 'D': return 101; + case 'I': return 102; + case 'R': return 103; + case 'S': return 104; + case 'T': return 105; + case 't': return 106; + case 'W': return 107; + case 'X': return 108; + case 'Z': return 109; + default: return 100; + } +} + +#if defined __ANDROID__ && defined __ANDROID_API__ && __ANDROID_API__ < 18 +/*- + * Copyright (c) 2011 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp) +{ + char *ptr, *eptr; + + if (*buf == NULL || *bufsiz == 0) { + *bufsiz = BUFSIZ; + if ((*buf = (char*)malloc(*bufsiz)) == NULL) + return -1; + } + + for (ptr = *buf, eptr = *buf + *bufsiz;;) { + int c = fgetc(fp); + if (c == -1) { + if (feof(fp)) + return ptr == *buf ? -1 : ptr - *buf; + else + return -1; + } + *ptr++ = c; + if (c == delimiter) { + *ptr = '\0'; + return ptr - *buf; + } + if (ptr + 2 >= eptr) { + char *nbuf; + size_t nbufsiz = *bufsiz * 2; + ssize_t d = ptr - *buf; + if ((nbuf = (char*)realloc(*buf, nbufsiz)) == NULL) + return -1; + *buf = nbuf; + *bufsiz = nbufsiz; + eptr = nbuf + nbufsiz; + ptr = nbuf + d; + } + } +} + +ssize_t getline(char **buf, size_t *bufsiz, FILE *fp) +{ + return getdelim(buf, bufsiz, '\n', fp); +} +#endif + +static void HandleTraceLine( const char* line ) +{ + line += 23; + while( *line != '[' ) line++; + line++; + const auto cpu = (uint8_t)ReadNumber( line ); + line++; // ']' + while( *line == ' ' ) line++; + +#if defined TRACY_HW_TIMER && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) + const auto time = ReadNumber( line ); +#else + const auto ts = ReadNumber( line ); + line++; // '.' + const auto tus = ReadNumber( line ); + const auto time = ts * 1000000000ll + tus * 1000ll; +#endif + + line += 2; // ': ' + if( memcmp( line, "sched_switch", 12 ) == 0 ) + { + line += 14; + + while( memcmp( line, "prev_pid", 8 ) != 0 ) line++; + line += 9; + + const auto oldPid = ReadNumber( line ); + line++; + + while( memcmp( line, "prev_state", 10 ) != 0 ) line++; + line += 11; + + const auto oldState = (uint8_t)ReadState( *line ); + line += 5; + + while( memcmp( line, "next_pid", 8 ) != 0 ) line++; + line += 9; + + const auto newPid = ReadNumber( line ); + + uint8_t reason = 100; + + TracyLfqPrepare( QueueType::ContextSwitch ); + MemWrite( &item->contextSwitch.time, time ); + MemWrite( &item->contextSwitch.oldThread, oldPid ); + MemWrite( &item->contextSwitch.newThread, newPid ); + MemWrite( &item->contextSwitch.cpu, cpu ); + MemWrite( &item->contextSwitch.reason, reason ); + MemWrite( &item->contextSwitch.state, oldState ); + TracyLfqCommit; + } + else if( memcmp( line, "sched_wakeup", 12 ) == 0 ) + { + line += 14; + + while( memcmp( line, "pid=", 4 ) != 0 ) line++; + line += 4; + + const auto pid = ReadNumber( line ); + + TracyLfqPrepare( QueueType::ThreadWakeup ); + MemWrite( &item->threadWakeup.time, time ); + MemWrite( &item->threadWakeup.thread, pid ); + TracyLfqCommit; + } +} + +#ifdef __ANDROID__ +static void ProcessTraceLines( int fd ) +{ + // Linux pipe buffer is 64KB, additional 1KB is for unfinished lines + char* buf = (char*)tracy_malloc( (64+1)*1024 ); + char* line = buf; + + for(;;) + { + if( !traceActive.load( std::memory_order_relaxed ) ) break; + + const auto rd = read( fd, line, 64*1024 ); + if( rd <= 0 ) break; + +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) + { + if( rd < 64*1024 ) + { + assert( line[rd-1] == '\n' ); + line = buf; + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + else + { + const auto end = line + rd; + line = end - 1; + while( line > buf && *line != '\n' ) line--; + if( line > buf ) + { + line++; + const auto lsz = end - line; + memmove( buf, line, lsz ); + line = buf + lsz; + } + } + continue; + } +#endif + + const auto end = line + rd; + line = buf; + for(;;) + { + auto next = (char*)memchr( line, '\n', end - line ); + if( !next ) + { + const auto lsz = end - line; + memmove( buf, line, lsz ); + line = buf + lsz; + break; + } + HandleTraceLine( line ); + line = ++next; + } + if( rd < 64*1024 ) + { + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + } + + tracy_free( buf ); +} + +void SysTraceWorker( void* ptr ) +{ + ThreadExitHandler threadExitHandler; + SetThreadName( "Tracy SysTrace" ); + int pipefd[2]; + if( pipe( pipefd ) == 0 ) + { + const auto pid = fork(); + if( pid == 0 ) + { + // child + close( pipefd[0] ); + dup2( open( "/dev/null", O_WRONLY ), STDERR_FILENO ); + if( dup2( pipefd[1], STDOUT_FILENO ) >= 0 ) + { + close( pipefd[1] ); + sched_param sp = { 4 }; + pthread_setschedparam( pthread_self(), SCHED_FIFO, &sp ); +#if defined __ANDROID__ && ( defined __aarch64__ || defined __ARM_ARCH ) + execlp( "su", "su", "root", "sh", "-c", "/data/tracy_systrace", (char*)nullptr ); +#endif + execlp( "su", "su", "root", "sh", "-c", "cat /sys/kernel/debug/tracing/trace_pipe", (char*)nullptr ); + exit( 1 ); + } + } + else if( pid > 0 ) + { + // parent + close( pipefd[1] ); + sched_param sp = { 5 }; + pthread_setschedparam( pthread_self(), SCHED_FIFO, &sp ); + ProcessTraceLines( pipefd[0] ); + close( pipefd[0] ); + waitpid( pid, nullptr, 0 ); + } + } +} +#else +static void ProcessTraceLines( int fd ) +{ + char* buf = (char*)tracy_malloc( 64*1024 ); + + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLIN | POLLERR; + + for(;;) + { + while( poll( &pfd, 1, 0 ) <= 0 ) + { + if( !traceActive.load( std::memory_order_relaxed ) ) break; + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + + const auto rd = read( fd, buf, 64*1024 ); + if( rd <= 0 ) break; + +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) continue; +#endif + + auto line = buf; + const auto end = buf + rd; + for(;;) + { + auto next = (char*)memchr( line, '\n', end - line ); + if( !next ) break; + HandleTraceLine( line ); + line = ++next; + } + } + + tracy_free( buf ); +} + +void SysTraceWorker( void* ptr ) +{ + ThreadExitHandler threadExitHandler; + SetThreadName( "Tracy SysTrace" ); + char tmp[256]; + memcpy( tmp, BasePath, sizeof( BasePath ) - 1 ); + memcpy( tmp + sizeof( BasePath ) - 1, TracePipe, sizeof( TracePipe ) ); + + int fd = open( tmp, O_RDONLY ); + if( fd < 0 ) return; + sched_param sp = { 5 }; + pthread_setschedparam( pthread_self(), SCHED_FIFO, &sp ); + ProcessTraceLines( fd ); + close( fd ); +} +#endif + +void SysTraceSendExternalName( uint64_t thread ) +{ + FILE* f; + char fn[256]; + sprintf( fn, "/proc/%" PRIu64 "/comm", thread ); + f = fopen( fn, "rb" ); + if( f ) + { + char buf[256]; + const auto sz = fread( buf, 1, 256, f ); + if( sz > 0 && buf[sz-1] == '\n' ) buf[sz-1] = '\0'; + GetProfiler().SendString( thread, buf, QueueType::ExternalThreadName ); + fclose( f ); + } + else + { + GetProfiler().SendString( thread, "???", 3, QueueType::ExternalThreadName ); + } + + sprintf( fn, "/proc/%" PRIu64 "/status", thread ); + f = fopen( fn, "rb" ); + if( f ) + { + int pid = -1; + size_t lsz = 1024; + auto line = (char*)tracy_malloc( lsz ); + for(;;) + { + auto rd = getline( &line, &lsz, f ); + if( rd <= 0 ) break; + if( memcmp( "Tgid:\t", line, 6 ) == 0 ) + { + pid = atoi( line + 6 ); + break; + } + } + tracy_free( line ); + fclose( f ); + if( pid >= 0 ) + { + { + uint64_t _pid = pid; + TracyLfqPrepare( QueueType::TidToPid ); + MemWrite( &item->tidToPid.tid, thread ); + MemWrite( &item->tidToPid.pid, _pid ); + TracyLfqCommit; + } + sprintf( fn, "/proc/%i/comm", pid ); + f = fopen( fn, "rb" ); + if( f ) + { + char buf[256]; + const auto sz = fread( buf, 1, 256, f ); + if( sz > 0 && buf[sz-1] == '\n' ) buf[sz-1] = '\0'; + GetProfiler().SendString( thread, buf, QueueType::ExternalName ); + fclose( f ); + return; + } + } + } + GetProfiler().SendString( thread, "???", 3, QueueType::ExternalName ); +} + +} + +# endif + +#endif diff --git a/Source/ThirdParty/tracy/client/TracySysTrace.hpp b/Source/ThirdParty/tracy/client/TracySysTrace.hpp new file mode 100644 index 000000000..688cbf2ae --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracySysTrace.hpp @@ -0,0 +1,25 @@ +#ifndef __TRACYSYSTRACE_HPP__ +#define __TRACYSYSTRACE_HPP__ + +#if !defined TRACY_NO_SYSTEM_TRACING && ( defined _WIN32 || defined __CYGWIN__ || defined __linux__ ) +# define TRACY_HAS_SYSTEM_TRACING +#endif + +#ifdef TRACY_HAS_SYSTEM_TRACING + +#include + +namespace tracy +{ + +bool SysTraceStart( int64_t& samplingPeriod ); +void SysTraceStop(); +void SysTraceWorker( void* ptr ); + +void SysTraceSendExternalName( uint64_t thread ); + +} + +#endif + +#endif diff --git a/Source/ThirdParty/tracy/client/TracySysTracePayload.hpp b/Source/ThirdParty/tracy/client/TracySysTracePayload.hpp new file mode 100644 index 000000000..7c292f9d0 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracySysTracePayload.hpp @@ -0,0 +1,78 @@ +// File: 'extra/systrace/tracy_systrace.armv7' (1149 bytes) +// File: 'extra/systrace/tracy_systrace.aarch64' (1650 bytes) + +// Exported using binary_to_compressed_c.cpp + +namespace tracy +{ + +static const unsigned int tracy_systrace_armv7_size = 1149; +static const unsigned int tracy_systrace_armv7_data[1152/4] = +{ + 0x464c457f, 0x00010101, 0x00000000, 0x00000000, 0x00280003, 0x00000001, 0x000001f0, 0x00000034, 0x00000000, 0x05000200, 0x00200034, 0x00280007, + 0x00000000, 0x00000006, 0x00000034, 0x00000034, 0x00000034, 0x000000e0, 0x000000e0, 0x00000004, 0x00000004, 0x00000003, 0x00000114, 0x00000114, + 0x00000114, 0x00000013, 0x00000013, 0x00000004, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x000003fd, 0x000003fd, 0x00000005, + 0x00001000, 0x00000001, 0x000003fd, 0x000013fd, 0x000013fd, 0x00000080, 0x000000b3, 0x00000006, 0x00001000, 0x00000002, 0x00000400, 0x00001400, + 0x00001400, 0x0000007d, 0x000000b0, 0x00000006, 0x00000004, 0x6474e551, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000006, + 0x00000004, 0x70000001, 0x000003a4, 0x000003a4, 0x000003a4, 0x00000008, 0x00000008, 0x00000004, 0x00000004, 0x7379732f, 0x2f6d6574, 0x2f6e6962, + 0x6b6e696c, 0x00007265, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000012, 0x00000016, 0x00000000, + 0x00000000, 0x00000012, 0x6f6c6400, 0x006e6570, 0x4342494c, 0x62696c00, 0x732e6c64, 0x6c64006f, 0x006d7973, 0x00000001, 0x00000003, 0x00000001, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00010001, 0x0000000d, 0x00000010, 0x00000000, 0x00050d63, 0x00020000, 0x00000008, + 0x00000000, 0x000014bc, 0x00000116, 0x000014c0, 0x00000216, 0xe52de004, 0xe59fe004, 0xe08fe00e, 0xe5bef008, 0x000012dc, 0xe28fc600, 0xe28cca01, + 0xe5bcf2dc, 0xe28fc600, 0xe28cca01, 0xe5bcf2d4, 0xe92d4ff0, 0xe28db01c, 0xe24dd024, 0xe24dd801, 0xe59f017c, 0xe3a01001, 0xe3a08001, 0xe08f0000, + 0xebfffff0, 0xe59f116c, 0xe1a04000, 0xe08f1001, 0xebffffef, 0xe59f1160, 0xe1a06000, 0xe1a00004, 0xe08f1001, 0xebffffea, 0xe59f1150, 0xe1a07000, + 0xe1a00004, 0xe08f1001, 0xebffffe5, 0xe59f1140, 0xe1a05000, 0xe1a00004, 0xe08f1001, 0xebffffe0, 0xe58d0004, 0xe1a00004, 0xe59f1128, 0xe08f1001, + 0xebffffdb, 0xe59f1120, 0xe1a0a000, 0xe1a00004, 0xe08f1001, 0xebffffd6, 0xe1a04000, 0xe59f010c, 0xe3a01000, 0xe3a09000, 0xe08f0000, 0xe12fff36, + 0xe1a06000, 0xe3700001, 0xca000001, 0xe3a00000, 0xe12fff37, 0xe3a00009, 0xe3a01001, 0xe1cd01bc, 0xe3a00008, 0xe1cd01b4, 0xe3090680, 0xe3400098, + 0xe3a02000, 0xe58d000c, 0xe28d0010, 0xe58d7000, 0xe58d6018, 0xe58d8010, 0xe58d9008, 0xe12fff35, 0xe3500000, 0xca00001d, 0xe28d7018, 0xe28d8010, + 0xe28d9020, 0xe1a00007, 0xe3a01001, 0xe3a02000, 0xe12fff35, 0xe3500000, 0xda00000a, 0xe1a00006, 0xe1a01009, 0xe3a02801, 0xe12fff3a, 0xe3500001, + 0xba00000e, 0xe1a02000, 0xe3a00001, 0xe1a01009, 0xe12fff34, 0xea000003, 0xe59d2004, 0xe28d0008, 0xe3a01000, 0xe12fff32, 0xe1a00008, 0xe3a01001, + 0xe3a02000, 0xe12fff35, 0xe3500001, 0xbaffffe4, 0xe59d1000, 0xe3a00000, 0xe12fff31, 0xe24bd01c, 0xe8bd8ff0, 0x00000198, 0x00000190, 0x00000181, + 0x00000172, 0x00000163, 0x00000159, 0x0000014a, 0x00000138, 0x7ffffe4c, 0x00000001, 0x6362696c, 0x006f732e, 0x6e65706f, 0x69786500, 0x6f700074, + 0x6e006c6c, 0x736f6e61, 0x7065656c, 0x61657200, 0x72770064, 0x00657469, 0x7379732f, 0x72656b2f, 0x2f6c656e, 0x75626564, 0x72742f67, 0x6e696361, + 0x72742f67, 0x5f656361, 0x65706970, 0x00000000, 0x00000003, 0x000014b0, 0x00000002, 0x00000010, 0x00000017, 0x000001b4, 0x00000014, 0x00000011, + 0x00000015, 0x00000000, 0x00000006, 0x00000128, 0x0000000b, 0x00000010, 0x00000005, 0x00000158, 0x0000000a, 0x0000001c, 0x6ffffef5, 0x00000174, + 0x00000001, 0x0000000d, 0x0000001e, 0x00000008, 0x6ffffffb, 0x00000001, 0x6ffffff0, 0x0000018c, 0x6ffffffe, 0x00000194, 0x6fffffff, 0x00000001, +}; + +static const unsigned int tracy_systrace_aarch64_size = 1650; +static const unsigned int tracy_systrace_aarch64_data[1652/4] = +{ + 0x464c457f, 0x00010102, 0x00000000, 0x00000000, 0x00b70003, 0x00000001, 0x000002e0, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00380040, 0x00400006, 0x00000000, 0x00000006, 0x00000005, 0x00000040, 0x00000000, 0x00000040, 0x00000000, 0x00000040, 0x00000000, + 0x00000150, 0x00000000, 0x00000150, 0x00000000, 0x00000008, 0x00000000, 0x00000003, 0x00000004, 0x00000190, 0x00000000, 0x00000190, 0x00000000, + 0x00000190, 0x00000000, 0x00000015, 0x00000000, 0x00000015, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000005, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000004e1, 0x00000000, 0x000004e1, 0x00000000, 0x00001000, 0x00000000, 0x00000001, 0x00000006, + 0x000004e8, 0x00000000, 0x000014e8, 0x00000000, 0x000014e8, 0x00000000, 0x0000018a, 0x00000000, 0x00000190, 0x00000000, 0x00001000, 0x00000000, + 0x00000002, 0x00000006, 0x000004e8, 0x00000000, 0x000014e8, 0x00000000, 0x000014e8, 0x00000000, 0x00000160, 0x00000000, 0x00000160, 0x00000000, + 0x00000008, 0x00000000, 0x6474e551, 0x00000006, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000008, 0x00000000, 0x7379732f, 0x2f6d6574, 0x2f6e6962, 0x6b6e696c, 0x34367265, 0x00000000, 0x00000001, 0x00000001, + 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00090003, 0x000002e0, 0x00000000, 0x00000000, 0x00000000, 0x00000010, 0x00000012, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000000a, 0x00000012, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x62696c00, 0x732e6c64, 0x6c64006f, 0x006d7973, 0x706f6c64, 0x4c006e65, + 0x00434249, 0x00000000, 0x00000000, 0x00000000, 0x00010001, 0x00000001, 0x00000010, 0x00000000, 0x00050d63, 0x00020000, 0x00000017, 0x00000000, + 0x00001668, 0x00000000, 0x00000402, 0x00000002, 0x00000000, 0x00000000, 0x00001670, 0x00000000, 0x00000402, 0x00000003, 0x00000000, 0x00000000, + 0xa9bf7bf0, 0xb0000010, 0xf9433211, 0x91198210, 0xd61f0220, 0xd503201f, 0xd503201f, 0xd503201f, 0xb0000010, 0xf9433611, 0x9119a210, 0xd61f0220, + 0xb0000010, 0xf9433a11, 0x9119c210, 0xd61f0220, 0xa9bb67fc, 0xa9015ff8, 0xa90257f6, 0xa9034ff4, 0xa9047bfd, 0x910103fd, 0xd14043ff, 0xd10083ff, + 0x90000000, 0x91124000, 0x52800021, 0x52800039, 0x97ffffec, 0x90000001, 0x91126021, 0xaa0003f7, 0x97ffffec, 0x90000001, 0xaa0003f8, 0x91127421, + 0xaa1703e0, 0x97ffffe7, 0x90000001, 0xaa0003f3, 0x91128821, 0xaa1703e0, 0x97ffffe2, 0x90000001, 0xaa0003f4, 0x91129c21, 0xaa1703e0, 0x97ffffdd, + 0x90000001, 0xaa0003f5, 0x9112c421, 0xaa1703e0, 0x97ffffd8, 0x90000001, 0xaa0003f6, 0x9112d821, 0xaa1703e0, 0x97ffffd3, 0xaa0003f7, 0x90000000, + 0x9112f000, 0x2a1f03e1, 0xd63f0300, 0x2a0003f8, 0x36f80060, 0x2a1f03e0, 0xd63f0260, 0x90000009, 0x3dc12120, 0x52800128, 0x79003be8, 0x52800108, + 0x910043e0, 0x52800021, 0x2a1f03e2, 0xb9001bf8, 0xb90013f9, 0x79002be8, 0x3d8003e0, 0xd63f0280, 0x7100001f, 0x5400036c, 0x910063e0, 0x52800021, + 0x2a1f03e2, 0xd63f0280, 0x7100001f, 0x5400018d, 0x910083e1, 0x52a00022, 0x2a1803e0, 0xd63f02c0, 0xf100041f, 0x540001eb, 0xaa0003e2, 0x910083e1, + 0x52800020, 0xd63f02e0, 0x14000004, 0x910003e0, 0xaa1f03e1, 0xd63f02a0, 0x910043e0, 0x52800021, 0x2a1f03e2, 0xd63f0280, 0x7100041f, 0x54fffceb, + 0x2a1f03e0, 0xd63f0260, 0x914043ff, 0x910083ff, 0xa9447bfd, 0xa9434ff4, 0xa94257f6, 0xa9415ff8, 0xa8c567fc, 0xd65f03c0, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00989680, 0x00000000, 0x6362696c, 0x006f732e, 0x6e65706f, 0x69786500, 0x6f700074, 0x6e006c6c, 0x736f6e61, 0x7065656c, + 0x61657200, 0x72770064, 0x00657469, 0x7379732f, 0x72656b2f, 0x2f6c656e, 0x75626564, 0x72742f67, 0x6e696361, 0x72742f67, 0x5f656361, 0x65706970, + 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x6ffffef5, 0x00000000, 0x000001a8, 0x00000000, 0x00000005, 0x00000000, + 0x00000228, 0x00000000, 0x00000006, 0x00000000, 0x000001c8, 0x00000000, 0x0000000a, 0x00000000, 0x0000001c, 0x00000000, 0x0000000b, 0x00000000, + 0x00000018, 0x00000000, 0x00000015, 0x00000000, 0x00000000, 0x00000000, 0x00000003, 0x00000000, 0x00001650, 0x00000000, 0x00000002, 0x00000000, + 0x00000030, 0x00000000, 0x00000014, 0x00000000, 0x00000007, 0x00000000, 0x00000017, 0x00000000, 0x00000270, 0x00000000, 0x0000001e, 0x00000000, + 0x00000008, 0x00000000, 0x6ffffffb, 0x00000000, 0x00000001, 0x00000000, 0x6ffffffe, 0x00000000, 0x00000250, 0x00000000, 0x6fffffff, 0x00000000, + 0x00000001, 0x00000000, 0x6ffffff0, 0x00000000, 0x00000244, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x000002a0, 0x00000000, 0x000002a0, +}; + +} diff --git a/Source/ThirdParty/tracy/client/TracyThread.hpp b/Source/ThirdParty/tracy/client/TracyThread.hpp new file mode 100644 index 000000000..edd255e87 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyThread.hpp @@ -0,0 +1,85 @@ +#ifndef __TRACYTHREAD_HPP__ +#define __TRACYTHREAD_HPP__ + +#if defined _WIN32 || defined __CYGWIN__ +# include +#else +# include +#endif + +#ifdef TRACY_MANUAL_LIFETIME +# include "tracy_rpmalloc.hpp" +#endif + +namespace tracy +{ + +class ThreadExitHandler +{ +public: + ~ThreadExitHandler() + { +#ifdef TRACY_MANUAL_LIFETIME + rpmalloc_thread_finalize(); +#endif + } +}; + +#if defined _WIN32 || defined __CYGWIN__ + +class Thread +{ +public: + Thread( void(*func)( void* ptr ), void* ptr ) + : m_func( func ) + , m_ptr( ptr ) + , m_hnd( CreateThread( nullptr, 0, Launch, this, 0, nullptr ) ) + {} + + ~Thread() + { + WaitForSingleObject( m_hnd, INFINITE ); + CloseHandle( m_hnd ); + } + + HANDLE Handle() const { return m_hnd; } + +private: + static DWORD WINAPI Launch( void* ptr ) { ((Thread*)ptr)->m_func( ((Thread*)ptr)->m_ptr ); return 0; } + + void(*m_func)( void* ptr ); + void* m_ptr; + HANDLE m_hnd; +}; + +#else + +class Thread +{ +public: + Thread( void(*func)( void* ptr ), void* ptr ) + : m_func( func ) + , m_ptr( ptr ) + { + pthread_create( &m_thread, nullptr, Launch, this ); + } + + ~Thread() + { + pthread_join( m_thread, nullptr ); + } + + pthread_t Handle() const { return m_thread; } + +private: + static void* Launch( void* ptr ) { ((Thread*)ptr)->m_func( ((Thread*)ptr)->m_ptr ); return nullptr; } + void(*m_func)( void* ptr ); + void* m_ptr; + pthread_t m_thread; +}; + +#endif + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/tracy_concurrentqueue.h b/Source/ThirdParty/tracy/client/tracy_concurrentqueue.h new file mode 100644 index 000000000..bf095bc36 --- /dev/null +++ b/Source/ThirdParty/tracy/client/tracy_concurrentqueue.h @@ -0,0 +1,1445 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + +// Simplified BSD license: +// Copyright (c) 2013-2016, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#pragma once + +#include "../common/TracyAlloc.hpp" +#include "../common/TracySystem.hpp" + +#if defined(__GNUC__) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +#include // Requires C++11. Sorry VS2010. +#include +#include // for max_align_t +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading + +namespace tracy +{ + +// Compiler-specific likely/unlikely hints +namespace moodycamel { namespace details { +#if defined(__GNUC__) + inline bool cqLikely(bool x) { return __builtin_expect((x), true); } + inline bool cqUnlikely(bool x) { return __builtin_expect((x), false); } +#else + inline bool cqLikely(bool x) { return x; } + inline bool cqUnlikely(bool x) { return x; } +#endif +} } + +namespace +{ + // to avoid MSVC warning 4127: conditional expression is constant + template + struct compile_time_condition + { + static const bool value = false; + }; + template <> + struct compile_time_condition + { + static const bool value = true; + }; +} + +namespace moodycamel { +namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; + +#if defined(__GLIBCXX__) + typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while +#else + typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: +#endif + + // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting + // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. + typedef union { + std_max_align_t x; + long long y; + void* z; + } max_align_t; +} + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. +struct ConcurrentQueueDefaultTraits +{ + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 64*1024; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. +#if defined(malloc) || defined(free) + // Gah, this is 2015, stop defining macros that break standard code already! + // Work around malloc/free being special macros: + static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } + static inline void WORKAROUND_free(void* ptr) { return free(ptr); } + static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } + static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } +#else + static inline void* malloc(size_t size) { return tracy::tracy_malloc(size); } + static inline void free(void* ptr) { return tracy::tracy_free(ptr); } +#endif +}; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). +struct ProducerToken; +struct ConsumerToken; + +template class ConcurrentQueue; + + +namespace details +{ + struct ConcurrentQueueProducerTypelessBase + { + ConcurrentQueueProducerTypelessBase* next; + std::atomic inactive; + ProducerToken* token; + uint64_t threadId; + + ConcurrentQueueProducerTypelessBase() + : next(nullptr), inactive(false), token(nullptr), threadId(0) + { + } + }; + + template + static inline bool circular_less_than(T a, T b) + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4554) +#endif + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > static_cast(static_cast(1) << static_cast(sizeof(T) * CHAR_BIT - 1)); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic& left, std::atomic& right) + { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const& nomove(T const& x) + { + return x; + } + + template + struct nomove_if + { + template + static inline T const& eval(T const& x) + { + return x; + } + }; + + template<> + struct nomove_if + { + template + static inline auto eval(U&& x) + -> decltype(std::forward(x)) + { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It& it) noexcept -> decltype(*it) + { + return *it; + } + +#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template struct is_trivially_destructible : std::is_trivially_destructible { }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + + template struct static_is_lock_free_num { enum { value = 0 }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; + template struct static_is_lock_free : static_is_lock_free_num::type> { }; + template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; + template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; +} + + +struct ProducerToken +{ + template + explicit ProducerToken(ConcurrentQueue& queue); + + ProducerToken(ProducerToken&& other) noexcept + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken& operator=(ProducerToken&& other) noexcept + { + swap(other); + return *this; + } + + void swap(ProducerToken& other) noexcept + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() + { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const&) = delete; + ProducerToken& operator=(ProducerToken const&) = delete; + +private: + template friend class ConcurrentQueue; + +protected: + details::ConcurrentQueueProducerTypelessBase* producer; +}; + + +struct ConsumerToken +{ + template + explicit ConsumerToken(ConcurrentQueue& q); + + ConsumerToken(ConsumerToken&& other) noexcept + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) + { + } + + inline ConsumerToken& operator=(ConsumerToken&& other) noexcept + { + swap(other); + return *this; + } + + void swap(ConsumerToken& other) noexcept + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const&) = delete; + ConsumerToken& operator=(ConsumerToken const&) = delete; + +private: + template friend class ConcurrentQueue; + +private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase* currentProducer; + details::ConcurrentQueueProducerTypelessBase* desiredProducer; +}; + + +template +class ConcurrentQueue +{ +public: + struct ExplicitProducer; + + typedef moodycamel::ProducerToken producer_token_t; + typedef moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers); + populate_initial_block_list(blocks); + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() + { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const&) = delete; + ConcurrentQueue(ConcurrentQueue&& other) = delete; + ConcurrentQueue& operator=(ConcurrentQueue const&) = delete; + ConcurrentQueue& operator=(ConcurrentQueue&& other) = delete; + +public: + tracy_force_inline T* enqueue_begin(producer_token_t const& token, index_t& currentTailIndex) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::enqueue_begin(currentTailIndex); + } + + template + size_t try_dequeue_bulk_single(consumer_token_t& token, NotifyThread notifyThread, ProcessData processData ) + { + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return 0; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(notifyThread, processData); + token.itemsConsumedFromCurrent += static_cast(count); + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + if( count == 0 ) + { + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(notifyThread, processData); + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + return dequeued; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return 0; + } + else + { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 0; + return count; + } + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const + { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static bool is_lock_free() + { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2; + } + + +private: + friend struct ProducerToken; + friend struct ConsumerToken; + friend struct ExplicitProducer; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + inline bool update_current_producer_after_rotation(consumer_token_t& token) + { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if (details::cqUnlikely(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode + { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList + { + FreeList() : freeListHead(nullptr) { } + FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } + void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const&) = delete; + FreeList& operator=(FreeList const&) = delete; + + inline void add(N* node) + { + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N* try_get() + { + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_sub(2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N* node) + { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + }; + + + /////////////////////////// + // Block + /////////////////////////// + + struct Block + { + Block() + : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true) + { + } + + inline bool is_empty() const + { + if (compile_time_condition::value) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + inline bool set_empty(index_t i) + { + if (BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + inline bool set_many_empty(index_t i, size_t count) + { + if (compile_time_condition::value) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + inline void set_all_empty() + { + if (BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + inline void reset_empty() + { + if (compile_time_condition::value) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T* operator[](index_t idx) noexcept { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const noexcept { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + + private: + // IMPORTANT: This must be the first member in Block, so that if T depends on the alignment of + // addresses returned by malloc, that alignment will be preserved. Apparently clang actually + // generates code that uses this assumption for AVX instructions in some cases. Ideally, we + // should also align Block to the alignment of T in case it's higher than malloc's 16-byte + // alignment, but this is hard to do in a cross-platform way. Assert for this case: + static_assert(std::alignment_of::value <= std::alignment_of::value, "The queue does not support super-aligned types at this time"); + // Additionally, we need the alignment of Block itself to be a multiple of max_align_t since + // otherwise the appropriate padding will not be added at the end of Block in order to make + // arrays of Blocks all be properly aligned (not just the first one). We use a union to force + // this. + union { + char elements[sizeof(T) * BLOCK_SIZE]; + details::max_align_t dummy; + }; + public: + Block* next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + std::atomic shouldBeOnFreeList; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + }; + static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); + + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase + { + ProducerBase(ConcurrentQueue* parent_) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + parent(parent_) + { + } + + virtual ~ProducerBase() { }; + + template + inline size_t dequeue_bulk(NotifyThread notifyThread, ProcessData processData) + { + return static_cast(this)->dequeue_bulk(notifyThread, processData); + } + + inline ProducerBase* next_prod() const { return static_cast(next); } + + inline size_t size_approx() const + { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block* tailBlock; + + public: + ConcurrentQueue* parent; + }; + + + public: + /////////////////////////// + // Explicit queue + /////////////////////////// + struct ExplicitProducer : public ProducerBase + { + explicit ExplicitProducer(ConcurrentQueue* _parent) : + ProducerBase(_parent), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) + { + size_t poolBasedIndexSize = details::ceil_to_pow_2(_parent->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() + { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block* halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto nextBlock = block->next; + if (block->dynamicallyAllocated) { + destroy(block); + } + else { + this->parent->add_block_to_free_list(block); + } + block = nextBlock; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + (Traits::free)(header); + header = prev; + } + } + + inline void enqueue_begin_alloc(index_t currentTailIndex) + { + // We reached the end of a block, start a new one + if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } + else { + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + new_block_index(pr_blockIndexSlotsUsed); + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::requisition_block(); + newBlock->ConcurrentQueue::Block::reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + // Add block to block index + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + tracy_force_inline T* enqueue_begin(index_t& currentTailIndex) + { + currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + if (details::cqUnlikely((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0)) { + this->enqueue_begin_alloc(currentTailIndex); + } + return (*this->tailBlock)[currentTailIndex]; + } + + tracy_force_inline std::atomic& get_tail_index() + { + return this->tailIndex; + } + + template + size_t dequeue_bulk(NotifyThread notifyThread, ProcessData processData) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < 8192 ? desiredCount : 8192; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + assert(overcommit <= myDequeueCount); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / BLOCK_SIZE); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + notifyThread( this->threadId ); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + auto endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + + const auto sz = endIndex - index; + processData( (*block)[index], sz ); + index += sz; + + block->ConcurrentQueue::Block::set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry + { + index_t base; + Block* block; + }; + + struct BlockIndexHeader + { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry* entries; + void* prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) + { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new (newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry* pr_blockIndexEntries; + void* pr_blockIndexRaw; + }; + + ExplicitProducer* get_explicit_producer(producer_token_t const& token) + { + return static_cast(token.producer); + } + + private: + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) + { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block* try_get_block_from_initial_pool() + { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block* block) + { + freeList.add(block); + } + + inline void add_blocks_to_free_list(Block* block) + { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block* try_get_block_from_free_list() + { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + Block* requisition_block() + { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + return create(); + } + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase* recycle_or_create_producer() + { + bool recycled; + return recycle_or_create_producer(recycled); + } + + ProducerBase* recycle_or_create_producer(bool& recycled) + { + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed)) { + if( ptr->size_approx() == 0 ) + { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + recycled = true; + return ptr; + } + } + } + } + + recycled = false; + return add_producer(static_cast(create(this))); + } + + ProducerBase* add_producer(ProducerBase* producer) + { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); + + return producer; + } + + void reown_producers() + { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline U* create_array(size_t count) + { + assert(count > 0); + return static_cast((Traits::malloc)(sizeof(U) * count)); + } + + template + static inline void destroy_array(U* p, size_t count) + { + ((void)count); + if (p != nullptr) { + assert(count > 0); + (Traits::free)(p); + } + } + + template + static inline U* create() + { + auto p = (Traits::malloc)(sizeof(U)); + return new (p) U; + } + + template + static inline U* create(A1&& a1) + { + auto p = (Traits::malloc)(sizeof(U)); + return new (p) U(std::forward(a1)); + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) { + p->~U(); + } + (Traits::free)(p); + } + +private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block* initialBlockPool; + size_t initialBlockPoolSize; + + FreeList freeList; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; +}; + + +template +ProducerToken::ProducerToken(ConcurrentQueue& queue) + : producer(queue.recycle_or_create_producer()) +{ + if (producer != nullptr) { + producer->token = this; + producer->threadId = detail::GetThreadHandleImpl(); + } +} + +template +ConsumerToken::ConsumerToken(ConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) noexcept +{ + a.swap(b); +} + +inline void swap(ProducerToken& a, ProducerToken& b) noexcept +{ + a.swap(b); +} + +inline void swap(ConsumerToken& a, ConsumerToken& b) noexcept +{ + a.swap(b); +} + +} + +} /* namespace tracy */ + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif diff --git a/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp b/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp new file mode 100644 index 000000000..8aae78e03 --- /dev/null +++ b/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp @@ -0,0 +1,2495 @@ +#ifdef TRACY_ENABLE + +/* rpmalloc.c - Memory allocator - Public Domain - 2016 Mattias Jansson + * + * This library provides a cross-platform lock free thread caching malloc implementation in C11. + * The latest source code is always available at + * + * https://github.com/mjansson/rpmalloc + * + * This library is put in the public domain; you can redistribute it and/or modify it without any restrictions. + * + */ + +#include "tracy_rpmalloc.hpp" + +/// Build time configurable limits +#ifndef HEAP_ARRAY_SIZE +//! Size of heap hashmap +#define HEAP_ARRAY_SIZE 47 +#endif +#ifndef ENABLE_THREAD_CACHE +//! Enable per-thread cache +#define ENABLE_THREAD_CACHE 1 +#endif +#ifndef ENABLE_GLOBAL_CACHE +//! Enable global cache shared between all threads, requires thread cache +#define ENABLE_GLOBAL_CACHE 1 +#endif +#ifndef ENABLE_VALIDATE_ARGS +//! Enable validation of args to public entry points +#define ENABLE_VALIDATE_ARGS 0 +#endif +#ifndef ENABLE_STATISTICS +//! Enable statistics collection +#define ENABLE_STATISTICS 0 +#endif +#ifndef ENABLE_ASSERTS +//! Enable asserts +#define ENABLE_ASSERTS 0 +#endif +#ifndef ENABLE_OVERRIDE +//! Override standard library malloc/free and new/delete entry points +#define ENABLE_OVERRIDE 0 +#endif +#ifndef ENABLE_PRELOAD +//! Support preloading +#define ENABLE_PRELOAD 0 +#endif +#ifndef DISABLE_UNMAP +//! Disable unmapping memory pages +#define DISABLE_UNMAP 0 +#endif +#ifndef DEFAULT_SPAN_MAP_COUNT +//! Default number of spans to map in call to map more virtual memory (default values yield 4MiB here) +#define DEFAULT_SPAN_MAP_COUNT 64 +#endif + +#if ENABLE_THREAD_CACHE +#ifndef ENABLE_UNLIMITED_CACHE +//! Unlimited thread and global cache +#define ENABLE_UNLIMITED_CACHE 0 +#endif +#ifndef ENABLE_UNLIMITED_THREAD_CACHE +//! Unlimited cache disables any thread cache limitations +#define ENABLE_UNLIMITED_THREAD_CACHE ENABLE_UNLIMITED_CACHE +#endif +#if !ENABLE_UNLIMITED_THREAD_CACHE +#ifndef THREAD_CACHE_MULTIPLIER +//! Multiplier for thread cache (cache limit will be span release count multiplied by this value) +#define THREAD_CACHE_MULTIPLIER 16 +#endif +#ifndef ENABLE_ADAPTIVE_THREAD_CACHE +//! Enable adaptive size of per-thread cache (still bounded by THREAD_CACHE_MULTIPLIER hard limit) +#define ENABLE_ADAPTIVE_THREAD_CACHE 0 +#endif +#endif +#endif + +#if ENABLE_GLOBAL_CACHE && ENABLE_THREAD_CACHE +#ifndef ENABLE_UNLIMITED_GLOBAL_CACHE +//! Unlimited cache disables any global cache limitations +#define ENABLE_UNLIMITED_GLOBAL_CACHE ENABLE_UNLIMITED_CACHE +#endif +#if !ENABLE_UNLIMITED_GLOBAL_CACHE +//! Multiplier for global cache (cache limit will be span release count multiplied by this value) +#define GLOBAL_CACHE_MULTIPLIER (THREAD_CACHE_MULTIPLIER * 6) +#endif +#else +# undef ENABLE_GLOBAL_CACHE +# define ENABLE_GLOBAL_CACHE 0 +#endif + +#if !ENABLE_THREAD_CACHE || ENABLE_UNLIMITED_THREAD_CACHE +# undef ENABLE_ADAPTIVE_THREAD_CACHE +# define ENABLE_ADAPTIVE_THREAD_CACHE 0 +#endif + +#if DISABLE_UNMAP && !ENABLE_GLOBAL_CACHE +# error Must use global cache if unmap is disabled +#endif + +#if defined( _WIN32 ) || defined( __WIN32__ ) || defined( _WIN64 ) +# define PLATFORM_WINDOWS 1 +# define PLATFORM_POSIX 0 +#else +# define PLATFORM_WINDOWS 0 +# define PLATFORM_POSIX 1 +#endif + +#define _Static_assert static_assert + +/// Platform and arch specifics +#ifndef FORCEINLINE +# if defined(_MSC_VER) && !defined(__clang__) +# define FORCEINLINE inline __forceinline +# else +# define FORCEINLINE inline __attribute__((__always_inline__)) +# endif +#endif +#if PLATFORM_WINDOWS +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# if ENABLE_VALIDATE_ARGS +# include +# endif +#else +# include +# include +# include +# if defined(__APPLE__) +# include +# include +# include +# endif +# if defined(__HAIKU__) +# include +# include +# endif +#endif + +#include +#include + +#if ENABLE_ASSERTS +# undef NDEBUG +# if defined(_MSC_VER) && !defined(_DEBUG) +# define _DEBUG +# endif +# include +#else +# undef assert +# define assert(x) do {} while(0) +#endif +#if ENABLE_STATISTICS +# include +#endif + +#include + +namespace tracy +{ + +typedef std::atomic atomic32_t; +typedef std::atomic atomic64_t; +typedef std::atomic atomicptr_t; + +#define atomic_thread_fence_acquire() std::atomic_thread_fence(std::memory_order_acquire) +#define atomic_thread_fence_release() std::atomic_thread_fence(std::memory_order_release) + +static FORCEINLINE int32_t atomic_load32(atomic32_t* src) { return std::atomic_load_explicit(src, std::memory_order_relaxed); } +static FORCEINLINE void atomic_store32(atomic32_t* dst, int32_t val) { std::atomic_store_explicit(dst, val, std::memory_order_relaxed); } +static FORCEINLINE int32_t atomic_incr32(atomic32_t* val) { return std::atomic_fetch_add_explicit(val, 1, std::memory_order_relaxed) + 1; } +#if ENABLE_STATISTICS || ENABLE_ADAPTIVE_THREAD_CACHE +static FORCEINLINE int32_t atomic_decr32(atomic32_t* val) { return atomic_fetch_add_explicit(val, -1, memory_order_relaxed) - 1; } +#endif +static FORCEINLINE int32_t atomic_add32(atomic32_t* val, int32_t add) { return std::atomic_fetch_add_explicit(val, add, std::memory_order_relaxed) + add; } +static FORCEINLINE void* atomic_load_ptr(atomicptr_t* src) { return std::atomic_load_explicit(src, std::memory_order_relaxed); } +static FORCEINLINE void atomic_store_ptr(atomicptr_t* dst, void* val) { std::atomic_store_explicit(dst, val, std::memory_order_relaxed); } +static FORCEINLINE int atomic_cas_ptr(atomicptr_t* dst, void* val, void* ref) { return std::atomic_compare_exchange_weak_explicit(dst, &ref, val, std::memory_order_release, std::memory_order_acquire); } + +#if defined(_MSC_VER) && !defined(__clang__) +# define EXPECTED(x) (x) +# define UNEXPECTED(x) (x) +#else +# define EXPECTED(x) __builtin_expect((x), 1) +# define UNEXPECTED(x) __builtin_expect((x), 0) +#endif + +/// Preconfigured limits and sizes +//! Granularity of a small allocation block +#define SMALL_GRANULARITY 16 +//! Small granularity shift count +#define SMALL_GRANULARITY_SHIFT 4 +//! Number of small block size classes +#define SMALL_CLASS_COUNT 65 +//! Maximum size of a small block +#define SMALL_SIZE_LIMIT (SMALL_GRANULARITY * (SMALL_CLASS_COUNT - 1)) +//! Granularity of a medium allocation block +#define MEDIUM_GRANULARITY 512 +//! Medium granularity shift count +#define MEDIUM_GRANULARITY_SHIFT 9 +//! Number of medium block size classes +#define MEDIUM_CLASS_COUNT 61 +//! Total number of small + medium size classes +#define SIZE_CLASS_COUNT (SMALL_CLASS_COUNT + MEDIUM_CLASS_COUNT) +//! Number of large block size classes +#define LARGE_CLASS_COUNT 32 +//! Maximum size of a medium block +#define MEDIUM_SIZE_LIMIT (SMALL_SIZE_LIMIT + (MEDIUM_GRANULARITY * MEDIUM_CLASS_COUNT)) +//! Maximum size of a large block +#define LARGE_SIZE_LIMIT ((LARGE_CLASS_COUNT * _memory_span_size) - SPAN_HEADER_SIZE) +//! Size of a span header (must be a multiple of SMALL_GRANULARITY) +#define SPAN_HEADER_SIZE 96 + +#if ENABLE_VALIDATE_ARGS +//! Maximum allocation size to avoid integer overflow +#undef MAX_ALLOC_SIZE +#define MAX_ALLOC_SIZE (((size_t)-1) - _memory_span_size) +#endif + +#define pointer_offset(ptr, ofs) (void*)((char*)(ptr) + (ptrdiff_t)(ofs)) +#define pointer_diff(first, second) (ptrdiff_t)((const char*)(first) - (const char*)(second)) + +#define INVALID_POINTER ((void*)((uintptr_t)-1)) + +/// Data types +//! A memory heap, per thread +typedef struct heap_t heap_t; +//! Heap spans per size class +typedef struct heap_class_t heap_class_t; +//! Span of memory pages +typedef struct span_t span_t; +//! Span list +typedef struct span_list_t span_list_t; +//! Span active data +typedef struct span_active_t span_active_t; +//! Size class definition +typedef struct size_class_t size_class_t; +//! Global cache +typedef struct global_cache_t global_cache_t; + +//! Flag indicating span is the first (master) span of a split superspan +#define SPAN_FLAG_MASTER 1U +//! Flag indicating span is a secondary (sub) span of a split superspan +#define SPAN_FLAG_SUBSPAN 2U +//! Flag indicating span has blocks with increased alignment +#define SPAN_FLAG_ALIGNED_BLOCKS 4U + +#if ENABLE_ADAPTIVE_THREAD_CACHE || ENABLE_STATISTICS +struct span_use_t { + //! Current number of spans used (actually used, not in cache) + atomic32_t current; + //! High water mark of spans used + uint32_t high; +#if ENABLE_STATISTICS + //! Number of spans transitioned to global cache + uint32_t spans_to_global; + //! Number of spans transitioned from global cache + uint32_t spans_from_global; + //! Number of spans transitioned to thread cache + uint32_t spans_to_cache; + //! Number of spans transitioned from thread cache + uint32_t spans_from_cache; + //! Number of spans transitioned to reserved state + uint32_t spans_to_reserved; + //! Number of spans transitioned from reserved state + uint32_t spans_from_reserved; + //! Number of raw memory map calls + uint32_t spans_map_calls; +#endif +}; +typedef struct span_use_t span_use_t; +#endif + +#if ENABLE_STATISTICS +struct size_class_use_t { + //! Current number of allocations + atomic32_t alloc_current; + //! Peak number of allocations + int32_t alloc_peak; + //! Total number of allocations + int32_t alloc_total; + //! Total number of frees + atomic32_t free_total; + //! Number of spans in use + uint32_t spans_current; + //! Number of spans transitioned to cache + uint32_t spans_peak; + //! Number of spans transitioned to cache + uint32_t spans_to_cache; + //! Number of spans transitioned from cache + uint32_t spans_from_cache; + //! Number of spans transitioned from reserved state + uint32_t spans_from_reserved; + //! Number of spans mapped + uint32_t spans_map_calls; +}; +typedef struct size_class_use_t size_class_use_t; +#endif + +typedef enum span_state_t { + SPAN_STATE_ACTIVE = 0, + SPAN_STATE_PARTIAL, + SPAN_STATE_FULL +} span_state_t; + +//A span can either represent a single span of memory pages with size declared by span_map_count configuration variable, +//or a set of spans in a continuous region, a super span. Any reference to the term "span" usually refers to both a single +//span or a super span. A super span can further be divided into multiple spans (or this, super spans), where the first +//(super)span is the master and subsequent (super)spans are subspans. The master span keeps track of how many subspans +//that are still alive and mapped in virtual memory, and once all subspans and master have been unmapped the entire +//superspan region is released and unmapped (on Windows for example, the entire superspan range has to be released +//in the same call to release the virtual memory range, but individual subranges can be decommitted individually +//to reduce physical memory use). +struct span_t { + //! Free list + void* free_list; + //! State + uint32_t state; + //! Used count when not active (not including deferred free list) + uint32_t used_count; + //! Block count + uint32_t block_count; + //! Size class + uint32_t size_class; + //! Index of last block initialized in free list + uint32_t free_list_limit; + //! Span list size when part of a cache list, or size of deferred free list when partial/full + uint32_t list_size; + //! Deferred free list + atomicptr_t free_list_deferred; + //! Size of a block + uint32_t block_size; + //! Flags and counters + uint32_t flags; + //! Number of spans + uint32_t span_count; + //! Total span counter for master spans, distance for subspans + uint32_t total_spans_or_distance; + //! Remaining span counter, for master spans + atomic32_t remaining_spans; + //! Alignment offset + uint32_t align_offset; + //! Owning heap + heap_t* heap; + //! Next span + span_t* next; + //! Previous span + span_t* prev; +}; +_Static_assert(sizeof(span_t) <= SPAN_HEADER_SIZE, "span size mismatch"); + +struct heap_class_t { + //! Free list of active span + void* free_list; + //! Double linked list of partially used spans with free blocks for each size class. + // Current active span is at head of list. Previous span pointer in head points to tail span of list. + span_t* partial_span; +}; + +struct heap_t { + //! Active and semi-used span data per size class + heap_class_t span_class[SIZE_CLASS_COUNT]; +#if ENABLE_THREAD_CACHE + //! List of free spans (single linked list) + span_t* span_cache[LARGE_CLASS_COUNT]; + //! List of deferred free spans of class 0 (single linked list) + atomicptr_t span_cache_deferred; +#endif +#if ENABLE_ADAPTIVE_THREAD_CACHE || ENABLE_STATISTICS + //! Current and high water mark of spans used per span count + span_use_t span_use[LARGE_CLASS_COUNT]; +#endif + //! Mapped but unused spans + span_t* span_reserve; + //! Master span for mapped but unused spans + span_t* span_reserve_master; + //! Number of mapped but unused spans + size_t spans_reserved; + //! Next heap in id list + heap_t* next_heap; + //! Next heap in orphan list + heap_t* next_orphan; + //! Memory pages alignment offset + size_t align_offset; + //! Heap ID + int32_t id; +#if ENABLE_STATISTICS + //! Number of bytes transitioned thread -> global + size_t thread_to_global; + //! Number of bytes transitioned global -> thread + size_t global_to_thread; + //! Allocation stats per size class + size_class_use_t size_class_use[SIZE_CLASS_COUNT + 1]; +#endif +}; + +struct size_class_t { + //! Size of blocks in this class + uint32_t block_size; + //! Number of blocks in each chunk + uint16_t block_count; + //! Class index this class is merged with + uint16_t class_idx; +}; +_Static_assert(sizeof(size_class_t) == 8, "Size class size mismatch"); + +struct global_cache_t { + //! Cache list pointer + atomicptr_t cache; + //! Cache size + atomic32_t size; + //! ABA counter + atomic32_t counter; +}; + +/// Global data +//! Initialized flag +static int _rpmalloc_initialized; +//! Configuration +static rpmalloc_config_t _memory_config; +//! Memory page size +static size_t _memory_page_size; +//! Shift to divide by page size +static size_t _memory_page_size_shift; +//! Granularity at which memory pages are mapped by OS +static size_t _memory_map_granularity; +#if RPMALLOC_CONFIGURABLE +//! Size of a span of memory pages +static size_t _memory_span_size; +//! Shift to divide by span size +static size_t _memory_span_size_shift; +//! Mask to get to start of a memory span +static uintptr_t _memory_span_mask; +#else +//! Hardwired span size (64KiB) +#define _memory_span_size (64 * 1024) +#define _memory_span_size_shift 16 +#define _memory_span_mask (~((uintptr_t)(_memory_span_size - 1))) +#endif +//! Number of spans to map in each map call +static size_t _memory_span_map_count; +//! Number of spans to release from thread cache to global cache (single spans) +static size_t _memory_span_release_count; +//! Number of spans to release from thread cache to global cache (large multiple spans) +static size_t _memory_span_release_count_large; +//! Global size classes +static size_class_t _memory_size_class[SIZE_CLASS_COUNT]; +//! Run-time size limit of medium blocks +static size_t _memory_medium_size_limit; +//! Heap ID counter +static atomic32_t _memory_heap_id; +//! Huge page support +static int _memory_huge_pages; +#if ENABLE_GLOBAL_CACHE +//! Global span cache +static global_cache_t _memory_span_cache[LARGE_CLASS_COUNT]; +#endif +//! All heaps +static atomicptr_t _memory_heaps[HEAP_ARRAY_SIZE]; +//! Orphaned heaps +static atomicptr_t _memory_orphan_heaps; +//! Running orphan counter to avoid ABA issues in linked list +static atomic32_t _memory_orphan_counter; +#if ENABLE_STATISTICS +//! Active heap count +static atomic32_t _memory_active_heaps; +//! Number of currently mapped memory pages +static atomic32_t _mapped_pages; +//! Peak number of concurrently mapped memory pages +static int32_t _mapped_pages_peak; +//! Number of currently unused spans +static atomic32_t _reserved_spans; +//! Running counter of total number of mapped memory pages since start +static atomic32_t _mapped_total; +//! Running counter of total number of unmapped memory pages since start +static atomic32_t _unmapped_total; +//! Number of currently mapped memory pages in OS calls +static atomic32_t _mapped_pages_os; +//! Number of currently allocated pages in huge allocations +static atomic32_t _huge_pages_current; +//! Peak number of currently allocated pages in huge allocations +static int32_t _huge_pages_peak; +#endif + +//! Current thread heap +#if (defined(__APPLE__) || defined(__HAIKU__)) && ENABLE_PRELOAD +static pthread_key_t _memory_thread_heap; +#else +# ifdef _MSC_VER +# define _Thread_local __declspec(thread) +# define TLS_MODEL +# else +# define TLS_MODEL __attribute__((tls_model("initial-exec"))) +# if !defined(__clang__) && defined(__GNUC__) +# define _Thread_local __thread +# endif +# endif +static _Thread_local heap_t* _memory_thread_heap TLS_MODEL; +#endif + +static inline heap_t* +get_thread_heap_raw(void) { +#if (defined(__APPLE__) || defined(__HAIKU__)) && ENABLE_PRELOAD + return pthread_getspecific(_memory_thread_heap); +#else + return _memory_thread_heap; +#endif +} + +//! Get the current thread heap +static inline heap_t* +get_thread_heap(void) { + heap_t* heap = get_thread_heap_raw(); +#if ENABLE_PRELOAD + if (EXPECTED(heap != 0)) + return heap; + rpmalloc_initialize(); + return get_thread_heap_raw(); +#else + return heap; +#endif +} + +//! Set the current thread heap +static void +set_thread_heap(heap_t* heap) { +#if (defined(__APPLE__) || defined(__HAIKU__)) && ENABLE_PRELOAD + pthread_setspecific(_memory_thread_heap, heap); +#else + _memory_thread_heap = heap; +#endif +} + +//! Default implementation to map more virtual memory +static void* +_memory_map_os(size_t size, size_t* offset); + +//! Default implementation to unmap virtual memory +static void +_memory_unmap_os(void* address, size_t size, size_t offset, size_t release); + +//! Lookup a memory heap from heap ID +static heap_t* +_memory_heap_lookup(int32_t id) { + uint32_t list_idx = id % HEAP_ARRAY_SIZE; + heap_t* heap = (heap_t*)atomic_load_ptr(&_memory_heaps[list_idx]); + while (heap && (heap->id != id)) + heap = heap->next_heap; + return heap; +} + +#if ENABLE_STATISTICS +# define _memory_statistics_inc(counter, value) counter += value +# define _memory_statistics_dec(counter, value) counter -= value +# define _memory_statistics_add(atomic_counter, value) atomic_add32(atomic_counter, (int32_t)(value)) +# define _memory_statistics_add_peak(atomic_counter, value, peak) do { int32_t _cur_count = atomic_add32(atomic_counter, (int32_t)(value)); if (_cur_count > (peak)) peak = _cur_count; } while (0) +# define _memory_statistics_sub(atomic_counter, value) atomic_add32(atomic_counter, -(int32_t)(value)) +# define _memory_statistics_inc_alloc(heap, class_idx) do { \ + int32_t alloc_current = atomic_incr32(&heap->size_class_use[class_idx].alloc_current); \ + if (alloc_current > heap->size_class_use[class_idx].alloc_peak) \ + heap->size_class_use[class_idx].alloc_peak = alloc_current; \ + heap->size_class_use[class_idx].alloc_total++; \ +} while(0) +# define _memory_statistics_inc_free(heap, class_idx) do { \ + atomic_decr32(&heap->size_class_use[class_idx].alloc_current); \ + atomic_incr32(&heap->size_class_use[class_idx].free_total); \ +} while(0) +#else +# define _memory_statistics_inc(counter, value) do {} while(0) +# define _memory_statistics_dec(counter, value) do {} while(0) +# define _memory_statistics_add(atomic_counter, value) do {} while(0) +# define _memory_statistics_add_peak(atomic_counter, value, peak) do {} while (0) +# define _memory_statistics_sub(atomic_counter, value) do {} while(0) +# define _memory_statistics_inc_alloc(heap, class_idx) do {} while(0) +# define _memory_statistics_inc_free(heap, class_idx) do {} while(0) +#endif + +static void +_memory_heap_cache_insert(heap_t* heap, span_t* span); + +//! Map more virtual memory +static void* +_memory_map(size_t size, size_t* offset) { + assert(!(size % _memory_page_size)); + assert(size >= _memory_page_size); + _memory_statistics_add_peak(&_mapped_pages, (size >> _memory_page_size_shift), _mapped_pages_peak); + _memory_statistics_add(&_mapped_total, (size >> _memory_page_size_shift)); + return _memory_config.memory_map(size, offset); +} + +//! Unmap virtual memory +static void +_memory_unmap(void* address, size_t size, size_t offset, size_t release) { + assert(!release || (release >= size)); + assert(!release || (release >= _memory_page_size)); + if (release) { + assert(!(release % _memory_page_size)); + _memory_statistics_sub(&_mapped_pages, (release >> _memory_page_size_shift)); + _memory_statistics_add(&_unmapped_total, (release >> _memory_page_size_shift)); + } + _memory_config.memory_unmap(address, size, offset, release); +} + +//! Declare the span to be a subspan and store distance from master span and span count +static void +_memory_span_mark_as_subspan_unless_master(span_t* master, span_t* subspan, size_t span_count) { + assert((subspan != master) || (subspan->flags & SPAN_FLAG_MASTER)); + if (subspan != master) { + subspan->flags = SPAN_FLAG_SUBSPAN; + subspan->total_spans_or_distance = (uint32_t)((uintptr_t)pointer_diff(subspan, master) >> _memory_span_size_shift); + subspan->align_offset = 0; + } + subspan->span_count = (uint32_t)span_count; +} + +//! Use reserved spans to fulfill a memory map request (reserve size must be checked by caller) +static span_t* +_memory_map_from_reserve(heap_t* heap, size_t span_count) { + //Update the heap span reserve + span_t* span = heap->span_reserve; + heap->span_reserve = (span_t*)pointer_offset(span, span_count * _memory_span_size); + heap->spans_reserved -= span_count; + + _memory_span_mark_as_subspan_unless_master(heap->span_reserve_master, span, span_count); + if (span_count <= LARGE_CLASS_COUNT) + _memory_statistics_inc(heap->span_use[span_count - 1].spans_from_reserved, 1); + + return span; +} + +//! Get the aligned number of spans to map in based on wanted count, configured mapping granularity and the page size +static size_t +_memory_map_align_span_count(size_t span_count) { + size_t request_count = (span_count > _memory_span_map_count) ? span_count : _memory_span_map_count; + if ((_memory_page_size > _memory_span_size) && ((request_count * _memory_span_size) % _memory_page_size)) + request_count += _memory_span_map_count - (request_count % _memory_span_map_count); + return request_count; +} + +//! Store the given spans as reserve in the given heap +static void +_memory_heap_set_reserved_spans(heap_t* heap, span_t* master, span_t* reserve, size_t reserve_span_count) { + heap->span_reserve_master = master; + heap->span_reserve = reserve; + heap->spans_reserved = reserve_span_count; +} + +//! Setup a newly mapped span +static void +_memory_span_initialize(span_t* span, size_t total_span_count, size_t span_count, size_t align_offset) { + span->total_spans_or_distance = (uint32_t)total_span_count; + span->span_count = (uint32_t)span_count; + span->align_offset = (uint32_t)align_offset; + span->flags = SPAN_FLAG_MASTER; + atomic_store32(&span->remaining_spans, (int32_t)total_span_count); +} + +//! Map a akigned set of spans, taking configured mapping granularity and the page size into account +static span_t* +_memory_map_aligned_span_count(heap_t* heap, size_t span_count) { + //If we already have some, but not enough, reserved spans, release those to heap cache and map a new + //full set of spans. Otherwise we would waste memory if page size > span size (huge pages) + size_t aligned_span_count = _memory_map_align_span_count(span_count); + size_t align_offset = 0; + span_t* span = (span_t*)_memory_map(aligned_span_count * _memory_span_size, &align_offset); + if (!span) + return 0; + _memory_span_initialize(span, aligned_span_count, span_count, align_offset); + _memory_statistics_add(&_reserved_spans, aligned_span_count); + if (span_count <= LARGE_CLASS_COUNT) + _memory_statistics_inc(heap->span_use[span_count - 1].spans_map_calls, 1); + if (aligned_span_count > span_count) { + if (heap->spans_reserved) { + _memory_span_mark_as_subspan_unless_master(heap->span_reserve_master, heap->span_reserve, heap->spans_reserved); + _memory_heap_cache_insert(heap, heap->span_reserve); + } + _memory_heap_set_reserved_spans(heap, span, (span_t*)pointer_offset(span, span_count * _memory_span_size), aligned_span_count - span_count); + } + return span; +} + +//! Map in memory pages for the given number of spans (or use previously reserved pages) +static span_t* +_memory_map_spans(heap_t* heap, size_t span_count) { + if (span_count <= heap->spans_reserved) + return _memory_map_from_reserve(heap, span_count); + return _memory_map_aligned_span_count(heap, span_count); +} + +//! Unmap memory pages for the given number of spans (or mark as unused if no partial unmappings) +static void +_memory_unmap_span(span_t* span) { + assert((span->flags & SPAN_FLAG_MASTER) || (span->flags & SPAN_FLAG_SUBSPAN)); + assert(!(span->flags & SPAN_FLAG_MASTER) || !(span->flags & SPAN_FLAG_SUBSPAN)); + + int is_master = !!(span->flags & SPAN_FLAG_MASTER); + span_t* master = is_master ? span : (span_t*)(pointer_offset(span, -(int32_t)(span->total_spans_or_distance * _memory_span_size))); + assert(is_master || (span->flags & SPAN_FLAG_SUBSPAN)); + assert(master->flags & SPAN_FLAG_MASTER); + + size_t span_count = span->span_count; + if (!is_master) { + //Directly unmap subspans (unless huge pages, in which case we defer and unmap entire page range with master) + assert(span->align_offset == 0); + if (_memory_span_size >= _memory_page_size) { + _memory_unmap(span, span_count * _memory_span_size, 0, 0); + _memory_statistics_sub(&_reserved_spans, span_count); + } + } else { + //Special double flag to denote an unmapped master + //It must be kept in memory since span header must be used + span->flags |= SPAN_FLAG_MASTER | SPAN_FLAG_SUBSPAN; + } + + if (atomic_add32(&master->remaining_spans, -(int32_t)span_count) <= 0) { + //Everything unmapped, unmap the master span with release flag to unmap the entire range of the super span + assert(!!(master->flags & SPAN_FLAG_MASTER) && !!(master->flags & SPAN_FLAG_SUBSPAN)); + size_t unmap_count = master->span_count; + if (_memory_span_size < _memory_page_size) + unmap_count = master->total_spans_or_distance; + _memory_statistics_sub(&_reserved_spans, unmap_count); + _memory_unmap(master, unmap_count * _memory_span_size, master->align_offset, master->total_spans_or_distance * _memory_span_size); + } +} + +#if ENABLE_THREAD_CACHE + +//! Unmap a single linked list of spans +static void +_memory_unmap_span_list(span_t* span) { + size_t list_size = span->list_size; + for (size_t ispan = 0; ispan < list_size; ++ispan) { + span_t* next_span = span->next; + _memory_unmap_span(span); + span = next_span; + } + assert(!span); +} + +//! Add span to head of single linked span list +static size_t +_memory_span_list_push(span_t** head, span_t* span) { + span->next = *head; + if (*head) + span->list_size = (*head)->list_size + 1; + else + span->list_size = 1; + *head = span; + return span->list_size; +} + +//! Remove span from head of single linked span list, returns the new list head +static span_t* +_memory_span_list_pop(span_t** head) { + span_t* span = *head; + span_t* next_span = 0; + if (span->list_size > 1) { + assert(span->next); + next_span = span->next; + assert(next_span); + next_span->list_size = span->list_size - 1; + } + *head = next_span; + return span; +} + +//! Split a single linked span list +static span_t* +_memory_span_list_split(span_t* span, size_t limit) { + span_t* next = 0; + if (limit < 2) + limit = 2; + if (span->list_size > limit) { + uint32_t list_size = 1; + span_t* last = span; + next = span->next; + while (list_size < limit) { + last = next; + next = next->next; + ++list_size; + } + last->next = 0; + assert(next); + next->list_size = span->list_size - list_size; + span->list_size = list_size; + span->prev = 0; + } + return next; +} + +#endif + +//! Add a span to partial span double linked list at the head +static void +_memory_span_partial_list_add(span_t** head, span_t* span) { + if (*head) { + span->next = *head; + //Maintain pointer to tail span + span->prev = (*head)->prev; + (*head)->prev = span; + } else { + span->next = 0; + span->prev = span; + } + *head = span; +} + +//! Add a span to partial span double linked list at the tail +static void +_memory_span_partial_list_add_tail(span_t** head, span_t* span) { + span->next = 0; + if (*head) { + span_t* tail = (*head)->prev; + tail->next = span; + span->prev = tail; + //Maintain pointer to tail span + (*head)->prev = span; + } else { + span->prev = span; + *head = span; + } +} + +//! Pop head span from partial span double linked list +static void +_memory_span_partial_list_pop_head(span_t** head) { + span_t* span = *head; + *head = span->next; + if (*head) { + //Maintain pointer to tail span + (*head)->prev = span->prev; + } +} + +//! Remove a span from partial span double linked list +static void +_memory_span_partial_list_remove(span_t** head, span_t* span) { + if (UNEXPECTED(*head == span)) { + _memory_span_partial_list_pop_head(head); + } else { + span_t* next_span = span->next; + span_t* prev_span = span->prev; + prev_span->next = next_span; + if (EXPECTED(next_span != 0)) { + next_span->prev = prev_span; + } else { + //Update pointer to tail span + (*head)->prev = prev_span; + } + } +} + +#if ENABLE_GLOBAL_CACHE + +//! Insert the given list of memory page spans in the global cache +static void +_memory_cache_insert(global_cache_t* cache, span_t* span, size_t cache_limit) { + assert((span->list_size == 1) || (span->next != 0)); + int32_t list_size = (int32_t)span->list_size; + //Unmap if cache has reached the limit + if (atomic_add32(&cache->size, list_size) > (int32_t)cache_limit) { +#if !ENABLE_UNLIMITED_GLOBAL_CACHE + _memory_unmap_span_list(span); + atomic_add32(&cache->size, -list_size); + return; +#endif + } + void* current_cache, *new_cache; + do { + current_cache = atomic_load_ptr(&cache->cache); + span->prev = (span_t*)((uintptr_t)current_cache & _memory_span_mask); + new_cache = (void*)((uintptr_t)span | ((uintptr_t)atomic_incr32(&cache->counter) & ~_memory_span_mask)); + } while (!atomic_cas_ptr(&cache->cache, new_cache, current_cache)); +} + +//! Extract a number of memory page spans from the global cache +static span_t* +_memory_cache_extract(global_cache_t* cache) { + uintptr_t span_ptr; + do { + void* global_span = atomic_load_ptr(&cache->cache); + span_ptr = (uintptr_t)global_span & _memory_span_mask; + if (span_ptr) { + span_t* span = (span_t*)span_ptr; + //By accessing the span ptr before it is swapped out of list we assume that a contending thread + //does not manage to traverse the span to being unmapped before we access it + void* new_cache = (void*)((uintptr_t)span->prev | ((uintptr_t)atomic_incr32(&cache->counter) & ~_memory_span_mask)); + if (atomic_cas_ptr(&cache->cache, new_cache, global_span)) { + atomic_add32(&cache->size, -(int32_t)span->list_size); + return span; + } + } + } while (span_ptr); + return 0; +} + +//! Finalize a global cache, only valid from allocator finalization (not thread safe) +static void +_memory_cache_finalize(global_cache_t* cache) { + void* current_cache = atomic_load_ptr(&cache->cache); + span_t* span = (span_t*)((uintptr_t)current_cache & _memory_span_mask); + while (span) { + span_t* skip_span = (span_t*)((uintptr_t)span->prev & _memory_span_mask); + atomic_add32(&cache->size, -(int32_t)span->list_size); + _memory_unmap_span_list(span); + span = skip_span; + } + assert(!atomic_load32(&cache->size)); + atomic_store_ptr(&cache->cache, 0); + atomic_store32(&cache->size, 0); +} + +//! Insert the given list of memory page spans in the global cache +static void +_memory_global_cache_insert(span_t* span) { + size_t span_count = span->span_count; +#if ENABLE_UNLIMITED_GLOBAL_CACHE + _memory_cache_insert(&_memory_span_cache[span_count - 1], span, 0); +#else + const size_t cache_limit = (GLOBAL_CACHE_MULTIPLIER * ((span_count == 1) ? _memory_span_release_count : _memory_span_release_count_large)); + _memory_cache_insert(&_memory_span_cache[span_count - 1], span, cache_limit); +#endif +} + +//! Extract a number of memory page spans from the global cache for large blocks +static span_t* +_memory_global_cache_extract(size_t span_count) { + span_t* span = _memory_cache_extract(&_memory_span_cache[span_count - 1]); + assert(!span || (span->span_count == span_count)); + return span; +} + +#endif + +#if ENABLE_THREAD_CACHE +//! Adopt the deferred span cache list +static void +_memory_heap_cache_adopt_deferred(heap_t* heap) { + atomic_thread_fence_acquire(); + span_t* span = (span_t*)atomic_load_ptr(&heap->span_cache_deferred); + if (!span) + return; + do { + span = (span_t*)atomic_load_ptr(&heap->span_cache_deferred); + } while (!atomic_cas_ptr(&heap->span_cache_deferred, 0, span)); + while (span) { + span_t* next_span = span->next; + _memory_span_list_push(&heap->span_cache[0], span); +#if ENABLE_STATISTICS + atomic_decr32(&heap->span_use[span->span_count - 1].current); + ++heap->size_class_use[span->size_class].spans_to_cache; + --heap->size_class_use[span->size_class].spans_current; +#endif + span = next_span; + } +} +#endif + +//! Insert a single span into thread heap cache, releasing to global cache if overflow +static void +_memory_heap_cache_insert(heap_t* heap, span_t* span) { +#if ENABLE_THREAD_CACHE + size_t span_count = span->span_count; + size_t idx = span_count - 1; + _memory_statistics_inc(heap->span_use[idx].spans_to_cache, 1); + if (!idx) + _memory_heap_cache_adopt_deferred(heap); +#if ENABLE_UNLIMITED_THREAD_CACHE + _memory_span_list_push(&heap->span_cache[idx], span); +#else + const size_t release_count = (!idx ? _memory_span_release_count : _memory_span_release_count_large); + size_t current_cache_size = _memory_span_list_push(&heap->span_cache[idx], span); + if (current_cache_size <= release_count) + return; + const size_t hard_limit = release_count * THREAD_CACHE_MULTIPLIER; + if (current_cache_size <= hard_limit) { +#if ENABLE_ADAPTIVE_THREAD_CACHE + //Require 25% of high water mark to remain in cache (and at least 1, if use is 0) + const size_t high_mark = heap->span_use[idx].high; + const size_t min_limit = (high_mark >> 2) + release_count + 1; + if (current_cache_size < min_limit) + return; +#else + return; +#endif + } + heap->span_cache[idx] = _memory_span_list_split(span, release_count); + assert(span->list_size == release_count); +#if ENABLE_STATISTICS + heap->thread_to_global += (size_t)span->list_size * span_count * _memory_span_size; + heap->span_use[idx].spans_to_global += span->list_size; +#endif +#if ENABLE_GLOBAL_CACHE + _memory_global_cache_insert(span); +#else + _memory_unmap_span_list(span); +#endif +#endif +#else + (void)sizeof(heap); + _memory_unmap_span(span); +#endif +} + +//! Extract the given number of spans from the different cache levels +static span_t* +_memory_heap_thread_cache_extract(heap_t* heap, size_t span_count) { +#if ENABLE_THREAD_CACHE + size_t idx = span_count - 1; + if (!idx) + _memory_heap_cache_adopt_deferred(heap); + if (heap->span_cache[idx]) { +#if ENABLE_STATISTICS + heap->span_use[idx].spans_from_cache++; +#endif + return _memory_span_list_pop(&heap->span_cache[idx]); + } +#endif + return 0; +} + +static span_t* +_memory_heap_reserved_extract(heap_t* heap, size_t span_count) { + if (heap->spans_reserved >= span_count) + return _memory_map_spans(heap, span_count); + return 0; +} + +//! Extract a span from the global cache +static span_t* +_memory_heap_global_cache_extract(heap_t* heap, size_t span_count) { +#if ENABLE_GLOBAL_CACHE + size_t idx = span_count - 1; + heap->span_cache[idx] = _memory_global_cache_extract(span_count); + if (heap->span_cache[idx]) { +#if ENABLE_STATISTICS + heap->global_to_thread += (size_t)heap->span_cache[idx]->list_size * span_count * _memory_span_size; + heap->span_use[idx].spans_from_global += heap->span_cache[idx]->list_size; +#endif + return _memory_span_list_pop(&heap->span_cache[idx]); + } +#endif + return 0; +} + +//! Get a span from one of the cache levels (thread cache, reserved, global cache) or fallback to mapping more memory +static span_t* +_memory_heap_extract_new_span(heap_t* heap, size_t span_count, uint32_t class_idx) { + (void)sizeof(class_idx); +#if ENABLE_ADAPTIVE_THREAD_CACHE || ENABLE_STATISTICS + uint32_t idx = (uint32_t)span_count - 1; + uint32_t current_count = (uint32_t)atomic_incr32(&heap->span_use[idx].current); + if (current_count > heap->span_use[idx].high) + heap->span_use[idx].high = current_count; +#if ENABLE_STATISTICS + uint32_t spans_current = ++heap->size_class_use[class_idx].spans_current; + if (spans_current > heap->size_class_use[class_idx].spans_peak) + heap->size_class_use[class_idx].spans_peak = spans_current; +#endif +#endif + span_t* span = _memory_heap_thread_cache_extract(heap, span_count); + if (EXPECTED(span != 0)) { + _memory_statistics_inc(heap->size_class_use[class_idx].spans_from_cache, 1); + return span; + } + span = _memory_heap_reserved_extract(heap, span_count); + if (EXPECTED(span != 0)) { + _memory_statistics_inc(heap->size_class_use[class_idx].spans_from_reserved, 1); + return span; + } + span = _memory_heap_global_cache_extract(heap, span_count); + if (EXPECTED(span != 0)) { + _memory_statistics_inc(heap->size_class_use[class_idx].spans_from_cache, 1); + return span; + } + //Final fallback, map in more virtual memory + span = _memory_map_spans(heap, span_count); + _memory_statistics_inc(heap->size_class_use[class_idx].spans_map_calls, 1); + return span; +} + +//! Move the span (used for small or medium allocations) to the heap thread cache +static void +_memory_span_release_to_cache(heap_t* heap, span_t* span) { + heap_class_t* heap_class = heap->span_class + span->size_class; + assert(heap_class->partial_span != span); + if (span->state == SPAN_STATE_PARTIAL) + _memory_span_partial_list_remove(&heap_class->partial_span, span); +#if ENABLE_ADAPTIVE_THREAD_CACHE || ENABLE_STATISTICS + atomic_decr32(&heap->span_use[0].current); +#endif + _memory_statistics_inc(heap->span_use[0].spans_to_cache, 1); + _memory_statistics_inc(heap->size_class_use[span->size_class].spans_to_cache, 1); + _memory_statistics_dec(heap->size_class_use[span->size_class].spans_current, 1); + _memory_heap_cache_insert(heap, span); +} + +//! Initialize a (partial) free list up to next system memory page, while reserving the first block +//! as allocated, returning number of blocks in list +static uint32_t +free_list_partial_init(void** list, void** first_block, void* page_start, void* block_start, + uint32_t block_count, uint32_t block_size) { + assert(block_count); + *first_block = block_start; + if (block_count > 1) { + void* free_block = pointer_offset(block_start, block_size); + void* block_end = pointer_offset(block_start, block_size * block_count); + //If block size is less than half a memory page, bound init to next memory page boundary + if (block_size < (_memory_page_size >> 1)) { + void* page_end = pointer_offset(page_start, _memory_page_size); + if (page_end < block_end) + block_end = page_end; + } + *list = free_block; + block_count = 2; + void* next_block = pointer_offset(free_block, block_size); + while (next_block < block_end) { + *((void**)free_block) = next_block; + free_block = next_block; + ++block_count; + next_block = pointer_offset(next_block, block_size); + } + *((void**)free_block) = 0; + } else { + *list = 0; + } + return block_count; +} + +//! Initialize an unused span (from cache or mapped) to be new active span +static void* +_memory_span_set_new_active(heap_t* heap, heap_class_t* heap_class, span_t* span, uint32_t class_idx) { + assert(span->span_count == 1); + size_class_t* size_class = _memory_size_class + class_idx; + span->size_class = class_idx; + span->heap = heap; + span->flags &= ~SPAN_FLAG_ALIGNED_BLOCKS; + span->block_count = size_class->block_count; + span->block_size = size_class->block_size; + span->state = SPAN_STATE_ACTIVE; + span->free_list = 0; + + //Setup free list. Only initialize one system page worth of free blocks in list + void* block; + span->free_list_limit = free_list_partial_init(&heap_class->free_list, &block, + span, pointer_offset(span, SPAN_HEADER_SIZE), size_class->block_count, size_class->block_size); + atomic_store_ptr(&span->free_list_deferred, 0); + span->list_size = 0; + atomic_thread_fence_release(); + + _memory_span_partial_list_add(&heap_class->partial_span, span); + return block; +} + +//! Promote a partially used span (from heap used list) to be new active span +static void +_memory_span_set_partial_active(heap_class_t* heap_class, span_t* span) { + assert(span->state == SPAN_STATE_PARTIAL); + assert(span->block_count == _memory_size_class[span->size_class].block_count); + //Move data to heap size class and set span as active + heap_class->free_list = span->free_list; + span->state = SPAN_STATE_ACTIVE; + span->free_list = 0; + assert(heap_class->free_list); +} + +//! Mark span as full (from active) +static void +_memory_span_set_active_full(heap_class_t* heap_class, span_t* span) { + assert(span->state == SPAN_STATE_ACTIVE); + assert(span == heap_class->partial_span); + _memory_span_partial_list_pop_head(&heap_class->partial_span); + span->used_count = span->block_count; + span->state = SPAN_STATE_FULL; + span->free_list = 0; +} + +//! Move span from full to partial state +static void +_memory_span_set_full_partial(heap_t* heap, span_t* span) { + assert(span->state == SPAN_STATE_FULL); + heap_class_t* heap_class = &heap->span_class[span->size_class]; + span->state = SPAN_STATE_PARTIAL; + _memory_span_partial_list_add_tail(&heap_class->partial_span, span); +} + +static void* +_memory_span_extract_deferred(span_t* span) { + void* free_list; + do { + free_list = atomic_load_ptr(&span->free_list_deferred); + } while ((free_list == INVALID_POINTER) || !atomic_cas_ptr(&span->free_list_deferred, INVALID_POINTER, free_list)); + span->list_size = 0; + atomic_store_ptr(&span->free_list_deferred, 0); + atomic_thread_fence_release(); + return free_list; +} + +//! Pop first block from a free list +static void* +free_list_pop(void** list) { + void* block = *list; + *list = *((void**)block); + return block; +} + +//! Allocate a small/medium sized memory block from the given heap +static void* +_memory_allocate_from_heap_fallback(heap_t* heap, uint32_t class_idx) { + heap_class_t* heap_class = &heap->span_class[class_idx]; + void* block; + + span_t* active_span = heap_class->partial_span; + if (EXPECTED(active_span != 0)) { + assert(active_span->state == SPAN_STATE_ACTIVE); + assert(active_span->block_count == _memory_size_class[active_span->size_class].block_count); + //Swap in free list if not empty + if (active_span->free_list) { + heap_class->free_list = active_span->free_list; + active_span->free_list = 0; + return free_list_pop(&heap_class->free_list); + } + //If the span did not fully initialize free list, link up another page worth of blocks + if (active_span->free_list_limit < active_span->block_count) { + void* block_start = pointer_offset(active_span, SPAN_HEADER_SIZE + (active_span->free_list_limit * active_span->block_size)); + active_span->free_list_limit += free_list_partial_init(&heap_class->free_list, &block, + (void*)((uintptr_t)block_start & ~(_memory_page_size - 1)), block_start, + active_span->block_count - active_span->free_list_limit, active_span->block_size); + return block; + } + //Swap in deferred free list + atomic_thread_fence_acquire(); + if (atomic_load_ptr(&active_span->free_list_deferred)) { + heap_class->free_list = _memory_span_extract_deferred(active_span); + return free_list_pop(&heap_class->free_list); + } + + //If the active span is fully allocated, mark span as free floating (fully allocated and not part of any list) + assert(!heap_class->free_list); + assert(active_span->free_list_limit >= active_span->block_count); + _memory_span_set_active_full(heap_class, active_span); + } + assert(!heap_class->free_list); + + //Try promoting a semi-used span to active + active_span = heap_class->partial_span; + if (EXPECTED(active_span != 0)) { + _memory_span_set_partial_active(heap_class, active_span); + return free_list_pop(&heap_class->free_list); + } + assert(!heap_class->free_list); + assert(!heap_class->partial_span); + + //Find a span in one of the cache levels + active_span = _memory_heap_extract_new_span(heap, 1, class_idx); + + //Mark span as owned by this heap and set base data, return first block + return _memory_span_set_new_active(heap, heap_class, active_span, class_idx); +} + +//! Allocate a small sized memory block from the given heap +static void* +_memory_allocate_small(heap_t* heap, size_t size) { + //Small sizes have unique size classes + const uint32_t class_idx = (uint32_t)((size + (SMALL_GRANULARITY - 1)) >> SMALL_GRANULARITY_SHIFT); + _memory_statistics_inc_alloc(heap, class_idx); + if (EXPECTED(heap->span_class[class_idx].free_list != 0)) + return free_list_pop(&heap->span_class[class_idx].free_list); + return _memory_allocate_from_heap_fallback(heap, class_idx); +} + +//! Allocate a medium sized memory block from the given heap +static void* +_memory_allocate_medium(heap_t* heap, size_t size) { + //Calculate the size class index and do a dependent lookup of the final class index (in case of merged classes) + const uint32_t base_idx = (uint32_t)(SMALL_CLASS_COUNT + ((size - (SMALL_SIZE_LIMIT + 1)) >> MEDIUM_GRANULARITY_SHIFT)); + const uint32_t class_idx = _memory_size_class[base_idx].class_idx; + _memory_statistics_inc_alloc(heap, class_idx); + if (EXPECTED(heap->span_class[class_idx].free_list != 0)) + return free_list_pop(&heap->span_class[class_idx].free_list); + return _memory_allocate_from_heap_fallback(heap, class_idx); +} + +//! Allocate a large sized memory block from the given heap +static void* +_memory_allocate_large(heap_t* heap, size_t size) { + //Calculate number of needed max sized spans (including header) + //Since this function is never called if size > LARGE_SIZE_LIMIT + //the span_count is guaranteed to be <= LARGE_CLASS_COUNT + size += SPAN_HEADER_SIZE; + size_t span_count = size >> _memory_span_size_shift; + if (size & (_memory_span_size - 1)) + ++span_count; + size_t idx = span_count - 1; + + //Find a span in one of the cache levels + span_t* span = _memory_heap_extract_new_span(heap, span_count, SIZE_CLASS_COUNT); + + //Mark span as owned by this heap and set base data + assert(span->span_count == span_count); + span->size_class = (uint32_t)(SIZE_CLASS_COUNT + idx); + span->heap = heap; + atomic_thread_fence_release(); + + return pointer_offset(span, SPAN_HEADER_SIZE); +} + +//! Allocate a huge block by mapping memory pages directly +static void* +_memory_allocate_huge(size_t size) { + size += SPAN_HEADER_SIZE; + size_t num_pages = size >> _memory_page_size_shift; + if (size & (_memory_page_size - 1)) + ++num_pages; + size_t align_offset = 0; + span_t* span = (span_t*)_memory_map(num_pages * _memory_page_size, &align_offset); + if (!span) + return span; + //Store page count in span_count + span->size_class = (uint32_t)-1; + span->span_count = (uint32_t)num_pages; + span->align_offset = (uint32_t)align_offset; + _memory_statistics_add_peak(&_huge_pages_current, num_pages, _huge_pages_peak); + + return pointer_offset(span, SPAN_HEADER_SIZE); +} + +//! Allocate a block larger than medium size +static void* +_memory_allocate_oversized(heap_t* heap, size_t size) { + if (size <= LARGE_SIZE_LIMIT) + return _memory_allocate_large(heap, size); + return _memory_allocate_huge(size); +} + +//! Allocate a block of the given size +static void* +_memory_allocate(heap_t* heap, size_t size) { + if (EXPECTED(size <= SMALL_SIZE_LIMIT)) + return _memory_allocate_small(heap, size); + else if (size <= _memory_medium_size_limit) + return _memory_allocate_medium(heap, size); + return _memory_allocate_oversized(heap, size); +} + +//! Allocate a new heap +static heap_t* +_memory_allocate_heap(void) { + void* raw_heap; + void* next_raw_heap; + uintptr_t orphan_counter; + heap_t* heap; + heap_t* next_heap; + //Try getting an orphaned heap + atomic_thread_fence_acquire(); + do { + raw_heap = atomic_load_ptr(&_memory_orphan_heaps); + heap = (heap_t*)((uintptr_t)raw_heap & ~(uintptr_t)0x1FF); + if (!heap) + break; + next_heap = heap->next_orphan; + orphan_counter = (uintptr_t)atomic_incr32(&_memory_orphan_counter); + next_raw_heap = (void*)((uintptr_t)next_heap | (orphan_counter & (uintptr_t)0x1FF)); + } while (!atomic_cas_ptr(&_memory_orphan_heaps, next_raw_heap, raw_heap)); + + if (!heap) { + //Map in pages for a new heap + size_t align_offset = 0; + heap = (heap_t*)_memory_map((1 + (sizeof(heap_t) >> _memory_page_size_shift)) * _memory_page_size, &align_offset); + if (!heap) + return heap; + memset((char*)heap, 0, sizeof(heap_t)); + heap->align_offset = align_offset; + + //Get a new heap ID + do { + heap->id = atomic_incr32(&_memory_heap_id); + if (_memory_heap_lookup(heap->id)) + heap->id = 0; + } while (!heap->id); + + //Link in heap in heap ID map + size_t list_idx = heap->id % HEAP_ARRAY_SIZE; + do { + next_heap = (heap_t*)atomic_load_ptr(&_memory_heaps[list_idx]); + heap->next_heap = next_heap; + } while (!atomic_cas_ptr(&_memory_heaps[list_idx], heap, next_heap)); + } + + return heap; +} + +//! Deallocate the given small/medium memory block in the current thread local heap +static void +_memory_deallocate_direct(span_t* span, void* block) { + assert(span->heap == get_thread_heap_raw()); + uint32_t state = span->state; + //Add block to free list + *((void**)block) = span->free_list; + span->free_list = block; + if (UNEXPECTED(state == SPAN_STATE_ACTIVE)) + return; + uint32_t used = --span->used_count; + uint32_t free = span->list_size; + if (UNEXPECTED(used == free)) + _memory_span_release_to_cache(span->heap, span); + else if (UNEXPECTED(state == SPAN_STATE_FULL)) + _memory_span_set_full_partial(span->heap, span); +} + +//! Put the block in the deferred free list of the owning span +static void +_memory_deallocate_defer(span_t* span, void* block) { + atomic_thread_fence_acquire(); + if (span->state == SPAN_STATE_FULL) { + if ((span->list_size + 1) == span->block_count) { + //Span will be completely freed by deferred deallocations, no other thread can + //currently touch it. Safe to move to owner heap deferred cache + span_t* last_head; + heap_t* heap = span->heap; + do { + last_head = (span_t*)atomic_load_ptr(&heap->span_cache_deferred); + span->next = last_head; + } while (!atomic_cas_ptr(&heap->span_cache_deferred, span, last_head)); + return; + } + } + + void* free_list; + do { + atomic_thread_fence_acquire(); + free_list = atomic_load_ptr(&span->free_list_deferred); + *((void**)block) = free_list; + } while ((free_list == INVALID_POINTER) || !atomic_cas_ptr(&span->free_list_deferred, INVALID_POINTER, free_list)); + ++span->list_size; + atomic_store_ptr(&span->free_list_deferred, block); +} + +static void +_memory_deallocate_small_or_medium(span_t* span, void* p) { + _memory_statistics_inc_free(span->heap, span->size_class); + if (span->flags & SPAN_FLAG_ALIGNED_BLOCKS) { + //Realign pointer to block start + void* blocks_start = pointer_offset(span, SPAN_HEADER_SIZE); + uint32_t block_offset = (uint32_t)pointer_diff(p, blocks_start); + p = pointer_offset(p, -(int32_t)(block_offset % span->block_size)); + } + //Check if block belongs to this heap or if deallocation should be deferred + if (span->heap == get_thread_heap_raw()) + _memory_deallocate_direct(span, p); + else + _memory_deallocate_defer(span, p); +} + +//! Deallocate the given large memory block to the current heap +static void +_memory_deallocate_large(span_t* span) { + //Decrease counter + assert(span->span_count == ((size_t)span->size_class - SIZE_CLASS_COUNT + 1)); + assert(span->size_class >= SIZE_CLASS_COUNT); + assert(span->size_class - SIZE_CLASS_COUNT < LARGE_CLASS_COUNT); + assert(!(span->flags & SPAN_FLAG_MASTER) || !(span->flags & SPAN_FLAG_SUBSPAN)); + assert((span->flags & SPAN_FLAG_MASTER) || (span->flags & SPAN_FLAG_SUBSPAN)); + //Large blocks can always be deallocated and transferred between heaps + //Investigate if it is better to defer large spans as well through span_cache_deferred, + //possibly with some heuristics to pick either scheme at runtime per deallocation + heap_t* heap = get_thread_heap(); + if (!heap) return; +#if ENABLE_ADAPTIVE_THREAD_CACHE || ENABLE_STATISTICS + size_t idx = span->span_count - 1; + atomic_decr32(&span->heap->span_use[idx].current); +#endif + if ((span->span_count > 1) && !heap->spans_reserved) { + heap->span_reserve = span; + heap->spans_reserved = span->span_count; + if (span->flags & SPAN_FLAG_MASTER) { + heap->span_reserve_master = span; + } else { //SPAN_FLAG_SUBSPAN + uint32_t distance = span->total_spans_or_distance; + span_t* master = (span_t*)pointer_offset(span, -(int32_t)(distance * _memory_span_size)); + heap->span_reserve_master = master; + assert(master->flags & SPAN_FLAG_MASTER); + assert(atomic_load32(&master->remaining_spans) >= (int32_t)span->span_count); + } + _memory_statistics_inc(heap->span_use[idx].spans_to_reserved, 1); + } else { + //Insert into cache list + _memory_heap_cache_insert(heap, span); + } +} + +//! Deallocate the given huge span +static void +_memory_deallocate_huge(span_t* span) { + //Oversized allocation, page count is stored in span_count + size_t num_pages = span->span_count; + _memory_unmap(span, num_pages * _memory_page_size, span->align_offset, num_pages * _memory_page_size); + _memory_statistics_sub(&_huge_pages_current, num_pages); +} + +//! Deallocate the given block +static void +_memory_deallocate(void* p) { + //Grab the span (always at start of span, using span alignment) + span_t* span = (span_t*)((uintptr_t)p & _memory_span_mask); + if (UNEXPECTED(!span)) + return; + if (EXPECTED(span->size_class < SIZE_CLASS_COUNT)) + _memory_deallocate_small_or_medium(span, p); + else if (span->size_class != (uint32_t)-1) + _memory_deallocate_large(span); + else + _memory_deallocate_huge(span); +} + +//! Reallocate the given block to the given size +static void* +_memory_reallocate(void* p, size_t size, size_t oldsize, unsigned int flags) { + if (p) { + //Grab the span using guaranteed span alignment + span_t* span = (span_t*)((uintptr_t)p & _memory_span_mask); + if (span->heap) { + if (span->size_class < SIZE_CLASS_COUNT) { + //Small/medium sized block + assert(span->span_count == 1); + void* blocks_start = pointer_offset(span, SPAN_HEADER_SIZE); + uint32_t block_offset = (uint32_t)pointer_diff(p, blocks_start); + uint32_t block_idx = block_offset / span->block_size; + void* block = pointer_offset(blocks_start, block_idx * span->block_size); + if (!oldsize) + oldsize = span->block_size - (uint32_t)pointer_diff(p, block); + if ((size_t)span->block_size >= size) { + //Still fits in block, never mind trying to save memory, but preserve data if alignment changed + if ((p != block) && !(flags & RPMALLOC_NO_PRESERVE)) + memmove(block, p, oldsize); + return block; + } + } else { + //Large block + size_t total_size = size + SPAN_HEADER_SIZE; + size_t num_spans = total_size >> _memory_span_size_shift; + if (total_size & (_memory_span_mask - 1)) + ++num_spans; + size_t current_spans = span->span_count; + assert(current_spans == ((span->size_class - SIZE_CLASS_COUNT) + 1)); + void* block = pointer_offset(span, SPAN_HEADER_SIZE); + if (!oldsize) + oldsize = (current_spans * _memory_span_size) - (size_t)pointer_diff(p, block) - SPAN_HEADER_SIZE; + if ((current_spans >= num_spans) && (num_spans >= (current_spans / 2))) { + //Still fits in block, never mind trying to save memory, but preserve data if alignment changed + if ((p != block) && !(flags & RPMALLOC_NO_PRESERVE)) + memmove(block, p, oldsize); + return block; + } + } + } else { + //Oversized block + size_t total_size = size + SPAN_HEADER_SIZE; + size_t num_pages = total_size >> _memory_page_size_shift; + if (total_size & (_memory_page_size - 1)) + ++num_pages; + //Page count is stored in span_count + size_t current_pages = span->span_count; + void* block = pointer_offset(span, SPAN_HEADER_SIZE); + if (!oldsize) + oldsize = (current_pages * _memory_page_size) - (size_t)pointer_diff(p, block) - SPAN_HEADER_SIZE; + if ((current_pages >= num_pages) && (num_pages >= (current_pages / 2))) { + //Still fits in block, never mind trying to save memory, but preserve data if alignment changed + if ((p != block) && !(flags & RPMALLOC_NO_PRESERVE)) + memmove(block, p, oldsize); + return block; + } + } + } else { + oldsize = 0; + } + + //Size is greater than block size, need to allocate a new block and deallocate the old + heap_t* heap = get_thread_heap(); + //Avoid hysteresis by overallocating if increase is small (below 37%) + size_t lower_bound = oldsize + (oldsize >> 2) + (oldsize >> 3); + size_t new_size = (size > lower_bound) ? size : ((size > oldsize) ? lower_bound : size); + void* block = _memory_allocate(heap, new_size); + if (p && block) { + if (!(flags & RPMALLOC_NO_PRESERVE)) + memcpy(block, p, oldsize < new_size ? oldsize : new_size); + _memory_deallocate(p); + } + + return block; +} + +//! Get the usable size of the given block +static size_t +_memory_usable_size(void* p) { + //Grab the span using guaranteed span alignment + span_t* span = (span_t*)((uintptr_t)p & _memory_span_mask); + if (span->heap) { + //Small/medium block + if (span->size_class < SIZE_CLASS_COUNT) { + void* blocks_start = pointer_offset(span, SPAN_HEADER_SIZE); + return span->block_size - ((size_t)pointer_diff(p, blocks_start) % span->block_size); + } + + //Large block + size_t current_spans = (span->size_class - SIZE_CLASS_COUNT) + 1; + return (current_spans * _memory_span_size) - (size_t)pointer_diff(p, span); + } + + //Oversized block, page count is stored in span_count + size_t current_pages = span->span_count; + return (current_pages * _memory_page_size) - (size_t)pointer_diff(p, span); +} + +//! Adjust and optimize the size class properties for the given class +static void +_memory_adjust_size_class(size_t iclass) { + size_t block_size = _memory_size_class[iclass].block_size; + size_t block_count = (_memory_span_size - SPAN_HEADER_SIZE) / block_size; + + _memory_size_class[iclass].block_count = (uint16_t)block_count; + _memory_size_class[iclass].class_idx = (uint16_t)iclass; + + //Check if previous size classes can be merged + size_t prevclass = iclass; + while (prevclass > 0) { + --prevclass; + //A class can be merged if number of pages and number of blocks are equal + if (_memory_size_class[prevclass].block_count == _memory_size_class[iclass].block_count) + memcpy(_memory_size_class + prevclass, _memory_size_class + iclass, sizeof(_memory_size_class[iclass])); + else + break; + } +} + +static void +_memory_heap_finalize(void* heapptr) { + heap_t* heap = (heap_t*)heapptr; + if (!heap) + return; + //Release thread cache spans back to global cache +#if ENABLE_THREAD_CACHE + _memory_heap_cache_adopt_deferred(heap); + for (size_t iclass = 0; iclass < LARGE_CLASS_COUNT; ++iclass) { + span_t* span = heap->span_cache[iclass]; +#if ENABLE_GLOBAL_CACHE + while (span) { + assert(span->span_count == (iclass + 1)); + size_t release_count = (!iclass ? _memory_span_release_count : _memory_span_release_count_large); + span_t* next = _memory_span_list_split(span, (uint32_t)release_count); +#if ENABLE_STATISTICS + heap->thread_to_global += (size_t)span->list_size * span->span_count * _memory_span_size; + heap->span_use[iclass].spans_to_global += span->list_size; +#endif + _memory_global_cache_insert(span); + span = next; + } +#else + if (span) + _memory_unmap_span_list(span); +#endif + heap->span_cache[iclass] = 0; + } +#endif + + //Orphan the heap + void* raw_heap; + uintptr_t orphan_counter; + heap_t* last_heap; + do { + last_heap = (heap_t*)atomic_load_ptr(&_memory_orphan_heaps); + heap->next_orphan = (heap_t*)((uintptr_t)last_heap & ~(uintptr_t)0x1FF); + orphan_counter = (uintptr_t)atomic_incr32(&_memory_orphan_counter); + raw_heap = (void*)((uintptr_t)heap | (orphan_counter & (uintptr_t)0x1FF)); + } while (!atomic_cas_ptr(&_memory_orphan_heaps, raw_heap, last_heap)); + + set_thread_heap(0); + +#if ENABLE_STATISTICS + atomic_decr32(&_memory_active_heaps); + assert(atomic_load32(&_memory_active_heaps) >= 0); +#endif +} + +#if defined(_MSC_VER) && !defined(__clang__) && (!defined(BUILD_DYNAMIC_LINK) || !BUILD_DYNAMIC_LINK) +#include +static DWORD fls_key; +static void NTAPI +rp_thread_destructor(void* value) { + if (value) + rpmalloc_thread_finalize(); +} +#endif + +#if PLATFORM_POSIX +# include +# include +# ifdef __FreeBSD__ +# include +# define MAP_HUGETLB MAP_ALIGNED_SUPER +# endif +# ifndef MAP_UNINITIALIZED +# define MAP_UNINITIALIZED 0 +# endif +#endif +#include + +//! Initialize the allocator and setup global data +TRACY_API int +rpmalloc_initialize(void) { + if (_rpmalloc_initialized) { + rpmalloc_thread_initialize(); + return 0; + } + memset(&_memory_config, 0, sizeof(rpmalloc_config_t)); + return rpmalloc_initialize_config(0); +} + +int +rpmalloc_initialize_config(const rpmalloc_config_t* config) { + if (_rpmalloc_initialized) { + rpmalloc_thread_initialize(); + return 0; + } + _rpmalloc_initialized = 1; + + if (config) + memcpy(&_memory_config, config, sizeof(rpmalloc_config_t)); + + if (!_memory_config.memory_map || !_memory_config.memory_unmap) { + _memory_config.memory_map = _memory_map_os; + _memory_config.memory_unmap = _memory_unmap_os; + } + +#if RPMALLOC_CONFIGURABLE + _memory_page_size = _memory_config.page_size; +#else + _memory_page_size = 0; +#endif + _memory_huge_pages = 0; + _memory_map_granularity = _memory_page_size; + if (!_memory_page_size) { +#if PLATFORM_WINDOWS + SYSTEM_INFO system_info; + memset(&system_info, 0, sizeof(system_info)); + GetSystemInfo(&system_info); + _memory_page_size = system_info.dwPageSize; + _memory_map_granularity = system_info.dwAllocationGranularity; + if (config && config->enable_huge_pages) { + HANDLE token = 0; + size_t large_page_minimum = GetLargePageMinimum(); + if (large_page_minimum) + OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token); + if (token) { + LUID luid; + if (LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) { + TOKEN_PRIVILEGES token_privileges; + memset(&token_privileges, 0, sizeof(token_privileges)); + token_privileges.PrivilegeCount = 1; + token_privileges.Privileges[0].Luid = luid; + token_privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (AdjustTokenPrivileges(token, FALSE, &token_privileges, 0, 0, 0)) { + DWORD err = GetLastError(); + if (err == ERROR_SUCCESS) { + _memory_huge_pages = 1; + _memory_page_size = large_page_minimum; + _memory_map_granularity = large_page_minimum; + } + } + } + CloseHandle(token); + } + } +#else + _memory_page_size = (size_t)sysconf(_SC_PAGESIZE); + _memory_map_granularity = _memory_page_size; + if (config && config->enable_huge_pages) { +#if defined(__linux__) + size_t huge_page_size = 0; + FILE* meminfo = fopen("/proc/meminfo", "r"); + if (meminfo) { + char line[128]; + while (!huge_page_size && fgets(line, sizeof(line) - 1, meminfo)) { + line[sizeof(line) - 1] = 0; + if (strstr(line, "Hugepagesize:")) + huge_page_size = (size_t)strtol(line + 13, 0, 10) * 1024; + } + fclose(meminfo); + } + if (huge_page_size) { + _memory_huge_pages = 1; + _memory_page_size = huge_page_size; + _memory_map_granularity = huge_page_size; + } +#elif defined(__FreeBSD__) + int rc; + size_t sz = sizeof(rc); + + if (sysctlbyname("vm.pmap.pg_ps_enabled", &rc, &sz, NULL, 0) == 0 && rc == 1) { + _memory_huge_pages = 1; + _memory_page_size = 2 * 1024 * 1024; + _memory_map_granularity = _memory_page_size; + } +#elif defined(__APPLE__) + _memory_huge_pages = 1; + _memory_page_size = 2 * 1024 * 1024; + _memory_map_granularity = _memory_page_size; +#endif + } +#endif + } else { + if (config && config->enable_huge_pages) + _memory_huge_pages = 1; + } + + //The ABA counter in heap orphan list is tied to using 512 (bitmask 0x1FF) + if (_memory_page_size < 512) + _memory_page_size = 512; + if (_memory_page_size > (64 * 1024 * 1024)) + _memory_page_size = (64 * 1024 * 1024); + _memory_page_size_shift = 0; + size_t page_size_bit = _memory_page_size; + while (page_size_bit != 1) { + ++_memory_page_size_shift; + page_size_bit >>= 1; + } + _memory_page_size = ((size_t)1 << _memory_page_size_shift); + +#if RPMALLOC_CONFIGURABLE + size_t span_size = _memory_config.span_size; + if (!span_size) + span_size = (64 * 1024); + if (span_size > (256 * 1024)) + span_size = (256 * 1024); + _memory_span_size = 4096; + _memory_span_size_shift = 12; + while (_memory_span_size < span_size) { + _memory_span_size <<= 1; + ++_memory_span_size_shift; + } + _memory_span_mask = ~(uintptr_t)(_memory_span_size - 1); +#endif + + _memory_span_map_count = ( _memory_config.span_map_count ? _memory_config.span_map_count : DEFAULT_SPAN_MAP_COUNT); + if ((_memory_span_size * _memory_span_map_count) < _memory_page_size) + _memory_span_map_count = (_memory_page_size / _memory_span_size); + if ((_memory_page_size >= _memory_span_size) && ((_memory_span_map_count * _memory_span_size) % _memory_page_size)) + _memory_span_map_count = (_memory_page_size / _memory_span_size); + + _memory_config.page_size = _memory_page_size; + _memory_config.span_size = _memory_span_size; + _memory_config.span_map_count = _memory_span_map_count; + _memory_config.enable_huge_pages = _memory_huge_pages; + + _memory_span_release_count = (_memory_span_map_count > 4 ? ((_memory_span_map_count < 64) ? _memory_span_map_count : 64) : 4); + _memory_span_release_count_large = (_memory_span_release_count > 8 ? (_memory_span_release_count / 4) : 2); + +#if (defined(__APPLE__) || defined(__HAIKU__)) && ENABLE_PRELOAD + if (pthread_key_create(&_memory_thread_heap, _memory_heap_finalize)) + return -1; +#endif +#if defined(_MSC_VER) && !defined(__clang__) && (!defined(BUILD_DYNAMIC_LINK) || !BUILD_DYNAMIC_LINK) + fls_key = FlsAlloc(&rp_thread_destructor); +#endif + + atomic_store32(&_memory_heap_id, 0); + atomic_store32(&_memory_orphan_counter, 0); +#if ENABLE_STATISTICS + atomic_store32(&_memory_active_heaps, 0); + atomic_store32(&_reserved_spans, 0); + atomic_store32(&_mapped_pages, 0); + _mapped_pages_peak = 0; + atomic_store32(&_mapped_total, 0); + atomic_store32(&_unmapped_total, 0); + atomic_store32(&_mapped_pages_os, 0); + atomic_store32(&_huge_pages_current, 0); + _huge_pages_peak = 0; +#endif + + //Setup all small and medium size classes + size_t iclass = 0; + _memory_size_class[iclass].block_size = SMALL_GRANULARITY; + _memory_adjust_size_class(iclass); + for (iclass = 1; iclass < SMALL_CLASS_COUNT; ++iclass) { + size_t size = iclass * SMALL_GRANULARITY; + _memory_size_class[iclass].block_size = (uint32_t)size; + _memory_adjust_size_class(iclass); + } + //At least two blocks per span, then fall back to large allocations + _memory_medium_size_limit = (_memory_span_size - SPAN_HEADER_SIZE) >> 1; + if (_memory_medium_size_limit > MEDIUM_SIZE_LIMIT) + _memory_medium_size_limit = MEDIUM_SIZE_LIMIT; + for (iclass = 0; iclass < MEDIUM_CLASS_COUNT; ++iclass) { + size_t size = SMALL_SIZE_LIMIT + ((iclass + 1) * MEDIUM_GRANULARITY); + if (size > _memory_medium_size_limit) + break; + _memory_size_class[SMALL_CLASS_COUNT + iclass].block_size = (uint32_t)size; + _memory_adjust_size_class(SMALL_CLASS_COUNT + iclass); + } + + for (size_t list_idx = 0; list_idx < HEAP_ARRAY_SIZE; ++list_idx) + atomic_store_ptr(&_memory_heaps[list_idx], 0); + + //Initialize this thread + rpmalloc_thread_initialize(); + return 0; +} + +//! Finalize the allocator +TRACY_API void +rpmalloc_finalize(void) { + atomic_thread_fence_acquire(); + + rpmalloc_thread_finalize(); + //rpmalloc_dump_statistics(stderr); + + //Free all thread caches + for (size_t list_idx = 0; list_idx < HEAP_ARRAY_SIZE; ++list_idx) { + heap_t* heap = (heap_t*)atomic_load_ptr(&_memory_heaps[list_idx]); + while (heap) { + if (heap->spans_reserved) { + span_t* span = _memory_map_spans(heap, heap->spans_reserved); + _memory_unmap_span(span); + } + + for (size_t iclass = 0; iclass < SIZE_CLASS_COUNT; ++iclass) { + heap_class_t* heap_class = heap->span_class + iclass; + span_t* span = heap_class->partial_span; + while (span) { + span_t* next = span->next; + if (span->state == SPAN_STATE_ACTIVE) { + uint32_t used_blocks = span->block_count; + if (span->free_list_limit < span->block_count) + used_blocks = span->free_list_limit; + uint32_t free_blocks = 0; + void* block = heap_class->free_list; + while (block) { + ++free_blocks; + block = *((void**)block); + } + block = span->free_list; + while (block) { + ++free_blocks; + block = *((void**)block); + } + if (used_blocks == (free_blocks + span->list_size)) + _memory_heap_cache_insert(heap, span); + } else { + if (span->used_count == span->list_size) + _memory_heap_cache_insert(heap, span); + } + span = next; + } + } + +#if ENABLE_THREAD_CACHE + //Free span caches (other thread might have deferred after the thread using this heap finalized) + _memory_heap_cache_adopt_deferred(heap); + for (size_t iclass = 0; iclass < LARGE_CLASS_COUNT; ++iclass) { + if (heap->span_cache[iclass]) + _memory_unmap_span_list(heap->span_cache[iclass]); + } +#endif + heap_t* next_heap = heap->next_heap; + size_t heap_size = (1 + (sizeof(heap_t) >> _memory_page_size_shift)) * _memory_page_size; + _memory_unmap(heap, heap_size, heap->align_offset, heap_size); + heap = next_heap; + } + } + +#if ENABLE_GLOBAL_CACHE + //Free global caches + for (size_t iclass = 0; iclass < LARGE_CLASS_COUNT; ++iclass) + _memory_cache_finalize(&_memory_span_cache[iclass]); +#endif + + atomic_store_ptr(&_memory_orphan_heaps, 0); + atomic_thread_fence_release(); + +#if (defined(__APPLE__) || defined(__HAIKU__)) && ENABLE_PRELOAD + pthread_key_delete(_memory_thread_heap); +#endif +#if defined(_MSC_VER) && !defined(__clang__) && (!defined(BUILD_DYNAMIC_LINK) || !BUILD_DYNAMIC_LINK) + FlsFree(fls_key); +#endif + +#if ENABLE_STATISTICS + //If you hit these asserts you probably have memory leaks or double frees in your code + assert(!atomic_load32(&_mapped_pages)); + assert(!atomic_load32(&_reserved_spans)); + assert(!atomic_load32(&_mapped_pages_os)); +#endif + + _rpmalloc_initialized = 0; +} + +//! Initialize thread, assign heap +TRACY_API void +rpmalloc_thread_initialize(void) { + if (!get_thread_heap_raw()) { + heap_t* heap = _memory_allocate_heap(); + if (heap) { + atomic_thread_fence_acquire(); +#if ENABLE_STATISTICS + atomic_incr32(&_memory_active_heaps); +#endif + set_thread_heap(heap); +#if defined(_MSC_VER) && !defined(__clang__) && (!defined(BUILD_DYNAMIC_LINK) || !BUILD_DYNAMIC_LINK) + FlsSetValue(fls_key, heap); +#endif + } + } +} + +//! Finalize thread, orphan heap +TRACY_API void +rpmalloc_thread_finalize(void) { + heap_t* heap = get_thread_heap_raw(); + if (heap) + _memory_heap_finalize(heap); +} + +int +rpmalloc_is_thread_initialized(void) { + return (get_thread_heap_raw() != 0) ? 1 : 0; +} + +const rpmalloc_config_t* +rpmalloc_config(void) { + return &_memory_config; +} + +//! Map new pages to virtual memory +static void* +_memory_map_os(size_t size, size_t* offset) { + //Either size is a heap (a single page) or a (multiple) span - we only need to align spans, and only if larger than map granularity + size_t padding = ((size >= _memory_span_size) && (_memory_span_size > _memory_map_granularity)) ? _memory_span_size : 0; + assert(size >= _memory_page_size); +#if PLATFORM_WINDOWS + //Ok to MEM_COMMIT - according to MSDN, "actual physical pages are not allocated unless/until the virtual addresses are actually accessed" + void* ptr = VirtualAlloc(0, size + padding, (_memory_huge_pages ? MEM_LARGE_PAGES : 0) | MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (!ptr) { + assert(!"Failed to map virtual memory block"); + return 0; + } +#else + int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED; +# if defined(__APPLE__) + int fd = (int)VM_MAKE_TAG(240U); + if (_memory_huge_pages) + fd |= VM_FLAGS_SUPERPAGE_SIZE_2MB; + void* ptr = mmap(0, size + padding, PROT_READ | PROT_WRITE, flags, fd, 0); +# elif defined(MAP_HUGETLB) + void* ptr = mmap(0, size + padding, PROT_READ | PROT_WRITE, (_memory_huge_pages ? MAP_HUGETLB : 0) | flags, -1, 0); +# else + void* ptr = mmap(0, size + padding, PROT_READ | PROT_WRITE, flags, -1, 0); +# endif + if ((ptr == MAP_FAILED) || !ptr) { + assert("Failed to map virtual memory block" == 0); + return 0; + } +#endif +#if ENABLE_STATISTICS + atomic_add32(&_mapped_pages_os, (int32_t)((size + padding) >> _memory_page_size_shift)); +#endif + if (padding) { + size_t final_padding = padding - ((uintptr_t)ptr & ~_memory_span_mask); + assert(final_padding <= _memory_span_size); + assert(final_padding <= padding); + assert(!(final_padding % 8)); + ptr = pointer_offset(ptr, final_padding); + *offset = final_padding >> 3; + } + assert((size < _memory_span_size) || !((uintptr_t)ptr & ~_memory_span_mask)); + return ptr; +} + +//! Unmap pages from virtual memory +static void +_memory_unmap_os(void* address, size_t size, size_t offset, size_t release) { + assert(release || (offset == 0)); + assert(!release || (release >= _memory_page_size)); + assert(size >= _memory_page_size); + if (release && offset) { + offset <<= 3; + address = pointer_offset(address, -(int32_t)offset); +#if PLATFORM_POSIX + //Padding is always one span size + release += _memory_span_size; +#endif + } +#if !DISABLE_UNMAP +#if PLATFORM_WINDOWS + if (!VirtualFree(address, release ? 0 : size, release ? MEM_RELEASE : MEM_DECOMMIT)) { + assert(!"Failed to unmap virtual memory block"); + } +#else + if (release) { + if (munmap(address, release)) { + assert("Failed to unmap virtual memory block" == 0); + } + } + else { +#if defined(POSIX_MADV_FREE) + if (posix_madvise(address, size, POSIX_MADV_FREE)) +#endif +#if defined(POSIX_MADV_DONTNEED) + if (posix_madvise(address, size, POSIX_MADV_DONTNEED)) { + assert("Failed to madvise virtual memory block as free" == 0); + } +#endif + } +#endif +#endif +#if ENABLE_STATISTICS + if (release) + atomic_add32(&_mapped_pages_os, -(int32_t)(release >> _memory_page_size_shift)); +#endif +} + +// Extern interface + +TRACY_API RPMALLOC_ALLOCATOR void* +rpmalloc(size_t size) { +#if ENABLE_VALIDATE_ARGS + if (size >= MAX_ALLOC_SIZE) { + errno = EINVAL; + return 0; + } +#endif + heap_t* heap = get_thread_heap(); + return _memory_allocate(heap, size); +} + +TRACY_API void +rpfree(void* ptr) { + _memory_deallocate(ptr); +} + +extern inline RPMALLOC_ALLOCATOR void* +rpcalloc(size_t num, size_t size) { + size_t total; +#if ENABLE_VALIDATE_ARGS +#if PLATFORM_WINDOWS + int err = SizeTMult(num, size, &total); + if ((err != S_OK) || (total >= MAX_ALLOC_SIZE)) { + errno = EINVAL; + return 0; + } +#else + int err = __builtin_umull_overflow(num, size, &total); + if (err || (total >= MAX_ALLOC_SIZE)) { + errno = EINVAL; + return 0; + } +#endif +#else + total = num * size; +#endif + heap_t* heap = get_thread_heap(); + void* block = _memory_allocate(heap, total); + memset(block, 0, total); + return block; +} + +TRACY_API RPMALLOC_ALLOCATOR void* +rprealloc(void* ptr, size_t size) { +#if ENABLE_VALIDATE_ARGS + if (size >= MAX_ALLOC_SIZE) { + errno = EINVAL; + return ptr; + } +#endif + return _memory_reallocate(ptr, size, 0, 0); +} + +extern RPMALLOC_ALLOCATOR void* +rpaligned_realloc(void* ptr, size_t alignment, size_t size, size_t oldsize, + unsigned int flags) { +#if ENABLE_VALIDATE_ARGS + if ((size + alignment < size) || (alignment > _memory_page_size)) { + errno = EINVAL; + return 0; + } +#endif + void* block; + if (alignment > 32) { + size_t usablesize = _memory_usable_size(ptr); + if ((usablesize >= size) && (size >= (usablesize / 2)) && !((uintptr_t)ptr & (alignment - 1))) + return ptr; + + block = rpaligned_alloc(alignment, size); + if (ptr) { + if (!oldsize) + oldsize = usablesize; + if (!(flags & RPMALLOC_NO_PRESERVE)) + memcpy(block, ptr, oldsize < size ? oldsize : size); + rpfree(ptr); + } + //Mark as having aligned blocks + span_t* span = (span_t*)((uintptr_t)block & _memory_span_mask); + span->flags |= SPAN_FLAG_ALIGNED_BLOCKS; + } else { + block = _memory_reallocate(ptr, size, oldsize, flags); + } + return block; +} + +extern RPMALLOC_ALLOCATOR void* +rpaligned_alloc(size_t alignment, size_t size) { + if (alignment <= 16) + return rpmalloc(size); + +#if ENABLE_VALIDATE_ARGS + if ((size + alignment) < size) { + errno = EINVAL; + return 0; + } + if (alignment & (alignment - 1)) { + errno = EINVAL; + return 0; + } +#endif + + void* ptr = 0; + size_t align_mask = alignment - 1; + if (alignment < _memory_page_size) { + ptr = rpmalloc(size + alignment); + if ((uintptr_t)ptr & align_mask) + ptr = (void*)(((uintptr_t)ptr & ~(uintptr_t)align_mask) + alignment); + //Mark as having aligned blocks + span_t* span = (span_t*)((uintptr_t)ptr & _memory_span_mask); + span->flags |= SPAN_FLAG_ALIGNED_BLOCKS; + return ptr; + } + + // Fallback to mapping new pages for this request. Since pointers passed + // to rpfree must be able to reach the start of the span by bitmasking of + // the address with the span size, the returned aligned pointer from this + // function must be with a span size of the start of the mapped area. + // In worst case this requires us to loop and map pages until we get a + // suitable memory address. It also means we can never align to span size + // or greater, since the span header will push alignment more than one + // span size away from span start (thus causing pointer mask to give us + // an invalid span start on free) + if (alignment & align_mask) { + errno = EINVAL; + return 0; + } + if (alignment >= _memory_span_size) { + errno = EINVAL; + return 0; + } + + size_t extra_pages = alignment / _memory_page_size; + + // Since each span has a header, we will at least need one extra memory page + size_t num_pages = 1 + (size / _memory_page_size); + if (size & (_memory_page_size - 1)) + ++num_pages; + + if (extra_pages > num_pages) + num_pages = 1 + extra_pages; + + size_t original_pages = num_pages; + size_t limit_pages = (_memory_span_size / _memory_page_size) * 2; + if (limit_pages < (original_pages * 2)) + limit_pages = original_pages * 2; + + size_t mapped_size, align_offset; + span_t* span; + +retry: + align_offset = 0; + mapped_size = num_pages * _memory_page_size; + + span = (span_t*)_memory_map(mapped_size, &align_offset); + if (!span) { + errno = ENOMEM; + return 0; + } + ptr = pointer_offset(span, SPAN_HEADER_SIZE); + + if ((uintptr_t)ptr & align_mask) + ptr = (void*)(((uintptr_t)ptr & ~(uintptr_t)align_mask) + alignment); + + if (((size_t)pointer_diff(ptr, span) >= _memory_span_size) || + (pointer_offset(ptr, size) > pointer_offset(span, mapped_size)) || + (((uintptr_t)ptr & _memory_span_mask) != (uintptr_t)span)) { + _memory_unmap(span, mapped_size, align_offset, mapped_size); + ++num_pages; + if (num_pages > limit_pages) { + errno = EINVAL; + return 0; + } + goto retry; + } + + //Store page count in span_count + span->size_class = (uint32_t)-1; + span->span_count = (uint32_t)num_pages; + span->align_offset = (uint32_t)align_offset; + _memory_statistics_add_peak(&_huge_pages_current, num_pages, _huge_pages_peak); + + return ptr; +} + +extern inline RPMALLOC_ALLOCATOR void* +rpmemalign(size_t alignment, size_t size) { + return rpaligned_alloc(alignment, size); +} + +extern inline int +rpposix_memalign(void **memptr, size_t alignment, size_t size) { + if (memptr) + *memptr = rpaligned_alloc(alignment, size); + else + return EINVAL; + return *memptr ? 0 : ENOMEM; +} + +extern inline size_t +rpmalloc_usable_size(void* ptr) { + return (ptr ? _memory_usable_size(ptr) : 0); +} + +extern inline void +rpmalloc_thread_collect(void) { +} + +void +rpmalloc_thread_statistics(rpmalloc_thread_statistics_t* stats) { + memset(stats, 0, sizeof(rpmalloc_thread_statistics_t)); + heap_t* heap = get_thread_heap_raw(); + if (!heap) + return; + + for (size_t iclass = 0; iclass < SIZE_CLASS_COUNT; ++iclass) { + size_class_t* size_class = _memory_size_class + iclass; + heap_class_t* heap_class = heap->span_class + iclass; + span_t* span = heap_class->partial_span; + while (span) { + atomic_thread_fence_acquire(); + size_t free_count = span->list_size; + if (span->state == SPAN_STATE_PARTIAL) + free_count += (size_class->block_count - span->used_count); + stats->sizecache = free_count * size_class->block_size; + span = span->next; + } + } + +#if ENABLE_THREAD_CACHE + for (size_t iclass = 0; iclass < LARGE_CLASS_COUNT; ++iclass) { + if (heap->span_cache[iclass]) + stats->spancache = (size_t)heap->span_cache[iclass]->list_size * (iclass + 1) * _memory_span_size; + span_t* deferred_list = !iclass ? (span_t*)atomic_load_ptr(&heap->span_cache_deferred) : 0; + //TODO: Incorrect, for deferred lists the size is NOT stored in list_size + if (deferred_list) + stats->spancache = (size_t)deferred_list->list_size * (iclass + 1) * _memory_span_size; + } +#endif +#if ENABLE_STATISTICS + stats->thread_to_global = heap->thread_to_global; + stats->global_to_thread = heap->global_to_thread; + + for (size_t iclass = 0; iclass < LARGE_CLASS_COUNT; ++iclass) { + stats->span_use[iclass].current = (size_t)atomic_load32(&heap->span_use[iclass].current); + stats->span_use[iclass].peak = (size_t)heap->span_use[iclass].high; + stats->span_use[iclass].to_global = (size_t)heap->span_use[iclass].spans_to_global; + stats->span_use[iclass].from_global = (size_t)heap->span_use[iclass].spans_from_global; + stats->span_use[iclass].to_cache = (size_t)heap->span_use[iclass].spans_to_cache; + stats->span_use[iclass].from_cache = (size_t)heap->span_use[iclass].spans_from_cache; + stats->span_use[iclass].to_reserved = (size_t)heap->span_use[iclass].spans_to_reserved; + stats->span_use[iclass].from_reserved = (size_t)heap->span_use[iclass].spans_from_reserved; + stats->span_use[iclass].map_calls = (size_t)heap->span_use[iclass].spans_map_calls; + } + for (size_t iclass = 0; iclass < SIZE_CLASS_COUNT; ++iclass) { + stats->size_use[iclass].alloc_current = (size_t)atomic_load32(&heap->size_class_use[iclass].alloc_current); + stats->size_use[iclass].alloc_peak = (size_t)heap->size_class_use[iclass].alloc_peak; + stats->size_use[iclass].alloc_total = (size_t)heap->size_class_use[iclass].alloc_total; + stats->size_use[iclass].free_total = (size_t)atomic_load32(&heap->size_class_use[iclass].free_total); + stats->size_use[iclass].spans_to_cache = (size_t)heap->size_class_use[iclass].spans_to_cache; + stats->size_use[iclass].spans_from_cache = (size_t)heap->size_class_use[iclass].spans_from_cache; + stats->size_use[iclass].spans_from_reserved = (size_t)heap->size_class_use[iclass].spans_from_reserved; + stats->size_use[iclass].map_calls = (size_t)heap->size_class_use[iclass].spans_map_calls; + } +#endif +} + +void +rpmalloc_global_statistics(rpmalloc_global_statistics_t* stats) { + memset(stats, 0, sizeof(rpmalloc_global_statistics_t)); +#if ENABLE_STATISTICS + stats->mapped = (size_t)atomic_load32(&_mapped_pages) * _memory_page_size; + stats->mapped_peak = (size_t)_mapped_pages_peak * _memory_page_size; + stats->mapped_total = (size_t)atomic_load32(&_mapped_total) * _memory_page_size; + stats->unmapped_total = (size_t)atomic_load32(&_unmapped_total) * _memory_page_size; + stats->huge_alloc = (size_t)atomic_load32(&_huge_pages_current) * _memory_page_size; + stats->huge_alloc_peak = (size_t)_huge_pages_peak * _memory_page_size; +#endif +#if ENABLE_GLOBAL_CACHE + for (size_t iclass = 0; iclass < LARGE_CLASS_COUNT; ++iclass) { + stats->cached += (size_t)atomic_load32(&_memory_span_cache[iclass].size) * (iclass + 1) * _memory_span_size; + } +#endif +} + +void +rpmalloc_dump_statistics(void* file) { +#if ENABLE_STATISTICS + //If you hit this assert, you still have active threads or forgot to finalize some thread(s) + assert(atomic_load32(&_memory_active_heaps) == 0); + + for (size_t list_idx = 0; list_idx < HEAP_ARRAY_SIZE; ++list_idx) { + heap_t* heap = atomic_load_ptr(&_memory_heaps[list_idx]); + while (heap) { + fprintf(file, "Heap %d stats:\n", heap->id); + fprintf(file, "Class CurAlloc PeakAlloc TotAlloc TotFree BlkSize BlkCount SpansCur SpansPeak PeakAllocMiB ToCacheMiB FromCacheMiB FromReserveMiB MmapCalls\n"); + for (size_t iclass = 0; iclass < SIZE_CLASS_COUNT; ++iclass) { + if (!heap->size_class_use[iclass].alloc_total) { + assert(!atomic_load32(&heap->size_class_use[iclass].free_total)); + assert(!heap->size_class_use[iclass].spans_map_calls); + continue; + } + fprintf(file, "%3u: %10u %10u %10u %10u %8u %8u %8d %9d %13zu %11zu %12zu %14zu %9u\n", (uint32_t)iclass, + atomic_load32(&heap->size_class_use[iclass].alloc_current), + heap->size_class_use[iclass].alloc_peak, + heap->size_class_use[iclass].alloc_total, + atomic_load32(&heap->size_class_use[iclass].free_total), + _memory_size_class[iclass].block_size, + _memory_size_class[iclass].block_count, + heap->size_class_use[iclass].spans_current, + heap->size_class_use[iclass].spans_peak, + ((size_t)heap->size_class_use[iclass].alloc_peak * (size_t)_memory_size_class[iclass].block_size) / (size_t)(1024 * 1024), + ((size_t)heap->size_class_use[iclass].spans_to_cache * _memory_span_size) / (size_t)(1024 * 1024), + ((size_t)heap->size_class_use[iclass].spans_from_cache * _memory_span_size) / (size_t)(1024 * 1024), + ((size_t)heap->size_class_use[iclass].spans_from_reserved * _memory_span_size) / (size_t)(1024 * 1024), + heap->size_class_use[iclass].spans_map_calls); + } + fprintf(file, "Spans Current Peak PeakMiB Cached ToCacheMiB FromCacheMiB ToReserveMiB FromReserveMiB ToGlobalMiB FromGlobalMiB MmapCalls\n"); + for (size_t iclass = 0; iclass < LARGE_CLASS_COUNT; ++iclass) { + if (!heap->span_use[iclass].high && !heap->span_use[iclass].spans_map_calls) + continue; + fprintf(file, "%4u: %8d %8u %8zu %7u %11zu %12zu %12zu %14zu %11zu %13zu %10u\n", (uint32_t)(iclass + 1), + atomic_load32(&heap->span_use[iclass].current), + heap->span_use[iclass].high, + ((size_t)heap->span_use[iclass].high * (size_t)_memory_span_size * (iclass + 1)) / (size_t)(1024 * 1024), + heap->span_cache[iclass] ? heap->span_cache[iclass]->list_size : 0, + ((size_t)heap->span_use[iclass].spans_to_cache * (iclass + 1) * _memory_span_size) / (size_t)(1024 * 1024), + ((size_t)heap->span_use[iclass].spans_from_cache * (iclass + 1) * _memory_span_size) / (size_t)(1024 * 1024), + ((size_t)heap->span_use[iclass].spans_to_reserved * (iclass + 1) * _memory_span_size) / (size_t)(1024 * 1024), + ((size_t)heap->span_use[iclass].spans_from_reserved * (iclass + 1) * _memory_span_size) / (size_t)(1024 * 1024), + ((size_t)heap->span_use[iclass].spans_to_global * (size_t)_memory_span_size * (iclass + 1)) / (size_t)(1024 * 1024), + ((size_t)heap->span_use[iclass].spans_from_global * (size_t)_memory_span_size * (iclass + 1)) / (size_t)(1024 * 1024), + heap->span_use[iclass].spans_map_calls); + } + fprintf(file, "ThreadToGlobalMiB GlobalToThreadMiB\n"); + fprintf(file, "%17zu %17zu\n", (size_t)heap->thread_to_global / (size_t)(1024 * 1024), (size_t)heap->global_to_thread / (size_t)(1024 * 1024)); + heap = heap->next_heap; + } + } + + fprintf(file, "Global stats:\n"); + size_t huge_current = (size_t)atomic_load32(&_huge_pages_current) * _memory_page_size; + size_t huge_peak = (size_t)_huge_pages_peak * _memory_page_size; + fprintf(file, "HugeCurrentMiB HugePeakMiB\n"); + fprintf(file, "%14zu %11zu\n", huge_current / (size_t)(1024 * 1024), huge_peak / (size_t)(1024 * 1024)); + + size_t mapped = (size_t)atomic_load32(&_mapped_pages) * _memory_page_size; + size_t mapped_os = (size_t)atomic_load32(&_mapped_pages_os) * _memory_page_size; + size_t mapped_peak = (size_t)_mapped_pages_peak * _memory_page_size; + size_t mapped_total = (size_t)atomic_load32(&_mapped_total) * _memory_page_size; + size_t unmapped_total = (size_t)atomic_load32(&_unmapped_total) * _memory_page_size; + size_t reserved_total = (size_t)atomic_load32(&_reserved_spans) * _memory_span_size; + fprintf(file, "MappedMiB MappedOSMiB MappedPeakMiB MappedTotalMiB UnmappedTotalMiB ReservedTotalMiB\n"); + fprintf(file, "%9zu %11zu %13zu %14zu %16zu %16zu\n", + mapped / (size_t)(1024 * 1024), + mapped_os / (size_t)(1024 * 1024), + mapped_peak / (size_t)(1024 * 1024), + mapped_total / (size_t)(1024 * 1024), + unmapped_total / (size_t)(1024 * 1024), + reserved_total / (size_t)(1024 * 1024)); + + fprintf(file, "\n"); +#else + (void)sizeof(file); +#endif +} + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/tracy_rpmalloc.hpp b/Source/ThirdParty/tracy/client/tracy_rpmalloc.hpp new file mode 100644 index 000000000..3e8c4f1b5 --- /dev/null +++ b/Source/ThirdParty/tracy/client/tracy_rpmalloc.hpp @@ -0,0 +1,261 @@ +/* rpmalloc.h - Memory allocator - Public Domain - 2016 Mattias Jansson + * + * This library provides a cross-platform lock free thread caching malloc implementation in C11. + * The latest source code is always available at + * + * https://github.com/mjansson/rpmalloc + * + * This library is put in the public domain; you can redistribute it and/or modify it without any restrictions. + * + */ + +#pragma once + +#include +#include "../common/TracySystem.hpp" + +namespace tracy +{ + +#if defined(__clang__) || defined(__GNUC__) +# define RPMALLOC_EXPORT __attribute__((visibility("default"))) +# define RPMALLOC_ALLOCATOR +# define RPMALLOC_ATTRIB_MALLOC __attribute__((__malloc__)) +# if defined(__clang_major__) && (__clang_major__ < 4) +# define RPMALLOC_ATTRIB_ALLOC_SIZE(size) +# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size) +# else +# define RPMALLOC_ATTRIB_ALLOC_SIZE(size) __attribute__((alloc_size(size))) +# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size) __attribute__((alloc_size(count, size))) +# endif +# define RPMALLOC_CDECL +#elif defined(_MSC_VER) +# define RPMALLOC_EXPORT +# define RPMALLOC_ALLOCATOR __declspec(allocator) __declspec(restrict) +# define RPMALLOC_ATTRIB_MALLOC +# define RPMALLOC_ATTRIB_ALLOC_SIZE(size) +# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size) +# define RPMALLOC_CDECL __cdecl +#else +# define RPMALLOC_EXPORT +# define RPMALLOC_ALLOCATOR +# define RPMALLOC_ATTRIB_MALLOC +# define RPMALLOC_ATTRIB_ALLOC_SIZE(size) +# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size) +# define RPMALLOC_CDECL +#endif + +//! Define RPMALLOC_CONFIGURABLE to enable configuring sizes +#ifndef RPMALLOC_CONFIGURABLE +#define RPMALLOC_CONFIGURABLE 0 +#endif + +//! Flag to rpaligned_realloc to not preserve content in reallocation +#define RPMALLOC_NO_PRESERVE 1 + +typedef struct rpmalloc_global_statistics_t { + //! Current amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1) + size_t mapped; + //! Peak amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1) + size_t mapped_peak; + //! Current amount of memory in global caches for small and medium sizes (<32KiB) + size_t cached; + //! Current amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1) + size_t huge_alloc; + //! Peak amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1) + size_t huge_alloc_peak; + //! Total amount of memory mapped since initialization (only if ENABLE_STATISTICS=1) + size_t mapped_total; + //! Total amount of memory unmapped since initialization (only if ENABLE_STATISTICS=1) + size_t unmapped_total; +} rpmalloc_global_statistics_t; + +typedef struct rpmalloc_thread_statistics_t { + //! Current number of bytes available in thread size class caches for small and medium sizes (<32KiB) + size_t sizecache; + //! Current number of bytes available in thread span caches for small and medium sizes (<32KiB) + size_t spancache; + //! Total number of bytes transitioned from thread cache to global cache (only if ENABLE_STATISTICS=1) + size_t thread_to_global; + //! Total number of bytes transitioned from global cache to thread cache (only if ENABLE_STATISTICS=1) + size_t global_to_thread; + //! Per span count statistics (only if ENABLE_STATISTICS=1) + struct { + //! Currently used number of spans + size_t current; + //! High water mark of spans used + size_t peak; + //! Number of spans transitioned to global cache + size_t to_global; + //! Number of spans transitioned from global cache + size_t from_global; + //! Number of spans transitioned to thread cache + size_t to_cache; + //! Number of spans transitioned from thread cache + size_t from_cache; + //! Number of spans transitioned to reserved state + size_t to_reserved; + //! Number of spans transitioned from reserved state + size_t from_reserved; + //! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls) + size_t map_calls; + } span_use[32]; + //! Per size class statistics (only if ENABLE_STATISTICS=1) + struct { + //! Current number of allocations + size_t alloc_current; + //! Peak number of allocations + size_t alloc_peak; + //! Total number of allocations + size_t alloc_total; + //! Total number of frees + size_t free_total; + //! Number of spans transitioned to cache + size_t spans_to_cache; + //! Number of spans transitioned from cache + size_t spans_from_cache; + //! Number of spans transitioned from reserved state + size_t spans_from_reserved; + //! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls) + size_t map_calls; + } size_use[128]; +} rpmalloc_thread_statistics_t; + +typedef struct rpmalloc_config_t { + //! Map memory pages for the given number of bytes. The returned address MUST be + // aligned to the rpmalloc span size, which will always be a power of two. + // Optionally the function can store an alignment offset in the offset variable + // in case it performs alignment and the returned pointer is offset from the + // actual start of the memory region due to this alignment. The alignment offset + // will be passed to the memory unmap function. The alignment offset MUST NOT be + // larger than 65535 (storable in an uint16_t), if it is you must use natural + // alignment to shift it into 16 bits. If you set a memory_map function, you + // must also set a memory_unmap function or else the default implementation will + // be used for both. + void* (*memory_map)(size_t size, size_t* offset); + //! Unmap the memory pages starting at address and spanning the given number of bytes. + // If release is set to non-zero, the unmap is for an entire span range as returned by + // a previous call to memory_map and that the entire range should be released. The + // release argument holds the size of the entire span range. If release is set to 0, + // the unmap is a partial decommit of a subset of the mapped memory range. + // If you set a memory_unmap function, you must also set a memory_map function or + // else the default implementation will be used for both. + void (*memory_unmap)(void* address, size_t size, size_t offset, size_t release); + //! Size of memory pages. The page size MUST be a power of two. All memory mapping + // requests to memory_map will be made with size set to a multiple of the page size. + // Used if RPMALLOC_CONFIGURABLE is defined to 1, otherwise system page size is used. + size_t page_size; + //! Size of a span of memory blocks. MUST be a power of two, and in [4096,262144] + // range (unless 0 - set to 0 to use the default span size). Used if RPMALLOC_CONFIGURABLE + // is defined to 1. + size_t span_size; + //! Number of spans to map at each request to map new virtual memory blocks. This can + // be used to minimize the system call overhead at the cost of virtual memory address + // space. The extra mapped pages will not be written until actually used, so physical + // committed memory should not be affected in the default implementation. Will be + // aligned to a multiple of spans that match memory page size in case of huge pages. + size_t span_map_count; + //! Enable use of large/huge pages. If this flag is set to non-zero and page size is + // zero, the allocator will try to enable huge pages and auto detect the configuration. + // If this is set to non-zero and page_size is also non-zero, the allocator will + // assume huge pages have been configured and enabled prior to initializing the + // allocator. + // For Windows, see https://docs.microsoft.com/en-us/windows/desktop/memory/large-page-support + // For Linux, see https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt + int enable_huge_pages; +} rpmalloc_config_t; + +//! Initialize allocator with default configuration +TRACY_API int +rpmalloc_initialize(void); + +//! Initialize allocator with given configuration +RPMALLOC_EXPORT int +rpmalloc_initialize_config(const rpmalloc_config_t* config); + +//! Get allocator configuration +RPMALLOC_EXPORT const rpmalloc_config_t* +rpmalloc_config(void); + +//! Finalize allocator +TRACY_API void +rpmalloc_finalize(void); + +//! Initialize allocator for calling thread +TRACY_API void +rpmalloc_thread_initialize(void); + +//! Finalize allocator for calling thread +TRACY_API void +rpmalloc_thread_finalize(void); + +//! Perform deferred deallocations pending for the calling thread heap +RPMALLOC_EXPORT void +rpmalloc_thread_collect(void); + +//! Query if allocator is initialized for calling thread +RPMALLOC_EXPORT int +rpmalloc_is_thread_initialized(void); + +//! Get per-thread statistics +RPMALLOC_EXPORT void +rpmalloc_thread_statistics(rpmalloc_thread_statistics_t* stats); + +//! Get global statistics +RPMALLOC_EXPORT void +rpmalloc_global_statistics(rpmalloc_global_statistics_t* stats); + +//! Dump all statistics in human readable format to file (should be a FILE*) +RPMALLOC_EXPORT void +rpmalloc_dump_statistics(void* file); + +//! Allocate a memory block of at least the given size +TRACY_API RPMALLOC_ALLOCATOR void* +rpmalloc(size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1); + +//! Free the given memory block +TRACY_API void +rpfree(void* ptr); + +//! Allocate a memory block of at least the given size and zero initialize it +RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void* +rpcalloc(size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(1, 2); + +//! Reallocate the given block to at least the given size +TRACY_API RPMALLOC_ALLOCATOR void* +rprealloc(void* ptr, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2); + +//! Reallocate the given block to at least the given size and alignment, +// with optional control flags (see RPMALLOC_NO_PRESERVE). +// Alignment must be a power of two and a multiple of sizeof(void*), +// and should ideally be less than memory page size. A caveat of rpmalloc +// internals is that this must also be strictly less than the span size (default 64KiB) +RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void* +rpaligned_realloc(void* ptr, size_t alignment, size_t size, size_t oldsize, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3); + +//! Allocate a memory block of at least the given size and alignment. +// Alignment must be a power of two and a multiple of sizeof(void*), +// and should ideally be less than memory page size. A caveat of rpmalloc +// internals is that this must also be strictly less than the span size (default 64KiB) +RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void* +rpaligned_alloc(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2); + +//! Allocate a memory block of at least the given size and alignment. +// Alignment must be a power of two and a multiple of sizeof(void*), +// and should ideally be less than memory page size. A caveat of rpmalloc +// internals is that this must also be strictly less than the span size (default 64KiB) +RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void* +rpmemalign(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2); + +//! Allocate a memory block of at least the given size and alignment. +// Alignment must be a power of two and a multiple of sizeof(void*), +// and should ideally be less than memory page size. A caveat of rpmalloc +// internals is that this must also be strictly less than the span size (default 64KiB) +RPMALLOC_EXPORT int +rpposix_memalign(void **memptr, size_t alignment, size_t size); + +//! Query the usable size of the given memory block (from given pointer to the end of block) +RPMALLOC_EXPORT size_t +rpmalloc_usable_size(void* ptr); + +} diff --git a/Source/ThirdParty/tracy/common/TracyAlign.hpp b/Source/ThirdParty/tracy/common/TracyAlign.hpp new file mode 100644 index 000000000..730342df0 --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracyAlign.hpp @@ -0,0 +1,25 @@ +#ifndef __TRACYALIGN_HPP__ +#define __TRACYALIGN_HPP__ + +#include + +namespace tracy +{ + +template +tracy_force_inline T MemRead( const void* ptr ) +{ + T val; + memcpy( &val, ptr, sizeof( T ) ); + return val; +} + +template +tracy_force_inline void MemWrite( void* ptr, T val ) +{ + memcpy( ptr, &val, sizeof( T ) ); +} + +} + +#endif diff --git a/Source/ThirdParty/tracy/common/TracyAlloc.hpp b/Source/ThirdParty/tracy/common/TracyAlloc.hpp new file mode 100644 index 000000000..a3cbec057 --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracyAlloc.hpp @@ -0,0 +1,42 @@ +#ifndef __TRACYALLOC_HPP__ +#define __TRACYALLOC_HPP__ + +#include + +#ifdef TRACY_ENABLE +# include "../client/tracy_rpmalloc.hpp" +#endif + +namespace tracy +{ + +static inline void* tracy_malloc( size_t size ) +{ +#ifdef TRACY_ENABLE + return rpmalloc( size ); +#else + return malloc( size ); +#endif +} + +static inline void tracy_free( void* ptr ) +{ +#ifdef TRACY_ENABLE + rpfree( ptr ); +#else + free( ptr ); +#endif +} + +static inline void* tracy_realloc( void* ptr, size_t size ) +{ +#ifdef TRACY_ENABLE + return rprealloc( ptr, size ); +#else + return realloc( ptr, size ); +#endif +} + +} + +#endif diff --git a/Source/ThirdParty/tracy/common/TracyMutex.hpp b/Source/ThirdParty/tracy/common/TracyMutex.hpp new file mode 100644 index 000000000..57fb01a0c --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracyMutex.hpp @@ -0,0 +1,24 @@ +#ifndef __TRACYMUTEX_HPP__ +#define __TRACYMUTEX_HPP__ + +#if defined _MSC_VER + +# include + +namespace tracy +{ +using TracyMutex = std::shared_mutex; +} + +#else + +#include + +namespace tracy +{ +using TracyMutex = std::mutex; +} + +#endif + +#endif diff --git a/Source/ThirdParty/tracy/common/TracyProtocol.hpp b/Source/ThirdParty/tracy/common/TracyProtocol.hpp new file mode 100644 index 000000000..2326a7f32 --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracyProtocol.hpp @@ -0,0 +1,128 @@ +#ifndef __TRACYPROTOCOL_HPP__ +#define __TRACYPROTOCOL_HPP__ + +#include +#include + +namespace tracy +{ + +constexpr unsigned Lz4CompressBound( unsigned isize ) { return isize + ( isize / 255 ) + 16; } + +enum : uint32_t { ProtocolVersion = 46 }; +enum : uint16_t { BroadcastVersion = 2 }; + +using lz4sz_t = uint32_t; + +enum { TargetFrameSize = 256 * 1024 }; +enum { LZ4Size = Lz4CompressBound( TargetFrameSize ) }; +static_assert( LZ4Size <= std::numeric_limits::max(), "LZ4Size greater than lz4sz_t" ); +static_assert( TargetFrameSize * 2 >= 64 * 1024, "Not enough space for LZ4 stream buffer" ); + +enum { HandshakeShibbolethSize = 8 }; +static const char HandshakeShibboleth[HandshakeShibbolethSize] = { 'T', 'r', 'a', 'c', 'y', 'P', 'r', 'f' }; + +enum HandshakeStatus : uint8_t +{ + HandshakePending, + HandshakeWelcome, + HandshakeProtocolMismatch, + HandshakeNotAvailable, + HandshakeDropped +}; + +enum { WelcomeMessageProgramNameSize = 64 }; +enum { WelcomeMessageHostInfoSize = 1024 }; + +#pragma pack( 1 ) + +// Must increase left query space after handling! +enum ServerQuery : uint8_t +{ + ServerQueryTerminate, + ServerQueryString, + ServerQueryThreadString, + ServerQuerySourceLocation, + ServerQueryPlotName, + ServerQueryCallstackFrame, + ServerQueryFrameName, + ServerQueryDisconnect, + ServerQueryExternalName, + ServerQueryParameter, + ServerQuerySymbol, + ServerQuerySymbolCode, + ServerQueryCodeLocation, + ServerQuerySourceCode, + ServerQueryDataTransfer, + ServerQueryDataTransferPart +}; + +struct ServerQueryPacket +{ + ServerQuery type; + uint64_t ptr; + uint32_t extra; +}; + +enum { ServerQueryPacketSize = sizeof( ServerQueryPacket ) }; + + +enum CpuArchitecture : uint8_t +{ + CpuArchUnknown, + CpuArchX86, + CpuArchX64, + CpuArchArm32, + CpuArchArm64 +}; + + +struct WelcomeMessage +{ + double timerMul; + int64_t initBegin; + int64_t initEnd; + uint64_t delay; + uint64_t resolution; + uint64_t epoch; + uint64_t exectime; + uint64_t pid; + int64_t samplingPeriod; + uint8_t onDemand; + uint8_t isApple; + uint8_t cpuArch; + uint8_t codeTransfer; + char cpuManufacturer[12]; + uint32_t cpuId; + char programName[WelcomeMessageProgramNameSize]; + char hostInfo[WelcomeMessageHostInfoSize]; +}; + +enum { WelcomeMessageSize = sizeof( WelcomeMessage ) }; + + +struct OnDemandPayloadMessage +{ + uint64_t frames; + uint64_t currentTime; +}; + +enum { OnDemandPayloadMessageSize = sizeof( OnDemandPayloadMessage ) }; + + +struct BroadcastMessage +{ + uint16_t broadcastVersion; + uint16_t listenPort; + uint32_t protocolVersion; + int32_t activeTime; // in seconds + char programName[WelcomeMessageProgramNameSize]; +}; + +enum { BroadcastMessageSize = sizeof( BroadcastMessage ) }; + +#pragma pack() + +} + +#endif diff --git a/Source/ThirdParty/tracy/common/TracyQueue.hpp b/Source/ThirdParty/tracy/common/TracyQueue.hpp new file mode 100644 index 000000000..d99945013 --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracyQueue.hpp @@ -0,0 +1,678 @@ +#ifndef __TRACYQUEUE_HPP__ +#define __TRACYQUEUE_HPP__ + +#include + +namespace tracy +{ + +enum class QueueType : uint8_t +{ + ZoneText, + ZoneName, + Message, + MessageColor, + MessageCallstack, + MessageColorCallstack, + MessageAppInfo, + ZoneBeginAllocSrcLoc, + ZoneBeginAllocSrcLocCallstack, + CallstackSerial, + Callstack, + CallstackAlloc, + CallstackSample, + FrameImage, + ZoneBegin, + ZoneBeginCallstack, + ZoneEnd, + LockWait, + LockObtain, + LockRelease, + LockSharedWait, + LockSharedObtain, + LockSharedRelease, + LockName, + MemAlloc, + MemAllocNamed, + MemFree, + MemFreeNamed, + MemAllocCallstack, + MemAllocCallstackNamed, + MemFreeCallstack, + MemFreeCallstackNamed, + GpuZoneBegin, + GpuZoneBeginCallstack, + GpuZoneBeginAllocSrcLoc, + GpuZoneBeginAllocSrcLocCallstack, + GpuZoneEnd, + GpuZoneBeginSerial, + GpuZoneBeginCallstackSerial, + GpuZoneBeginAllocSrcLocSerial, + GpuZoneBeginAllocSrcLocCallstackSerial, + GpuZoneEndSerial, + PlotData, + ContextSwitch, + ThreadWakeup, + GpuTime, + GpuContextName, + Terminate, + KeepAlive, + ThreadContext, + GpuCalibration, + Crash, + CrashReport, + ZoneValidation, + ZoneColor, + ZoneValue, + FrameMarkMsg, + FrameMarkMsgStart, + FrameMarkMsgEnd, + SourceLocation, + LockAnnounce, + LockTerminate, + LockMark, + MessageLiteral, + MessageLiteralColor, + MessageLiteralCallstack, + MessageLiteralColorCallstack, + GpuNewContext, + CallstackFrameSize, + CallstackFrame, + SymbolInformation, + CodeInformation, + SysTimeReport, + TidToPid, + PlotConfig, + ParamSetup, + AckServerQueryNoop, + AckSourceCodeNotAvailable, + CpuTopology, + SingleStringData, + SecondStringData, + MemNamePayload, + StringData, + ThreadName, + PlotName, + SourceLocationPayload, + CallstackPayload, + CallstackAllocPayload, + FrameName, + FrameImageData, + ExternalName, + ExternalThreadName, + SymbolCode, + SourceCode, + NUM_TYPES +}; + +#pragma pack( 1 ) + +struct QueueThreadContext +{ + uint64_t thread; +}; + +struct QueueZoneBeginLean +{ + int64_t time; +}; + +struct QueueZoneBegin : public QueueZoneBeginLean +{ + uint64_t srcloc; // ptr +}; + +struct QueueZoneEnd +{ + int64_t time; +}; + +struct QueueZoneValidation +{ + uint32_t id; +}; + +struct QueueZoneColor +{ + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct QueueZoneValue +{ + uint64_t value; +}; + +struct QueueStringTransfer +{ + uint64_t ptr; +}; + +struct QueueFrameMark +{ + int64_t time; + uint64_t name; // ptr +}; + +struct QueueFrameImage +{ + uint32_t frame; + uint16_t w; + uint16_t h; + uint8_t flip; +}; + +struct QueueFrameImageFat : public QueueFrameImage +{ + uint64_t image; // ptr +}; + +struct QueueSourceLocation +{ + uint64_t name; + uint64_t function; // ptr + uint64_t file; // ptr + uint32_t line; + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct QueueZoneTextFat +{ + uint64_t text; // ptr + uint16_t size; +}; + +enum class LockType : uint8_t +{ + Lockable, + SharedLockable +}; + +struct QueueLockAnnounce +{ + uint32_t id; + int64_t time; + uint64_t lckloc; // ptr + LockType type; +}; + +struct QueueLockTerminate +{ + uint32_t id; + int64_t time; +}; + +struct QueueLockWait +{ + uint64_t thread; + uint32_t id; + int64_t time; +}; + +struct QueueLockObtain +{ + uint64_t thread; + uint32_t id; + int64_t time; +}; + +struct QueueLockRelease +{ + uint64_t thread; + uint32_t id; + int64_t time; +}; + +struct QueueLockMark +{ + uint64_t thread; + uint32_t id; + uint64_t srcloc; // ptr +}; + +struct QueueLockName +{ + uint32_t id; +}; + +struct QueueLockNameFat : public QueueLockName +{ + uint64_t name; // ptr + uint16_t size; +}; + +enum class PlotDataType : uint8_t +{ + Float, + Double, + Int +}; + +struct QueuePlotData +{ + uint64_t name; // ptr + int64_t time; + PlotDataType type; + union + { + double d; + float f; + int64_t i; + } data; +}; + +struct QueueMessage +{ + int64_t time; +}; + +struct QueueMessageColor : public QueueMessage +{ + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct QueueMessageLiteral : public QueueMessage +{ + uint64_t text; // ptr +}; + +struct QueueMessageColorLiteral : public QueueMessageColor +{ + uint64_t text; // ptr +}; + +struct QueueMessageFat : public QueueMessage +{ + uint64_t text; // ptr + uint16_t size; +}; + +struct QueueMessageColorFat : public QueueMessageColor +{ + uint64_t text; // ptr + uint16_t size; +}; + +// Don't change order, only add new entries at the end, this is also used on trace dumps! +enum class GpuContextType : uint8_t +{ + Invalid, + OpenGl, + Vulkan, + OpenCL, + Direct3D12 +}; + +enum GpuContextFlags : uint8_t +{ + GpuContextCalibration = 1 << 0 +}; + +struct QueueGpuNewContext +{ + int64_t cpuTime; + int64_t gpuTime; + uint64_t thread; + float period; + uint8_t context; + GpuContextFlags flags; + GpuContextType type; +}; + +struct QueueGpuZoneBeginLean +{ + int64_t cpuTime; + uint64_t thread; + uint16_t queryId; + uint8_t context; +}; + +struct QueueGpuZoneBegin : public QueueGpuZoneBeginLean +{ + uint64_t srcloc; +}; + +struct QueueGpuZoneEnd +{ + int64_t cpuTime; + uint64_t thread; + uint16_t queryId; + uint8_t context; +}; + +struct QueueGpuTime +{ + int64_t gpuTime; + uint16_t queryId; + uint8_t context; +}; + +struct QueueGpuCalibration +{ + int64_t gpuTime; + int64_t cpuTime; + int64_t cpuDelta; + uint8_t context; +}; + +struct QueueGpuContextName +{ + uint8_t context; +}; + +struct QueueGpuContextNameFat : public QueueGpuContextName +{ + uint64_t ptr; + uint16_t size; +}; + +struct QueueMemNamePayload +{ + uint64_t name; +}; + +struct QueueMemAlloc +{ + int64_t time; + uint64_t thread; + uint64_t ptr; + char size[6]; +}; + +struct QueueMemFree +{ + int64_t time; + uint64_t thread; + uint64_t ptr; +}; + +struct QueueCallstackFat +{ + uint64_t ptr; +}; + +struct QueueCallstackAllocFat +{ + uint64_t ptr; + uint64_t nativePtr; +}; + +struct QueueCallstackSample +{ + int64_t time; + uint64_t thread; +}; + +struct QueueCallstackSampleFat : public QueueCallstackSample +{ + uint64_t ptr; +}; + +struct QueueCallstackFrameSize +{ + uint64_t ptr; + uint8_t size; +}; + +struct QueueCallstackFrame +{ + uint32_t line; + uint64_t symAddr; + uint32_t symLen; +}; + +struct QueueSymbolInformation +{ + uint32_t line; + uint64_t symAddr; +}; + +struct QueueCodeInformation +{ + uint64_t ptr; + uint32_t line; +}; + +struct QueueCrashReport +{ + int64_t time; + uint64_t text; // ptr +}; + +struct QueueSysTime +{ + int64_t time; + float sysTime; +}; + +struct QueueContextSwitch +{ + int64_t time; + uint64_t oldThread; + uint64_t newThread; + uint8_t cpu; + uint8_t reason; + uint8_t state; +}; + +struct QueueThreadWakeup +{ + int64_t time; + uint64_t thread; +}; + +struct QueueTidToPid +{ + uint64_t tid; + uint64_t pid; +}; + +struct QueuePlotConfig +{ + uint64_t name; // ptr + uint8_t type; +}; + +struct QueueParamSetup +{ + uint32_t idx; + uint64_t name; // ptr + uint8_t isBool; + int32_t val; +}; + +struct QueueCpuTopology +{ + uint32_t package; + uint32_t core; + uint32_t thread; +}; + +struct QueueHeader +{ + union + { + QueueType type; + uint8_t idx; + }; +}; + +struct QueueItem +{ + QueueHeader hdr; + union + { + QueueThreadContext threadCtx; + QueueZoneBegin zoneBegin; + QueueZoneBeginLean zoneBeginLean; + QueueZoneEnd zoneEnd; + QueueZoneValidation zoneValidation; + QueueZoneColor zoneColor; + QueueZoneValue zoneValue; + QueueStringTransfer stringTransfer; + QueueFrameMark frameMark; + QueueFrameImage frameImage; + QueueFrameImageFat frameImageFat; + QueueSourceLocation srcloc; + QueueZoneTextFat zoneTextFat; + QueueLockAnnounce lockAnnounce; + QueueLockTerminate lockTerminate; + QueueLockWait lockWait; + QueueLockObtain lockObtain; + QueueLockRelease lockRelease; + QueueLockMark lockMark; + QueueLockName lockName; + QueueLockNameFat lockNameFat; + QueuePlotData plotData; + QueueMessage message; + QueueMessageColor messageColor; + QueueMessageLiteral messageLiteral; + QueueMessageColorLiteral messageColorLiteral; + QueueMessageFat messageFat; + QueueMessageColorFat messageColorFat; + QueueGpuNewContext gpuNewContext; + QueueGpuZoneBegin gpuZoneBegin; + QueueGpuZoneBeginLean gpuZoneBeginLean; + QueueGpuZoneEnd gpuZoneEnd; + QueueGpuTime gpuTime; + QueueGpuCalibration gpuCalibration; + QueueGpuContextName gpuContextName; + QueueGpuContextNameFat gpuContextNameFat; + QueueMemAlloc memAlloc; + QueueMemFree memFree; + QueueMemNamePayload memName; + QueueCallstackFat callstackFat; + QueueCallstackAllocFat callstackAllocFat; + QueueCallstackSample callstackSample; + QueueCallstackSampleFat callstackSampleFat; + QueueCallstackFrameSize callstackFrameSize; + QueueCallstackFrame callstackFrame; + QueueSymbolInformation symbolInformation; + QueueCodeInformation codeInformation; + QueueCrashReport crashReport; + QueueSysTime sysTime; + QueueContextSwitch contextSwitch; + QueueThreadWakeup threadWakeup; + QueueTidToPid tidToPid; + QueuePlotConfig plotConfig; + QueueParamSetup paramSetup; + QueueCpuTopology cpuTopology; + }; +}; +#pragma pack() + + +enum { QueueItemSize = sizeof( QueueItem ) }; + +static constexpr size_t QueueDataSize[] = { + sizeof( QueueHeader ), // zone text + sizeof( QueueHeader ), // zone name + sizeof( QueueHeader ) + sizeof( QueueMessage ), + sizeof( QueueHeader ) + sizeof( QueueMessageColor ), + sizeof( QueueHeader ) + sizeof( QueueMessage ), // callstack + sizeof( QueueHeader ) + sizeof( QueueMessageColor ), // callstack + sizeof( QueueHeader ) + sizeof( QueueMessage ), // app info + sizeof( QueueHeader ) + sizeof( QueueZoneBeginLean ), // allocated source location + sizeof( QueueHeader ) + sizeof( QueueZoneBeginLean ), // allocated source location, callstack + sizeof( QueueHeader ), // callstack memory + sizeof( QueueHeader ), // callstack + sizeof( QueueHeader ), // callstack alloc + sizeof( QueueHeader ) + sizeof( QueueCallstackSample ), + sizeof( QueueHeader ) + sizeof( QueueFrameImage ), + sizeof( QueueHeader ) + sizeof( QueueZoneBegin ), + sizeof( QueueHeader ) + sizeof( QueueZoneBegin ), // callstack + sizeof( QueueHeader ) + sizeof( QueueZoneEnd ), + sizeof( QueueHeader ) + sizeof( QueueLockWait ), + sizeof( QueueHeader ) + sizeof( QueueLockObtain ), + sizeof( QueueHeader ) + sizeof( QueueLockRelease ), + sizeof( QueueHeader ) + sizeof( QueueLockWait ), // shared + sizeof( QueueHeader ) + sizeof( QueueLockObtain ), // shared + sizeof( QueueHeader ) + sizeof( QueueLockRelease ), // shared + sizeof( QueueHeader ) + sizeof( QueueLockName ), + sizeof( QueueHeader ) + sizeof( QueueMemAlloc ), + sizeof( QueueHeader ) + sizeof( QueueMemAlloc ), // named + sizeof( QueueHeader ) + sizeof( QueueMemFree ), + sizeof( QueueHeader ) + sizeof( QueueMemFree ), // named + sizeof( QueueHeader ) + sizeof( QueueMemAlloc ), // callstack + sizeof( QueueHeader ) + sizeof( QueueMemAlloc ), // callstack, named + sizeof( QueueHeader ) + sizeof( QueueMemFree ), // callstack + sizeof( QueueHeader ) + sizeof( QueueMemFree ), // callstack, named + sizeof( QueueHeader ) + sizeof( QueueGpuZoneBegin ), + sizeof( QueueHeader ) + sizeof( QueueGpuZoneBegin ), // callstack + sizeof( QueueHeader ) + sizeof( QueueGpuZoneBeginLean ),// allocated source location + sizeof( QueueHeader ) + sizeof( QueueGpuZoneBeginLean ),// allocated source location, callstack + sizeof( QueueHeader ) + sizeof( QueueGpuZoneEnd ), + sizeof( QueueHeader ) + sizeof( QueueGpuZoneBegin ), // serial + sizeof( QueueHeader ) + sizeof( QueueGpuZoneBegin ), // serial, callstack + sizeof( QueueHeader ) + sizeof( QueueGpuZoneBeginLean ),// serial, allocated source location + sizeof( QueueHeader ) + sizeof( QueueGpuZoneBeginLean ),// serial, allocated source location, callstack + sizeof( QueueHeader ) + sizeof( QueueGpuZoneEnd ), // serial + sizeof( QueueHeader ) + sizeof( QueuePlotData ), + sizeof( QueueHeader ) + sizeof( QueueContextSwitch ), + sizeof( QueueHeader ) + sizeof( QueueThreadWakeup ), + sizeof( QueueHeader ) + sizeof( QueueGpuTime ), + sizeof( QueueHeader ) + sizeof( QueueGpuContextName ), + // above items must be first + sizeof( QueueHeader ), // terminate + sizeof( QueueHeader ), // keep alive + sizeof( QueueHeader ) + sizeof( QueueThreadContext ), + sizeof( QueueHeader ) + sizeof( QueueGpuCalibration ), + sizeof( QueueHeader ), // crash + sizeof( QueueHeader ) + sizeof( QueueCrashReport ), + sizeof( QueueHeader ) + sizeof( QueueZoneValidation ), + sizeof( QueueHeader ) + sizeof( QueueZoneColor ), + sizeof( QueueHeader ) + sizeof( QueueZoneValue ), + sizeof( QueueHeader ) + sizeof( QueueFrameMark ), // continuous frames + sizeof( QueueHeader ) + sizeof( QueueFrameMark ), // start + sizeof( QueueHeader ) + sizeof( QueueFrameMark ), // end + sizeof( QueueHeader ) + sizeof( QueueSourceLocation ), + sizeof( QueueHeader ) + sizeof( QueueLockAnnounce ), + sizeof( QueueHeader ) + sizeof( QueueLockTerminate ), + sizeof( QueueHeader ) + sizeof( QueueLockMark ), + sizeof( QueueHeader ) + sizeof( QueueMessageLiteral ), + sizeof( QueueHeader ) + sizeof( QueueMessageColorLiteral ), + sizeof( QueueHeader ) + sizeof( QueueMessageLiteral ), // callstack + sizeof( QueueHeader ) + sizeof( QueueMessageColorLiteral ), // callstack + sizeof( QueueHeader ) + sizeof( QueueGpuNewContext ), + sizeof( QueueHeader ) + sizeof( QueueCallstackFrameSize ), + sizeof( QueueHeader ) + sizeof( QueueCallstackFrame ), + sizeof( QueueHeader ) + sizeof( QueueSymbolInformation ), + sizeof( QueueHeader ) + sizeof( QueueCodeInformation ), + sizeof( QueueHeader ) + sizeof( QueueSysTime ), + sizeof( QueueHeader ) + sizeof( QueueTidToPid ), + sizeof( QueueHeader ) + sizeof( QueuePlotConfig ), + sizeof( QueueHeader ) + sizeof( QueueParamSetup ), + sizeof( QueueHeader ), // server query acknowledgement + sizeof( QueueHeader ), // source code not available + sizeof( QueueHeader ) + sizeof( QueueCpuTopology ), + sizeof( QueueHeader ), // single string data + sizeof( QueueHeader ), // second string data + sizeof( QueueHeader ) + sizeof( QueueMemNamePayload ), + // keep all QueueStringTransfer below + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // string data + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // thread name + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // plot name + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // allocated source location payload + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // callstack payload + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // callstack alloc payload + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // frame name + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // frame image data + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // external name + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // external thread name + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // symbol code + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // source code +}; + +static_assert( QueueItemSize == 32, "Queue item size not 32 bytes" ); +static_assert( sizeof( QueueDataSize ) / sizeof( size_t ) == (uint8_t)QueueType::NUM_TYPES, "QueueDataSize mismatch" ); +static_assert( sizeof( void* ) <= sizeof( uint64_t ), "Pointer size > 8 bytes" ); +static_assert( sizeof( void* ) == sizeof( uintptr_t ), "Pointer size != uintptr_t" ); + +} + +#endif diff --git a/Source/ThirdParty/tracy/common/TracySocket.cpp b/Source/ThirdParty/tracy/common/TracySocket.cpp new file mode 100644 index 000000000..f16569b06 --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracySocket.cpp @@ -0,0 +1,748 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "TracyAlloc.hpp" +#include "TracySocket.hpp" + +#ifdef _WIN32 +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +# include +# ifdef _MSC_VER +# pragma warning(disable:4244) +# pragma warning(disable:4267) +# endif +# define poll WSAPoll +#else +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + +namespace tracy +{ + +#ifdef _WIN32 +typedef SOCKET socket_t; +#else +typedef int socket_t; +#endif + +#ifdef _WIN32 +struct __wsinit +{ + __wsinit() + { + WSADATA wsaData; + if( WSAStartup( MAKEWORD( 2, 2 ), &wsaData ) != 0 ) + { + fprintf( stderr, "Cannot init winsock.\n" ); + exit( 1 ); + } + } +}; + +void InitWinSock() +{ + static __wsinit init; +} +#endif + + +enum { BufSize = 128 * 1024 }; + +Socket::Socket() + : m_buf( (char*)tracy_malloc( BufSize ) ) + , m_bufPtr( nullptr ) + , m_sock( -1 ) + , m_bufLeft( 0 ) + , m_ptr( nullptr ) +{ +#ifdef _WIN32 + InitWinSock(); +#endif +} + +Socket::Socket( int sock ) + : m_buf( (char*)tracy_malloc( BufSize ) ) + , m_bufPtr( nullptr ) + , m_sock( sock ) + , m_bufLeft( 0 ) + , m_ptr( nullptr ) +{ +} + +Socket::~Socket() +{ + tracy_free( m_buf ); + if( m_sock.load( std::memory_order_relaxed ) != -1 ) + { + Close(); + } + if( m_ptr ) + { + freeaddrinfo( m_res ); +#ifdef _WIN32 + closesocket( m_connSock ); +#else + close( m_connSock ); +#endif + } +} + +bool Socket::Connect( const char* addr, uint16_t port ) +{ + assert( !IsValid() ); + + if( m_ptr ) + { + const auto c = connect( m_connSock, m_ptr->ai_addr, m_ptr->ai_addrlen ); + if( c == -1 ) + { +#if defined _WIN32 + const auto err = WSAGetLastError(); + if( err == WSAEALREADY || err == WSAEINPROGRESS ) return false; + if( err != WSAEISCONN ) + { + freeaddrinfo( m_res ); + closesocket( m_connSock ); + m_ptr = nullptr; + return false; + } +#else + const auto err = errno; + if( err == EALREADY || err == EINPROGRESS ) return false; + if( err != EISCONN ) + { + freeaddrinfo( m_res ); + close( m_connSock ); + m_ptr = nullptr; + return false; + } +#endif + } + +#if defined _WIN32 + u_long nonblocking = 0; + ioctlsocket( m_connSock, FIONBIO, &nonblocking ); +#else + int flags = fcntl( m_connSock, F_GETFL, 0 ); + fcntl( m_connSock, F_SETFL, flags & ~O_NONBLOCK ); +#endif + m_sock.store( m_connSock, std::memory_order_relaxed ); + freeaddrinfo( m_res ); + m_ptr = nullptr; + return true; + } + + struct addrinfo hints; + struct addrinfo *res, *ptr; + + memset( &hints, 0, sizeof( hints ) ); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + char portbuf[32]; + sprintf( portbuf, "%" PRIu16, port ); + + if( getaddrinfo( addr, portbuf, &hints, &res ) != 0 ) return false; + int sock = 0; + for( ptr = res; ptr; ptr = ptr->ai_next ) + { + if( ( sock = socket( ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol ) ) == -1 ) continue; +#if defined __APPLE__ + int val = 1; + setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof( val ) ); +#endif +#if defined _WIN32 + u_long nonblocking = 1; + ioctlsocket( sock, FIONBIO, &nonblocking ); +#else + int flags = fcntl( sock, F_GETFL, 0 ); + fcntl( sock, F_SETFL, flags | O_NONBLOCK ); +#endif + if( connect( sock, ptr->ai_addr, ptr->ai_addrlen ) == 0 ) + { + break; + } + else + { +#if defined _WIN32 + const auto err = WSAGetLastError(); + if( err != WSAEWOULDBLOCK ) + { + closesocket( sock ); + continue; + } +#else + if( errno != EINPROGRESS ) + { + close( sock ); + continue; + } +#endif + } + m_res = res; + m_ptr = ptr; + m_connSock = sock; + return false; + } + freeaddrinfo( res ); + if( !ptr ) return false; + +#if defined _WIN32 + u_long nonblocking = 0; + ioctlsocket( sock, FIONBIO, &nonblocking ); +#else + int flags = fcntl( sock, F_GETFL, 0 ); + fcntl( sock, F_SETFL, flags & ~O_NONBLOCK ); +#endif + + m_sock.store( sock, std::memory_order_relaxed ); + return true; +} + +bool Socket::ConnectBlocking( const char* addr, uint16_t port ) +{ + assert( !IsValid() ); + assert( !m_ptr ); + + struct addrinfo hints; + struct addrinfo *res, *ptr; + + memset( &hints, 0, sizeof( hints ) ); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + char portbuf[32]; + sprintf( portbuf, "%" PRIu16, port ); + + if( getaddrinfo( addr, portbuf, &hints, &res ) != 0 ) return false; + int sock = 0; + for( ptr = res; ptr; ptr = ptr->ai_next ) + { + if( ( sock = socket( ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol ) ) == -1 ) continue; +#if defined __APPLE__ + int val = 1; + setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof( val ) ); +#endif + if( connect( sock, ptr->ai_addr, ptr->ai_addrlen ) == -1 ) + { +#ifdef _WIN32 + closesocket( sock ); +#else + close( sock ); +#endif + continue; + } + break; + } + freeaddrinfo( res ); + if( !ptr ) return false; + + m_sock.store( sock, std::memory_order_relaxed ); + return true; +} + +void Socket::Close() +{ + const auto sock = m_sock.load( std::memory_order_relaxed ); + assert( sock != -1 ); +#ifdef _WIN32 + closesocket( sock ); +#else + close( sock ); +#endif + m_sock.store( -1, std::memory_order_relaxed ); +} + +int Socket::Send( const void* _buf, int len ) +{ + const auto sock = m_sock.load( std::memory_order_relaxed ); + auto buf = (const char*)_buf; + assert( sock != -1 ); + auto start = buf; + while( len > 0 ) + { + auto ret = send( sock, buf, len, MSG_NOSIGNAL ); + if( ret == -1 ) return -1; + len -= ret; + buf += ret; + } + return int( buf - start ); +} + +int Socket::GetSendBufSize() +{ + const auto sock = m_sock.load( std::memory_order_relaxed ); + int bufSize; +#if defined _WIN32 + int sz = sizeof( bufSize ); + getsockopt( sock, SOL_SOCKET, SO_SNDBUF, (char*)&bufSize, &sz ); +#else + socklen_t sz = sizeof( bufSize ); + getsockopt( sock, SOL_SOCKET, SO_SNDBUF, &bufSize, &sz ); +#endif + return bufSize; +} + +int Socket::RecvBuffered( void* buf, int len, int timeout ) +{ + if( len <= m_bufLeft ) + { + memcpy( buf, m_bufPtr, len ); + m_bufPtr += len; + m_bufLeft -= len; + return len; + } + + if( m_bufLeft > 0 ) + { + memcpy( buf, m_bufPtr, m_bufLeft ); + const auto ret = m_bufLeft; + m_bufLeft = 0; + return ret; + } + + if( len >= BufSize ) return Recv( buf, len, timeout ); + + m_bufLeft = Recv( m_buf, BufSize, timeout ); + if( m_bufLeft <= 0 ) return m_bufLeft; + + const auto sz = len < m_bufLeft ? len : m_bufLeft; + memcpy( buf, m_buf, sz ); + m_bufPtr = m_buf + sz; + m_bufLeft -= sz; + return sz; +} + +int Socket::Recv( void* _buf, int len, int timeout ) +{ + const auto sock = m_sock.load( std::memory_order_relaxed ); + auto buf = (char*)_buf; + + struct pollfd fd; + fd.fd = (socket_t)sock; + fd.events = POLLIN; + + if( poll( &fd, 1, timeout ) > 0 ) + { + return recv( sock, buf, len, 0 ); + } + else + { + return -1; + } +} + +int Socket::ReadUpTo( void* _buf, int len, int timeout ) +{ + const auto sock = m_sock.load( std::memory_order_relaxed ); + auto buf = (char*)_buf; + + int rd = 0; + while( len > 0 ) + { + const auto res = recv( sock, buf, len, 0 ); + if( res == 0 ) break; + if( res == -1 ) return -1; + len -= res; + rd += res; + buf += res; + } + return rd; +} + +bool Socket::Read( void* buf, int len, int timeout ) +{ + auto cbuf = (char*)buf; + while( len > 0 ) + { + if( !ReadImpl( cbuf, len, timeout ) ) return false; + } + return true; +} + +bool Socket::ReadImpl( char*& buf, int& len, int timeout ) +{ + const auto sz = RecvBuffered( buf, len, timeout ); + switch( sz ) + { + case 0: + return false; + case -1: +#ifdef _WIN32 + { + auto err = WSAGetLastError(); + if( err == WSAECONNABORTED || err == WSAECONNRESET ) return false; + } +#endif + break; + default: + len -= sz; + buf += sz; + break; + } + return true; +} + +bool Socket::ReadRaw( void* _buf, int len, int timeout ) +{ + auto buf = (char*)_buf; + while( len > 0 ) + { + const auto sz = Recv( buf, len, timeout ); + if( sz <= 0 ) return false; + len -= sz; + buf += sz; + } + return true; +} + +bool Socket::HasData() +{ + const auto sock = m_sock.load( std::memory_order_relaxed ); + if( m_bufLeft > 0 ) return true; + + struct pollfd fd; + fd.fd = (socket_t)sock; + fd.events = POLLIN; + + return poll( &fd, 1, 0 ) > 0; +} + +bool Socket::IsValid() const +{ + return m_sock.load( std::memory_order_relaxed ) >= 0; +} + + +ListenSocket::ListenSocket() + : m_sock( -1 ) +{ +#ifdef _WIN32 + InitWinSock(); +#endif +} + +ListenSocket::~ListenSocket() +{ + if( m_sock != -1 ) Close(); +} + +static int addrinfo_and_socket_for_family( uint16_t port, int ai_family, struct addrinfo** res ) +{ + struct addrinfo hints; + memset( &hints, 0, sizeof( hints ) ); + hints.ai_family = ai_family; + hints.ai_socktype = SOCK_STREAM; +#ifndef TRACY_ONLY_LOCALHOST + const char* onlyLocalhost = getenv( "TRACY_ONLY_LOCALHOST" ); + if( !onlyLocalhost || onlyLocalhost[0] != '1' ) + { + hints.ai_flags = AI_PASSIVE; + } +#endif + char portbuf[32]; + sprintf( portbuf, "%" PRIu16, port ); + if( getaddrinfo( nullptr, portbuf, &hints, res ) != 0 ) return -1; + int sock = socket( (*res)->ai_family, (*res)->ai_socktype, (*res)->ai_protocol ); + if (sock == -1) freeaddrinfo( *res ); + return sock; +} + +bool ListenSocket::Listen( uint16_t port, int backlog ) +{ + assert( m_sock == -1 ); + + struct addrinfo* res = nullptr; + +#if !defined TRACY_ONLY_IPV4 && !defined TRACY_ONLY_LOCALHOST + const char* onlyIPv4 = getenv( "TRACY_ONLY_IPV4" ); + if( !onlyIPv4 || onlyIPv4[0] != '1' ) + { + m_sock = addrinfo_and_socket_for_family( port, AF_INET6, &res ); + } +#endif + if (m_sock == -1) + { + // IPV6 protocol may not be available/is disabled. Try to create a socket + // with the IPV4 protocol + m_sock = addrinfo_and_socket_for_family( port, AF_INET, &res ); + if( m_sock == -1 ) return false; + } +#if defined _WIN32 || defined __CYGWIN__ + unsigned long val = 0; + setsockopt( m_sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&val, sizeof( val ) ); +#elif defined BSD + int val = 0; + setsockopt( m_sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&val, sizeof( val ) ); + val = 1; + setsockopt( m_sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof( val ) ); +#else + int val = 1; + setsockopt( m_sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof( val ) ); +#endif + if( bind( m_sock, res->ai_addr, res->ai_addrlen ) == -1 ) { freeaddrinfo( res ); Close(); return false; } + if( listen( m_sock, backlog ) == -1 ) { freeaddrinfo( res ); Close(); return false; } + freeaddrinfo( res ); + return true; +} + +Socket* ListenSocket::Accept() +{ + struct sockaddr_storage remote; + socklen_t sz = sizeof( remote ); + + struct pollfd fd; + fd.fd = (socket_t)m_sock; + fd.events = POLLIN; + + if( poll( &fd, 1, 10 ) > 0 ) + { + int sock = accept( m_sock, (sockaddr*)&remote, &sz); + if( sock == -1 ) return nullptr; + +#if defined __APPLE__ + int val = 1; + setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof( val ) ); +#endif + + auto ptr = (Socket*)tracy_malloc( sizeof( Socket ) ); + new(ptr) Socket( sock ); + return ptr; + } + else + { + return nullptr; + } +} + +void ListenSocket::Close() +{ + assert( m_sock != -1 ); +#ifdef _WIN32 + closesocket( m_sock ); +#else + close( m_sock ); +#endif + m_sock = -1; +} + +UdpBroadcast::UdpBroadcast() + : m_sock( -1 ) +{ +#ifdef _WIN32 + InitWinSock(); +#endif +} + +UdpBroadcast::~UdpBroadcast() +{ + if( m_sock != -1 ) Close(); +} + +bool UdpBroadcast::Open( const char* addr, uint16_t port ) +{ + assert( m_sock == -1 ); + + struct addrinfo hints; + struct addrinfo *res, *ptr; + + memset( &hints, 0, sizeof( hints ) ); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + + char portbuf[32]; + sprintf( portbuf, "%" PRIu16, port ); + + if( getaddrinfo( addr, portbuf, &hints, &res ) != 0 ) return false; + int sock = 0; + for( ptr = res; ptr; ptr = ptr->ai_next ) + { + if( ( sock = socket( ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol ) ) == -1 ) continue; +#if defined __APPLE__ + int val = 1; + setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof( val ) ); +#endif +#if defined _WIN32 + unsigned long broadcast = 1; + if( setsockopt( sock, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof( broadcast ) ) == -1 ) +#else + int broadcast = 1; + if( setsockopt( sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof( broadcast ) ) == -1 ) +#endif + { +#ifdef _WIN32 + closesocket( sock ); +#else + close( sock ); +#endif + continue; + } + break; + } + freeaddrinfo( res ); + if( !ptr ) return false; + + m_sock = sock; + inet_pton( AF_INET, addr, &m_addr ); + return true; +} + +void UdpBroadcast::Close() +{ + assert( m_sock != -1 ); +#ifdef _WIN32 + closesocket( m_sock ); +#else + close( m_sock ); +#endif + m_sock = -1; +} + +int UdpBroadcast::Send( uint16_t port, const void* data, int len ) +{ + assert( m_sock != -1 ); + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons( port ); + addr.sin_addr.s_addr = m_addr; + return sendto( m_sock, (const char*)data, len, MSG_NOSIGNAL, (sockaddr*)&addr, sizeof( addr ) ); +} + +IpAddress::IpAddress() + : m_number( 0 ) +{ + *m_text = '\0'; +} + +IpAddress::~IpAddress() +{ +} + +void IpAddress::Set( const struct sockaddr& addr ) +{ +#if defined _WIN32 && ( !defined NTDDI_WIN10 || NTDDI_VERSION < NTDDI_WIN10 ) + struct sockaddr_in tmp; + memcpy( &tmp, &addr, sizeof( tmp ) ); + auto ai = &tmp; +#else + auto ai = (const struct sockaddr_in*)&addr; +#endif + inet_ntop( AF_INET, &ai->sin_addr, m_text, 17 ); + m_number = ai->sin_addr.s_addr; +} + +UdpListen::UdpListen() + : m_sock( -1 ) +{ +#ifdef _WIN32 + InitWinSock(); +#endif +} + +UdpListen::~UdpListen() +{ + if( m_sock != -1 ) Close(); +} + +bool UdpListen::Listen( uint16_t port ) +{ + assert( m_sock == -1 ); + + int sock; + if( ( sock = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 ) return false; + +#if defined __APPLE__ + int val = 1; + setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof( val ) ); +#endif +#if defined _WIN32 + unsigned long reuse = 1; + setsockopt( m_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof( reuse ) ); +#else + int reuse = 1; + setsockopt( m_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) ); +#endif +#if defined _WIN32 + unsigned long broadcast = 1; + if( setsockopt( sock, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof( broadcast ) ) == -1 ) +#else + int broadcast = 1; + if( setsockopt( sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof( broadcast ) ) == -1 ) +#endif + { +#ifdef _WIN32 + closesocket( sock ); +#else + close( sock ); +#endif + return false; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons( port ); + addr.sin_addr.s_addr = INADDR_ANY; + + if( bind( sock, (sockaddr*)&addr, sizeof( addr ) ) == -1 ) + { +#ifdef _WIN32 + closesocket( sock ); +#else + close( sock ); +#endif + return false; + } + + m_sock = sock; + return true; +} + +void UdpListen::Close() +{ + assert( m_sock != -1 ); +#ifdef _WIN32 + closesocket( m_sock ); +#else + close( m_sock ); +#endif + m_sock = -1; +} + +const char* UdpListen::Read( size_t& len, IpAddress& addr, int timeout ) +{ + static char buf[2048]; + + struct pollfd fd; + fd.fd = (socket_t)m_sock; + fd.events = POLLIN; + if( poll( &fd, 1, timeout ) <= 0 ) return nullptr; + + sockaddr sa; + socklen_t salen = sizeof( struct sockaddr ); + len = (size_t)recvfrom( m_sock, buf, 2048, 0, &sa, &salen ); + addr.Set( sa ); + + return buf; +} + +} diff --git a/Source/ThirdParty/tracy/common/TracySocket.hpp b/Source/ThirdParty/tracy/common/TracySocket.hpp new file mode 100644 index 000000000..4fbb3278a --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracySocket.hpp @@ -0,0 +1,154 @@ +#ifndef __TRACYSOCKET_HPP__ +#define __TRACYSOCKET_HPP__ + +#include +#include + +struct addrinfo; +struct sockaddr; + +namespace tracy +{ + +#ifdef _WIN32 +void InitWinSock(); +#endif + +class Socket +{ +public: + Socket(); + Socket( int sock ); + ~Socket(); + + bool Connect( const char* addr, uint16_t port ); + bool ConnectBlocking( const char* addr, uint16_t port ); + void Close(); + + int Send( const void* buf, int len ); + int GetSendBufSize(); + + int ReadUpTo( void* buf, int len, int timeout ); + bool Read( void* buf, int len, int timeout ); + + template + bool Read( void* buf, int len, int timeout, ShouldExit exitCb ) + { + auto cbuf = (char*)buf; + while( len > 0 ) + { + if( exitCb() ) return false; + if( !ReadImpl( cbuf, len, timeout ) ) return false; + } + return true; + } + + bool ReadRaw( void* buf, int len, int timeout ); + bool HasData(); + bool IsValid() const; + + Socket( const Socket& ) = delete; + Socket( Socket&& ) = delete; + Socket& operator=( const Socket& ) = delete; + Socket& operator=( Socket&& ) = delete; + +private: + int RecvBuffered( void* buf, int len, int timeout ); + int Recv( void* buf, int len, int timeout ); + + bool ReadImpl( char*& buf, int& len, int timeout ); + + char* m_buf; + char* m_bufPtr; + std::atomic m_sock; + int m_bufLeft; + + struct addrinfo *m_res; + struct addrinfo *m_ptr; + int m_connSock; +}; + +class ListenSocket +{ +public: + ListenSocket(); + ~ListenSocket(); + + bool Listen( uint16_t port, int backlog ); + Socket* Accept(); + void Close(); + + ListenSocket( const ListenSocket& ) = delete; + ListenSocket( ListenSocket&& ) = delete; + ListenSocket& operator=( const ListenSocket& ) = delete; + ListenSocket& operator=( ListenSocket&& ) = delete; + +private: + int m_sock; +}; + +class UdpBroadcast +{ +public: + UdpBroadcast(); + ~UdpBroadcast(); + + bool Open( const char* addr, uint16_t port ); + void Close(); + + int Send( uint16_t port, const void* data, int len ); + + UdpBroadcast( const UdpBroadcast& ) = delete; + UdpBroadcast( UdpBroadcast&& ) = delete; + UdpBroadcast& operator=( const UdpBroadcast& ) = delete; + UdpBroadcast& operator=( UdpBroadcast&& ) = delete; + +private: + int m_sock; + uint32_t m_addr; +}; + +class IpAddress +{ +public: + IpAddress(); + ~IpAddress(); + + void Set( const struct sockaddr& addr ); + + uint32_t GetNumber() const { return m_number; } + const char* GetText() const { return m_text; } + + IpAddress( const IpAddress& ) = delete; + IpAddress( IpAddress&& ) = delete; + IpAddress& operator=( const IpAddress& ) = delete; + IpAddress& operator=( IpAddress&& ) = delete; + +private: + uint32_t m_number; + char m_text[17]; +}; + +class UdpListen +{ +public: + UdpListen(); + ~UdpListen(); + + bool Listen( uint16_t port ); + void Close(); + + const char* Read( size_t& len, IpAddress& addr, int timeout ); + + UdpListen( const UdpListen& ) = delete; + UdpListen( UdpListen&& ) = delete; + UdpListen& operator=( const UdpListen& ) = delete; + UdpListen& operator=( UdpListen&& ) = delete; + +private: + int m_sock; +}; + +} + +#endif diff --git a/Source/ThirdParty/tracy/common/TracySystem.cpp b/Source/ThirdParty/tracy/common/TracySystem.cpp new file mode 100644 index 000000000..25ccf9f8a --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracySystem.cpp @@ -0,0 +1,239 @@ +#if defined _MSC_VER || defined __CYGWIN__ || defined _WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef NOMINMAX +# define NOMINMAX +# endif +#endif +#ifdef _MSC_VER +# pragma warning(disable:4996) +#endif +#if defined _WIN32 || defined __CYGWIN__ +# include +#else +# include +# include +# include +#endif + +#ifdef __linux__ +# ifdef __ANDROID__ +# include +# else +# include +# endif +# include +#elif defined __FreeBSD__ +# include +#elif defined __NetBSD__ || defined __DragonFly__ +# include +#endif + +#ifdef __MINGW32__ +# define __STDC_FORMAT_MACROS +#endif +#include +#include +#include + +#include "TracySystem.hpp" + +#if defined _WIN32 || defined __CYGWIN__ +extern "C" typedef HRESULT (WINAPI *t_SetThreadDescription)( HANDLE, PCWSTR ); +extern "C" typedef HRESULT (WINAPI *t_GetThreadDescription)( HANDLE, PWSTR* ); +#endif + +#ifdef TRACY_ENABLE +# include +# include "TracyAlloc.hpp" +#endif + +namespace tracy +{ + +namespace detail +{ + +TRACY_API uint64_t GetThreadHandleImpl() +{ +#if defined _WIN32 || defined __CYGWIN__ + static_assert( sizeof( decltype( GetCurrentThreadId() ) ) <= sizeof( uint64_t ), "Thread handle too big to fit in protocol" ); + return uint64_t( GetCurrentThreadId() ); +#elif defined __APPLE__ + uint64_t id; + pthread_threadid_np( pthread_self(), &id ); + return id; +#elif defined __ANDROID__ + return (uint64_t)gettid(); +#elif defined __linux__ + return (uint64_t)syscall( SYS_gettid ); +#elif defined __FreeBSD__ + long id; + thr_self( &id ); + return id; +#elif defined __NetBSD__ + return _lwp_self(); +#elif defined __DragonFly__ + return lwp_gettid(); +#elif defined __OpenBSD__ + return getthrid(); +#else + static_assert( sizeof( decltype( pthread_self() ) ) <= sizeof( uint64_t ), "Thread handle too big to fit in protocol" ); + return uint64_t( pthread_self() ); +#endif + +} + +} + +#ifdef TRACY_ENABLE +struct ThreadNameData +{ + uint64_t id; + const char* name; + ThreadNameData* next; +}; +std::atomic& GetThreadNameData(); +TRACY_API void InitRPMallocThread(); +#endif + +TRACY_API void SetThreadName( const char* name ) +{ +#if defined _WIN32 || defined __CYGWIN__ + static auto _SetThreadDescription = (t_SetThreadDescription)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "SetThreadDescription" ); + if( _SetThreadDescription ) + { + wchar_t buf[256]; + mbstowcs( buf, name, 256 ); + _SetThreadDescription( GetCurrentThread(), buf ); + } + else + { +# if defined _MSC_VER + const DWORD MS_VC_EXCEPTION=0x406D1388; +# pragma pack( push, 8 ) + struct THREADNAME_INFO + { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; + }; +# pragma pack(pop) + + DWORD ThreadId = GetCurrentThreadId(); + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = ThreadId; + info.dwFlags = 0; + + __try + { + RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } +# endif + } +#elif defined _GNU_SOURCE && !defined __EMSCRIPTEN__ && !defined __CYGWIN__ + { + const auto sz = strlen( name ); + if( sz <= 15 ) + { + pthread_setname_np( pthread_self(), name ); + } + else + { + char buf[16]; + memcpy( buf, name, 15 ); + buf[15] = '\0'; + pthread_setname_np( pthread_self(), buf ); + } + } +#endif +#ifdef TRACY_ENABLE + { + InitRPMallocThread(); + const auto sz = strlen( name ); + char* buf = (char*)tracy_malloc( sz+1 ); + memcpy( buf, name, sz ); + buf[sz] = '\0'; + auto data = (ThreadNameData*)tracy_malloc( sizeof( ThreadNameData ) ); + data->id = detail::GetThreadHandleImpl(); + data->name = buf; + data->next = GetThreadNameData().load( std::memory_order_relaxed ); + while( !GetThreadNameData().compare_exchange_weak( data->next, data, std::memory_order_release, std::memory_order_relaxed ) ) {} + } +#endif +} + +TRACY_API const char* GetThreadName( uint64_t id ) +{ + static char buf[256]; +#ifdef TRACY_ENABLE + auto ptr = GetThreadNameData().load( std::memory_order_relaxed ); + while( ptr ) + { + if( ptr->id == id ) + { + return ptr->name; + } + ptr = ptr->next; + } +#else +# if defined _WIN32 || defined __CYGWIN__ + static auto _GetThreadDescription = (t_GetThreadDescription)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "GetThreadDescription" ); + if( _GetThreadDescription ) + { + auto hnd = OpenThread( THREAD_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)id ); + if( hnd != 0 ) + { + PWSTR tmp; + _GetThreadDescription( hnd, &tmp ); + auto ret = wcstombs( buf, tmp, 256 ); + CloseHandle( hnd ); + if( ret != 0 ) + { + return buf; + } + } + } +# elif defined __linux__ + int cs, fd; + char path[32]; +# ifdef __ANDROID__ + int tid = gettid(); +# else + int tid = (int) syscall( SYS_gettid ); +# endif + snprintf( path, sizeof( path ), "/proc/self/task/%d/comm", tid ); + sprintf( buf, "%" PRIu64, id ); +# ifndef __ANDROID__ + pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &cs ); +# endif + if ( ( fd = open( path, O_RDONLY ) ) > 0) { + int len = read( fd, buf, 255 ); + if( len > 0 ) + { + buf[len] = 0; + if( len > 1 && buf[len-1] == '\n' ) + { + buf[len-1] = 0; + } + } + close( fd ); + } +# ifndef __ANDROID__ + pthread_setcancelstate( cs, 0 ); +# endif + return buf; +# endif +#endif + sprintf( buf, "%" PRIu64, id ); + return buf; +} + +} diff --git a/Source/ThirdParty/tracy/common/TracySystem.hpp b/Source/ThirdParty/tracy/common/TracySystem.hpp new file mode 100644 index 000000000..f285b762a --- /dev/null +++ b/Source/ThirdParty/tracy/common/TracySystem.hpp @@ -0,0 +1,95 @@ +#ifndef __TRACYSYSTEM_HPP__ +#define __TRACYSYSTEM_HPP__ + +#include + +// Tracy -> Flax integration: +// - use LZ4 from Flax +// - use engine symbols export +// - use engine types and macros +// - remove AddVectoredExceptionHandler from win32 to prevent messing with Flax crashes reporting +// - hide implementation from includers to reduce compilation overhead +// - optimize includes (faster compilation) +// - remove some features (colors, frame image, dx1 compression) +#include "Engine/Core/Types/BaseTypes.h" +#define TRACY_API FLAXENGINE_API +#define tracy_force_inline FORCE_INLINE +#define tracy_no_inline FORCE_NOINLINE + +#ifndef TracyConcat +# define TracyConcat(x,y) TracyConcatIndirect(x,y) +#endif +#ifndef TracyConcatIndirect +# define TracyConcatIndirect(x,y) x##y +#endif + +namespace tracy +{ +enum class PlotFormatType : uint8_t +{ + Number, + Memory, + Percentage +}; + +typedef void(*ParameterCallback)( uint32_t idx, int32_t val ); + +struct TRACY_API SourceLocationData +{ + const char* name; + const char* function; + const char* file; + uint32_t line; + uint32_t color; +}; + +class TRACY_API ScopedZone +{ +public: + ScopedZone( const ScopedZone& ) = delete; + ScopedZone( ScopedZone&& ) = delete; + ScopedZone& operator=( const ScopedZone& ) = delete; + ScopedZone& operator=( ScopedZone&& ) = delete; + + ScopedZone( const SourceLocationData* srcloc, bool is_active = true ); + ScopedZone( const SourceLocationData* srcloc, int depth, bool is_active = true ); + ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, bool is_active = true ); + ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, int depth, bool is_active = true ); + + ~ScopedZone(); + + void Text( const char* txt, size_t size ); + void Name( const char* txt, size_t size ); + void Name( const Char* txt, size_t size ); + void Color( uint32_t color ); + void Value( uint64_t value ); + bool IsActive() const; + +private: + const bool m_active; + +#ifdef TRACY_ON_DEMAND + uint64_t m_connectionId; +#endif +}; + +namespace detail +{ +TRACY_API uint64_t GetThreadHandleImpl(); +} + +#ifdef TRACY_ENABLE +TRACY_API uint64_t GetThreadHandle(); +#else +static inline uint64_t GetThreadHandle() +{ + return detail::GetThreadHandleImpl(); +} +#endif + +TRACY_API void SetThreadName( const char* name ); +TRACY_API const char* GetThreadName( uint64_t id ); + +} + +#endif diff --git a/Source/ThirdParty/tracy/libbacktrace/LICENSE b/Source/ThirdParty/tracy/libbacktrace/LICENSE new file mode 100644 index 000000000..097d2774e --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/LICENSE @@ -0,0 +1,29 @@ +# Copyright (C) 2012-2016 Free Software Foundation, Inc. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: + +# (1) Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. + +# (2) Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. + +# (3) The name of the author may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. diff --git a/Source/ThirdParty/tracy/libbacktrace/alloc.cpp b/Source/ThirdParty/tracy/libbacktrace/alloc.cpp new file mode 100644 index 000000000..a365a4860 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/alloc.cpp @@ -0,0 +1,174 @@ +/* alloc.c -- Memory allocation without mmap. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include +#include + +#include "backtrace.hpp" +#include "internal.hpp" + +#include "../common/TracyAlloc.hpp" + +namespace tracy +{ + +/* Allocation routines to use on systems that do not support anonymous + mmap. This implementation just uses malloc, which means that the + backtrace functions may not be safely invoked from a signal + handler. */ + +/* Allocate memory like malloc. If ERROR_CALLBACK is NULL, don't + report an error. */ + +void * +backtrace_alloc (struct backtrace_state *state ATTRIBUTE_UNUSED, + size_t size, backtrace_error_callback error_callback, + void *data) +{ + void *ret; + + ret = tracy_malloc (size); + if (ret == NULL) + { + if (error_callback) + error_callback (data, "malloc", errno); + } + return ret; +} + +/* Free memory. */ + +void +backtrace_free (struct backtrace_state *state ATTRIBUTE_UNUSED, + void *p, size_t size ATTRIBUTE_UNUSED, + backtrace_error_callback error_callback ATTRIBUTE_UNUSED, + void *data ATTRIBUTE_UNUSED) +{ + tracy_free (p); +} + +/* Grow VEC by SIZE bytes. */ + +void * +backtrace_vector_grow (struct backtrace_state *state ATTRIBUTE_UNUSED, + size_t size, backtrace_error_callback error_callback, + void *data, struct backtrace_vector *vec) +{ + void *ret; + + if (size > vec->alc) + { + size_t alc; + void *base; + + if (vec->size == 0) + alc = 32 * size; + else if (vec->size >= 4096) + alc = vec->size + 4096; + else + alc = 2 * vec->size; + + if (alc < vec->size + size) + alc = vec->size + size; + + base = tracy_realloc (vec->base, alc); + if (base == NULL) + { + error_callback (data, "realloc", errno); + return NULL; + } + + vec->base = base; + vec->alc = alc - vec->size; + } + + ret = (char *) vec->base + vec->size; + vec->size += size; + vec->alc -= size; + return ret; +} + +/* Finish the current allocation on VEC. */ + +void * +backtrace_vector_finish (struct backtrace_state *state, + struct backtrace_vector *vec, + backtrace_error_callback error_callback, + void *data) +{ + void *ret; + + /* With this allocator we call realloc in backtrace_vector_grow, + which means we can't easily reuse the memory here. So just + release it. */ + if (!backtrace_vector_release (state, vec, error_callback, data)) + return NULL; + ret = vec->base; + vec->base = NULL; + vec->size = 0; + vec->alc = 0; + return ret; +} + +/* Release any extra space allocated for VEC. */ + +int +backtrace_vector_release (struct backtrace_state *state ATTRIBUTE_UNUSED, + struct backtrace_vector *vec, + backtrace_error_callback error_callback, + void *data) +{ + vec->alc = 0; + + if (vec->size == 0) + { + /* As of C17, realloc with size 0 is marked as an obsolescent feature, use + free instead. */ + tracy_free (vec->base); + vec->base = NULL; + return 1; + } + + vec->base = tracy_realloc (vec->base, vec->size); + if (vec->base == NULL) + { + error_callback (data, "realloc", errno); + return 0; + } + + return 1; +} + +} diff --git a/Source/ThirdParty/tracy/libbacktrace/backtrace.hpp b/Source/ThirdParty/tracy/libbacktrace/backtrace.hpp new file mode 100644 index 000000000..d999803c8 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/backtrace.hpp @@ -0,0 +1,185 @@ +/* backtrace.h -- Public header file for stack backtrace library. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#ifndef BACKTRACE_H +#define BACKTRACE_H + +#include +#include +#include + +namespace tracy +{ + +/* The backtrace state. This struct is intentionally not defined in + the public interface. */ + +struct backtrace_state; + +/* The type of the error callback argument to backtrace functions. + This function, if not NULL, will be called for certain error cases. + The DATA argument is passed to the function that calls this one. + The MSG argument is an error message. The ERRNUM argument, if + greater than 0, holds an errno value. The MSG buffer may become + invalid after this function returns. + + As a special case, the ERRNUM argument will be passed as -1 if no + debug info can be found for the executable, but the function + requires debug info (e.g., backtrace_full, backtrace_pcinfo). The + MSG in this case will be something along the lines of "no debug + info". Similarly, ERRNUM will be passed as -1 if there is no + symbol table, but the function requires a symbol table (e.g., + backtrace_syminfo). This may be used as a signal that some other + approach should be tried. */ + +typedef void (*backtrace_error_callback) (void *data, const char *msg, + int errnum); + +/* Create state information for the backtrace routines. This must be + called before any of the other routines, and its return value must + be passed to all of the other routines. FILENAME is the path name + of the executable file; if it is NULL the library will try + system-specific path names. If not NULL, FILENAME must point to a + permanent buffer. If THREADED is non-zero the state may be + accessed by multiple threads simultaneously, and the library will + use appropriate atomic operations. If THREADED is zero the state + may only be accessed by one thread at a time. This returns a state + pointer on success, NULL on error. If an error occurs, this will + call the ERROR_CALLBACK routine. + + Calling this function allocates resources that cannot be freed. + There is no backtrace_free_state function. The state is used to + cache information that is expensive to recompute. Programs are + expected to call this function at most once and to save the return + value for all later calls to backtrace functions. */ + +extern struct backtrace_state *backtrace_create_state ( + const char *filename, int threaded, + backtrace_error_callback error_callback, void *data); + +/* The type of the callback argument to the backtrace_full function. + DATA is the argument passed to backtrace_full. PC is the program + counter. FILENAME is the name of the file containing PC, or NULL + if not available. LINENO is the line number in FILENAME containing + PC, or 0 if not available. FUNCTION is the name of the function + containing PC, or NULL if not available. This should return 0 to + continuing tracing. The FILENAME and FUNCTION buffers may become + invalid after this function returns. */ + +typedef int (*backtrace_full_callback) (void *data, uintptr_t pc, uintptr_t lowaddr, + const char *filename, int lineno, + const char *function); + +/* Get a full stack backtrace. SKIP is the number of frames to skip; + passing 0 will start the trace with the function calling + backtrace_full. DATA is passed to the callback routine. If any + call to CALLBACK returns a non-zero value, the stack backtrace + stops, and backtrace returns that value; this may be used to limit + the number of stack frames desired. If all calls to CALLBACK + return 0, backtrace returns 0. The backtrace_full function will + make at least one call to either CALLBACK or ERROR_CALLBACK. This + function requires debug info for the executable. */ + +extern int backtrace_full (struct backtrace_state *state, int skip, + backtrace_full_callback callback, + backtrace_error_callback error_callback, + void *data); + +/* The type of the callback argument to the backtrace_simple function. + DATA is the argument passed to simple_backtrace. PC is the program + counter. This should return 0 to continue tracing. */ + +typedef int (*backtrace_simple_callback) (void *data, uintptr_t pc); + +/* Get a simple backtrace. SKIP is the number of frames to skip, as + in backtrace. DATA is passed to the callback routine. If any call + to CALLBACK returns a non-zero value, the stack backtrace stops, + and backtrace_simple returns that value. Otherwise + backtrace_simple returns 0. The backtrace_simple function will + make at least one call to either CALLBACK or ERROR_CALLBACK. This + function does not require any debug info for the executable. */ + +extern int backtrace_simple (struct backtrace_state *state, int skip, + backtrace_simple_callback callback, + backtrace_error_callback error_callback, + void *data); + +/* Print the current backtrace in a user readable format to a FILE. + SKIP is the number of frames to skip, as in backtrace_full. Any + error messages are printed to stderr. This function requires debug + info for the executable. */ + +extern void backtrace_print (struct backtrace_state *state, int skip, FILE *); + +/* Given PC, a program counter in the current program, call the + callback function with filename, line number, and function name + information. This will normally call the callback function exactly + once. However, if the PC happens to describe an inlined call, and + the debugging information contains the necessary information, then + this may call the callback function multiple times. This will make + at least one call to either CALLBACK or ERROR_CALLBACK. This + returns the first non-zero value returned by CALLBACK, or 0. */ + +extern int backtrace_pcinfo (struct backtrace_state *state, uintptr_t pc, + backtrace_full_callback callback, + backtrace_error_callback error_callback, + void *data); + +/* The type of the callback argument to backtrace_syminfo. DATA and + PC are the arguments passed to backtrace_syminfo. SYMNAME is the + name of the symbol for the corresponding code. SYMVAL is the + value and SYMSIZE is the size of the symbol. SYMNAME will be NULL + if no error occurred but the symbol could not be found. */ + +typedef void (*backtrace_syminfo_callback) (void *data, uintptr_t pc, + const char *symname, + uintptr_t symval, + uintptr_t symsize); + +/* Given ADDR, an address or program counter in the current program, + call the callback information with the symbol name and value + describing the function or variable in which ADDR may be found. + This will call either CALLBACK or ERROR_CALLBACK exactly once. + This returns 1 on success, 0 on failure. This function requires + the symbol table but does not require the debug info. Note that if + the symbol table is present but ADDR could not be found in the + table, CALLBACK will be called with a NULL SYMNAME argument. + Returns 1 on success, 0 on error. */ + +extern int backtrace_syminfo (struct backtrace_state *state, uintptr_t addr, + backtrace_syminfo_callback callback, + backtrace_error_callback error_callback, + void *data); + +} + +#endif diff --git a/Source/ThirdParty/tracy/libbacktrace/config.h b/Source/ThirdParty/tracy/libbacktrace/config.h new file mode 100644 index 000000000..aa3259d11 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/config.h @@ -0,0 +1,22 @@ +#include +#if __WORDSIZE == 64 +# define BACKTRACE_ELF_SIZE 64 +#else +# define BACKTRACE_ELF_SIZE 32 +#endif + +#define HAVE_DLFCN_H 1 +#define HAVE_FCNTL 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LSTAT 1 +#define HAVE_READLINK 1 +#define HAVE_DL_ITERATE_PHDR 1 +#define HAVE_ATOMIC_FUNCTIONS 1 +#define HAVE_DECL_STRNLEN 1 + +#ifdef __APPLE__ +# define HAVE_MACH_O_DYLD_H 1 +#elif defined BSD +# define HAVE_KERN_PROC 1 +# define HAVE_KERN_PROC_ARGS 1 +#endif diff --git a/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp b/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp new file mode 100644 index 000000000..f76e03cfb --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp @@ -0,0 +1,4393 @@ +/* dwarf.c -- Get file/line information from DWARF for backtraces. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include +#include +#include + +#include "filenames.hpp" + +#include "backtrace.hpp" +#include "internal.hpp" + +namespace tracy +{ + +/* DWARF constants. */ + +enum dwarf_tag { + DW_TAG_entry_point = 0x3, + DW_TAG_compile_unit = 0x11, + DW_TAG_inlined_subroutine = 0x1d, + DW_TAG_subprogram = 0x2e, +}; + +enum dwarf_form { + DW_FORM_addr = 0x01, + DW_FORM_block2 = 0x03, + DW_FORM_block4 = 0x04, + DW_FORM_data2 = 0x05, + DW_FORM_data4 = 0x06, + DW_FORM_data8 = 0x07, + DW_FORM_string = 0x08, + DW_FORM_block = 0x09, + DW_FORM_block1 = 0x0a, + DW_FORM_data1 = 0x0b, + DW_FORM_flag = 0x0c, + DW_FORM_sdata = 0x0d, + DW_FORM_strp = 0x0e, + DW_FORM_udata = 0x0f, + DW_FORM_ref_addr = 0x10, + DW_FORM_ref1 = 0x11, + DW_FORM_ref2 = 0x12, + DW_FORM_ref4 = 0x13, + DW_FORM_ref8 = 0x14, + DW_FORM_ref_udata = 0x15, + DW_FORM_indirect = 0x16, + DW_FORM_sec_offset = 0x17, + DW_FORM_exprloc = 0x18, + DW_FORM_flag_present = 0x19, + DW_FORM_ref_sig8 = 0x20, + DW_FORM_strx = 0x1a, + DW_FORM_addrx = 0x1b, + DW_FORM_ref_sup4 = 0x1c, + DW_FORM_strp_sup = 0x1d, + DW_FORM_data16 = 0x1e, + DW_FORM_line_strp = 0x1f, + DW_FORM_implicit_const = 0x21, + DW_FORM_loclistx = 0x22, + DW_FORM_rnglistx = 0x23, + DW_FORM_ref_sup8 = 0x24, + DW_FORM_strx1 = 0x25, + DW_FORM_strx2 = 0x26, + DW_FORM_strx3 = 0x27, + DW_FORM_strx4 = 0x28, + DW_FORM_addrx1 = 0x29, + DW_FORM_addrx2 = 0x2a, + DW_FORM_addrx3 = 0x2b, + DW_FORM_addrx4 = 0x2c, + DW_FORM_GNU_addr_index = 0x1f01, + DW_FORM_GNU_str_index = 0x1f02, + DW_FORM_GNU_ref_alt = 0x1f20, + DW_FORM_GNU_strp_alt = 0x1f21 +}; + +enum dwarf_attribute { + DW_AT_sibling = 0x01, + DW_AT_location = 0x02, + DW_AT_name = 0x03, + DW_AT_ordering = 0x09, + DW_AT_subscr_data = 0x0a, + DW_AT_byte_size = 0x0b, + DW_AT_bit_offset = 0x0c, + DW_AT_bit_size = 0x0d, + DW_AT_element_list = 0x0f, + DW_AT_stmt_list = 0x10, + DW_AT_low_pc = 0x11, + DW_AT_high_pc = 0x12, + DW_AT_language = 0x13, + DW_AT_member = 0x14, + DW_AT_discr = 0x15, + DW_AT_discr_value = 0x16, + DW_AT_visibility = 0x17, + DW_AT_import = 0x18, + DW_AT_string_length = 0x19, + DW_AT_common_reference = 0x1a, + DW_AT_comp_dir = 0x1b, + DW_AT_const_value = 0x1c, + DW_AT_containing_type = 0x1d, + DW_AT_default_value = 0x1e, + DW_AT_inline = 0x20, + DW_AT_is_optional = 0x21, + DW_AT_lower_bound = 0x22, + DW_AT_producer = 0x25, + DW_AT_prototyped = 0x27, + DW_AT_return_addr = 0x2a, + DW_AT_start_scope = 0x2c, + DW_AT_bit_stride = 0x2e, + DW_AT_upper_bound = 0x2f, + DW_AT_abstract_origin = 0x31, + DW_AT_accessibility = 0x32, + DW_AT_address_class = 0x33, + DW_AT_artificial = 0x34, + DW_AT_base_types = 0x35, + DW_AT_calling_convention = 0x36, + DW_AT_count = 0x37, + DW_AT_data_member_location = 0x38, + DW_AT_decl_column = 0x39, + DW_AT_decl_file = 0x3a, + DW_AT_decl_line = 0x3b, + DW_AT_declaration = 0x3c, + DW_AT_discr_list = 0x3d, + DW_AT_encoding = 0x3e, + DW_AT_external = 0x3f, + DW_AT_frame_base = 0x40, + DW_AT_friend = 0x41, + DW_AT_identifier_case = 0x42, + DW_AT_macro_info = 0x43, + DW_AT_namelist_items = 0x44, + DW_AT_priority = 0x45, + DW_AT_segment = 0x46, + DW_AT_specification = 0x47, + DW_AT_static_link = 0x48, + DW_AT_type = 0x49, + DW_AT_use_location = 0x4a, + DW_AT_variable_parameter = 0x4b, + DW_AT_virtuality = 0x4c, + DW_AT_vtable_elem_location = 0x4d, + DW_AT_allocated = 0x4e, + DW_AT_associated = 0x4f, + DW_AT_data_location = 0x50, + DW_AT_byte_stride = 0x51, + DW_AT_entry_pc = 0x52, + DW_AT_use_UTF8 = 0x53, + DW_AT_extension = 0x54, + DW_AT_ranges = 0x55, + DW_AT_trampoline = 0x56, + DW_AT_call_column = 0x57, + DW_AT_call_file = 0x58, + DW_AT_call_line = 0x59, + DW_AT_description = 0x5a, + DW_AT_binary_scale = 0x5b, + DW_AT_decimal_scale = 0x5c, + DW_AT_small = 0x5d, + DW_AT_decimal_sign = 0x5e, + DW_AT_digit_count = 0x5f, + DW_AT_picture_string = 0x60, + DW_AT_mutable = 0x61, + DW_AT_threads_scaled = 0x62, + DW_AT_explicit = 0x63, + DW_AT_object_pointer = 0x64, + DW_AT_endianity = 0x65, + DW_AT_elemental = 0x66, + DW_AT_pure = 0x67, + DW_AT_recursive = 0x68, + DW_AT_signature = 0x69, + DW_AT_main_subprogram = 0x6a, + DW_AT_data_bit_offset = 0x6b, + DW_AT_const_expr = 0x6c, + DW_AT_enum_class = 0x6d, + DW_AT_linkage_name = 0x6e, + DW_AT_string_length_bit_size = 0x6f, + DW_AT_string_length_byte_size = 0x70, + DW_AT_rank = 0x71, + DW_AT_str_offsets_base = 0x72, + DW_AT_addr_base = 0x73, + DW_AT_rnglists_base = 0x74, + DW_AT_dwo_name = 0x76, + DW_AT_reference = 0x77, + DW_AT_rvalue_reference = 0x78, + DW_AT_macros = 0x79, + DW_AT_call_all_calls = 0x7a, + DW_AT_call_all_source_calls = 0x7b, + DW_AT_call_all_tail_calls = 0x7c, + DW_AT_call_return_pc = 0x7d, + DW_AT_call_value = 0x7e, + DW_AT_call_origin = 0x7f, + DW_AT_call_parameter = 0x80, + DW_AT_call_pc = 0x81, + DW_AT_call_tail_call = 0x82, + DW_AT_call_target = 0x83, + DW_AT_call_target_clobbered = 0x84, + DW_AT_call_data_location = 0x85, + DW_AT_call_data_value = 0x86, + DW_AT_noreturn = 0x87, + DW_AT_alignment = 0x88, + DW_AT_export_symbols = 0x89, + DW_AT_deleted = 0x8a, + DW_AT_defaulted = 0x8b, + DW_AT_loclists_base = 0x8c, + DW_AT_lo_user = 0x2000, + DW_AT_hi_user = 0x3fff, + DW_AT_MIPS_fde = 0x2001, + DW_AT_MIPS_loop_begin = 0x2002, + DW_AT_MIPS_tail_loop_begin = 0x2003, + DW_AT_MIPS_epilog_begin = 0x2004, + DW_AT_MIPS_loop_unroll_factor = 0x2005, + DW_AT_MIPS_software_pipeline_depth = 0x2006, + DW_AT_MIPS_linkage_name = 0x2007, + DW_AT_MIPS_stride = 0x2008, + DW_AT_MIPS_abstract_name = 0x2009, + DW_AT_MIPS_clone_origin = 0x200a, + DW_AT_MIPS_has_inlines = 0x200b, + DW_AT_HP_block_index = 0x2000, + DW_AT_HP_unmodifiable = 0x2001, + DW_AT_HP_prologue = 0x2005, + DW_AT_HP_epilogue = 0x2008, + DW_AT_HP_actuals_stmt_list = 0x2010, + DW_AT_HP_proc_per_section = 0x2011, + DW_AT_HP_raw_data_ptr = 0x2012, + DW_AT_HP_pass_by_reference = 0x2013, + DW_AT_HP_opt_level = 0x2014, + DW_AT_HP_prof_version_id = 0x2015, + DW_AT_HP_opt_flags = 0x2016, + DW_AT_HP_cold_region_low_pc = 0x2017, + DW_AT_HP_cold_region_high_pc = 0x2018, + DW_AT_HP_all_variables_modifiable = 0x2019, + DW_AT_HP_linkage_name = 0x201a, + DW_AT_HP_prof_flags = 0x201b, + DW_AT_HP_unit_name = 0x201f, + DW_AT_HP_unit_size = 0x2020, + DW_AT_HP_widened_byte_size = 0x2021, + DW_AT_HP_definition_points = 0x2022, + DW_AT_HP_default_location = 0x2023, + DW_AT_HP_is_result_param = 0x2029, + DW_AT_sf_names = 0x2101, + DW_AT_src_info = 0x2102, + DW_AT_mac_info = 0x2103, + DW_AT_src_coords = 0x2104, + DW_AT_body_begin = 0x2105, + DW_AT_body_end = 0x2106, + DW_AT_GNU_vector = 0x2107, + DW_AT_GNU_guarded_by = 0x2108, + DW_AT_GNU_pt_guarded_by = 0x2109, + DW_AT_GNU_guarded = 0x210a, + DW_AT_GNU_pt_guarded = 0x210b, + DW_AT_GNU_locks_excluded = 0x210c, + DW_AT_GNU_exclusive_locks_required = 0x210d, + DW_AT_GNU_shared_locks_required = 0x210e, + DW_AT_GNU_odr_signature = 0x210f, + DW_AT_GNU_template_name = 0x2110, + DW_AT_GNU_call_site_value = 0x2111, + DW_AT_GNU_call_site_data_value = 0x2112, + DW_AT_GNU_call_site_target = 0x2113, + DW_AT_GNU_call_site_target_clobbered = 0x2114, + DW_AT_GNU_tail_call = 0x2115, + DW_AT_GNU_all_tail_call_sites = 0x2116, + DW_AT_GNU_all_call_sites = 0x2117, + DW_AT_GNU_all_source_call_sites = 0x2118, + DW_AT_GNU_macros = 0x2119, + DW_AT_GNU_deleted = 0x211a, + DW_AT_GNU_dwo_name = 0x2130, + DW_AT_GNU_dwo_id = 0x2131, + DW_AT_GNU_ranges_base = 0x2132, + DW_AT_GNU_addr_base = 0x2133, + DW_AT_GNU_pubnames = 0x2134, + DW_AT_GNU_pubtypes = 0x2135, + DW_AT_GNU_discriminator = 0x2136, + DW_AT_GNU_locviews = 0x2137, + DW_AT_GNU_entry_view = 0x2138, + DW_AT_VMS_rtnbeg_pd_address = 0x2201, + DW_AT_use_GNAT_descriptive_type = 0x2301, + DW_AT_GNAT_descriptive_type = 0x2302, + DW_AT_GNU_numerator = 0x2303, + DW_AT_GNU_denominator = 0x2304, + DW_AT_GNU_bias = 0x2305, + DW_AT_upc_threads_scaled = 0x3210, + DW_AT_PGI_lbase = 0x3a00, + DW_AT_PGI_soffset = 0x3a01, + DW_AT_PGI_lstride = 0x3a02, + DW_AT_APPLE_optimized = 0x3fe1, + DW_AT_APPLE_flags = 0x3fe2, + DW_AT_APPLE_isa = 0x3fe3, + DW_AT_APPLE_block = 0x3fe4, + DW_AT_APPLE_major_runtime_vers = 0x3fe5, + DW_AT_APPLE_runtime_class = 0x3fe6, + DW_AT_APPLE_omit_frame_ptr = 0x3fe7, + DW_AT_APPLE_property_name = 0x3fe8, + DW_AT_APPLE_property_getter = 0x3fe9, + DW_AT_APPLE_property_setter = 0x3fea, + DW_AT_APPLE_property_attribute = 0x3feb, + DW_AT_APPLE_objc_complete_type = 0x3fec, + DW_AT_APPLE_property = 0x3fed +}; + +enum dwarf_line_number_op { + DW_LNS_extended_op = 0x0, + DW_LNS_copy = 0x1, + DW_LNS_advance_pc = 0x2, + DW_LNS_advance_line = 0x3, + DW_LNS_set_file = 0x4, + DW_LNS_set_column = 0x5, + DW_LNS_negate_stmt = 0x6, + DW_LNS_set_basic_block = 0x7, + DW_LNS_const_add_pc = 0x8, + DW_LNS_fixed_advance_pc = 0x9, + DW_LNS_set_prologue_end = 0xa, + DW_LNS_set_epilogue_begin = 0xb, + DW_LNS_set_isa = 0xc, +}; + +enum dwarf_extended_line_number_op { + DW_LNE_end_sequence = 0x1, + DW_LNE_set_address = 0x2, + DW_LNE_define_file = 0x3, + DW_LNE_set_discriminator = 0x4, +}; + +enum dwarf_line_number_content_type { + DW_LNCT_path = 0x1, + DW_LNCT_directory_index = 0x2, + DW_LNCT_timestamp = 0x3, + DW_LNCT_size = 0x4, + DW_LNCT_MD5 = 0x5, + DW_LNCT_lo_user = 0x2000, + DW_LNCT_hi_user = 0x3fff +}; + +enum dwarf_range_list_entry { + DW_RLE_end_of_list = 0x00, + DW_RLE_base_addressx = 0x01, + DW_RLE_startx_endx = 0x02, + DW_RLE_startx_length = 0x03, + DW_RLE_offset_pair = 0x04, + DW_RLE_base_address = 0x05, + DW_RLE_start_end = 0x06, + DW_RLE_start_length = 0x07 +}; + +enum dwarf_unit_type { + DW_UT_compile = 0x01, + DW_UT_type = 0x02, + DW_UT_partial = 0x03, + DW_UT_skeleton = 0x04, + DW_UT_split_compile = 0x05, + DW_UT_split_type = 0x06, + DW_UT_lo_user = 0x80, + DW_UT_hi_user = 0xff +}; + +#if !defined(HAVE_DECL_STRNLEN) || !HAVE_DECL_STRNLEN + +/* If strnlen is not declared, provide our own version. */ + +static size_t +xstrnlen (const char *s, size_t maxlen) +{ + size_t i; + + for (i = 0; i < maxlen; ++i) + if (s[i] == '\0') + break; + return i; +} + +#define strnlen xstrnlen + +#endif + +/* A buffer to read DWARF info. */ + +struct dwarf_buf +{ + /* Buffer name for error messages. */ + const char *name; + /* Start of the buffer. */ + const unsigned char *start; + /* Next byte to read. */ + const unsigned char *buf; + /* The number of bytes remaining. */ + size_t left; + /* Whether the data is big-endian. */ + int is_bigendian; + /* Error callback routine. */ + backtrace_error_callback error_callback; + /* Data for error_callback. */ + void *data; + /* Non-zero if we've reported an underflow error. */ + int reported_underflow; +}; + +/* A single attribute in a DWARF abbreviation. */ + +struct attr +{ + /* The attribute name. */ + enum dwarf_attribute name; + /* The attribute form. */ + enum dwarf_form form; + /* The attribute value, for DW_FORM_implicit_const. */ + int64_t val; +}; + +/* A single DWARF abbreviation. */ + +struct abbrev +{ + /* The abbrev code--the number used to refer to the abbrev. */ + uint64_t code; + /* The entry tag. */ + enum dwarf_tag tag; + /* Non-zero if this abbrev has child entries. */ + int has_children; + /* The number of attributes. */ + size_t num_attrs; + /* The attributes. */ + struct attr *attrs; +}; + +/* The DWARF abbreviations for a compilation unit. This structure + only exists while reading the compilation unit. Most DWARF readers + seem to a hash table to map abbrev ID's to abbrev entries. + However, we primarily care about GCC, and GCC simply issues ID's in + numerical order starting at 1. So we simply keep a sorted vector, + and try to just look up the code. */ + +struct abbrevs +{ + /* The number of abbrevs in the vector. */ + size_t num_abbrevs; + /* The abbrevs, sorted by the code field. */ + struct abbrev *abbrevs; +}; + +/* The different kinds of attribute values. */ + +enum attr_val_encoding +{ + /* No attribute value. */ + ATTR_VAL_NONE, + /* An address. */ + ATTR_VAL_ADDRESS, + /* An index into the .debug_addr section, whose value is relative to + * the DW_AT_addr_base attribute of the compilation unit. */ + ATTR_VAL_ADDRESS_INDEX, + /* A unsigned integer. */ + ATTR_VAL_UINT, + /* A sigd integer. */ + ATTR_VAL_SINT, + /* A string. */ + ATTR_VAL_STRING, + /* An index into the .debug_str_offsets section. */ + ATTR_VAL_STRING_INDEX, + /* An offset to other data in the containing unit. */ + ATTR_VAL_REF_UNIT, + /* An offset to other data within the .debug_info section. */ + ATTR_VAL_REF_INFO, + /* An offset to other data within the alt .debug_info section. */ + ATTR_VAL_REF_ALT_INFO, + /* An offset to data in some other section. */ + ATTR_VAL_REF_SECTION, + /* A type signature. */ + ATTR_VAL_REF_TYPE, + /* An index into the .debug_rnglists section. */ + ATTR_VAL_RNGLISTS_INDEX, + /* A block of data (not represented). */ + ATTR_VAL_BLOCK, + /* An expression (not represented). */ + ATTR_VAL_EXPR, +}; + +/* An attribute value. */ + +struct attr_val +{ + /* How the value is stored in the field u. */ + enum attr_val_encoding encoding; + union + { + /* ATTR_VAL_ADDRESS*, ATTR_VAL_UINT, ATTR_VAL_REF*. */ + uint64_t uint; + /* ATTR_VAL_SINT. */ + int64_t sint; + /* ATTR_VAL_STRING. */ + const char *string; + /* ATTR_VAL_BLOCK not stored. */ + } u; +}; + +/* The line number program header. */ + +struct line_header +{ + /* The version of the line number information. */ + int version; + /* Address size. */ + int addrsize; + /* The minimum instruction length. */ + unsigned int min_insn_len; + /* The maximum number of ops per instruction. */ + unsigned int max_ops_per_insn; + /* The line base for special opcodes. */ + int line_base; + /* The line range for special opcodes. */ + unsigned int line_range; + /* The opcode base--the first special opcode. */ + unsigned int opcode_base; + /* Opcode lengths, indexed by opcode - 1. */ + const unsigned char *opcode_lengths; + /* The number of directory entries. */ + size_t dirs_count; + /* The directory entries. */ + const char **dirs; + /* The number of filenames. */ + size_t filenames_count; + /* The filenames. */ + const char **filenames; +}; + +/* A format description from a line header. */ + +struct line_header_format +{ + int lnct; /* LNCT code. */ + enum dwarf_form form; /* Form of entry data. */ +}; + +/* Map a single PC value to a file/line. We will keep a vector of + these sorted by PC value. Each file/line will be correct from the + PC up to the PC of the next entry if there is one. We allocate one + extra entry at the end so that we can use bsearch. */ + +struct line +{ + /* PC. */ + uintptr_t pc; + /* File name. Many entries in the array are expected to point to + the same file name. */ + const char *filename; + /* Line number. */ + int lineno; + /* Index of the object in the original array read from the DWARF + section, before it has been sorted. The index makes it possible + to use Quicksort and maintain stability. */ + int idx; +}; + +/* A growable vector of line number information. This is used while + reading the line numbers. */ + +struct line_vector +{ + /* Memory. This is an array of struct line. */ + struct backtrace_vector vec; + /* Number of valid mappings. */ + size_t count; +}; + +/* A function described in the debug info. */ + +struct function +{ + /* The name of the function. */ + const char *name; + /* If this is an inlined function, the filename of the call + site. */ + const char *caller_filename; + /* If this is an inlined function, the line number of the call + site. */ + int caller_lineno; + /* Map PC ranges to inlined functions. */ + struct function_addrs *function_addrs; + size_t function_addrs_count; +}; + +/* An address range for a function. This maps a PC value to a + specific function. */ + +struct function_addrs +{ + /* Range is LOW <= PC < HIGH. */ + uint64_t low; + uint64_t high; + /* Function for this address range. */ + struct function *function; +}; + +/* A growable vector of function address ranges. */ + +struct function_vector +{ + /* Memory. This is an array of struct function_addrs. */ + struct backtrace_vector vec; + /* Number of address ranges present. */ + size_t count; +}; + +/* A DWARF compilation unit. This only holds the information we need + to map a PC to a file and line. */ + +struct unit +{ + /* The first entry for this compilation unit. */ + const unsigned char *unit_data; + /* The length of the data for this compilation unit. */ + size_t unit_data_len; + /* The offset of UNIT_DATA from the start of the information for + this compilation unit. */ + size_t unit_data_offset; + /* Offset of the start of the compilation unit from the start of the + .debug_info section. */ + size_t low_offset; + /* Offset of the end of the compilation unit from the start of the + .debug_info section. */ + size_t high_offset; + /* DWARF version. */ + int version; + /* Whether unit is DWARF64. */ + int is_dwarf64; + /* Address size. */ + int addrsize; + /* Offset into line number information. */ + off_t lineoff; + /* Offset of compilation unit in .debug_str_offsets. */ + uint64_t str_offsets_base; + /* Offset of compilation unit in .debug_addr. */ + uint64_t addr_base; + /* Offset of compilation unit in .debug_rnglists. */ + uint64_t rnglists_base; + /* Primary source file. */ + const char *filename; + /* Compilation command working directory. */ + const char *comp_dir; + /* Absolute file name, only set if needed. */ + const char *abs_filename; + /* The abbreviations for this unit. */ + struct abbrevs abbrevs; + + /* The fields above this point are read in during initialization and + may be accessed freely. The fields below this point are read in + as needed, and therefore require care, as different threads may + try to initialize them simultaneously. */ + + /* PC to line number mapping. This is NULL if the values have not + been read. This is (struct line *) -1 if there was an error + reading the values. */ + struct line *lines; + /* Number of entries in lines. */ + size_t lines_count; + /* PC ranges to function. */ + struct function_addrs *function_addrs; + size_t function_addrs_count; +}; + +/* An address range for a compilation unit. This maps a PC value to a + specific compilation unit. Note that we invert the representation + in DWARF: instead of listing the units and attaching a list of + ranges, we list the ranges and have each one point to the unit. + This lets us do a binary search to find the unit. */ + +struct unit_addrs +{ + /* Range is LOW <= PC < HIGH. */ + uint64_t low; + uint64_t high; + /* Compilation unit for this address range. */ + struct unit *u; +}; + +/* A growable vector of compilation unit address ranges. */ + +struct unit_addrs_vector +{ + /* Memory. This is an array of struct unit_addrs. */ + struct backtrace_vector vec; + /* Number of address ranges present. */ + size_t count; +}; + +/* A growable vector of compilation unit pointer. */ + +struct unit_vector +{ + struct backtrace_vector vec; + size_t count; +}; + +/* The information we need to map a PC to a file and line. */ + +struct dwarf_data +{ + /* The data for the next file we know about. */ + struct dwarf_data *next; + /* The data for .gnu_debugaltlink. */ + struct dwarf_data *altlink; + /* The base address for this file. */ + uintptr_t base_address; + /* A sorted list of address ranges. */ + struct unit_addrs *addrs; + /* Number of address ranges in list. */ + size_t addrs_count; + /* A sorted list of units. */ + struct unit **units; + /* Number of units in the list. */ + size_t units_count; + /* The unparsed DWARF debug data. */ + struct dwarf_sections dwarf_sections; + /* Whether the data is big-endian or not. */ + int is_bigendian; + /* A vector used for function addresses. We keep this here so that + we can grow the vector as we read more functions. */ + struct function_vector fvec; +}; + +/* Report an error for a DWARF buffer. */ + +static void +dwarf_buf_error (struct dwarf_buf *buf, const char *msg) +{ + char b[200]; + + snprintf (b, sizeof b, "%s in %s at %d", + msg, buf->name, (int) (buf->buf - buf->start)); + buf->error_callback (buf->data, b, 0); +} + +/* Require at least COUNT bytes in BUF. Return 1 if all is well, 0 on + error. */ + +static int +require (struct dwarf_buf *buf, size_t count) +{ + if (buf->left >= count) + return 1; + + if (!buf->reported_underflow) + { + dwarf_buf_error (buf, "DWARF underflow"); + buf->reported_underflow = 1; + } + + return 0; +} + +/* Advance COUNT bytes in BUF. Return 1 if all is well, 0 on + error. */ + +static int +advance (struct dwarf_buf *buf, size_t count) +{ + if (!require (buf, count)) + return 0; + buf->buf += count; + buf->left -= count; + return 1; +} + +/* Read one zero-terminated string from BUF and advance past the string. */ + +static const char * +read_string (struct dwarf_buf *buf) +{ + const char *p = (const char *)buf->buf; + size_t len = strnlen (p, buf->left); + + /* - If len == left, we ran out of buffer before finding the zero terminator. + Generate an error by advancing len + 1. + - If len < left, advance by len + 1 to skip past the zero terminator. */ + size_t count = len + 1; + + if (!advance (buf, count)) + return NULL; + + return p; +} + +/* Read one byte from BUF and advance 1 byte. */ + +static unsigned char +read_byte (struct dwarf_buf *buf) +{ + const unsigned char *p = buf->buf; + + if (!advance (buf, 1)) + return 0; + return p[0]; +} + +/* Read a signed char from BUF and advance 1 byte. */ + +static signed char +read_sbyte (struct dwarf_buf *buf) +{ + const unsigned char *p = buf->buf; + + if (!advance (buf, 1)) + return 0; + return (*p ^ 0x80) - 0x80; +} + +/* Read a uint16 from BUF and advance 2 bytes. */ + +static uint16_t +read_uint16 (struct dwarf_buf *buf) +{ + const unsigned char *p = buf->buf; + + if (!advance (buf, 2)) + return 0; + if (buf->is_bigendian) + return ((uint16_t) p[0] << 8) | (uint16_t) p[1]; + else + return ((uint16_t) p[1] << 8) | (uint16_t) p[0]; +} + +/* Read a 24 bit value from BUF and advance 3 bytes. */ + +static uint32_t +read_uint24 (struct dwarf_buf *buf) +{ + const unsigned char *p = buf->buf; + + if (!advance (buf, 3)) + return 0; + if (buf->is_bigendian) + return (((uint32_t) p[0] << 16) | ((uint32_t) p[1] << 8) + | (uint32_t) p[2]); + else + return (((uint32_t) p[2] << 16) | ((uint32_t) p[1] << 8) + | (uint32_t) p[0]); +} + +/* Read a uint32 from BUF and advance 4 bytes. */ + +static uint32_t +read_uint32 (struct dwarf_buf *buf) +{ + const unsigned char *p = buf->buf; + + if (!advance (buf, 4)) + return 0; + if (buf->is_bigendian) + return (((uint32_t) p[0] << 24) | ((uint32_t) p[1] << 16) + | ((uint32_t) p[2] << 8) | (uint32_t) p[3]); + else + return (((uint32_t) p[3] << 24) | ((uint32_t) p[2] << 16) + | ((uint32_t) p[1] << 8) | (uint32_t) p[0]); +} + +/* Read a uint64 from BUF and advance 8 bytes. */ + +static uint64_t +read_uint64 (struct dwarf_buf *buf) +{ + const unsigned char *p = buf->buf; + + if (!advance (buf, 8)) + return 0; + if (buf->is_bigendian) + return (((uint64_t) p[0] << 56) | ((uint64_t) p[1] << 48) + | ((uint64_t) p[2] << 40) | ((uint64_t) p[3] << 32) + | ((uint64_t) p[4] << 24) | ((uint64_t) p[5] << 16) + | ((uint64_t) p[6] << 8) | (uint64_t) p[7]); + else + return (((uint64_t) p[7] << 56) | ((uint64_t) p[6] << 48) + | ((uint64_t) p[5] << 40) | ((uint64_t) p[4] << 32) + | ((uint64_t) p[3] << 24) | ((uint64_t) p[2] << 16) + | ((uint64_t) p[1] << 8) | (uint64_t) p[0]); +} + +/* Read an offset from BUF and advance the appropriate number of + bytes. */ + +static uint64_t +read_offset (struct dwarf_buf *buf, int is_dwarf64) +{ + if (is_dwarf64) + return read_uint64 (buf); + else + return read_uint32 (buf); +} + +/* Read an address from BUF and advance the appropriate number of + bytes. */ + +static uint64_t +read_address (struct dwarf_buf *buf, int addrsize) +{ + switch (addrsize) + { + case 1: + return read_byte (buf); + case 2: + return read_uint16 (buf); + case 4: + return read_uint32 (buf); + case 8: + return read_uint64 (buf); + default: + dwarf_buf_error (buf, "unrecognized address size"); + return 0; + } +} + +/* Return whether a value is the highest possible address, given the + address size. */ + +static int +is_highest_address (uint64_t address, int addrsize) +{ + switch (addrsize) + { + case 1: + return address == (unsigned char) -1; + case 2: + return address == (uint16_t) -1; + case 4: + return address == (uint32_t) -1; + case 8: + return address == (uint64_t) -1; + default: + return 0; + } +} + +/* Read an unsigned LEB128 number. */ + +static uint64_t +read_uleb128 (struct dwarf_buf *buf) +{ + uint64_t ret; + unsigned int shift; + int overflow; + unsigned char b; + + ret = 0; + shift = 0; + overflow = 0; + do + { + const unsigned char *p; + + p = buf->buf; + if (!advance (buf, 1)) + return 0; + b = *p; + if (shift < 64) + ret |= ((uint64_t) (b & 0x7f)) << shift; + else if (!overflow) + { + dwarf_buf_error (buf, "LEB128 overflows uint64_t"); + overflow = 1; + } + shift += 7; + } + while ((b & 0x80) != 0); + + return ret; +} + +/* Read a signed LEB128 number. */ + +static int64_t +read_sleb128 (struct dwarf_buf *buf) +{ + uint64_t val; + unsigned int shift; + int overflow; + unsigned char b; + + val = 0; + shift = 0; + overflow = 0; + do + { + const unsigned char *p; + + p = buf->buf; + if (!advance (buf, 1)) + return 0; + b = *p; + if (shift < 64) + val |= ((uint64_t) (b & 0x7f)) << shift; + else if (!overflow) + { + dwarf_buf_error (buf, "signed LEB128 overflows uint64_t"); + overflow = 1; + } + shift += 7; + } + while ((b & 0x80) != 0); + + if ((b & 0x40) != 0 && shift < 64) + val |= ((uint64_t) -1) << shift; + + return (int64_t) val; +} + +/* Return the length of an LEB128 number. */ + +static size_t +leb128_len (const unsigned char *p) +{ + size_t ret; + + ret = 1; + while ((*p & 0x80) != 0) + { + ++p; + ++ret; + } + return ret; +} + +/* Read initial_length from BUF and advance the appropriate number of bytes. */ + +static uint64_t +read_initial_length (struct dwarf_buf *buf, int *is_dwarf64) +{ + uint64_t len; + + len = read_uint32 (buf); + if (len == 0xffffffff) + { + len = read_uint64 (buf); + *is_dwarf64 = 1; + } + else + *is_dwarf64 = 0; + + return len; +} + +/* Free an abbreviations structure. */ + +static void +free_abbrevs (struct backtrace_state *state, struct abbrevs *abbrevs, + backtrace_error_callback error_callback, void *data) +{ + size_t i; + + for (i = 0; i < abbrevs->num_abbrevs; ++i) + backtrace_free (state, abbrevs->abbrevs[i].attrs, + abbrevs->abbrevs[i].num_attrs * sizeof (struct attr), + error_callback, data); + backtrace_free (state, abbrevs->abbrevs, + abbrevs->num_abbrevs * sizeof (struct abbrev), + error_callback, data); + abbrevs->num_abbrevs = 0; + abbrevs->abbrevs = NULL; +} + +/* Read an attribute value. Returns 1 on success, 0 on failure. If + the value can be represented as a uint64_t, sets *VAL and sets + *IS_VALID to 1. We don't try to store the value of other attribute + forms, because we don't care about them. */ + +static int +read_attribute (enum dwarf_form form, uint64_t implicit_val, + struct dwarf_buf *buf, int is_dwarf64, int version, + int addrsize, const struct dwarf_sections *dwarf_sections, + struct dwarf_data *altlink, struct attr_val *val) +{ + /* Avoid warnings about val.u.FIELD may be used uninitialized if + this function is inlined. The warnings aren't valid but can + occur because the different fields are set and used + conditionally. */ + memset (val, 0, sizeof *val); + + switch (form) + { + case DW_FORM_addr: + val->encoding = ATTR_VAL_ADDRESS; + val->u.uint = read_address (buf, addrsize); + return 1; + case DW_FORM_block2: + val->encoding = ATTR_VAL_BLOCK; + return advance (buf, read_uint16 (buf)); + case DW_FORM_block4: + val->encoding = ATTR_VAL_BLOCK; + return advance (buf, read_uint32 (buf)); + case DW_FORM_data2: + val->encoding = ATTR_VAL_UINT; + val->u.uint = read_uint16 (buf); + return 1; + case DW_FORM_data4: + val->encoding = ATTR_VAL_UINT; + val->u.uint = read_uint32 (buf); + return 1; + case DW_FORM_data8: + val->encoding = ATTR_VAL_UINT; + val->u.uint = read_uint64 (buf); + return 1; + case DW_FORM_data16: + val->encoding = ATTR_VAL_BLOCK; + return advance (buf, 16); + case DW_FORM_string: + val->encoding = ATTR_VAL_STRING; + val->u.string = read_string (buf); + return val->u.string == NULL ? 0 : 1; + case DW_FORM_block: + val->encoding = ATTR_VAL_BLOCK; + return advance (buf, read_uleb128 (buf)); + case DW_FORM_block1: + val->encoding = ATTR_VAL_BLOCK; + return advance (buf, read_byte (buf)); + case DW_FORM_data1: + val->encoding = ATTR_VAL_UINT; + val->u.uint = read_byte (buf); + return 1; + case DW_FORM_flag: + val->encoding = ATTR_VAL_UINT; + val->u.uint = read_byte (buf); + return 1; + case DW_FORM_sdata: + val->encoding = ATTR_VAL_SINT; + val->u.sint = read_sleb128 (buf); + return 1; + case DW_FORM_strp: + { + uint64_t offset; + + offset = read_offset (buf, is_dwarf64); + if (offset >= dwarf_sections->size[DEBUG_STR]) + { + dwarf_buf_error (buf, "DW_FORM_strp out of range"); + return 0; + } + val->encoding = ATTR_VAL_STRING; + val->u.string = + (const char *) dwarf_sections->data[DEBUG_STR] + offset; + return 1; + } + case DW_FORM_line_strp: + { + uint64_t offset; + + offset = read_offset (buf, is_dwarf64); + if (offset >= dwarf_sections->size[DEBUG_LINE_STR]) + { + dwarf_buf_error (buf, "DW_FORM_line_strp out of range"); + return 0; + } + val->encoding = ATTR_VAL_STRING; + val->u.string = + (const char *) dwarf_sections->data[DEBUG_LINE_STR] + offset; + return 1; + } + case DW_FORM_udata: + val->encoding = ATTR_VAL_UINT; + val->u.uint = read_uleb128 (buf); + return 1; + case DW_FORM_ref_addr: + val->encoding = ATTR_VAL_REF_INFO; + if (version == 2) + val->u.uint = read_address (buf, addrsize); + else + val->u.uint = read_offset (buf, is_dwarf64); + return 1; + case DW_FORM_ref1: + val->encoding = ATTR_VAL_REF_UNIT; + val->u.uint = read_byte (buf); + return 1; + case DW_FORM_ref2: + val->encoding = ATTR_VAL_REF_UNIT; + val->u.uint = read_uint16 (buf); + return 1; + case DW_FORM_ref4: + val->encoding = ATTR_VAL_REF_UNIT; + val->u.uint = read_uint32 (buf); + return 1; + case DW_FORM_ref8: + val->encoding = ATTR_VAL_REF_UNIT; + val->u.uint = read_uint64 (buf); + return 1; + case DW_FORM_ref_udata: + val->encoding = ATTR_VAL_REF_UNIT; + val->u.uint = read_uleb128 (buf); + return 1; + case DW_FORM_indirect: + { + uint64_t form; + + form = read_uleb128 (buf); + if (form == DW_FORM_implicit_const) + { + dwarf_buf_error (buf, + "DW_FORM_indirect to DW_FORM_implicit_const"); + return 0; + } + return read_attribute ((enum dwarf_form) form, 0, buf, is_dwarf64, + version, addrsize, dwarf_sections, altlink, + val); + } + case DW_FORM_sec_offset: + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_offset (buf, is_dwarf64); + return 1; + case DW_FORM_exprloc: + val->encoding = ATTR_VAL_EXPR; + return advance (buf, read_uleb128 (buf)); + case DW_FORM_flag_present: + val->encoding = ATTR_VAL_UINT; + val->u.uint = 1; + return 1; + case DW_FORM_ref_sig8: + val->encoding = ATTR_VAL_REF_TYPE; + val->u.uint = read_uint64 (buf); + return 1; + case DW_FORM_strx: case DW_FORM_strx1: case DW_FORM_strx2: + case DW_FORM_strx3: case DW_FORM_strx4: + { + uint64_t offset; + + switch (form) + { + case DW_FORM_strx: + offset = read_uleb128 (buf); + break; + case DW_FORM_strx1: + offset = read_byte (buf); + break; + case DW_FORM_strx2: + offset = read_uint16 (buf); + break; + case DW_FORM_strx3: + offset = read_uint24 (buf); + break; + case DW_FORM_strx4: + offset = read_uint32 (buf); + break; + default: + /* This case can't happen. */ + return 0; + } + val->encoding = ATTR_VAL_STRING_INDEX; + val->u.uint = offset; + return 1; + } + case DW_FORM_addrx: case DW_FORM_addrx1: case DW_FORM_addrx2: + case DW_FORM_addrx3: case DW_FORM_addrx4: + { + uint64_t offset; + + switch (form) + { + case DW_FORM_addrx: + offset = read_uleb128 (buf); + break; + case DW_FORM_addrx1: + offset = read_byte (buf); + break; + case DW_FORM_addrx2: + offset = read_uint16 (buf); + break; + case DW_FORM_addrx3: + offset = read_uint24 (buf); + break; + case DW_FORM_addrx4: + offset = read_uint32 (buf); + break; + default: + /* This case can't happen. */ + return 0; + } + val->encoding = ATTR_VAL_ADDRESS_INDEX; + val->u.uint = offset; + return 1; + } + case DW_FORM_ref_sup4: + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_uint32 (buf); + return 1; + case DW_FORM_ref_sup8: + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_uint64 (buf); + return 1; + case DW_FORM_implicit_const: + val->encoding = ATTR_VAL_UINT; + val->u.uint = implicit_val; + return 1; + case DW_FORM_loclistx: + /* We don't distinguish this from DW_FORM_sec_offset. It + * shouldn't matter since we don't care about loclists. */ + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_uleb128 (buf); + return 1; + case DW_FORM_rnglistx: + val->encoding = ATTR_VAL_RNGLISTS_INDEX; + val->u.uint = read_uleb128 (buf); + return 1; + case DW_FORM_GNU_addr_index: + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_uleb128 (buf); + return 1; + case DW_FORM_GNU_str_index: + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_uleb128 (buf); + return 1; + case DW_FORM_GNU_ref_alt: + val->u.uint = read_offset (buf, is_dwarf64); + if (altlink == NULL) + { + val->encoding = ATTR_VAL_NONE; + return 1; + } + val->encoding = ATTR_VAL_REF_ALT_INFO; + return 1; + case DW_FORM_strp_sup: case DW_FORM_GNU_strp_alt: + { + uint64_t offset; + + offset = read_offset (buf, is_dwarf64); + if (altlink == NULL) + { + val->encoding = ATTR_VAL_NONE; + return 1; + } + if (offset >= altlink->dwarf_sections.size[DEBUG_STR]) + { + dwarf_buf_error (buf, "DW_FORM_strp_sup out of range"); + return 0; + } + val->encoding = ATTR_VAL_STRING; + val->u.string = + (const char *) altlink->dwarf_sections.data[DEBUG_STR] + offset; + return 1; + } + default: + dwarf_buf_error (buf, "unrecognized DWARF form"); + return 0; + } +} + +/* If we can determine the value of a string attribute, set *STRING to + point to the string. Return 1 on success, 0 on error. If we don't + know the value, we consider that a success, and we don't change + *STRING. An error is only reported for some sort of out of range + offset. */ + +static int +resolve_string (const struct dwarf_sections *dwarf_sections, int is_dwarf64, + int is_bigendian, uint64_t str_offsets_base, + const struct attr_val *val, + backtrace_error_callback error_callback, void *data, + const char **string) +{ + switch (val->encoding) + { + case ATTR_VAL_STRING: + *string = val->u.string; + return 1; + + case ATTR_VAL_STRING_INDEX: + { + uint64_t offset; + struct dwarf_buf offset_buf; + + offset = val->u.uint * (is_dwarf64 ? 8 : 4) + str_offsets_base; + if (offset + (is_dwarf64 ? 8 : 4) + > dwarf_sections->size[DEBUG_STR_OFFSETS]) + { + error_callback (data, "DW_FORM_strx value out of range", 0); + return 0; + } + + offset_buf.name = ".debug_str_offsets"; + offset_buf.start = dwarf_sections->data[DEBUG_STR_OFFSETS]; + offset_buf.buf = dwarf_sections->data[DEBUG_STR_OFFSETS] + offset; + offset_buf.left = dwarf_sections->size[DEBUG_STR_OFFSETS] - offset; + offset_buf.is_bigendian = is_bigendian; + offset_buf.error_callback = error_callback; + offset_buf.data = data; + offset_buf.reported_underflow = 0; + + offset = read_offset (&offset_buf, is_dwarf64); + if (offset >= dwarf_sections->size[DEBUG_STR]) + { + dwarf_buf_error (&offset_buf, "DW_FORM_strx offset out of range"); + return 0; + } + *string = (const char *) dwarf_sections->data[DEBUG_STR] + offset; + return 1; + } + + default: + return 1; + } +} + +/* Set *ADDRESS to the real address for a ATTR_VAL_ADDRESS_INDEX. + Return 1 on success, 0 on error. */ + +static int +resolve_addr_index (const struct dwarf_sections *dwarf_sections, + uint64_t addr_base, int addrsize, int is_bigendian, + uint64_t addr_index, + backtrace_error_callback error_callback, void *data, + uint64_t *address) +{ + uint64_t offset; + struct dwarf_buf addr_buf; + + offset = addr_index * addrsize + addr_base; + if (offset + addrsize > dwarf_sections->size[DEBUG_ADDR]) + { + error_callback (data, "DW_FORM_addrx value out of range", 0); + return 0; + } + + addr_buf.name = ".debug_addr"; + addr_buf.start = dwarf_sections->data[DEBUG_ADDR]; + addr_buf.buf = dwarf_sections->data[DEBUG_ADDR] + offset; + addr_buf.left = dwarf_sections->size[DEBUG_ADDR] - offset; + addr_buf.is_bigendian = is_bigendian; + addr_buf.error_callback = error_callback; + addr_buf.data = data; + addr_buf.reported_underflow = 0; + + *address = read_address (&addr_buf, addrsize); + return 1; +} + +/* Compare a unit offset against a unit for bsearch. */ + +static int +units_search (const void *vkey, const void *ventry) +{ + const size_t *key = (const size_t *) vkey; + const struct unit *entry = *((const struct unit *const *) ventry); + size_t offset; + + offset = *key; + if (offset < entry->low_offset) + return -1; + else if (offset >= entry->high_offset) + return 1; + else + return 0; +} + +/* Find a unit in PU containing OFFSET. */ + +static struct unit * +find_unit (struct unit **pu, size_t units_count, size_t offset) +{ + struct unit **u; + u = (struct unit**)bsearch (&offset, pu, units_count, sizeof (struct unit *), units_search); + return u == NULL ? NULL : *u; +} + +/* Compare function_addrs for qsort. When ranges are nested, make the + smallest one sort last. */ + +static int +function_addrs_compare (const void *v1, const void *v2) +{ + const struct function_addrs *a1 = (const struct function_addrs *) v1; + const struct function_addrs *a2 = (const struct function_addrs *) v2; + + if (a1->low < a2->low) + return -1; + if (a1->low > a2->low) + return 1; + if (a1->high < a2->high) + return 1; + if (a1->high > a2->high) + return -1; + return strcmp (a1->function->name, a2->function->name); +} + +/* Compare a PC against a function_addrs for bsearch. We always + allocate an entra entry at the end of the vector, so that this + routine can safely look at the next entry. Note that if there are + multiple ranges containing PC, which one will be returned is + unpredictable. We compensate for that in dwarf_fileline. */ + +static int +function_addrs_search (const void *vkey, const void *ventry) +{ + const uintptr_t *key = (const uintptr_t *) vkey; + const struct function_addrs *entry = (const struct function_addrs *) ventry; + uintptr_t pc; + + pc = *key; + if (pc < entry->low) + return -1; + else if (pc > (entry + 1)->low) + return 1; + else + return 0; +} + +/* Add a new compilation unit address range to a vector. This is + called via add_ranges. Returns 1 on success, 0 on failure. */ + +static int +add_unit_addr (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, void *data, + void *pvec) +{ + struct unit *u = (struct unit *) rdata; + struct unit_addrs_vector *vec = (struct unit_addrs_vector *) pvec; + struct unit_addrs *p; + + /* Try to merge with the last entry. */ + if (vec->count > 0) + { + p = (struct unit_addrs *) vec->vec.base + (vec->count - 1); + if ((lowpc == p->high || lowpc == p->high + 1) + && u == p->u) + { + if (highpc > p->high) + p->high = highpc; + return 1; + } + } + + p = ((struct unit_addrs *) + backtrace_vector_grow (state, sizeof (struct unit_addrs), + error_callback, data, &vec->vec)); + if (p == NULL) + return 0; + + p->low = lowpc; + p->high = highpc; + p->u = u; + + ++vec->count; + + return 1; +} + +/* Compare unit_addrs for qsort. When ranges are nested, make the + smallest one sort last. */ + +static int +unit_addrs_compare (const void *v1, const void *v2) +{ + const struct unit_addrs *a1 = (const struct unit_addrs *) v1; + const struct unit_addrs *a2 = (const struct unit_addrs *) v2; + + if (a1->low < a2->low) + return -1; + if (a1->low > a2->low) + return 1; + if (a1->high < a2->high) + return 1; + if (a1->high > a2->high) + return -1; + if (a1->u->lineoff < a2->u->lineoff) + return -1; + if (a1->u->lineoff > a2->u->lineoff) + return 1; + return 0; +} + +/* Compare a PC against a unit_addrs for bsearch. We always allocate + an entry entry at the end of the vector, so that this routine can + safely look at the next entry. Note that if there are multiple + ranges containing PC, which one will be returned is unpredictable. + We compensate for that in dwarf_fileline. */ + +static int +unit_addrs_search (const void *vkey, const void *ventry) +{ + const uintptr_t *key = (const uintptr_t *) vkey; + const struct unit_addrs *entry = (const struct unit_addrs *) ventry; + uintptr_t pc; + + pc = *key; + if (pc < entry->low) + return -1; + else if (pc > (entry + 1)->low) + return 1; + else + return 0; +} + +/* Sort the line vector by PC. We want a stable sort here to maintain + the order of lines for the same PC values. Since the sequence is + being sorted in place, their addresses cannot be relied on to + maintain stability. That is the purpose of the index member. */ + +static int +line_compare (const void *v1, const void *v2) +{ + const struct line *ln1 = (const struct line *) v1; + const struct line *ln2 = (const struct line *) v2; + + if (ln1->pc < ln2->pc) + return -1; + else if (ln1->pc > ln2->pc) + return 1; + else if (ln1->idx < ln2->idx) + return -1; + else if (ln1->idx > ln2->idx) + return 1; + else + return 0; +} + +/* Find a PC in a line vector. We always allocate an extra entry at + the end of the lines vector, so that this routine can safely look + at the next entry. Note that when there are multiple mappings for + the same PC value, this will return the last one. */ + +static int +line_search (const void *vkey, const void *ventry) +{ + const uintptr_t *key = (const uintptr_t *) vkey; + const struct line *entry = (const struct line *) ventry; + uintptr_t pc; + + pc = *key; + if (pc < entry->pc) + return -1; + else if (pc >= (entry + 1)->pc) + return 1; + else + return 0; +} + +/* Sort the abbrevs by the abbrev code. This function is passed to + both qsort and bsearch. */ + +static int +abbrev_compare (const void *v1, const void *v2) +{ + const struct abbrev *a1 = (const struct abbrev *) v1; + const struct abbrev *a2 = (const struct abbrev *) v2; + + if (a1->code < a2->code) + return -1; + else if (a1->code > a2->code) + return 1; + else + { + /* This really shouldn't happen. It means there are two + different abbrevs with the same code, and that means we don't + know which one lookup_abbrev should return. */ + return 0; + } +} + +/* Read the abbreviation table for a compilation unit. Returns 1 on + success, 0 on failure. */ + +static int +read_abbrevs (struct backtrace_state *state, uint64_t abbrev_offset, + const unsigned char *dwarf_abbrev, size_t dwarf_abbrev_size, + int is_bigendian, backtrace_error_callback error_callback, + void *data, struct abbrevs *abbrevs) +{ + struct dwarf_buf abbrev_buf; + struct dwarf_buf count_buf; + size_t num_abbrevs; + + abbrevs->num_abbrevs = 0; + abbrevs->abbrevs = NULL; + + if (abbrev_offset >= dwarf_abbrev_size) + { + error_callback (data, "abbrev offset out of range", 0); + return 0; + } + + abbrev_buf.name = ".debug_abbrev"; + abbrev_buf.start = dwarf_abbrev; + abbrev_buf.buf = dwarf_abbrev + abbrev_offset; + abbrev_buf.left = dwarf_abbrev_size - abbrev_offset; + abbrev_buf.is_bigendian = is_bigendian; + abbrev_buf.error_callback = error_callback; + abbrev_buf.data = data; + abbrev_buf.reported_underflow = 0; + + /* Count the number of abbrevs in this list. */ + + count_buf = abbrev_buf; + num_abbrevs = 0; + while (read_uleb128 (&count_buf) != 0) + { + if (count_buf.reported_underflow) + return 0; + ++num_abbrevs; + // Skip tag. + read_uleb128 (&count_buf); + // Skip has_children. + read_byte (&count_buf); + // Skip attributes. + while (read_uleb128 (&count_buf) != 0) + { + uint64_t form; + + form = read_uleb128 (&count_buf); + if ((enum dwarf_form) form == DW_FORM_implicit_const) + read_sleb128 (&count_buf); + } + // Skip form of last attribute. + read_uleb128 (&count_buf); + } + + if (count_buf.reported_underflow) + return 0; + + if (num_abbrevs == 0) + return 1; + + abbrevs->abbrevs = ((struct abbrev *) + backtrace_alloc (state, + num_abbrevs * sizeof (struct abbrev), + error_callback, data)); + if (abbrevs->abbrevs == NULL) + return 0; + abbrevs->num_abbrevs = num_abbrevs; + memset (abbrevs->abbrevs, 0, num_abbrevs * sizeof (struct abbrev)); + + num_abbrevs = 0; + while (1) + { + uint64_t code; + struct abbrev a; + size_t num_attrs; + struct attr *attrs; + + if (abbrev_buf.reported_underflow) + goto fail; + + code = read_uleb128 (&abbrev_buf); + if (code == 0) + break; + + a.code = code; + a.tag = (enum dwarf_tag) read_uleb128 (&abbrev_buf); + a.has_children = read_byte (&abbrev_buf); + + count_buf = abbrev_buf; + num_attrs = 0; + while (read_uleb128 (&count_buf) != 0) + { + uint64_t form; + + ++num_attrs; + form = read_uleb128 (&count_buf); + if ((enum dwarf_form) form == DW_FORM_implicit_const) + read_sleb128 (&count_buf); + } + + if (num_attrs == 0) + { + attrs = NULL; + read_uleb128 (&abbrev_buf); + read_uleb128 (&abbrev_buf); + } + else + { + attrs = ((struct attr *) + backtrace_alloc (state, num_attrs * sizeof *attrs, + error_callback, data)); + if (attrs == NULL) + goto fail; + num_attrs = 0; + while (1) + { + uint64_t name; + uint64_t form; + + name = read_uleb128 (&abbrev_buf); + form = read_uleb128 (&abbrev_buf); + if (name == 0) + break; + attrs[num_attrs].name = (enum dwarf_attribute) name; + attrs[num_attrs].form = (enum dwarf_form) form; + if ((enum dwarf_form) form == DW_FORM_implicit_const) + attrs[num_attrs].val = read_sleb128 (&abbrev_buf); + else + attrs[num_attrs].val = 0; + ++num_attrs; + } + } + + a.num_attrs = num_attrs; + a.attrs = attrs; + + abbrevs->abbrevs[num_abbrevs] = a; + ++num_abbrevs; + } + + backtrace_qsort (abbrevs->abbrevs, abbrevs->num_abbrevs, + sizeof (struct abbrev), abbrev_compare); + + return 1; + + fail: + free_abbrevs (state, abbrevs, error_callback, data); + return 0; +} + +/* Return the abbrev information for an abbrev code. */ + +static const struct abbrev * +lookup_abbrev (struct abbrevs *abbrevs, uint64_t code, + backtrace_error_callback error_callback, void *data) +{ + struct abbrev key; + void *p; + + /* With GCC, where abbrevs are simply numbered in order, we should + be able to just look up the entry. */ + if (code - 1 < abbrevs->num_abbrevs + && abbrevs->abbrevs[code - 1].code == code) + return &abbrevs->abbrevs[code - 1]; + + /* Otherwise we have to search. */ + memset (&key, 0, sizeof key); + key.code = code; + p = bsearch (&key, abbrevs->abbrevs, abbrevs->num_abbrevs, + sizeof (struct abbrev), abbrev_compare); + if (p == NULL) + { + error_callback (data, "invalid abbreviation code", 0); + return NULL; + } + return (const struct abbrev *) p; +} + +/* This struct is used to gather address range information while + reading attributes. We use this while building a mapping from + address ranges to compilation units and then again while mapping + from address ranges to function entries. Normally either + lowpc/highpc is set or ranges is set. */ + +struct pcrange { + uint64_t lowpc; /* The low PC value. */ + int have_lowpc; /* Whether a low PC value was found. */ + int lowpc_is_addr_index; /* Whether lowpc is in .debug_addr. */ + uint64_t highpc; /* The high PC value. */ + int have_highpc; /* Whether a high PC value was found. */ + int highpc_is_relative; /* Whether highpc is relative to lowpc. */ + int highpc_is_addr_index; /* Whether highpc is in .debug_addr. */ + uint64_t ranges; /* Offset in ranges section. */ + int have_ranges; /* Whether ranges is valid. */ + int ranges_is_index; /* Whether ranges is DW_FORM_rnglistx. */ +}; + +/* Update PCRANGE from an attribute value. */ + +static void +update_pcrange (const struct attr* attr, const struct attr_val* val, + struct pcrange *pcrange) +{ + switch (attr->name) + { + case DW_AT_low_pc: + if (val->encoding == ATTR_VAL_ADDRESS) + { + pcrange->lowpc = val->u.uint; + pcrange->have_lowpc = 1; + } + else if (val->encoding == ATTR_VAL_ADDRESS_INDEX) + { + pcrange->lowpc = val->u.uint; + pcrange->have_lowpc = 1; + pcrange->lowpc_is_addr_index = 1; + } + break; + + case DW_AT_high_pc: + if (val->encoding == ATTR_VAL_ADDRESS) + { + pcrange->highpc = val->u.uint; + pcrange->have_highpc = 1; + } + else if (val->encoding == ATTR_VAL_UINT) + { + pcrange->highpc = val->u.uint; + pcrange->have_highpc = 1; + pcrange->highpc_is_relative = 1; + } + else if (val->encoding == ATTR_VAL_ADDRESS_INDEX) + { + pcrange->highpc = val->u.uint; + pcrange->have_highpc = 1; + pcrange->highpc_is_addr_index = 1; + } + break; + + case DW_AT_ranges: + if (val->encoding == ATTR_VAL_UINT + || val->encoding == ATTR_VAL_REF_SECTION) + { + pcrange->ranges = val->u.uint; + pcrange->have_ranges = 1; + } + else if (val->encoding == ATTR_VAL_RNGLISTS_INDEX) + { + pcrange->ranges = val->u.uint; + pcrange->have_ranges = 1; + pcrange->ranges_is_index = 1; + } + break; + + default: + break; + } +} + +/* Call ADD_RANGE for a low/high PC pair. Returns 1 on success, 0 on + error. */ + +static int +add_low_high_range (struct backtrace_state *state, + const struct dwarf_sections *dwarf_sections, + uintptr_t base_address, int is_bigendian, + struct unit *u, const struct pcrange *pcrange, + int (*add_range) (struct backtrace_state *state, + void *rdata, uint64_t lowpc, + uint64_t highpc, + backtrace_error_callback error_callback, + void *data, void *vec), + void *rdata, + backtrace_error_callback error_callback, void *data, + void *vec) +{ + uint64_t lowpc; + uint64_t highpc; + + lowpc = pcrange->lowpc; + if (pcrange->lowpc_is_addr_index) + { + if (!resolve_addr_index (dwarf_sections, u->addr_base, u->addrsize, + is_bigendian, lowpc, error_callback, data, + &lowpc)) + return 0; + } + + highpc = pcrange->highpc; + if (pcrange->highpc_is_addr_index) + { + if (!resolve_addr_index (dwarf_sections, u->addr_base, u->addrsize, + is_bigendian, highpc, error_callback, data, + &highpc)) + return 0; + } + if (pcrange->highpc_is_relative) + highpc += lowpc; + + /* Add in the base address of the module when recording PC values, + so that we can look up the PC directly. */ + lowpc += base_address; + highpc += base_address; + + return add_range (state, rdata, lowpc, highpc, error_callback, data, vec); +} + +/* Call ADD_RANGE for each range read from .debug_ranges, as used in + DWARF versions 2 through 4. */ + +static int +add_ranges_from_ranges ( + struct backtrace_state *state, + const struct dwarf_sections *dwarf_sections, + uintptr_t base_address, int is_bigendian, + struct unit *u, uint64_t base, + const struct pcrange *pcrange, + int (*add_range) (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, void *data, + void *vec), + void *rdata, + backtrace_error_callback error_callback, void *data, + void *vec) +{ + struct dwarf_buf ranges_buf; + + if (pcrange->ranges >= dwarf_sections->size[DEBUG_RANGES]) + { + error_callback (data, "ranges offset out of range", 0); + return 0; + } + + ranges_buf.name = ".debug_ranges"; + ranges_buf.start = dwarf_sections->data[DEBUG_RANGES]; + ranges_buf.buf = dwarf_sections->data[DEBUG_RANGES] + pcrange->ranges; + ranges_buf.left = dwarf_sections->size[DEBUG_RANGES] - pcrange->ranges; + ranges_buf.is_bigendian = is_bigendian; + ranges_buf.error_callback = error_callback; + ranges_buf.data = data; + ranges_buf.reported_underflow = 0; + + while (1) + { + uint64_t low; + uint64_t high; + + if (ranges_buf.reported_underflow) + return 0; + + low = read_address (&ranges_buf, u->addrsize); + high = read_address (&ranges_buf, u->addrsize); + + if (low == 0 && high == 0) + break; + + if (is_highest_address (low, u->addrsize)) + base = high; + else + { + if (!add_range (state, rdata, + low + base + base_address, + high + base + base_address, + error_callback, data, vec)) + return 0; + } + } + + if (ranges_buf.reported_underflow) + return 0; + + return 1; +} + +/* Call ADD_RANGE for each range read from .debug_rnglists, as used in + DWARF version 5. */ + +static int +add_ranges_from_rnglists ( + struct backtrace_state *state, + const struct dwarf_sections *dwarf_sections, + uintptr_t base_address, int is_bigendian, + struct unit *u, uint64_t base, + const struct pcrange *pcrange, + int (*add_range) (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, void *data, + void *vec), + void *rdata, + backtrace_error_callback error_callback, void *data, + void *vec) +{ + uint64_t offset; + struct dwarf_buf rnglists_buf; + + if (!pcrange->ranges_is_index) + offset = pcrange->ranges; + else + offset = u->rnglists_base + pcrange->ranges * (u->is_dwarf64 ? 8 : 4); + if (offset >= dwarf_sections->size[DEBUG_RNGLISTS]) + { + error_callback (data, "rnglists offset out of range", 0); + return 0; + } + + rnglists_buf.name = ".debug_rnglists"; + rnglists_buf.start = dwarf_sections->data[DEBUG_RNGLISTS]; + rnglists_buf.buf = dwarf_sections->data[DEBUG_RNGLISTS] + offset; + rnglists_buf.left = dwarf_sections->size[DEBUG_RNGLISTS] - offset; + rnglists_buf.is_bigendian = is_bigendian; + rnglists_buf.error_callback = error_callback; + rnglists_buf.data = data; + rnglists_buf.reported_underflow = 0; + + if (pcrange->ranges_is_index) + { + offset = read_offset (&rnglists_buf, u->is_dwarf64); + offset += u->rnglists_base; + if (offset >= dwarf_sections->size[DEBUG_RNGLISTS]) + { + error_callback (data, "rnglists index offset out of range", 0); + return 0; + } + rnglists_buf.buf = dwarf_sections->data[DEBUG_RNGLISTS] + offset; + rnglists_buf.left = dwarf_sections->size[DEBUG_RNGLISTS] - offset; + } + + while (1) + { + unsigned char rle; + + rle = read_byte (&rnglists_buf); + if (rle == DW_RLE_end_of_list) + break; + switch (rle) + { + case DW_RLE_base_addressx: + { + uint64_t index; + + index = read_uleb128 (&rnglists_buf); + if (!resolve_addr_index (dwarf_sections, u->addr_base, + u->addrsize, is_bigendian, index, + error_callback, data, &base)) + return 0; + } + break; + + case DW_RLE_startx_endx: + { + uint64_t index; + uint64_t low; + uint64_t high; + + index = read_uleb128 (&rnglists_buf); + if (!resolve_addr_index (dwarf_sections, u->addr_base, + u->addrsize, is_bigendian, index, + error_callback, data, &low)) + return 0; + index = read_uleb128 (&rnglists_buf); + if (!resolve_addr_index (dwarf_sections, u->addr_base, + u->addrsize, is_bigendian, index, + error_callback, data, &high)) + return 0; + if (!add_range (state, rdata, low + base_address, + high + base_address, error_callback, data, + vec)) + return 0; + } + break; + + case DW_RLE_startx_length: + { + uint64_t index; + uint64_t low; + uint64_t length; + + index = read_uleb128 (&rnglists_buf); + if (!resolve_addr_index (dwarf_sections, u->addr_base, + u->addrsize, is_bigendian, index, + error_callback, data, &low)) + return 0; + length = read_uleb128 (&rnglists_buf); + low += base_address; + if (!add_range (state, rdata, low, low + length, + error_callback, data, vec)) + return 0; + } + break; + + case DW_RLE_offset_pair: + { + uint64_t low; + uint64_t high; + + low = read_uleb128 (&rnglists_buf); + high = read_uleb128 (&rnglists_buf); + if (!add_range (state, rdata, low + base + base_address, + high + base + base_address, + error_callback, data, vec)) + return 0; + } + break; + + case DW_RLE_base_address: + base = read_address (&rnglists_buf, u->addrsize); + break; + + case DW_RLE_start_end: + { + uint64_t low; + uint64_t high; + + low = read_address (&rnglists_buf, u->addrsize); + high = read_address (&rnglists_buf, u->addrsize); + if (!add_range (state, rdata, low + base_address, + high + base_address, error_callback, data, + vec)) + return 0; + } + break; + + case DW_RLE_start_length: + { + uint64_t low; + uint64_t length; + + low = read_address (&rnglists_buf, u->addrsize); + length = read_uleb128 (&rnglists_buf); + low += base_address; + if (!add_range (state, rdata, low, low + length, + error_callback, data, vec)) + return 0; + } + break; + + default: + dwarf_buf_error (&rnglists_buf, "unrecognized DW_RLE value"); + return 0; + } + } + + if (rnglists_buf.reported_underflow) + return 0; + + return 1; +} + +/* Call ADD_RANGE for each lowpc/highpc pair in PCRANGE. RDATA is + passed to ADD_RANGE, and is either a struct unit * or a struct + function *. VEC is the vector we are adding ranges to, and is + either a struct unit_addrs_vector * or a struct function_vector *. + Returns 1 on success, 0 on error. */ + +static int +add_ranges (struct backtrace_state *state, + const struct dwarf_sections *dwarf_sections, + uintptr_t base_address, int is_bigendian, + struct unit *u, uint64_t base, const struct pcrange *pcrange, + int (*add_range) (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, + void *data, void *vec), + void *rdata, + backtrace_error_callback error_callback, void *data, + void *vec) +{ + if (pcrange->have_lowpc && pcrange->have_highpc) + return add_low_high_range (state, dwarf_sections, base_address, + is_bigendian, u, pcrange, add_range, rdata, + error_callback, data, vec); + + if (!pcrange->have_ranges) + { + /* Did not find any address ranges to add. */ + return 1; + } + + if (u->version < 5) + return add_ranges_from_ranges (state, dwarf_sections, base_address, + is_bigendian, u, base, pcrange, add_range, + rdata, error_callback, data, vec); + else + return add_ranges_from_rnglists (state, dwarf_sections, base_address, + is_bigendian, u, base, pcrange, add_range, + rdata, error_callback, data, vec); +} + +/* Find the address range covered by a compilation unit, reading from + UNIT_BUF and adding values to U. Returns 1 if all data could be + read, 0 if there is some error. */ + +static int +find_address_ranges (struct backtrace_state *state, uintptr_t base_address, + struct dwarf_buf *unit_buf, + const struct dwarf_sections *dwarf_sections, + int is_bigendian, struct dwarf_data *altlink, + backtrace_error_callback error_callback, void *data, + struct unit *u, struct unit_addrs_vector *addrs, + enum dwarf_tag *unit_tag) +{ + while (unit_buf->left > 0) + { + uint64_t code; + const struct abbrev *abbrev; + struct pcrange pcrange; + struct attr_val name_val; + int have_name_val; + struct attr_val comp_dir_val; + int have_comp_dir_val; + size_t i; + + code = read_uleb128 (unit_buf); + if (code == 0) + return 1; + + abbrev = lookup_abbrev (&u->abbrevs, code, error_callback, data); + if (abbrev == NULL) + return 0; + + if (unit_tag != NULL) + *unit_tag = abbrev->tag; + + memset (&pcrange, 0, sizeof pcrange); + memset (&name_val, 0, sizeof name_val); + have_name_val = 0; + memset (&comp_dir_val, 0, sizeof comp_dir_val); + have_comp_dir_val = 0; + for (i = 0; i < abbrev->num_attrs; ++i) + { + struct attr_val val; + + if (!read_attribute (abbrev->attrs[i].form, abbrev->attrs[i].val, + unit_buf, u->is_dwarf64, u->version, + u->addrsize, dwarf_sections, altlink, &val)) + return 0; + + switch (abbrev->attrs[i].name) + { + case DW_AT_low_pc: case DW_AT_high_pc: case DW_AT_ranges: + update_pcrange (&abbrev->attrs[i], &val, &pcrange); + break; + + case DW_AT_stmt_list: + if (abbrev->tag == DW_TAG_compile_unit + && (val.encoding == ATTR_VAL_UINT + || val.encoding == ATTR_VAL_REF_SECTION)) + u->lineoff = val.u.uint; + break; + + case DW_AT_name: + if (abbrev->tag == DW_TAG_compile_unit) + { + name_val = val; + have_name_val = 1; + } + break; + + case DW_AT_comp_dir: + if (abbrev->tag == DW_TAG_compile_unit) + { + comp_dir_val = val; + have_comp_dir_val = 1; + } + break; + + case DW_AT_str_offsets_base: + if (abbrev->tag == DW_TAG_compile_unit + && val.encoding == ATTR_VAL_REF_SECTION) + u->str_offsets_base = val.u.uint; + break; + + case DW_AT_addr_base: + if (abbrev->tag == DW_TAG_compile_unit + && val.encoding == ATTR_VAL_REF_SECTION) + u->addr_base = val.u.uint; + break; + + case DW_AT_rnglists_base: + if (abbrev->tag == DW_TAG_compile_unit + && val.encoding == ATTR_VAL_REF_SECTION) + u->rnglists_base = val.u.uint; + break; + + default: + break; + } + } + + // Resolve strings after we're sure that we have seen + // DW_AT_str_offsets_base. + if (have_name_val) + { + if (!resolve_string (dwarf_sections, u->is_dwarf64, is_bigendian, + u->str_offsets_base, &name_val, + error_callback, data, &u->filename)) + return 0; + } + if (have_comp_dir_val) + { + if (!resolve_string (dwarf_sections, u->is_dwarf64, is_bigendian, + u->str_offsets_base, &comp_dir_val, + error_callback, data, &u->comp_dir)) + return 0; + } + + if (abbrev->tag == DW_TAG_compile_unit + || abbrev->tag == DW_TAG_subprogram) + { + if (!add_ranges (state, dwarf_sections, base_address, + is_bigendian, u, pcrange.lowpc, &pcrange, + add_unit_addr, (void *) u, error_callback, data, + (void *) addrs)) + return 0; + + /* If we found the PC range in the DW_TAG_compile_unit, we + can stop now. */ + if (abbrev->tag == DW_TAG_compile_unit + && (pcrange.have_ranges + || (pcrange.have_lowpc && pcrange.have_highpc))) + return 1; + } + + if (abbrev->has_children) + { + if (!find_address_ranges (state, base_address, unit_buf, + dwarf_sections, is_bigendian, altlink, + error_callback, data, u, addrs, NULL)) + return 0; + } + } + + return 1; +} + +/* Build a mapping from address ranges to the compilation units where + the line number information for that range can be found. Returns 1 + on success, 0 on failure. */ + +static int +build_address_map (struct backtrace_state *state, uintptr_t base_address, + const struct dwarf_sections *dwarf_sections, + int is_bigendian, struct dwarf_data *altlink, + backtrace_error_callback error_callback, void *data, + struct unit_addrs_vector *addrs, + struct unit_vector *unit_vec) +{ + struct dwarf_buf info; + struct backtrace_vector units; + size_t units_count; + size_t i; + struct unit **pu; + size_t unit_offset = 0; + struct unit_addrs *pa; + + memset (&addrs->vec, 0, sizeof addrs->vec); + memset (&unit_vec->vec, 0, sizeof unit_vec->vec); + addrs->count = 0; + unit_vec->count = 0; + + /* Read through the .debug_info section. FIXME: Should we use the + .debug_aranges section? gdb and addr2line don't use it, but I'm + not sure why. */ + + info.name = ".debug_info"; + info.start = dwarf_sections->data[DEBUG_INFO]; + info.buf = info.start; + info.left = dwarf_sections->size[DEBUG_INFO]; + info.is_bigendian = is_bigendian; + info.error_callback = error_callback; + info.data = data; + info.reported_underflow = 0; + + memset (&units, 0, sizeof units); + units_count = 0; + + while (info.left > 0) + { + const unsigned char *unit_data_start; + uint64_t len; + int is_dwarf64; + struct dwarf_buf unit_buf; + int version; + int unit_type; + uint64_t abbrev_offset; + int addrsize; + struct unit *u; + enum dwarf_tag unit_tag; + + if (info.reported_underflow) + goto fail; + + unit_data_start = info.buf; + + len = read_initial_length (&info, &is_dwarf64); + unit_buf = info; + unit_buf.left = len; + + if (!advance (&info, len)) + goto fail; + + version = read_uint16 (&unit_buf); + if (version < 2 || version > 5) + { + dwarf_buf_error (&unit_buf, "unrecognized DWARF version"); + goto fail; + } + + if (version < 5) + unit_type = 0; + else + { + unit_type = read_byte (&unit_buf); + if (unit_type == DW_UT_type || unit_type == DW_UT_split_type) + { + /* This unit doesn't have anything we need. */ + continue; + } + } + + pu = ((struct unit **) + backtrace_vector_grow (state, sizeof (struct unit *), + error_callback, data, &units)); + if (pu == NULL) + goto fail; + + u = ((struct unit *) + backtrace_alloc (state, sizeof *u, error_callback, data)); + if (u == NULL) + goto fail; + + *pu = u; + ++units_count; + + if (version < 5) + addrsize = 0; /* Set below. */ + else + addrsize = read_byte (&unit_buf); + + memset (&u->abbrevs, 0, sizeof u->abbrevs); + abbrev_offset = read_offset (&unit_buf, is_dwarf64); + if (!read_abbrevs (state, abbrev_offset, + dwarf_sections->data[DEBUG_ABBREV], + dwarf_sections->size[DEBUG_ABBREV], + is_bigendian, error_callback, data, &u->abbrevs)) + goto fail; + + if (version < 5) + addrsize = read_byte (&unit_buf); + + switch (unit_type) + { + case 0: + break; + case DW_UT_compile: case DW_UT_partial: + break; + case DW_UT_skeleton: case DW_UT_split_compile: + read_uint64 (&unit_buf); /* dwo_id */ + break; + default: + break; + } + + u->low_offset = unit_offset; + unit_offset += len + (is_dwarf64 ? 12 : 4); + u->high_offset = unit_offset; + u->unit_data = unit_buf.buf; + u->unit_data_len = unit_buf.left; + u->unit_data_offset = unit_buf.buf - unit_data_start; + u->version = version; + u->is_dwarf64 = is_dwarf64; + u->addrsize = addrsize; + u->filename = NULL; + u->comp_dir = NULL; + u->abs_filename = NULL; + u->lineoff = 0; + + /* The actual line number mappings will be read as needed. */ + u->lines = NULL; + u->lines_count = 0; + u->function_addrs = NULL; + u->function_addrs_count = 0; + + if (!find_address_ranges (state, base_address, &unit_buf, dwarf_sections, + is_bigendian, altlink, error_callback, data, + u, addrs, &unit_tag)) + goto fail; + + if (unit_buf.reported_underflow) + goto fail; + } + if (info.reported_underflow) + goto fail; + + /* Add a trailing addrs entry, but don't include it in addrs->count. */ + pa = ((struct unit_addrs *) + backtrace_vector_grow (state, sizeof (struct unit_addrs), + error_callback, data, &addrs->vec)); + if (pa == NULL) + goto fail; + pa->low = 0; + --pa->low; + pa->high = pa->low; + pa->u = NULL; + + unit_vec->vec = units; + unit_vec->count = units_count; + return 1; + + fail: + if (units_count > 0) + { + pu = (struct unit **) units.base; + for (i = 0; i < units_count; i++) + { + free_abbrevs (state, &pu[i]->abbrevs, error_callback, data); + backtrace_free (state, pu[i], sizeof **pu, error_callback, data); + } + backtrace_vector_free (state, &units, error_callback, data); + } + if (addrs->count > 0) + { + backtrace_vector_free (state, &addrs->vec, error_callback, data); + addrs->count = 0; + } + return 0; +} + +/* Add a new mapping to the vector of line mappings that we are + building. Returns 1 on success, 0 on failure. */ + +static int +add_line (struct backtrace_state *state, struct dwarf_data *ddata, + uintptr_t pc, const char *filename, int lineno, + backtrace_error_callback error_callback, void *data, + struct line_vector *vec) +{ + struct line *ln; + + /* If we are adding the same mapping, ignore it. This can happen + when using discriminators. */ + if (vec->count > 0) + { + ln = (struct line *) vec->vec.base + (vec->count - 1); + if (pc == ln->pc && filename == ln->filename && lineno == ln->lineno) + return 1; + } + + ln = ((struct line *) + backtrace_vector_grow (state, sizeof (struct line), error_callback, + data, &vec->vec)); + if (ln == NULL) + return 0; + + /* Add in the base address here, so that we can look up the PC + directly. */ + ln->pc = pc + ddata->base_address; + + ln->filename = filename; + ln->lineno = lineno; + ln->idx = vec->count; + + ++vec->count; + + return 1; +} + +/* Free the line header information. */ + +static void +free_line_header (struct backtrace_state *state, struct line_header *hdr, + backtrace_error_callback error_callback, void *data) +{ + if (hdr->dirs_count != 0) + backtrace_free (state, hdr->dirs, hdr->dirs_count * sizeof (const char *), + error_callback, data); + backtrace_free (state, hdr->filenames, + hdr->filenames_count * sizeof (char *), + error_callback, data); +} + +/* Read the directories and file names for a line header for version + 2, setting fields in HDR. Return 1 on success, 0 on failure. */ + +static int +read_v2_paths (struct backtrace_state *state, struct unit *u, + struct dwarf_buf *hdr_buf, struct line_header *hdr) +{ + const unsigned char *p; + const unsigned char *pend; + size_t i; + + /* Count the number of directory entries. */ + hdr->dirs_count = 0; + p = hdr_buf->buf; + pend = p + hdr_buf->left; + while (p < pend && *p != '\0') + { + p += strnlen((const char *) p, pend - p) + 1; + ++hdr->dirs_count; + } + + /* The index of the first entry in the list of directories is 1. Index 0 is + used for the current directory of the compilation. To simplify index + handling, we set entry 0 to the compilation unit directory. */ + ++hdr->dirs_count; + hdr->dirs = ((const char **) + backtrace_alloc (state, + hdr->dirs_count * sizeof (const char *), + hdr_buf->error_callback, + hdr_buf->data)); + if (hdr->dirs == NULL) + return 0; + + hdr->dirs[0] = u->comp_dir; + i = 1; + while (*hdr_buf->buf != '\0') + { + if (hdr_buf->reported_underflow) + return 0; + + hdr->dirs[i] = read_string (hdr_buf); + if (hdr->dirs[i] == NULL) + return 0; + ++i; + } + if (!advance (hdr_buf, 1)) + return 0; + + /* Count the number of file entries. */ + hdr->filenames_count = 0; + p = hdr_buf->buf; + pend = p + hdr_buf->left; + while (p < pend && *p != '\0') + { + p += strnlen ((const char *) p, pend - p) + 1; + p += leb128_len (p); + p += leb128_len (p); + p += leb128_len (p); + ++hdr->filenames_count; + } + + /* The index of the first entry in the list of file names is 1. Index 0 is + used for the DW_AT_name of the compilation unit. To simplify index + handling, we set entry 0 to the compilation unit file name. */ + ++hdr->filenames_count; + hdr->filenames = ((const char **) + backtrace_alloc (state, + hdr->filenames_count * sizeof (char *), + hdr_buf->error_callback, + hdr_buf->data)); + if (hdr->filenames == NULL) + return 0; + hdr->filenames[0] = u->filename; + i = 1; + while (*hdr_buf->buf != '\0') + { + const char *filename; + uint64_t dir_index; + + if (hdr_buf->reported_underflow) + return 0; + + filename = read_string (hdr_buf); + if (filename == NULL) + return 0; + dir_index = read_uleb128 (hdr_buf); + if (IS_ABSOLUTE_PATH (filename) + || (dir_index < hdr->dirs_count && hdr->dirs[dir_index] == NULL)) + hdr->filenames[i] = filename; + else + { + const char *dir; + size_t dir_len; + size_t filename_len; + char *s; + + if (dir_index < hdr->dirs_count) + dir = hdr->dirs[dir_index]; + else + { + dwarf_buf_error (hdr_buf, + ("invalid directory index in " + "line number program header")); + return 0; + } + dir_len = strlen (dir); + filename_len = strlen (filename); + s = ((char *) backtrace_alloc (state, dir_len + filename_len + 2, + hdr_buf->error_callback, + hdr_buf->data)); + if (s == NULL) + return 0; + memcpy (s, dir, dir_len); + /* FIXME: If we are on a DOS-based file system, and the + directory or the file name use backslashes, then we + should use a backslash here. */ + s[dir_len] = '/'; + memcpy (s + dir_len + 1, filename, filename_len + 1); + hdr->filenames[i] = s; + } + + /* Ignore the modification time and size. */ + read_uleb128 (hdr_buf); + read_uleb128 (hdr_buf); + + ++i; + } + + return 1; +} + +/* Read a single version 5 LNCT entry for a directory or file name in a + line header. Sets *STRING to the resulting name, ignoring other + data. Return 1 on success, 0 on failure. */ + +static int +read_lnct (struct backtrace_state *state, struct dwarf_data *ddata, + struct unit *u, struct dwarf_buf *hdr_buf, + const struct line_header *hdr, size_t formats_count, + const struct line_header_format *formats, const char **string) +{ + size_t i; + const char *dir; + const char *path; + + dir = NULL; + path = NULL; + for (i = 0; i < formats_count; i++) + { + struct attr_val val; + + if (!read_attribute (formats[i].form, 0, hdr_buf, u->is_dwarf64, + u->version, hdr->addrsize, &ddata->dwarf_sections, + ddata->altlink, &val)) + return 0; + switch (formats[i].lnct) + { + case DW_LNCT_path: + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, u->str_offsets_base, + &val, hdr_buf->error_callback, hdr_buf->data, + &path)) + return 0; + break; + case DW_LNCT_directory_index: + if (val.encoding == ATTR_VAL_UINT) + { + if (val.u.uint >= hdr->dirs_count) + { + dwarf_buf_error (hdr_buf, + ("invalid directory index in " + "line number program header")); + return 0; + } + dir = hdr->dirs[val.u.uint]; + } + break; + default: + /* We don't care about timestamps or sizes or hashes. */ + break; + } + } + + if (path == NULL) + { + dwarf_buf_error (hdr_buf, + "missing file name in line number program header"); + return 0; + } + + if (dir == NULL) + *string = path; + else + { + size_t dir_len; + size_t path_len; + char *s; + + dir_len = strlen (dir); + path_len = strlen (path); + s = (char *) backtrace_alloc (state, dir_len + path_len + 2, + hdr_buf->error_callback, hdr_buf->data); + if (s == NULL) + return 0; + memcpy (s, dir, dir_len); + /* FIXME: If we are on a DOS-based file system, and the + directory or the path name use backslashes, then we should + use a backslash here. */ + s[dir_len] = '/'; + memcpy (s + dir_len + 1, path, path_len + 1); + *string = s; + } + + return 1; +} + +/* Read a set of DWARF 5 line header format entries, setting *PCOUNT + and *PPATHS. Return 1 on success, 0 on failure. */ + +static int +read_line_header_format_entries (struct backtrace_state *state, + struct dwarf_data *ddata, + struct unit *u, + struct dwarf_buf *hdr_buf, + struct line_header *hdr, + size_t *pcount, + const char ***ppaths) +{ + size_t formats_count; + struct line_header_format *formats; + size_t paths_count; + const char **paths; + size_t i; + int ret; + + formats_count = read_byte (hdr_buf); + if (formats_count == 0) + formats = NULL; + else + { + formats = ((struct line_header_format *) + backtrace_alloc (state, + (formats_count + * sizeof (struct line_header_format)), + hdr_buf->error_callback, + hdr_buf->data)); + if (formats == NULL) + return 0; + + for (i = 0; i < formats_count; i++) + { + formats[i].lnct = (int) read_uleb128(hdr_buf); + formats[i].form = (enum dwarf_form) read_uleb128 (hdr_buf); + } + } + + paths_count = read_uleb128 (hdr_buf); + if (paths_count == 0) + { + *pcount = 0; + *ppaths = NULL; + ret = 1; + goto exit; + } + + paths = ((const char **) + backtrace_alloc (state, paths_count * sizeof (const char *), + hdr_buf->error_callback, hdr_buf->data)); + if (paths == NULL) + { + ret = 0; + goto exit; + } + for (i = 0; i < paths_count; i++) + { + if (!read_lnct (state, ddata, u, hdr_buf, hdr, formats_count, + formats, &paths[i])) + { + backtrace_free (state, paths, + paths_count * sizeof (const char *), + hdr_buf->error_callback, hdr_buf->data); + ret = 0; + goto exit; + } + } + + *pcount = paths_count; + *ppaths = paths; + + ret = 1; + + exit: + if (formats != NULL) + backtrace_free (state, formats, + formats_count * sizeof (struct line_header_format), + hdr_buf->error_callback, hdr_buf->data); + + return ret; +} + +/* Read the line header. Return 1 on success, 0 on failure. */ + +static int +read_line_header (struct backtrace_state *state, struct dwarf_data *ddata, + struct unit *u, int is_dwarf64, struct dwarf_buf *line_buf, + struct line_header *hdr) +{ + uint64_t hdrlen; + struct dwarf_buf hdr_buf; + + hdr->version = read_uint16 (line_buf); + if (hdr->version < 2 || hdr->version > 5) + { + dwarf_buf_error (line_buf, "unsupported line number version"); + return 0; + } + + if (hdr->version < 5) + hdr->addrsize = u->addrsize; + else + { + hdr->addrsize = read_byte (line_buf); + /* We could support a non-zero segment_selector_size but I doubt + we'll ever see it. */ + if (read_byte (line_buf) != 0) + { + dwarf_buf_error (line_buf, + "non-zero segment_selector_size not supported"); + return 0; + } + } + + hdrlen = read_offset (line_buf, is_dwarf64); + + hdr_buf = *line_buf; + hdr_buf.left = hdrlen; + + if (!advance (line_buf, hdrlen)) + return 0; + + hdr->min_insn_len = read_byte (&hdr_buf); + if (hdr->version < 4) + hdr->max_ops_per_insn = 1; + else + hdr->max_ops_per_insn = read_byte (&hdr_buf); + + /* We don't care about default_is_stmt. */ + read_byte (&hdr_buf); + + hdr->line_base = read_sbyte (&hdr_buf); + hdr->line_range = read_byte (&hdr_buf); + + hdr->opcode_base = read_byte (&hdr_buf); + hdr->opcode_lengths = hdr_buf.buf; + if (!advance (&hdr_buf, hdr->opcode_base - 1)) + return 0; + + if (hdr->version < 5) + { + if (!read_v2_paths (state, u, &hdr_buf, hdr)) + return 0; + } + else + { + if (!read_line_header_format_entries (state, ddata, u, &hdr_buf, hdr, + &hdr->dirs_count, + &hdr->dirs)) + return 0; + if (!read_line_header_format_entries (state, ddata, u, &hdr_buf, hdr, + &hdr->filenames_count, + &hdr->filenames)) + return 0; + } + + if (hdr_buf.reported_underflow) + return 0; + + return 1; +} + +/* Read the line program, adding line mappings to VEC. Return 1 on + success, 0 on failure. */ + +static int +read_line_program (struct backtrace_state *state, struct dwarf_data *ddata, + const struct line_header *hdr, struct dwarf_buf *line_buf, + struct line_vector *vec) +{ + uint64_t address; + unsigned int op_index; + const char *reset_filename; + const char *filename; + int lineno; + + address = 0; + op_index = 0; + if (hdr->filenames_count > 1) + reset_filename = hdr->filenames[1]; + else + reset_filename = ""; + filename = reset_filename; + lineno = 1; + while (line_buf->left > 0) + { + unsigned int op; + + op = read_byte (line_buf); + if (op >= hdr->opcode_base) + { + unsigned int advance; + + /* Special opcode. */ + op -= hdr->opcode_base; + advance = op / hdr->line_range; + address += (hdr->min_insn_len * (op_index + advance) + / hdr->max_ops_per_insn); + op_index = (op_index + advance) % hdr->max_ops_per_insn; + lineno += hdr->line_base + (int) (op % hdr->line_range); + add_line (state, ddata, address, filename, lineno, + line_buf->error_callback, line_buf->data, vec); + } + else if (op == DW_LNS_extended_op) + { + uint64_t len; + + len = read_uleb128 (line_buf); + op = read_byte (line_buf); + switch (op) + { + case DW_LNE_end_sequence: + /* FIXME: Should we mark the high PC here? It seems + that we already have that information from the + compilation unit. */ + address = 0; + op_index = 0; + filename = reset_filename; + lineno = 1; + break; + case DW_LNE_set_address: + address = read_address (line_buf, hdr->addrsize); + break; + case DW_LNE_define_file: + { + const char *f; + unsigned int dir_index; + + f = read_string (line_buf); + if (f == NULL) + return 0; + dir_index = read_uleb128 (line_buf); + /* Ignore that time and length. */ + read_uleb128 (line_buf); + read_uleb128 (line_buf); + if (IS_ABSOLUTE_PATH (f)) + filename = f; + else + { + const char *dir; + size_t dir_len; + size_t f_len; + char *p; + + if (dir_index < hdr->dirs_count) + dir = hdr->dirs[dir_index]; + else + { + dwarf_buf_error (line_buf, + ("invalid directory index " + "in line number program")); + return 0; + } + dir_len = strlen (dir); + f_len = strlen (f); + p = ((char *) + backtrace_alloc (state, dir_len + f_len + 2, + line_buf->error_callback, + line_buf->data)); + if (p == NULL) + return 0; + memcpy (p, dir, dir_len); + /* FIXME: If we are on a DOS-based file system, + and the directory or the file name use + backslashes, then we should use a backslash + here. */ + p[dir_len] = '/'; + memcpy (p + dir_len + 1, f, f_len + 1); + filename = p; + } + } + break; + case DW_LNE_set_discriminator: + /* We don't care about discriminators. */ + read_uleb128 (line_buf); + break; + default: + if (!advance (line_buf, len - 1)) + return 0; + break; + } + } + else + { + switch (op) + { + case DW_LNS_copy: + add_line (state, ddata, address, filename, lineno, + line_buf->error_callback, line_buf->data, vec); + break; + case DW_LNS_advance_pc: + { + uint64_t advance; + + advance = read_uleb128 (line_buf); + address += (hdr->min_insn_len * (op_index + advance) + / hdr->max_ops_per_insn); + op_index = (op_index + advance) % hdr->max_ops_per_insn; + } + break; + case DW_LNS_advance_line: + lineno += (int) read_sleb128 (line_buf); + break; + case DW_LNS_set_file: + { + uint64_t fileno; + + fileno = read_uleb128 (line_buf); + if (fileno == 0) + filename = ""; + else + { + if (fileno >= hdr->filenames_count) + { + dwarf_buf_error (line_buf, + ("invalid file number in " + "line number program")); + return 0; + } + filename = hdr->filenames[fileno]; + } + } + break; + case DW_LNS_set_column: + read_uleb128 (line_buf); + break; + case DW_LNS_negate_stmt: + break; + case DW_LNS_set_basic_block: + break; + case DW_LNS_const_add_pc: + { + unsigned int advance; + + op = 255 - hdr->opcode_base; + advance = op / hdr->line_range; + address += (hdr->min_insn_len * (op_index + advance) + / hdr->max_ops_per_insn); + op_index = (op_index + advance) % hdr->max_ops_per_insn; + } + break; + case DW_LNS_fixed_advance_pc: + address += read_uint16 (line_buf); + op_index = 0; + break; + case DW_LNS_set_prologue_end: + break; + case DW_LNS_set_epilogue_begin: + break; + case DW_LNS_set_isa: + read_uleb128 (line_buf); + break; + default: + { + unsigned int i; + + for (i = hdr->opcode_lengths[op - 1]; i > 0; --i) + read_uleb128 (line_buf); + } + break; + } + } + } + + return 1; +} + +/* Read the line number information for a compilation unit. Returns 1 + on success, 0 on failure. */ + +static int +read_line_info (struct backtrace_state *state, struct dwarf_data *ddata, + backtrace_error_callback error_callback, void *data, + struct unit *u, struct line_header *hdr, struct line **lines, + size_t *lines_count) +{ + struct line_vector vec; + struct dwarf_buf line_buf; + uint64_t len; + int is_dwarf64; + struct line *ln; + + memset (&vec.vec, 0, sizeof vec.vec); + vec.count = 0; + + memset (hdr, 0, sizeof *hdr); + + if (u->lineoff != (off_t) (size_t) u->lineoff + || (size_t) u->lineoff >= ddata->dwarf_sections.size[DEBUG_LINE]) + { + error_callback (data, "unit line offset out of range", 0); + goto fail; + } + + line_buf.name = ".debug_line"; + line_buf.start = ddata->dwarf_sections.data[DEBUG_LINE]; + line_buf.buf = ddata->dwarf_sections.data[DEBUG_LINE] + u->lineoff; + line_buf.left = ddata->dwarf_sections.size[DEBUG_LINE] - u->lineoff; + line_buf.is_bigendian = ddata->is_bigendian; + line_buf.error_callback = error_callback; + line_buf.data = data; + line_buf.reported_underflow = 0; + + len = read_initial_length (&line_buf, &is_dwarf64); + line_buf.left = len; + + if (!read_line_header (state, ddata, u, is_dwarf64, &line_buf, hdr)) + goto fail; + + if (!read_line_program (state, ddata, hdr, &line_buf, &vec)) + goto fail; + + if (line_buf.reported_underflow) + goto fail; + + if (vec.count == 0) + { + /* This is not a failure in the sense of a generating an error, + but it is a failure in that sense that we have no useful + information. */ + goto fail; + } + + /* Allocate one extra entry at the end. */ + ln = ((struct line *) + backtrace_vector_grow (state, sizeof (struct line), error_callback, + data, &vec.vec)); + if (ln == NULL) + goto fail; + ln->pc = (uintptr_t) -1; + ln->filename = NULL; + ln->lineno = 0; + ln->idx = 0; + + if (!backtrace_vector_release (state, &vec.vec, error_callback, data)) + goto fail; + + ln = (struct line *) vec.vec.base; + backtrace_qsort (ln, vec.count, sizeof (struct line), line_compare); + + *lines = ln; + *lines_count = vec.count; + + return 1; + + fail: + backtrace_vector_free (state, &vec.vec, error_callback, data); + free_line_header (state, hdr, error_callback, data); + *lines = (struct line *) (uintptr_t) -1; + *lines_count = 0; + return 0; +} + +static const char *read_referenced_name (struct dwarf_data *, struct unit *, + uint64_t, backtrace_error_callback, + void *); + +/* Read the name of a function from a DIE referenced by ATTR with VAL. */ + +static const char * +read_referenced_name_from_attr (struct dwarf_data *ddata, struct unit *u, + struct attr *attr, struct attr_val *val, + backtrace_error_callback error_callback, + void *data) +{ + switch (attr->name) + { + case DW_AT_abstract_origin: + case DW_AT_specification: + break; + default: + return NULL; + } + + if (attr->form == DW_FORM_ref_sig8) + return NULL; + + if (val->encoding == ATTR_VAL_REF_INFO) + { + struct unit *unit + = find_unit (ddata->units, ddata->units_count, + val->u.uint); + if (unit == NULL) + return NULL; + + uint64_t offset = val->u.uint - unit->low_offset; + return read_referenced_name (ddata, unit, offset, error_callback, data); + } + + if (val->encoding == ATTR_VAL_UINT + || val->encoding == ATTR_VAL_REF_UNIT) + return read_referenced_name (ddata, u, val->u.uint, error_callback, data); + + if (val->encoding == ATTR_VAL_REF_ALT_INFO) + { + struct unit *alt_unit + = find_unit (ddata->altlink->units, ddata->altlink->units_count, + val->u.uint); + if (alt_unit == NULL) + return NULL; + + uint64_t offset = val->u.uint - alt_unit->low_offset; + return read_referenced_name (ddata->altlink, alt_unit, offset, + error_callback, data); + } + + return NULL; +} + +/* Read the name of a function from a DIE referenced by a + DW_AT_abstract_origin or DW_AT_specification tag. OFFSET is within + the same compilation unit. */ + +static const char * +read_referenced_name (struct dwarf_data *ddata, struct unit *u, + uint64_t offset, backtrace_error_callback error_callback, + void *data) +{ + struct dwarf_buf unit_buf; + uint64_t code; + const struct abbrev *abbrev; + const char *ret; + size_t i; + + /* OFFSET is from the start of the data for this compilation unit. + U->unit_data is the data, but it starts U->unit_data_offset bytes + from the beginning. */ + + if (offset < u->unit_data_offset + || offset - u->unit_data_offset >= u->unit_data_len) + { + error_callback (data, + "abstract origin or specification out of range", + 0); + return NULL; + } + + offset -= u->unit_data_offset; + + unit_buf.name = ".debug_info"; + unit_buf.start = ddata->dwarf_sections.data[DEBUG_INFO]; + unit_buf.buf = u->unit_data + offset; + unit_buf.left = u->unit_data_len - offset; + unit_buf.is_bigendian = ddata->is_bigendian; + unit_buf.error_callback = error_callback; + unit_buf.data = data; + unit_buf.reported_underflow = 0; + + code = read_uleb128 (&unit_buf); + if (code == 0) + { + dwarf_buf_error (&unit_buf, "invalid abstract origin or specification"); + return NULL; + } + + abbrev = lookup_abbrev (&u->abbrevs, code, error_callback, data); + if (abbrev == NULL) + return NULL; + + ret = NULL; + for (i = 0; i < abbrev->num_attrs; ++i) + { + struct attr_val val; + + if (!read_attribute (abbrev->attrs[i].form, abbrev->attrs[i].val, + &unit_buf, u->is_dwarf64, u->version, u->addrsize, + &ddata->dwarf_sections, ddata->altlink, &val)) + return NULL; + + switch (abbrev->attrs[i].name) + { + case DW_AT_name: + /* Third name preference: don't override. A name we found in some + other way, will normally be more useful -- e.g., this name is + normally not mangled. */ + if (ret != NULL) + break; + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, u->str_offsets_base, + &val, error_callback, data, &ret)) + return NULL; + break; + + case DW_AT_linkage_name: + case DW_AT_MIPS_linkage_name: + /* First name preference: override all. */ + { + const char *s; + + s = NULL; + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, u->str_offsets_base, + &val, error_callback, data, &s)) + return NULL; + if (s != NULL) + return s; + } + break; + + case DW_AT_specification: + /* Second name preference: override DW_AT_name, don't override + DW_AT_linkage_name. */ + { + const char *name; + + name = read_referenced_name_from_attr (ddata, u, &abbrev->attrs[i], + &val, error_callback, data); + if (name != NULL) + ret = name; + } + break; + + default: + break; + } + } + + return ret; +} + +/* Add a range to a unit that maps to a function. This is called via + add_ranges. Returns 1 on success, 0 on error. */ + +static int +add_function_range (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, void *data, + void *pvec) +{ + struct function *function = (struct function *) rdata; + struct function_vector *vec = (struct function_vector *) pvec; + struct function_addrs *p; + + if (vec->count > 0) + { + p = (struct function_addrs *) vec->vec.base + (vec->count - 1); + if ((lowpc == p->high || lowpc == p->high + 1) + && function == p->function) + { + if (highpc > p->high) + p->high = highpc; + return 1; + } + } + + p = ((struct function_addrs *) + backtrace_vector_grow (state, sizeof (struct function_addrs), + error_callback, data, &vec->vec)); + if (p == NULL) + return 0; + + p->low = lowpc; + p->high = highpc; + p->function = function; + + ++vec->count; + + return 1; +} + +/* Read one entry plus all its children. Add function addresses to + VEC. Returns 1 on success, 0 on error. */ + +static int +read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, + struct unit *u, uint64_t base, struct dwarf_buf *unit_buf, + const struct line_header *lhdr, + backtrace_error_callback error_callback, void *data, + struct function_vector *vec_function, + struct function_vector *vec_inlined) +{ + while (unit_buf->left > 0) + { + uint64_t code; + const struct abbrev *abbrev; + int is_function; + struct function *function; + struct function_vector *vec; + size_t i; + struct pcrange pcrange; + int have_linkage_name; + + code = read_uleb128 (unit_buf); + if (code == 0) + return 1; + + abbrev = lookup_abbrev (&u->abbrevs, code, error_callback, data); + if (abbrev == NULL) + return 0; + + is_function = (abbrev->tag == DW_TAG_subprogram + || abbrev->tag == DW_TAG_entry_point + || abbrev->tag == DW_TAG_inlined_subroutine); + + if (abbrev->tag == DW_TAG_inlined_subroutine) + vec = vec_inlined; + else + vec = vec_function; + + function = NULL; + if (is_function) + { + function = ((struct function *) + backtrace_alloc (state, sizeof *function, + error_callback, data)); + if (function == NULL) + return 0; + memset (function, 0, sizeof *function); + } + + memset (&pcrange, 0, sizeof pcrange); + have_linkage_name = 0; + for (i = 0; i < abbrev->num_attrs; ++i) + { + struct attr_val val; + + if (!read_attribute (abbrev->attrs[i].form, abbrev->attrs[i].val, + unit_buf, u->is_dwarf64, u->version, + u->addrsize, &ddata->dwarf_sections, + ddata->altlink, &val)) + return 0; + + /* The compile unit sets the base address for any address + ranges in the function entries. */ + if (abbrev->tag == DW_TAG_compile_unit + && abbrev->attrs[i].name == DW_AT_low_pc) + { + if (val.encoding == ATTR_VAL_ADDRESS) + base = val.u.uint; + else if (val.encoding == ATTR_VAL_ADDRESS_INDEX) + { + if (!resolve_addr_index (&ddata->dwarf_sections, + u->addr_base, u->addrsize, + ddata->is_bigendian, val.u.uint, + error_callback, data, &base)) + return 0; + } + } + + if (is_function) + { + switch (abbrev->attrs[i].name) + { + case DW_AT_call_file: + if (val.encoding == ATTR_VAL_UINT) + { + if (val.u.uint == 0) + function->caller_filename = ""; + else + { + if (val.u.uint >= lhdr->filenames_count) + { + dwarf_buf_error (unit_buf, + ("invalid file number in " + "DW_AT_call_file attribute")); + return 0; + } + function->caller_filename = + lhdr->filenames[val.u.uint]; + } + } + break; + + case DW_AT_call_line: + if (val.encoding == ATTR_VAL_UINT) + function->caller_lineno = val.u.uint; + break; + + case DW_AT_abstract_origin: + case DW_AT_specification: + /* Second name preference: override DW_AT_name, don't override + DW_AT_linkage_name. */ + if (have_linkage_name) + break; + { + const char *name; + + name + = read_referenced_name_from_attr (ddata, u, + &abbrev->attrs[i], &val, + error_callback, data); + if (name != NULL) + function->name = name; + } + break; + + case DW_AT_name: + /* Third name preference: don't override. */ + if (function->name != NULL) + break; + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, + u->str_offsets_base, &val, + error_callback, data, &function->name)) + return 0; + break; + + case DW_AT_linkage_name: + case DW_AT_MIPS_linkage_name: + /* First name preference: override all. */ + { + const char *s; + + s = NULL; + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, + u->str_offsets_base, &val, + error_callback, data, &s)) + return 0; + if (s != NULL) + { + function->name = s; + have_linkage_name = 1; + } + } + break; + + case DW_AT_low_pc: case DW_AT_high_pc: case DW_AT_ranges: + update_pcrange (&abbrev->attrs[i], &val, &pcrange); + break; + + default: + break; + } + } + } + + /* If we couldn't find a name for the function, we have no use + for it. */ + if (is_function && function->name == NULL) + { + backtrace_free (state, function, sizeof *function, + error_callback, data); + is_function = 0; + } + + if (is_function) + { + if (pcrange.have_ranges + || (pcrange.have_lowpc && pcrange.have_highpc)) + { + if (!add_ranges (state, &ddata->dwarf_sections, + ddata->base_address, ddata->is_bigendian, + u, base, &pcrange, add_function_range, + (void *) function, error_callback, data, + (void *) vec)) + return 0; + } + else + { + backtrace_free (state, function, sizeof *function, + error_callback, data); + is_function = 0; + } + } + + if (abbrev->has_children) + { + if (!is_function) + { + if (!read_function_entry (state, ddata, u, base, unit_buf, lhdr, + error_callback, data, vec_function, + vec_inlined)) + return 0; + } + else + { + struct function_vector fvec; + + /* Gather any information for inlined functions in + FVEC. */ + + memset (&fvec, 0, sizeof fvec); + + if (!read_function_entry (state, ddata, u, base, unit_buf, lhdr, + error_callback, data, vec_function, + &fvec)) + return 0; + + if (fvec.count > 0) + { + struct function_addrs *p; + struct function_addrs *faddrs; + + /* Allocate a trailing entry, but don't include it + in fvec.count. */ + p = ((struct function_addrs *) + backtrace_vector_grow (state, + sizeof (struct function_addrs), + error_callback, data, + &fvec.vec)); + if (p == NULL) + return 0; + p->low = 0; + --p->low; + p->high = p->low; + p->function = NULL; + + if (!backtrace_vector_release (state, &fvec.vec, + error_callback, data)) + return 0; + + faddrs = (struct function_addrs *) fvec.vec.base; + backtrace_qsort (faddrs, fvec.count, + sizeof (struct function_addrs), + function_addrs_compare); + + function->function_addrs = faddrs; + function->function_addrs_count = fvec.count; + } + } + } + } + + return 1; +} + +/* Read function name information for a compilation unit. We look + through the whole unit looking for function tags. */ + +static void +read_function_info (struct backtrace_state *state, struct dwarf_data *ddata, + const struct line_header *lhdr, + backtrace_error_callback error_callback, void *data, + struct unit *u, struct function_vector *fvec, + struct function_addrs **ret_addrs, + size_t *ret_addrs_count) +{ + struct function_vector lvec; + struct function_vector *pfvec; + struct dwarf_buf unit_buf; + struct function_addrs *p; + struct function_addrs *addrs; + size_t addrs_count; + + /* Use FVEC if it is not NULL. Otherwise use our own vector. */ + if (fvec != NULL) + pfvec = fvec; + else + { + memset (&lvec, 0, sizeof lvec); + pfvec = &lvec; + } + + unit_buf.name = ".debug_info"; + unit_buf.start = ddata->dwarf_sections.data[DEBUG_INFO]; + unit_buf.buf = u->unit_data; + unit_buf.left = u->unit_data_len; + unit_buf.is_bigendian = ddata->is_bigendian; + unit_buf.error_callback = error_callback; + unit_buf.data = data; + unit_buf.reported_underflow = 0; + + while (unit_buf.left > 0) + { + if (!read_function_entry (state, ddata, u, 0, &unit_buf, lhdr, + error_callback, data, pfvec, pfvec)) + return; + } + + if (pfvec->count == 0) + return; + + /* Allocate a trailing entry, but don't include it in + pfvec->count. */ + p = ((struct function_addrs *) + backtrace_vector_grow (state, sizeof (struct function_addrs), + error_callback, data, &pfvec->vec)); + if (p == NULL) + return; + p->low = 0; + --p->low; + p->high = p->low; + p->function = NULL; + + addrs_count = pfvec->count; + + if (fvec == NULL) + { + if (!backtrace_vector_release (state, &lvec.vec, error_callback, data)) + return; + addrs = (struct function_addrs *) pfvec->vec.base; + } + else + { + /* Finish this list of addresses, but leave the remaining space in + the vector available for the next function unit. */ + addrs = ((struct function_addrs *) + backtrace_vector_finish (state, &fvec->vec, + error_callback, data)); + if (addrs == NULL) + return; + fvec->count = 0; + } + + backtrace_qsort (addrs, addrs_count, sizeof (struct function_addrs), + function_addrs_compare); + + *ret_addrs = addrs; + *ret_addrs_count = addrs_count; +} + +/* See if PC is inlined in FUNCTION. If it is, print out the inlined + information, and update FILENAME and LINENO for the caller. + Returns whatever CALLBACK returns, or 0 to keep going. */ + +static int +report_inlined_functions (uintptr_t pc, struct function *function, + backtrace_full_callback callback, void *data, + const char **filename, int *lineno) +{ + struct function_addrs *p; + struct function_addrs *match; + struct function *inlined; + int ret; + + if (function->function_addrs_count == 0) + return 0; + + /* Our search isn't safe if pc == -1, as that is the sentinel + value. */ + if (pc + 1 == 0) + return 0; + + p = ((struct function_addrs *) + bsearch (&pc, function->function_addrs, + function->function_addrs_count, + sizeof (struct function_addrs), + function_addrs_search)); + if (p == NULL) + return 0; + + /* Here pc >= p->low && pc < (p + 1)->low. The function_addrs are + sorted by low, so if pc > p->low we are at the end of a range of + function_addrs with the same low value. If pc == p->low walk + forward to the end of the range with that low value. Then walk + backward and use the first range that includes pc. */ + while (pc == (p + 1)->low) + ++p; + match = NULL; + while (1) + { + if (pc < p->high) + { + match = p; + break; + } + if (p == function->function_addrs) + break; + if ((p - 1)->low < p->low) + break; + --p; + } + if (match == NULL) + return 0; + + /* We found an inlined call. */ + + inlined = match->function; + + /* Report any calls inlined into this one. */ + ret = report_inlined_functions (pc, inlined, callback, data, + filename, lineno); + if (ret != 0) + return ret; + + /* Report this inlined call. */ + ret = callback (data, pc, match->low, *filename, *lineno, inlined->name); + if (ret != 0) + return ret; + + /* Our caller will report the caller of the inlined function; tell + it the appropriate filename and line number. */ + *filename = inlined->caller_filename; + *lineno = inlined->caller_lineno; + + return 0; +} + +/* Look for a PC in the DWARF mapping for one module. On success, + call CALLBACK and return whatever it returns. On error, call + ERROR_CALLBACK and return 0. Sets *FOUND to 1 if the PC is found, + 0 if not. */ + +static int +dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, + uintptr_t pc, backtrace_full_callback callback, + backtrace_error_callback error_callback, void *data, + int *found) +{ + struct unit_addrs *entry; + int found_entry; + struct unit *u; + int new_data; + struct line *lines; + struct line *ln; + struct function_addrs *p; + struct function_addrs *fmatch; + struct function *function; + const char *filename; + int lineno; + int ret; + + *found = 1; + + /* Find an address range that includes PC. Our search isn't safe if + PC == -1, as we use that as a sentinel value, so skip the search + in that case. */ + entry = (ddata->addrs_count == 0 || pc + 1 == 0 + ? NULL + : (struct unit_addrs*)bsearch (&pc, ddata->addrs, ddata->addrs_count, + sizeof (struct unit_addrs), unit_addrs_search)); + + if (entry == NULL) + { + *found = 0; + return 0; + } + + /* Here pc >= entry->low && pc < (entry + 1)->low. The unit_addrs + are sorted by low, so if pc > p->low we are at the end of a range + of unit_addrs with the same low value. If pc == p->low walk + forward to the end of the range with that low value. Then walk + backward and use the first range that includes pc. */ + while (pc == (entry + 1)->low) + ++entry; + found_entry = 0; + while (1) + { + if (pc < entry->high) + { + found_entry = 1; + break; + } + if (entry == ddata->addrs) + break; + if ((entry - 1)->low < entry->low) + break; + --entry; + } + if (!found_entry) + { + *found = 0; + return 0; + } + + /* We need the lines, lines_count, function_addrs, + function_addrs_count fields of u. If they are not set, we need + to set them. When running in threaded mode, we need to allow for + the possibility that some other thread is setting them + simultaneously. */ + + u = entry->u; + lines = u->lines; + + /* Skip units with no useful line number information by walking + backward. Useless line number information is marked by setting + lines == -1. */ + while (entry > ddata->addrs + && pc >= (entry - 1)->low + && pc < (entry - 1)->high) + { + if (state->threaded) + lines = (struct line *) backtrace_atomic_load_pointer (&u->lines); + + if (lines != (struct line *) (uintptr_t) -1) + break; + + --entry; + + u = entry->u; + lines = u->lines; + } + + if (state->threaded) + lines = backtrace_atomic_load_pointer (&u->lines); + + new_data = 0; + if (lines == NULL) + { + struct function_addrs *function_addrs; + size_t function_addrs_count; + struct line_header lhdr; + size_t count; + + /* We have never read the line information for this unit. Read + it now. */ + + function_addrs = NULL; + function_addrs_count = 0; + if (read_line_info (state, ddata, error_callback, data, entry->u, &lhdr, + &lines, &count)) + { + struct function_vector *pfvec; + + /* If not threaded, reuse DDATA->FVEC for better memory + consumption. */ + if (state->threaded) + pfvec = NULL; + else + pfvec = &ddata->fvec; + read_function_info (state, ddata, &lhdr, error_callback, data, + entry->u, pfvec, &function_addrs, + &function_addrs_count); + free_line_header (state, &lhdr, error_callback, data); + new_data = 1; + } + + /* Atomically store the information we just read into the unit. + If another thread is simultaneously writing, it presumably + read the same information, and we don't care which one we + wind up with; we just leak the other one. We do have to + write the lines field last, so that the acquire-loads above + ensure that the other fields are set. */ + + if (!state->threaded) + { + u->lines_count = count; + u->function_addrs = function_addrs; + u->function_addrs_count = function_addrs_count; + u->lines = lines; + } + else + { + backtrace_atomic_store_size_t (&u->lines_count, count); + backtrace_atomic_store_pointer (&u->function_addrs, function_addrs); + backtrace_atomic_store_size_t (&u->function_addrs_count, + function_addrs_count); + backtrace_atomic_store_pointer (&u->lines, lines); + } + } + + /* Now all fields of U have been initialized. */ + + if (lines == (struct line *) (uintptr_t) -1) + { + /* If reading the line number information failed in some way, + try again to see if there is a better compilation unit for + this PC. */ + if (new_data) + return dwarf_lookup_pc (state, ddata, pc, callback, error_callback, + data, found); + return callback (data, pc, 0, NULL, 0, NULL); + } + + /* Search for PC within this unit. */ + + ln = (struct line *) bsearch (&pc, lines, entry->u->lines_count, + sizeof (struct line), line_search); + if (ln == NULL) + { + /* The PC is between the low_pc and high_pc attributes of the + compilation unit, but no entry in the line table covers it. + This implies that the start of the compilation unit has no + line number information. */ + + if (entry->u->abs_filename == NULL) + { + const char *filename; + + filename = entry->u->filename; + if (filename != NULL + && !IS_ABSOLUTE_PATH (filename) + && entry->u->comp_dir != NULL) + { + size_t filename_len; + const char *dir; + size_t dir_len; + char *s; + + filename_len = strlen (filename); + dir = entry->u->comp_dir; + dir_len = strlen (dir); + s = (char *) backtrace_alloc (state, dir_len + filename_len + 2, + error_callback, data); + if (s == NULL) + { + *found = 0; + return 0; + } + memcpy (s, dir, dir_len); + /* FIXME: Should use backslash if DOS file system. */ + s[dir_len] = '/'; + memcpy (s + dir_len + 1, filename, filename_len + 1); + filename = s; + } + entry->u->abs_filename = filename; + } + + return callback (data, pc, 0, entry->u->abs_filename, 0, NULL); + } + + /* Search for function name within this unit. */ + + if (entry->u->function_addrs_count == 0) + return callback (data, pc, 0, ln->filename, ln->lineno, NULL); + + p = ((struct function_addrs *) + bsearch (&pc, entry->u->function_addrs, + entry->u->function_addrs_count, + sizeof (struct function_addrs), + function_addrs_search)); + if (p == NULL) + return callback (data, pc, 0, ln->filename, ln->lineno, NULL); + + /* Here pc >= p->low && pc < (p + 1)->low. The function_addrs are + sorted by low, so if pc > p->low we are at the end of a range of + function_addrs with the same low value. If pc == p->low walk + forward to the end of the range with that low value. Then walk + backward and use the first range that includes pc. */ + while (pc == (p + 1)->low) + ++p; + fmatch = NULL; + while (1) + { + if (pc < p->high) + { + fmatch = p; + break; + } + if (p == entry->u->function_addrs) + break; + if ((p - 1)->low < p->low) + break; + --p; + } + if (fmatch == NULL) + return callback (data, pc, 0, ln->filename, ln->lineno, NULL); + + function = fmatch->function; + + filename = ln->filename; + lineno = ln->lineno; + + ret = report_inlined_functions (pc, function, callback, data, + &filename, &lineno); + if (ret != 0) + return ret; + + return callback (data, pc, fmatch->low, filename, lineno, function->name); +} + + +/* Return the file/line information for a PC using the DWARF mapping + we built earlier. */ + +static int +dwarf_fileline (struct backtrace_state *state, uintptr_t pc, + backtrace_full_callback callback, + backtrace_error_callback error_callback, void *data) +{ + struct dwarf_data *ddata; + int found; + int ret; + + if (!state->threaded) + { + for (ddata = (struct dwarf_data *) state->fileline_data; + ddata != NULL; + ddata = ddata->next) + { + ret = dwarf_lookup_pc (state, ddata, pc, callback, error_callback, + data, &found); + if (ret != 0 || found) + return ret; + } + } + else + { + struct dwarf_data **pp; + + pp = (struct dwarf_data **) (void *) &state->fileline_data; + while (1) + { + ddata = backtrace_atomic_load_pointer (pp); + if (ddata == NULL) + break; + + ret = dwarf_lookup_pc (state, ddata, pc, callback, error_callback, + data, &found); + if (ret != 0 || found) + return ret; + + pp = &ddata->next; + } + } + + /* FIXME: See if any libraries have been dlopen'ed. */ + + return callback (data, pc, 0, NULL, 0, NULL); +} + +/* Initialize our data structures from the DWARF debug info for a + file. Return NULL on failure. */ + +static struct dwarf_data * +build_dwarf_data (struct backtrace_state *state, + uintptr_t base_address, + const struct dwarf_sections *dwarf_sections, + int is_bigendian, + struct dwarf_data *altlink, + backtrace_error_callback error_callback, + void *data) +{ + struct unit_addrs_vector addrs_vec; + struct unit_addrs *addrs; + size_t addrs_count; + struct unit_vector units_vec; + struct unit **units; + size_t units_count; + struct dwarf_data *fdata; + + if (!build_address_map (state, base_address, dwarf_sections, is_bigendian, + altlink, error_callback, data, &addrs_vec, + &units_vec)) + return NULL; + + if (!backtrace_vector_release (state, &addrs_vec.vec, error_callback, data)) + return NULL; + if (!backtrace_vector_release (state, &units_vec.vec, error_callback, data)) + return NULL; + addrs = (struct unit_addrs *) addrs_vec.vec.base; + units = (struct unit **) units_vec.vec.base; + addrs_count = addrs_vec.count; + units_count = units_vec.count; + backtrace_qsort (addrs, addrs_count, sizeof (struct unit_addrs), + unit_addrs_compare); + /* No qsort for units required, already sorted. */ + + fdata = ((struct dwarf_data *) + backtrace_alloc (state, sizeof (struct dwarf_data), + error_callback, data)); + if (fdata == NULL) + return NULL; + + fdata->next = NULL; + fdata->altlink = altlink; + fdata->base_address = base_address; + fdata->addrs = addrs; + fdata->addrs_count = addrs_count; + fdata->units = units; + fdata->units_count = units_count; + fdata->dwarf_sections = *dwarf_sections; + fdata->is_bigendian = is_bigendian; + memset (&fdata->fvec, 0, sizeof fdata->fvec); + + return fdata; +} + +/* Build our data structures from the DWARF sections for a module. + Set FILELINE_FN and STATE->FILELINE_DATA. Return 1 on success, 0 + on failure. */ + +int +backtrace_dwarf_add (struct backtrace_state *state, + uintptr_t base_address, + const struct dwarf_sections *dwarf_sections, + int is_bigendian, + struct dwarf_data *fileline_altlink, + backtrace_error_callback error_callback, + void *data, fileline *fileline_fn, + struct dwarf_data **fileline_entry) +{ + struct dwarf_data *fdata; + + fdata = build_dwarf_data (state, base_address, dwarf_sections, is_bigendian, + fileline_altlink, error_callback, data); + if (fdata == NULL) + return 0; + + if (fileline_entry != NULL) + *fileline_entry = fdata; + + if (!state->threaded) + { + struct dwarf_data **pp; + + for (pp = (struct dwarf_data **) (void *) &state->fileline_data; + *pp != NULL; + pp = &(*pp)->next) + ; + *pp = fdata; + } + else + { + while (1) + { + struct dwarf_data **pp; + + pp = (struct dwarf_data **) (void *) &state->fileline_data; + + while (1) + { + struct dwarf_data *p; + + p = backtrace_atomic_load_pointer (pp); + + if (p == NULL) + break; + + pp = &p->next; + } + + if (__sync_bool_compare_and_swap (pp, NULL, fdata)) + break; + } + } + + *fileline_fn = dwarf_fileline; + + return 1; +} + +} diff --git a/Source/ThirdParty/tracy/libbacktrace/elf.cpp b/Source/ThirdParty/tracy/libbacktrace/elf.cpp new file mode 100644 index 000000000..50715bf95 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/elf.cpp @@ -0,0 +1,4928 @@ +/* elf.c -- Get debug data from an ELF file for backtraces. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_DL_ITERATE_PHDR +#include +#endif + +#include "backtrace.hpp" +#include "internal.hpp" + +#ifndef S_ISLNK + #ifndef S_IFLNK + #define S_IFLNK 0120000 + #endif + #ifndef S_IFMT + #define S_IFMT 0170000 + #endif + #define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#endif + +#ifndef __GNUC__ +#define __builtin_prefetch(p, r, l) +#ifndef unlikely +#define unlikely(x) (x) +#endif +#else +#ifndef unlikely +#define unlikely(x) __builtin_expect(!!(x), 0) +#endif +#endif + +namespace tracy +{ + +#if !defined(HAVE_DECL_STRNLEN) || !HAVE_DECL_STRNLEN + +/* If strnlen is not declared, provide our own version. */ + +static size_t +xstrnlen (const char *s, size_t maxlen) +{ + size_t i; + + for (i = 0; i < maxlen; ++i) + if (s[i] == '\0') + break; + return i; +} + +#define strnlen xstrnlen + +#endif + +#ifndef HAVE_LSTAT + +/* Dummy version of lstat for systems that don't have it. */ + +static int +xlstat (const char *path ATTRIBUTE_UNUSED, struct stat *st ATTRIBUTE_UNUSED) +{ + return -1; +} + +#define lstat xlstat + +#endif + +#ifndef HAVE_READLINK + +/* Dummy version of readlink for systems that don't have it. */ + +static ssize_t +xreadlink (const char *path ATTRIBUTE_UNUSED, char *buf ATTRIBUTE_UNUSED, + size_t bufsz ATTRIBUTE_UNUSED) +{ + return -1; +} + +#define readlink xreadlink + +#endif + +#ifndef HAVE_DL_ITERATE_PHDR + +/* Dummy version of dl_iterate_phdr for systems that don't have it. */ + +#define dl_phdr_info x_dl_phdr_info +#define dl_iterate_phdr x_dl_iterate_phdr + +struct dl_phdr_info +{ + uintptr_t dlpi_addr; + const char *dlpi_name; +}; + +static int +dl_iterate_phdr (int (*callback) (struct dl_phdr_info *, + size_t, void *) ATTRIBUTE_UNUSED, + void *data ATTRIBUTE_UNUSED) +{ + return 0; +} + +#endif /* ! defined (HAVE_DL_ITERATE_PHDR) */ + +/* The configure script must tell us whether we are 32-bit or 64-bit + ELF. We could make this code test and support either possibility, + but there is no point. This code only works for the currently + running executable, which means that we know the ELF mode at + configure time. */ + +#if BACKTRACE_ELF_SIZE != 32 && BACKTRACE_ELF_SIZE != 64 +#error "Unknown BACKTRACE_ELF_SIZE" +#endif + +/* might #include which might define our constants + with slightly different values. Undefine them to be safe. */ + +#undef EI_NIDENT +#undef EI_MAG0 +#undef EI_MAG1 +#undef EI_MAG2 +#undef EI_MAG3 +#undef EI_CLASS +#undef EI_DATA +#undef EI_VERSION +#undef ELF_MAG0 +#undef ELF_MAG1 +#undef ELF_MAG2 +#undef ELF_MAG3 +#undef ELFCLASS32 +#undef ELFCLASS64 +#undef ELFDATA2LSB +#undef ELFDATA2MSB +#undef EV_CURRENT +#undef ET_DYN +#undef EM_PPC64 +#undef EF_PPC64_ABI +#undef SHN_LORESERVE +#undef SHN_XINDEX +#undef SHN_UNDEF +#undef SHT_PROGBITS +#undef SHT_SYMTAB +#undef SHT_STRTAB +#undef SHT_DYNSYM +#undef SHF_COMPRESSED +#undef STT_OBJECT +#undef STT_FUNC +#undef NT_GNU_BUILD_ID +#undef ELFCOMPRESS_ZLIB + +/* Basic types. */ + +typedef uint16_t b_elf_half; /* Elf_Half. */ +typedef uint32_t b_elf_word; /* Elf_Word. */ +typedef int32_t b_elf_sword; /* Elf_Sword. */ + +#if BACKTRACE_ELF_SIZE == 32 + +typedef uint32_t b_elf_addr; /* Elf_Addr. */ +typedef uint32_t b_elf_off; /* Elf_Off. */ + +typedef uint32_t b_elf_wxword; /* 32-bit Elf_Word, 64-bit ELF_Xword. */ + +#else + +typedef uint64_t b_elf_addr; /* Elf_Addr. */ +typedef uint64_t b_elf_off; /* Elf_Off. */ +typedef uint64_t b_elf_xword; /* Elf_Xword. */ +typedef int64_t b_elf_sxword; /* Elf_Sxword. */ + +typedef uint64_t b_elf_wxword; /* 32-bit Elf_Word, 64-bit ELF_Xword. */ + +#endif + +/* Data structures and associated constants. */ + +#define EI_NIDENT 16 + +typedef struct { + unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */ + b_elf_half e_type; /* Identifies object file type */ + b_elf_half e_machine; /* Specifies required architecture */ + b_elf_word e_version; /* Identifies object file version */ + b_elf_addr e_entry; /* Entry point virtual address */ + b_elf_off e_phoff; /* Program header table file offset */ + b_elf_off e_shoff; /* Section header table file offset */ + b_elf_word e_flags; /* Processor-specific flags */ + b_elf_half e_ehsize; /* ELF header size in bytes */ + b_elf_half e_phentsize; /* Program header table entry size */ + b_elf_half e_phnum; /* Program header table entry count */ + b_elf_half e_shentsize; /* Section header table entry size */ + b_elf_half e_shnum; /* Section header table entry count */ + b_elf_half e_shstrndx; /* Section header string table index */ +} b_elf_ehdr; /* Elf_Ehdr. */ + +#define EI_MAG0 0 +#define EI_MAG1 1 +#define EI_MAG2 2 +#define EI_MAG3 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 + +#define ELFMAG0 0x7f +#define ELFMAG1 'E' +#define ELFMAG2 'L' +#define ELFMAG3 'F' + +#define ELFCLASS32 1 +#define ELFCLASS64 2 + +#define ELFDATA2LSB 1 +#define ELFDATA2MSB 2 + +#define EV_CURRENT 1 + +#define ET_DYN 3 + +#define EM_PPC64 21 +#define EF_PPC64_ABI 3 + +typedef struct { + b_elf_word sh_name; /* Section name, index in string tbl */ + b_elf_word sh_type; /* Type of section */ + b_elf_wxword sh_flags; /* Miscellaneous section attributes */ + b_elf_addr sh_addr; /* Section virtual addr at execution */ + b_elf_off sh_offset; /* Section file offset */ + b_elf_wxword sh_size; /* Size of section in bytes */ + b_elf_word sh_link; /* Index of another section */ + b_elf_word sh_info; /* Additional section information */ + b_elf_wxword sh_addralign; /* Section alignment */ + b_elf_wxword sh_entsize; /* Entry size if section holds table */ +} b_elf_shdr; /* Elf_Shdr. */ + +#define SHN_UNDEF 0x0000 /* Undefined section */ +#define SHN_LORESERVE 0xFF00 /* Begin range of reserved indices */ +#define SHN_XINDEX 0xFFFF /* Section index is held elsewhere */ + +#define SHT_PROGBITS 1 +#define SHT_SYMTAB 2 +#define SHT_STRTAB 3 +#define SHT_DYNSYM 11 + +#define SHF_COMPRESSED 0x800 + +#if BACKTRACE_ELF_SIZE == 32 + +typedef struct +{ + b_elf_word st_name; /* Symbol name, index in string tbl */ + b_elf_addr st_value; /* Symbol value */ + b_elf_word st_size; /* Symbol size */ + unsigned char st_info; /* Symbol binding and type */ + unsigned char st_other; /* Visibility and other data */ + b_elf_half st_shndx; /* Symbol section index */ +} b_elf_sym; /* Elf_Sym. */ + +#else /* BACKTRACE_ELF_SIZE != 32 */ + +typedef struct +{ + b_elf_word st_name; /* Symbol name, index in string tbl */ + unsigned char st_info; /* Symbol binding and type */ + unsigned char st_other; /* Visibility and other data */ + b_elf_half st_shndx; /* Symbol section index */ + b_elf_addr st_value; /* Symbol value */ + b_elf_xword st_size; /* Symbol size */ +} b_elf_sym; /* Elf_Sym. */ + +#endif /* BACKTRACE_ELF_SIZE != 32 */ + +#define STT_OBJECT 1 +#define STT_FUNC 2 + +typedef struct +{ + uint32_t namesz; + uint32_t descsz; + uint32_t type; + char name[1]; +} b_elf_note; + +#define NT_GNU_BUILD_ID 3 + +#if BACKTRACE_ELF_SIZE == 32 + +typedef struct +{ + b_elf_word ch_type; /* Compresstion algorithm */ + b_elf_word ch_size; /* Uncompressed size */ + b_elf_word ch_addralign; /* Alignment for uncompressed data */ +} b_elf_chdr; /* Elf_Chdr */ + +#else /* BACKTRACE_ELF_SIZE != 32 */ + +typedef struct +{ + b_elf_word ch_type; /* Compression algorithm */ + b_elf_word ch_reserved; /* Reserved */ + b_elf_xword ch_size; /* Uncompressed size */ + b_elf_xword ch_addralign; /* Alignment for uncompressed data */ +} b_elf_chdr; /* Elf_Chdr */ + +#endif /* BACKTRACE_ELF_SIZE != 32 */ + +#define ELFCOMPRESS_ZLIB 1 + +/* Names of sections, indexed by enum dwarf_section in internal.h. */ + +static const char * const dwarf_section_names[DEBUG_MAX] = +{ + ".debug_info", + ".debug_line", + ".debug_abbrev", + ".debug_ranges", + ".debug_str", + ".debug_addr", + ".debug_str_offsets", + ".debug_line_str", + ".debug_rnglists" +}; + +/* Information we gather for the sections we care about. */ + +struct debug_section_info +{ + /* Section file offset. */ + off_t offset; + /* Section size. */ + size_t size; + /* Section contents, after read from file. */ + const unsigned char *data; + /* Whether the SHF_COMPRESSED flag is set for the section. */ + int compressed; +}; + +/* Information we keep for an ELF symbol. */ + +struct elf_symbol +{ + /* The name of the symbol. */ + const char *name; + /* The address of the symbol. */ + uintptr_t address; + /* The size of the symbol. */ + size_t size; +}; + +/* Information to pass to elf_syminfo. */ + +struct elf_syminfo_data +{ + /* Symbols for the next module. */ + struct elf_syminfo_data *next; + /* The ELF symbols, sorted by address. */ + struct elf_symbol *symbols; + /* The number of symbols. */ + size_t count; +}; + +/* A view that works for either a file or memory. */ + +struct elf_view +{ + struct backtrace_view view; + int release; /* If non-zero, must call backtrace_release_view. */ +}; + +/* Information about PowerPC64 ELFv1 .opd section. */ + +struct elf_ppc64_opd_data +{ + /* Address of the .opd section. */ + b_elf_addr addr; + /* Section data. */ + const char *data; + /* Size of the .opd section. */ + size_t size; + /* Corresponding section view. */ + struct elf_view view; +}; + +/* Create a view of SIZE bytes from DESCRIPTOR/MEMORY at OFFSET. */ + +static int +elf_get_view (struct backtrace_state *state, int descriptor, + const unsigned char *memory, size_t memory_size, off_t offset, + uint64_t size, backtrace_error_callback error_callback, + void *data, struct elf_view *view) +{ + if (memory == NULL) + { + view->release = 1; + return backtrace_get_view (state, descriptor, offset, size, + error_callback, data, &view->view); + } + else + { + if ((uint64_t) offset + size > (uint64_t) memory_size) + { + error_callback (data, "out of range for in-memory file", 0); + return 0; + } + view->view.data = (const void *) (memory + offset); + view->view.base = NULL; + view->view.len = size; + view->release = 0; + return 1; + } +} + +/* Release a view read by elf_get_view. */ + +static void +elf_release_view (struct backtrace_state *state, struct elf_view *view, + backtrace_error_callback error_callback, void *data) +{ + if (view->release) + backtrace_release_view (state, &view->view, error_callback, data); +} + +/* Compute the CRC-32 of BUF/LEN. This uses the CRC used for + .gnu_debuglink files. */ + +static uint32_t +elf_crc32 (uint32_t crc, const unsigned char *buf, size_t len) +{ + static const uint32_t crc32_table[256] = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, + 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, + 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, + 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, + 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, + 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, + 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, + 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, + 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, + 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, + 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, + 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, + 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, + 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, + 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, + 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, + 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, + 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, + 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, + 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, + 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, + 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, + 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, + 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, + 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, + 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, + 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, + 0x2d02ef8d + }; + const unsigned char *end; + + crc = ~crc; + for (end = buf + len; buf < end; ++ buf) + crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8); + return ~crc; +} + +/* Return the CRC-32 of the entire file open at DESCRIPTOR. */ + +static uint32_t +elf_crc32_file (struct backtrace_state *state, int descriptor, + backtrace_error_callback error_callback, void *data) +{ + struct stat st; + struct backtrace_view file_view; + uint32_t ret; + + if (fstat (descriptor, &st) < 0) + { + error_callback (data, "fstat", errno); + return 0; + } + + if (!backtrace_get_view (state, descriptor, 0, st.st_size, error_callback, + data, &file_view)) + return 0; + + ret = elf_crc32 (0, (const unsigned char *) file_view.data, st.st_size); + + backtrace_release_view (state, &file_view, error_callback, data); + + return ret; +} + +/* A dummy callback function used when we can't find a symbol + table. */ + +static void +elf_nosyms (struct backtrace_state *state ATTRIBUTE_UNUSED, + uintptr_t addr ATTRIBUTE_UNUSED, + backtrace_syminfo_callback callback ATTRIBUTE_UNUSED, + backtrace_error_callback error_callback, void *data) +{ + error_callback (data, "no symbol table in ELF executable", -1); +} + +/* A callback function used when we can't find any debug info. */ + +static int +elf_nodebug (struct backtrace_state *state, uintptr_t pc, + backtrace_full_callback callback, + backtrace_error_callback error_callback, void *data) +{ + if (state->syminfo_fn != NULL && state->syminfo_fn != elf_nosyms) + { + struct backtrace_call_full bdata; + + /* Fetch symbol information so that we can least get the + function name. */ + + bdata.full_callback = callback; + bdata.full_error_callback = error_callback; + bdata.full_data = data; + bdata.ret = 0; + state->syminfo_fn (state, pc, backtrace_syminfo_to_full_callback, + backtrace_syminfo_to_full_error_callback, &bdata); + return bdata.ret; + } + + error_callback (data, "no debug info in ELF executable", -1); + return 0; +} + +/* Compare struct elf_symbol for qsort. */ + +static int +elf_symbol_compare (const void *v1, const void *v2) +{ + const struct elf_symbol *e1 = (const struct elf_symbol *) v1; + const struct elf_symbol *e2 = (const struct elf_symbol *) v2; + + if (e1->address < e2->address) + return -1; + else if (e1->address > e2->address) + return 1; + else + return 0; +} + +/* Compare an ADDR against an elf_symbol for bsearch. We allocate one + extra entry in the array so that this can look safely at the next + entry. */ + +static int +elf_symbol_search (const void *vkey, const void *ventry) +{ + const uintptr_t *key = (const uintptr_t *) vkey; + const struct elf_symbol *entry = (const struct elf_symbol *) ventry; + uintptr_t addr; + + addr = *key; + if (addr < entry->address) + return -1; + else if (addr >= entry->address + entry->size) + return 1; + else + return 0; +} + +/* Initialize the symbol table info for elf_syminfo. */ + +static int +elf_initialize_syminfo (struct backtrace_state *state, + uintptr_t base_address, + const unsigned char *symtab_data, size_t symtab_size, + const unsigned char *strtab, size_t strtab_size, + backtrace_error_callback error_callback, + void *data, struct elf_syminfo_data *sdata, + struct elf_ppc64_opd_data *opd) +{ + size_t sym_count; + const b_elf_sym *sym; + size_t elf_symbol_count; + size_t elf_symbol_size; + struct elf_symbol *elf_symbols; + size_t i; + unsigned int j; + + sym_count = symtab_size / sizeof (b_elf_sym); + + /* We only care about function symbols. Count them. */ + sym = (const b_elf_sym *) symtab_data; + elf_symbol_count = 0; + for (i = 0; i < sym_count; ++i, ++sym) + { + int info; + + info = sym->st_info & 0xf; + if ((info == STT_FUNC || info == STT_OBJECT) + && sym->st_shndx != SHN_UNDEF) + ++elf_symbol_count; + } + + elf_symbol_size = elf_symbol_count * sizeof (struct elf_symbol); + elf_symbols = ((struct elf_symbol *) + backtrace_alloc (state, elf_symbol_size, error_callback, + data)); + if (elf_symbols == NULL) + return 0; + + sym = (const b_elf_sym *) symtab_data; + j = 0; + for (i = 0; i < sym_count; ++i, ++sym) + { + int info; + + info = sym->st_info & 0xf; + if (info != STT_FUNC && info != STT_OBJECT) + continue; + if (sym->st_shndx == SHN_UNDEF) + continue; + if (sym->st_name >= strtab_size) + { + error_callback (data, "symbol string index out of range", 0); + backtrace_free (state, elf_symbols, elf_symbol_size, error_callback, + data); + return 0; + } + elf_symbols[j].name = (const char *) strtab + sym->st_name; + /* Special case PowerPC64 ELFv1 symbols in .opd section, if the symbol + is a function descriptor, read the actual code address from the + descriptor. */ + if (opd + && sym->st_value >= opd->addr + && sym->st_value < opd->addr + opd->size) + elf_symbols[j].address + = *(const b_elf_addr *) (opd->data + (sym->st_value - opd->addr)); + else + elf_symbols[j].address = sym->st_value; + elf_symbols[j].address += base_address; + elf_symbols[j].size = sym->st_size; + ++j; + } + + backtrace_qsort (elf_symbols, elf_symbol_count, sizeof (struct elf_symbol), + elf_symbol_compare); + + sdata->next = NULL; + sdata->symbols = elf_symbols; + sdata->count = elf_symbol_count; + + return 1; +} + +/* Add EDATA to the list in STATE. */ + +static void +elf_add_syminfo_data (struct backtrace_state *state, + struct elf_syminfo_data *edata) +{ + if (!state->threaded) + { + struct elf_syminfo_data **pp; + + for (pp = (struct elf_syminfo_data **) (void *) &state->syminfo_data; + *pp != NULL; + pp = &(*pp)->next) + ; + *pp = edata; + } + else + { + while (1) + { + struct elf_syminfo_data **pp; + + pp = (struct elf_syminfo_data **) (void *) &state->syminfo_data; + + while (1) + { + struct elf_syminfo_data *p; + + p = backtrace_atomic_load_pointer (pp); + + if (p == NULL) + break; + + pp = &p->next; + } + + if (__sync_bool_compare_and_swap (pp, NULL, edata)) + break; + } + } +} + +/* Return the symbol name and value for an ADDR. */ + +static void +elf_syminfo (struct backtrace_state *state, uintptr_t addr, + backtrace_syminfo_callback callback, + backtrace_error_callback error_callback ATTRIBUTE_UNUSED, + void *data) +{ + struct elf_syminfo_data *edata; + struct elf_symbol *sym = NULL; + + if (!state->threaded) + { + for (edata = (struct elf_syminfo_data *) state->syminfo_data; + edata != NULL; + edata = edata->next) + { + sym = ((struct elf_symbol *) + bsearch (&addr, edata->symbols, edata->count, + sizeof (struct elf_symbol), elf_symbol_search)); + if (sym != NULL) + break; + } + } + else + { + struct elf_syminfo_data **pp; + + pp = (struct elf_syminfo_data **) (void *) &state->syminfo_data; + while (1) + { + edata = backtrace_atomic_load_pointer (pp); + if (edata == NULL) + break; + + sym = ((struct elf_symbol *) + bsearch (&addr, edata->symbols, edata->count, + sizeof (struct elf_symbol), elf_symbol_search)); + if (sym != NULL) + break; + + pp = &edata->next; + } + } + + if (sym == NULL) + callback (data, addr, NULL, 0, 0); + else + callback (data, addr, sym->name, sym->address, sym->size); +} + +/* Return whether FILENAME is a symlink. */ + +static int +elf_is_symlink (const char *filename) +{ + struct stat st; + + if (lstat (filename, &st) < 0) + return 0; + return S_ISLNK (st.st_mode); +} + +/* Return the results of reading the symlink FILENAME in a buffer + allocated by backtrace_alloc. Return the length of the buffer in + *LEN. */ + +static char * +elf_readlink (struct backtrace_state *state, const char *filename, + backtrace_error_callback error_callback, void *data, + size_t *plen) +{ + size_t len; + char *buf; + + len = 128; + while (1) + { + ssize_t rl; + + buf = (char*)backtrace_alloc (state, len, error_callback, data); + if (buf == NULL) + return NULL; + rl = readlink (filename, buf, len); + if (rl < 0) + { + backtrace_free (state, buf, len, error_callback, data); + return NULL; + } + if ((size_t) rl < len - 1) + { + buf[rl] = '\0'; + *plen = len; + return buf; + } + backtrace_free (state, buf, len, error_callback, data); + len *= 2; + } +} + +#define SYSTEM_BUILD_ID_DIR "/usr/lib/debug/.build-id/" + +/* Open a separate debug info file, using the build ID to find it. + Returns an open file descriptor, or -1. + + The GDB manual says that the only place gdb looks for a debug file + when the build ID is known is in /usr/lib/debug/.build-id. */ + +static int +elf_open_debugfile_by_buildid (struct backtrace_state *state, + const char *buildid_data, size_t buildid_size, + backtrace_error_callback error_callback, + void *data) +{ + const char * const prefix = SYSTEM_BUILD_ID_DIR; + const size_t prefix_len = strlen (prefix); + const char * const suffix = ".debug"; + const size_t suffix_len = strlen (suffix); + size_t len; + char *bd_filename; + char *t; + size_t i; + int ret; + int does_not_exist; + + len = prefix_len + buildid_size * 2 + suffix_len + 2; + bd_filename = (char*)backtrace_alloc (state, len, error_callback, data); + if (bd_filename == NULL) + return -1; + + t = bd_filename; + memcpy (t, prefix, prefix_len); + t += prefix_len; + for (i = 0; i < buildid_size; i++) + { + unsigned char b; + unsigned char nib; + + b = (unsigned char) buildid_data[i]; + nib = (b & 0xf0) >> 4; + *t++ = nib < 10 ? '0' + nib : 'a' + nib - 10; + nib = b & 0x0f; + *t++ = nib < 10 ? '0' + nib : 'a' + nib - 10; + if (i == 0) + *t++ = '/'; + } + memcpy (t, suffix, suffix_len); + t[suffix_len] = '\0'; + + ret = backtrace_open (bd_filename, error_callback, data, &does_not_exist); + + backtrace_free (state, bd_filename, len, error_callback, data); + + /* gdb checks that the debuginfo file has the same build ID note. + That seems kind of pointless to me--why would it have the right + name but not the right build ID?--so skipping the check. */ + + return ret; +} + +/* Try to open a file whose name is PREFIX (length PREFIX_LEN) + concatenated with PREFIX2 (length PREFIX2_LEN) concatenated with + DEBUGLINK_NAME. Returns an open file descriptor, or -1. */ + +static int +elf_try_debugfile (struct backtrace_state *state, const char *prefix, + size_t prefix_len, const char *prefix2, size_t prefix2_len, + const char *debuglink_name, + backtrace_error_callback error_callback, void *data) +{ + size_t debuglink_len; + size_t try_len; + char *Try; + int does_not_exist; + int ret; + + debuglink_len = strlen (debuglink_name); + try_len = prefix_len + prefix2_len + debuglink_len + 1; + Try = (char*)backtrace_alloc (state, try_len, error_callback, data); + if (Try == NULL) + return -1; + + memcpy (Try, prefix, prefix_len); + memcpy (Try + prefix_len, prefix2, prefix2_len); + memcpy (Try + prefix_len + prefix2_len, debuglink_name, debuglink_len); + Try[prefix_len + prefix2_len + debuglink_len] = '\0'; + + ret = backtrace_open (Try, error_callback, data, &does_not_exist); + + backtrace_free (state, Try, try_len, error_callback, data); + + return ret; +} + +/* Find a separate debug info file, using the debuglink section data + to find it. Returns an open file descriptor, or -1. */ + +static int +elf_find_debugfile_by_debuglink (struct backtrace_state *state, + const char *filename, + const char *debuglink_name, + backtrace_error_callback error_callback, + void *data) +{ + int ret; + char *alc; + size_t alc_len; + const char *slash; + int ddescriptor; + const char *prefix; + size_t prefix_len; + + /* Resolve symlinks in FILENAME. Since FILENAME is fairly likely to + be /proc/self/exe, symlinks are common. We don't try to resolve + the whole path name, just the base name. */ + ret = -1; + alc = NULL; + alc_len = 0; + while (elf_is_symlink (filename)) + { + char *new_buf; + size_t new_len; + + new_buf = elf_readlink (state, filename, error_callback, data, &new_len); + if (new_buf == NULL) + break; + + if (new_buf[0] == '/') + filename = new_buf; + else + { + slash = strrchr (filename, '/'); + if (slash == NULL) + filename = new_buf; + else + { + size_t clen; + char *c; + + slash++; + clen = slash - filename + strlen (new_buf) + 1; + c = (char*)backtrace_alloc (state, clen, error_callback, data); + if (c == NULL) + goto done; + + memcpy (c, filename, slash - filename); + memcpy (c + (slash - filename), new_buf, strlen (new_buf)); + c[slash - filename + strlen (new_buf)] = '\0'; + backtrace_free (state, new_buf, new_len, error_callback, data); + filename = c; + new_buf = c; + new_len = clen; + } + } + + if (alc != NULL) + backtrace_free (state, alc, alc_len, error_callback, data); + alc = new_buf; + alc_len = new_len; + } + + /* Look for DEBUGLINK_NAME in the same directory as FILENAME. */ + + slash = strrchr (filename, '/'); + if (slash == NULL) + { + prefix = ""; + prefix_len = 0; + } + else + { + slash++; + prefix = filename; + prefix_len = slash - filename; + } + + ddescriptor = elf_try_debugfile (state, prefix, prefix_len, "", 0, + debuglink_name, error_callback, data); + if (ddescriptor >= 0) + { + ret = ddescriptor; + goto done; + } + + /* Look for DEBUGLINK_NAME in a .debug subdirectory of FILENAME. */ + + ddescriptor = elf_try_debugfile (state, prefix, prefix_len, ".debug/", + strlen (".debug/"), debuglink_name, + error_callback, data); + if (ddescriptor >= 0) + { + ret = ddescriptor; + goto done; + } + + /* Look for DEBUGLINK_NAME in /usr/lib/debug. */ + + ddescriptor = elf_try_debugfile (state, "/usr/lib/debug/", + strlen ("/usr/lib/debug/"), prefix, + prefix_len, debuglink_name, + error_callback, data); + if (ddescriptor >= 0) + ret = ddescriptor; + + done: + if (alc != NULL && alc_len > 0) + backtrace_free (state, alc, alc_len, error_callback, data); + return ret; +} + +/* Open a separate debug info file, using the debuglink section data + to find it. Returns an open file descriptor, or -1. */ + +static int +elf_open_debugfile_by_debuglink (struct backtrace_state *state, + const char *filename, + const char *debuglink_name, + uint32_t debuglink_crc, + backtrace_error_callback error_callback, + void *data) +{ + int ddescriptor; + + ddescriptor = elf_find_debugfile_by_debuglink (state, filename, + debuglink_name, + error_callback, data); + if (ddescriptor < 0) + return -1; + + if (debuglink_crc != 0) + { + uint32_t got_crc; + + got_crc = elf_crc32_file (state, ddescriptor, error_callback, data); + if (got_crc != debuglink_crc) + { + backtrace_close (ddescriptor, error_callback, data); + return -1; + } + } + + return ddescriptor; +} + +/* A function useful for setting a breakpoint for an inflation failure + when this code is compiled with -g. */ + +static void +elf_uncompress_failed(void) +{ +} + +/* *PVAL is the current value being read from the stream, and *PBITS + is the number of valid bits. Ensure that *PVAL holds at least 15 + bits by reading additional bits from *PPIN, up to PINEND, as + needed. Updates *PPIN, *PVAL and *PBITS. Returns 1 on success, 0 + on error. */ + +static int +elf_zlib_fetch (const unsigned char **ppin, const unsigned char *pinend, + uint64_t *pval, unsigned int *pbits) +{ + unsigned int bits; + const unsigned char *pin; + uint64_t val; + uint32_t next; + + bits = *pbits; + if (bits >= 15) + return 1; + pin = *ppin; + val = *pval; + + if (unlikely (pinend - pin < 4)) + { + elf_uncompress_failed (); + return 0; + } + +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) \ + && defined(__ORDER_BIG_ENDIAN__) \ + && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ \ + || __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + /* We've ensured that PIN is aligned. */ + next = *(const uint32_t *)pin; + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + next = __builtin_bswap32 (next); +#endif +#else + next = pin[0] | (pin[1] << 8) | (pin[2] << 16) | (pin[3] << 24); +#endif + + val |= (uint64_t)next << bits; + bits += 32; + pin += 4; + + /* We will need the next four bytes soon. */ + __builtin_prefetch (pin, 0, 0); + + *ppin = pin; + *pval = val; + *pbits = bits; + return 1; +} + +/* Huffman code tables, like the rest of the zlib format, are defined + by RFC 1951. We store a Huffman code table as a series of tables + stored sequentially in memory. Each entry in a table is 16 bits. + The first, main, table has 256 entries. It is followed by a set of + secondary tables of length 2 to 128 entries. The maximum length of + a code sequence in the deflate format is 15 bits, so that is all we + need. Each secondary table has an index, which is the offset of + the table in the overall memory storage. + + The deflate format says that all codes of a given bit length are + lexicographically consecutive. Perhaps we could have 130 values + that require a 15-bit code, perhaps requiring three secondary + tables of size 128. I don't know if this is actually possible, but + it suggests that the maximum size required for secondary tables is + 3 * 128 + 3 * 64 ... == 768. The zlib enough program reports 660 + as the maximum. We permit 768, since in addition to the 256 for + the primary table, with two bytes per entry, and with the two + tables we need, that gives us a page. + + A single table entry needs to store a value or (for the main table + only) the index and size of a secondary table. Values range from 0 + to 285, inclusive. Secondary table indexes, per above, range from + 0 to 510. For a value we need to store the number of bits we need + to determine that value (one value may appear multiple times in the + table), which is 1 to 8. For a secondary table we need to store + the number of bits used to index into the table, which is 1 to 7. + And of course we need 1 bit to decide whether we have a value or a + secondary table index. So each entry needs 9 bits for value/table + index, 3 bits for size, 1 bit what it is. For simplicity we use 16 + bits per entry. */ + +/* Number of entries we allocate to for one code table. We get a page + for the two code tables we need. */ + +#define HUFFMAN_TABLE_SIZE (1024) + +/* Bit masks and shifts for the values in the table. */ + +#define HUFFMAN_VALUE_MASK 0x01ff +#define HUFFMAN_BITS_SHIFT 9 +#define HUFFMAN_BITS_MASK 0x7 +#define HUFFMAN_SECONDARY_SHIFT 12 + +/* For working memory while inflating we need two code tables, we need + an array of code lengths (max value 15, so we use unsigned char), + and an array of unsigned shorts used while building a table. The + latter two arrays must be large enough to hold the maximum number + of code lengths, which RFC 1951 defines as 286 + 30. */ + +#define ZDEBUG_TABLE_SIZE \ + (2 * HUFFMAN_TABLE_SIZE * sizeof (uint16_t) \ + + (286 + 30) * sizeof (uint16_t) \ + + (286 + 30) * sizeof (unsigned char)) + +#define ZDEBUG_TABLE_CODELEN_OFFSET \ + (2 * HUFFMAN_TABLE_SIZE * sizeof (uint16_t) \ + + (286 + 30) * sizeof (uint16_t)) + +#define ZDEBUG_TABLE_WORK_OFFSET \ + (2 * HUFFMAN_TABLE_SIZE * sizeof (uint16_t)) + +#ifdef BACKTRACE_GENERATE_FIXED_HUFFMAN_TABLE + +/* Used by the main function that generates the fixed table to learn + the table size. */ +static size_t final_next_secondary; + +#endif + +/* Build a Huffman code table from an array of lengths in CODES of + length CODES_LEN. The table is stored into *TABLE. ZDEBUG_TABLE + is the same as for elf_zlib_inflate, used to find some work space. + Returns 1 on success, 0 on error. */ + +static int +elf_zlib_inflate_table (unsigned char *codes, size_t codes_len, + uint16_t *zdebug_table, uint16_t *table) +{ + uint16_t count[16]; + uint16_t start[16]; + uint16_t prev[16]; + uint16_t firstcode[7]; + uint16_t *next; + size_t i; + size_t j; + unsigned int code; + size_t next_secondary; + + /* Count the number of code of each length. Set NEXT[val] to be the + next value after VAL with the same bit length. */ + + next = (uint16_t *) (((unsigned char *) zdebug_table) + + ZDEBUG_TABLE_WORK_OFFSET); + + memset (&count[0], 0, 16 * sizeof (uint16_t)); + for (i = 0; i < codes_len; ++i) + { + if (unlikely (codes[i] >= 16)) + { + elf_uncompress_failed (); + return 0; + } + + if (count[codes[i]] == 0) + { + start[codes[i]] = i; + prev[codes[i]] = i; + } + else + { + next[prev[codes[i]]] = i; + prev[codes[i]] = i; + } + + ++count[codes[i]]; + } + + /* For each length, fill in the table for the codes of that + length. */ + + memset (table, 0, HUFFMAN_TABLE_SIZE * sizeof (uint16_t)); + + /* Handle the values that do not require a secondary table. */ + + code = 0; + for (j = 1; j <= 8; ++j) + { + unsigned int jcnt; + unsigned int val; + + jcnt = count[j]; + if (jcnt == 0) + continue; + + if (unlikely (jcnt > (1U << j))) + { + elf_uncompress_failed (); + return 0; + } + + /* There are JCNT values that have this length, the values + starting from START[j] continuing through NEXT[VAL]. Those + values are assigned consecutive values starting at CODE. */ + + val = start[j]; + for (i = 0; i < jcnt; ++i) + { + uint16_t tval; + size_t ind; + unsigned int incr; + + /* In the compressed bit stream, the value VAL is encoded as + J bits with the value C. */ + + if (unlikely ((val & ~HUFFMAN_VALUE_MASK) != 0)) + { + elf_uncompress_failed (); + return 0; + } + + tval = val | ((j - 1) << HUFFMAN_BITS_SHIFT); + + /* The table lookup uses 8 bits. If J is less than 8, we + don't know what the other bits will be. We need to fill + in all possibilities in the table. Since the Huffman + code is unambiguous, those entries can't be used for any + other code. */ + + for (ind = code; ind < 0x100; ind += 1 << j) + { + if (unlikely (table[ind] != 0)) + { + elf_uncompress_failed (); + return 0; + } + table[ind] = tval; + } + + /* Advance to the next value with this length. */ + if (i + 1 < jcnt) + val = next[val]; + + /* The Huffman codes are stored in the bitstream with the + most significant bit first, as is required to make them + unambiguous. The effect is that when we read them from + the bitstream we see the bit sequence in reverse order: + the most significant bit of the Huffman code is the least + significant bit of the value we read from the bitstream. + That means that to make our table lookups work, we need + to reverse the bits of CODE. Since reversing bits is + tedious and in general requires using a table, we instead + increment CODE in reverse order. That is, if the number + of bits we are currently using, here named J, is 3, we + count as 000, 100, 010, 110, 001, 101, 011, 111, which is + to say the numbers from 0 to 7 but with the bits + reversed. Going to more bits, aka incrementing J, + effectively just adds more zero bits as the beginning, + and as such does not change the numeric value of CODE. + + To increment CODE of length J in reverse order, find the + most significant zero bit and set it to one while + clearing all higher bits. In other words, add 1 modulo + 2^J, only reversed. */ + + incr = 1U << (j - 1); + while ((code & incr) != 0) + incr >>= 1; + if (incr == 0) + code = 0; + else + { + code &= incr - 1; + code += incr; + } + } + } + + /* Handle the values that require a secondary table. */ + + /* Set FIRSTCODE, the number at which the codes start, for each + length. */ + + for (j = 9; j < 16; j++) + { + unsigned int jcnt; + unsigned int k; + + jcnt = count[j]; + if (jcnt == 0) + continue; + + /* There are JCNT values that have this length, the values + starting from START[j]. Those values are assigned + consecutive values starting at CODE. */ + + firstcode[j - 9] = code; + + /* Reverse add JCNT to CODE modulo 2^J. */ + for (k = 0; k < j; ++k) + { + if ((jcnt & (1U << k)) != 0) + { + unsigned int m; + unsigned int bit; + + bit = 1U << (j - k - 1); + for (m = 0; m < j - k; ++m, bit >>= 1) + { + if ((code & bit) == 0) + { + code += bit; + break; + } + code &= ~bit; + } + jcnt &= ~(1U << k); + } + } + if (unlikely (jcnt != 0)) + { + elf_uncompress_failed (); + return 0; + } + } + + /* For J from 9 to 15, inclusive, we store COUNT[J] consecutive + values starting at START[J] with consecutive codes starting at + FIRSTCODE[J - 9]. In the primary table we need to point to the + secondary table, and the secondary table will be indexed by J - 9 + bits. We count down from 15 so that we install the larger + secondary tables first, as the smaller ones may be embedded in + the larger ones. */ + + next_secondary = 0; /* Index of next secondary table (after primary). */ + for (j = 15; j >= 9; j--) + { + unsigned int jcnt; + unsigned int val; + size_t primary; /* Current primary index. */ + size_t secondary; /* Offset to current secondary table. */ + size_t secondary_bits; /* Bit size of current secondary table. */ + + jcnt = count[j]; + if (jcnt == 0) + continue; + + val = start[j]; + code = firstcode[j - 9]; + primary = 0x100; + secondary = 0; + secondary_bits = 0; + for (i = 0; i < jcnt; ++i) + { + uint16_t tval; + size_t ind; + unsigned int incr; + + if ((code & 0xff) != primary) + { + uint16_t tprimary; + + /* Fill in a new primary table entry. */ + + primary = code & 0xff; + + tprimary = table[primary]; + if (tprimary == 0) + { + /* Start a new secondary table. */ + + if (unlikely ((next_secondary & HUFFMAN_VALUE_MASK) + != next_secondary)) + { + elf_uncompress_failed (); + return 0; + } + + secondary = next_secondary; + secondary_bits = j - 8; + next_secondary += 1 << secondary_bits; + table[primary] = (secondary + + ((j - 8) << HUFFMAN_BITS_SHIFT) + + (1U << HUFFMAN_SECONDARY_SHIFT)); + } + else + { + /* There is an existing entry. It had better be a + secondary table with enough bits. */ + if (unlikely ((tprimary & (1U << HUFFMAN_SECONDARY_SHIFT)) + == 0)) + { + elf_uncompress_failed (); + return 0; + } + secondary = tprimary & HUFFMAN_VALUE_MASK; + secondary_bits = ((tprimary >> HUFFMAN_BITS_SHIFT) + & HUFFMAN_BITS_MASK); + if (unlikely (secondary_bits < j - 8)) + { + elf_uncompress_failed (); + return 0; + } + } + } + + /* Fill in secondary table entries. */ + + tval = val | ((j - 8) << HUFFMAN_BITS_SHIFT); + + for (ind = code >> 8; + ind < (1U << secondary_bits); + ind += 1U << (j - 8)) + { + if (unlikely (table[secondary + 0x100 + ind] != 0)) + { + elf_uncompress_failed (); + return 0; + } + table[secondary + 0x100 + ind] = tval; + } + + if (i + 1 < jcnt) + val = next[val]; + + incr = 1U << (j - 1); + while ((code & incr) != 0) + incr >>= 1; + if (incr == 0) + code = 0; + else + { + code &= incr - 1; + code += incr; + } + } + } + +#ifdef BACKTRACE_GENERATE_FIXED_HUFFMAN_TABLE + final_next_secondary = next_secondary; +#endif + + return 1; +} + +#ifdef BACKTRACE_GENERATE_FIXED_HUFFMAN_TABLE + +/* Used to generate the fixed Huffman table for block type 1. */ + +#include + +static uint16_t table[ZDEBUG_TABLE_SIZE]; +static unsigned char codes[288]; + +int +main () +{ + size_t i; + + for (i = 0; i <= 143; ++i) + codes[i] = 8; + for (i = 144; i <= 255; ++i) + codes[i] = 9; + for (i = 256; i <= 279; ++i) + codes[i] = 7; + for (i = 280; i <= 287; ++i) + codes[i] = 8; + if (!elf_zlib_inflate_table (&codes[0], 288, &table[0], &table[0])) + { + fprintf (stderr, "elf_zlib_inflate_table failed\n"); + exit (EXIT_FAILURE); + } + + printf ("static const uint16_t elf_zlib_default_table[%#zx] =\n", + final_next_secondary + 0x100); + printf ("{\n"); + for (i = 0; i < final_next_secondary + 0x100; i += 8) + { + size_t j; + + printf (" "); + for (j = i; j < final_next_secondary + 0x100 && j < i + 8; ++j) + printf (" %#x,", table[j]); + printf ("\n"); + } + printf ("};\n"); + printf ("\n"); + + for (i = 0; i < 32; ++i) + codes[i] = 5; + if (!elf_zlib_inflate_table (&codes[0], 32, &table[0], &table[0])) + { + fprintf (stderr, "elf_zlib_inflate_table failed\n"); + exit (EXIT_FAILURE); + } + + printf ("static const uint16_t elf_zlib_default_dist_table[%#zx] =\n", + final_next_secondary + 0x100); + printf ("{\n"); + for (i = 0; i < final_next_secondary + 0x100; i += 8) + { + size_t j; + + printf (" "); + for (j = i; j < final_next_secondary + 0x100 && j < i + 8; ++j) + printf (" %#x,", table[j]); + printf ("\n"); + } + printf ("};\n"); + + return 0; +} + +#endif + +/* The fixed tables generated by the #ifdef'ed out main function + above. */ + +static const uint16_t elf_zlib_default_table[0x170] = +{ + 0xd00, 0xe50, 0xe10, 0xf18, 0xd10, 0xe70, 0xe30, 0x1230, + 0xd08, 0xe60, 0xe20, 0x1210, 0xe00, 0xe80, 0xe40, 0x1250, + 0xd04, 0xe58, 0xe18, 0x1200, 0xd14, 0xe78, 0xe38, 0x1240, + 0xd0c, 0xe68, 0xe28, 0x1220, 0xe08, 0xe88, 0xe48, 0x1260, + 0xd02, 0xe54, 0xe14, 0xf1c, 0xd12, 0xe74, 0xe34, 0x1238, + 0xd0a, 0xe64, 0xe24, 0x1218, 0xe04, 0xe84, 0xe44, 0x1258, + 0xd06, 0xe5c, 0xe1c, 0x1208, 0xd16, 0xe7c, 0xe3c, 0x1248, + 0xd0e, 0xe6c, 0xe2c, 0x1228, 0xe0c, 0xe8c, 0xe4c, 0x1268, + 0xd01, 0xe52, 0xe12, 0xf1a, 0xd11, 0xe72, 0xe32, 0x1234, + 0xd09, 0xe62, 0xe22, 0x1214, 0xe02, 0xe82, 0xe42, 0x1254, + 0xd05, 0xe5a, 0xe1a, 0x1204, 0xd15, 0xe7a, 0xe3a, 0x1244, + 0xd0d, 0xe6a, 0xe2a, 0x1224, 0xe0a, 0xe8a, 0xe4a, 0x1264, + 0xd03, 0xe56, 0xe16, 0xf1e, 0xd13, 0xe76, 0xe36, 0x123c, + 0xd0b, 0xe66, 0xe26, 0x121c, 0xe06, 0xe86, 0xe46, 0x125c, + 0xd07, 0xe5e, 0xe1e, 0x120c, 0xd17, 0xe7e, 0xe3e, 0x124c, + 0xd0f, 0xe6e, 0xe2e, 0x122c, 0xe0e, 0xe8e, 0xe4e, 0x126c, + 0xd00, 0xe51, 0xe11, 0xf19, 0xd10, 0xe71, 0xe31, 0x1232, + 0xd08, 0xe61, 0xe21, 0x1212, 0xe01, 0xe81, 0xe41, 0x1252, + 0xd04, 0xe59, 0xe19, 0x1202, 0xd14, 0xe79, 0xe39, 0x1242, + 0xd0c, 0xe69, 0xe29, 0x1222, 0xe09, 0xe89, 0xe49, 0x1262, + 0xd02, 0xe55, 0xe15, 0xf1d, 0xd12, 0xe75, 0xe35, 0x123a, + 0xd0a, 0xe65, 0xe25, 0x121a, 0xe05, 0xe85, 0xe45, 0x125a, + 0xd06, 0xe5d, 0xe1d, 0x120a, 0xd16, 0xe7d, 0xe3d, 0x124a, + 0xd0e, 0xe6d, 0xe2d, 0x122a, 0xe0d, 0xe8d, 0xe4d, 0x126a, + 0xd01, 0xe53, 0xe13, 0xf1b, 0xd11, 0xe73, 0xe33, 0x1236, + 0xd09, 0xe63, 0xe23, 0x1216, 0xe03, 0xe83, 0xe43, 0x1256, + 0xd05, 0xe5b, 0xe1b, 0x1206, 0xd15, 0xe7b, 0xe3b, 0x1246, + 0xd0d, 0xe6b, 0xe2b, 0x1226, 0xe0b, 0xe8b, 0xe4b, 0x1266, + 0xd03, 0xe57, 0xe17, 0xf1f, 0xd13, 0xe77, 0xe37, 0x123e, + 0xd0b, 0xe67, 0xe27, 0x121e, 0xe07, 0xe87, 0xe47, 0x125e, + 0xd07, 0xe5f, 0xe1f, 0x120e, 0xd17, 0xe7f, 0xe3f, 0x124e, + 0xd0f, 0xe6f, 0xe2f, 0x122e, 0xe0f, 0xe8f, 0xe4f, 0x126e, + 0x290, 0x291, 0x292, 0x293, 0x294, 0x295, 0x296, 0x297, + 0x298, 0x299, 0x29a, 0x29b, 0x29c, 0x29d, 0x29e, 0x29f, + 0x2a0, 0x2a1, 0x2a2, 0x2a3, 0x2a4, 0x2a5, 0x2a6, 0x2a7, + 0x2a8, 0x2a9, 0x2aa, 0x2ab, 0x2ac, 0x2ad, 0x2ae, 0x2af, + 0x2b0, 0x2b1, 0x2b2, 0x2b3, 0x2b4, 0x2b5, 0x2b6, 0x2b7, + 0x2b8, 0x2b9, 0x2ba, 0x2bb, 0x2bc, 0x2bd, 0x2be, 0x2bf, + 0x2c0, 0x2c1, 0x2c2, 0x2c3, 0x2c4, 0x2c5, 0x2c6, 0x2c7, + 0x2c8, 0x2c9, 0x2ca, 0x2cb, 0x2cc, 0x2cd, 0x2ce, 0x2cf, + 0x2d0, 0x2d1, 0x2d2, 0x2d3, 0x2d4, 0x2d5, 0x2d6, 0x2d7, + 0x2d8, 0x2d9, 0x2da, 0x2db, 0x2dc, 0x2dd, 0x2de, 0x2df, + 0x2e0, 0x2e1, 0x2e2, 0x2e3, 0x2e4, 0x2e5, 0x2e6, 0x2e7, + 0x2e8, 0x2e9, 0x2ea, 0x2eb, 0x2ec, 0x2ed, 0x2ee, 0x2ef, + 0x2f0, 0x2f1, 0x2f2, 0x2f3, 0x2f4, 0x2f5, 0x2f6, 0x2f7, + 0x2f8, 0x2f9, 0x2fa, 0x2fb, 0x2fc, 0x2fd, 0x2fe, 0x2ff, +}; + +static const uint16_t elf_zlib_default_dist_table[0x100] = +{ + 0x800, 0x810, 0x808, 0x818, 0x804, 0x814, 0x80c, 0x81c, + 0x802, 0x812, 0x80a, 0x81a, 0x806, 0x816, 0x80e, 0x81e, + 0x801, 0x811, 0x809, 0x819, 0x805, 0x815, 0x80d, 0x81d, + 0x803, 0x813, 0x80b, 0x81b, 0x807, 0x817, 0x80f, 0x81f, + 0x800, 0x810, 0x808, 0x818, 0x804, 0x814, 0x80c, 0x81c, + 0x802, 0x812, 0x80a, 0x81a, 0x806, 0x816, 0x80e, 0x81e, + 0x801, 0x811, 0x809, 0x819, 0x805, 0x815, 0x80d, 0x81d, + 0x803, 0x813, 0x80b, 0x81b, 0x807, 0x817, 0x80f, 0x81f, + 0x800, 0x810, 0x808, 0x818, 0x804, 0x814, 0x80c, 0x81c, + 0x802, 0x812, 0x80a, 0x81a, 0x806, 0x816, 0x80e, 0x81e, + 0x801, 0x811, 0x809, 0x819, 0x805, 0x815, 0x80d, 0x81d, + 0x803, 0x813, 0x80b, 0x81b, 0x807, 0x817, 0x80f, 0x81f, + 0x800, 0x810, 0x808, 0x818, 0x804, 0x814, 0x80c, 0x81c, + 0x802, 0x812, 0x80a, 0x81a, 0x806, 0x816, 0x80e, 0x81e, + 0x801, 0x811, 0x809, 0x819, 0x805, 0x815, 0x80d, 0x81d, + 0x803, 0x813, 0x80b, 0x81b, 0x807, 0x817, 0x80f, 0x81f, + 0x800, 0x810, 0x808, 0x818, 0x804, 0x814, 0x80c, 0x81c, + 0x802, 0x812, 0x80a, 0x81a, 0x806, 0x816, 0x80e, 0x81e, + 0x801, 0x811, 0x809, 0x819, 0x805, 0x815, 0x80d, 0x81d, + 0x803, 0x813, 0x80b, 0x81b, 0x807, 0x817, 0x80f, 0x81f, + 0x800, 0x810, 0x808, 0x818, 0x804, 0x814, 0x80c, 0x81c, + 0x802, 0x812, 0x80a, 0x81a, 0x806, 0x816, 0x80e, 0x81e, + 0x801, 0x811, 0x809, 0x819, 0x805, 0x815, 0x80d, 0x81d, + 0x803, 0x813, 0x80b, 0x81b, 0x807, 0x817, 0x80f, 0x81f, + 0x800, 0x810, 0x808, 0x818, 0x804, 0x814, 0x80c, 0x81c, + 0x802, 0x812, 0x80a, 0x81a, 0x806, 0x816, 0x80e, 0x81e, + 0x801, 0x811, 0x809, 0x819, 0x805, 0x815, 0x80d, 0x81d, + 0x803, 0x813, 0x80b, 0x81b, 0x807, 0x817, 0x80f, 0x81f, + 0x800, 0x810, 0x808, 0x818, 0x804, 0x814, 0x80c, 0x81c, + 0x802, 0x812, 0x80a, 0x81a, 0x806, 0x816, 0x80e, 0x81e, + 0x801, 0x811, 0x809, 0x819, 0x805, 0x815, 0x80d, 0x81d, + 0x803, 0x813, 0x80b, 0x81b, 0x807, 0x817, 0x80f, 0x81f, +}; + +/* Inflate a zlib stream from PIN/SIN to POUT/SOUT. Return 1 on + success, 0 on some error parsing the stream. */ + +static int +elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, + unsigned char *pout, size_t sout) +{ + unsigned char *porigout; + const unsigned char *pinend; + unsigned char *poutend; + + /* We can apparently see multiple zlib streams concatenated + together, so keep going as long as there is something to read. + The last 4 bytes are the checksum. */ + porigout = pout; + pinend = pin + sin; + poutend = pout + sout; + while ((pinend - pin) > 4) + { + uint64_t val; + unsigned int bits; + int last; + + /* Read the two byte zlib header. */ + + if (unlikely ((pin[0] & 0xf) != 8)) /* 8 is zlib encoding. */ + { + /* Unknown compression method. */ + elf_uncompress_failed (); + return 0; + } + if (unlikely ((pin[0] >> 4) > 7)) + { + /* Window size too large. Other than this check, we don't + care about the window size. */ + elf_uncompress_failed (); + return 0; + } + if (unlikely ((pin[1] & 0x20) != 0)) + { + /* Stream expects a predefined dictionary, but we have no + dictionary. */ + elf_uncompress_failed (); + return 0; + } + val = (pin[0] << 8) | pin[1]; + if (unlikely (val % 31 != 0)) + { + /* Header check failure. */ + elf_uncompress_failed (); + return 0; + } + pin += 2; + + /* Align PIN to a 32-bit boundary. */ + + val = 0; + bits = 0; + while ((((uintptr_t) pin) & 3) != 0) + { + val |= (uint64_t)*pin << bits; + bits += 8; + ++pin; + } + + /* Read blocks until one is marked last. */ + + last = 0; + + while (!last) + { + unsigned int type; + const uint16_t *tlit; + const uint16_t *tdist; + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + last = val & 1; + type = (val >> 1) & 3; + val >>= 3; + bits -= 3; + + if (unlikely (type == 3)) + { + /* Invalid block type. */ + elf_uncompress_failed (); + return 0; + } + + if (type == 0) + { + uint16_t len; + uint16_t lenc; + + /* An uncompressed block. */ + + /* If we've read ahead more than a byte, back up. */ + while (bits > 8) + { + --pin; + bits -= 8; + } + + val = 0; + bits = 0; + if (unlikely ((pinend - pin) < 4)) + { + /* Missing length. */ + elf_uncompress_failed (); + return 0; + } + len = pin[0] | (pin[1] << 8); + lenc = pin[2] | (pin[3] << 8); + pin += 4; + lenc = ~lenc; + if (unlikely (len != lenc)) + { + /* Corrupt data. */ + elf_uncompress_failed (); + return 0; + } + if (unlikely (len > (unsigned int) (pinend - pin) + || len > (unsigned int) (poutend - pout))) + { + /* Not enough space in buffers. */ + elf_uncompress_failed (); + return 0; + } + memcpy (pout, pin, len); + pout += len; + pin += len; + + /* Align PIN. */ + while ((((uintptr_t) pin) & 3) != 0) + { + val |= (uint64_t)*pin << bits; + bits += 8; + ++pin; + } + + /* Go around to read the next block. */ + continue; + } + + if (type == 1) + { + tlit = elf_zlib_default_table; + tdist = elf_zlib_default_dist_table; + } + else + { + unsigned int nlit; + unsigned int ndist; + unsigned int nclen; + unsigned char codebits[19]; + unsigned char *plenbase; + unsigned char *plen; + unsigned char *plenend; + + /* Read a Huffman encoding table. The various magic + numbers here are from RFC 1951. */ + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + nlit = (val & 0x1f) + 257; + val >>= 5; + ndist = (val & 0x1f) + 1; + val >>= 5; + nclen = (val & 0xf) + 4; + val >>= 4; + bits -= 14; + if (unlikely (nlit > 286 || ndist > 30)) + { + /* Values out of range. */ + elf_uncompress_failed (); + return 0; + } + + /* Read and build the table used to compress the + literal, length, and distance codes. */ + + memset(&codebits[0], 0, 19); + + /* There are always at least 4 elements in the + table. */ + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + codebits[16] = val & 7; + codebits[17] = (val >> 3) & 7; + codebits[18] = (val >> 6) & 7; + codebits[0] = (val >> 9) & 7; + val >>= 12; + bits -= 12; + + if (nclen == 4) + goto codebitsdone; + + codebits[8] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 5) + goto codebitsdone; + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + codebits[7] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 6) + goto codebitsdone; + + codebits[9] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 7) + goto codebitsdone; + + codebits[6] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 8) + goto codebitsdone; + + codebits[10] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 9) + goto codebitsdone; + + codebits[5] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 10) + goto codebitsdone; + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + codebits[11] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 11) + goto codebitsdone; + + codebits[4] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 12) + goto codebitsdone; + + codebits[12] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 13) + goto codebitsdone; + + codebits[3] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 14) + goto codebitsdone; + + codebits[13] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 15) + goto codebitsdone; + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + codebits[2] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 16) + goto codebitsdone; + + codebits[14] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 17) + goto codebitsdone; + + codebits[1] = val & 7; + val >>= 3; + bits -= 3; + + if (nclen == 18) + goto codebitsdone; + + codebits[15] = val & 7; + val >>= 3; + bits -= 3; + + codebitsdone: + + if (!elf_zlib_inflate_table (codebits, 19, zdebug_table, + zdebug_table)) + return 0; + + /* Read the compressed bit lengths of the literal, + length, and distance codes. We have allocated space + at the end of zdebug_table to hold them. */ + + plenbase = (((unsigned char *) zdebug_table) + + ZDEBUG_TABLE_CODELEN_OFFSET); + plen = plenbase; + plenend = plen + nlit + ndist; + while (plen < plenend) + { + uint16_t t; + unsigned int b; + uint16_t v; + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + t = zdebug_table[val & 0xff]; + + /* The compression here uses bit lengths up to 7, so + a secondary table is never necessary. */ + if (unlikely ((t & (1U << HUFFMAN_SECONDARY_SHIFT)) != 0)) + { + elf_uncompress_failed (); + return 0; + } + + b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; + val >>= b + 1; + bits -= b + 1; + + v = t & HUFFMAN_VALUE_MASK; + if (v < 16) + *plen++ = v; + else if (v == 16) + { + unsigned int c; + unsigned int prev; + + /* Copy previous entry 3 to 6 times. */ + + if (unlikely (plen == plenbase)) + { + elf_uncompress_failed (); + return 0; + } + + /* We used up to 7 bits since the last + elf_zlib_fetch, so we have at least 8 bits + available here. */ + + c = 3 + (val & 0x3); + val >>= 2; + bits -= 2; + if (unlikely ((unsigned int) (plenend - plen) < c)) + { + elf_uncompress_failed (); + return 0; + } + + prev = plen[-1]; + switch (c) + { + case 6: + *plen++ = prev; + ATTRIBUTE_FALLTHROUGH; + case 5: + *plen++ = prev; + ATTRIBUTE_FALLTHROUGH; + case 4: + *plen++ = prev; + } + *plen++ = prev; + *plen++ = prev; + *plen++ = prev; + } + else if (v == 17) + { + unsigned int c; + + /* Store zero 3 to 10 times. */ + + /* We used up to 7 bits since the last + elf_zlib_fetch, so we have at least 8 bits + available here. */ + + c = 3 + (val & 0x7); + val >>= 3; + bits -= 3; + if (unlikely ((unsigned int) (plenend - plen) < c)) + { + elf_uncompress_failed (); + return 0; + } + + switch (c) + { + case 10: + *plen++ = 0; + ATTRIBUTE_FALLTHROUGH; + case 9: + *plen++ = 0; + ATTRIBUTE_FALLTHROUGH; + case 8: + *plen++ = 0; + ATTRIBUTE_FALLTHROUGH; + case 7: + *plen++ = 0; + ATTRIBUTE_FALLTHROUGH; + case 6: + *plen++ = 0; + ATTRIBUTE_FALLTHROUGH; + case 5: + *plen++ = 0; + ATTRIBUTE_FALLTHROUGH; + case 4: + *plen++ = 0; + } + *plen++ = 0; + *plen++ = 0; + *plen++ = 0; + } + else if (v == 18) + { + unsigned int c; + + /* Store zero 11 to 138 times. */ + + /* We used up to 7 bits since the last + elf_zlib_fetch, so we have at least 8 bits + available here. */ + + c = 11 + (val & 0x7f); + val >>= 7; + bits -= 7; + if (unlikely ((unsigned int) (plenend - plen) < c)) + { + elf_uncompress_failed (); + return 0; + } + + memset (plen, 0, c); + plen += c; + } + else + { + elf_uncompress_failed (); + return 0; + } + } + + /* Make sure that the stop code can appear. */ + + plen = plenbase; + if (unlikely (plen[256] == 0)) + { + elf_uncompress_failed (); + return 0; + } + + /* Build the decompression tables. */ + + if (!elf_zlib_inflate_table (plen, nlit, zdebug_table, + zdebug_table)) + return 0; + if (!elf_zlib_inflate_table (plen + nlit, ndist, zdebug_table, + zdebug_table + HUFFMAN_TABLE_SIZE)) + return 0; + tlit = zdebug_table; + tdist = zdebug_table + HUFFMAN_TABLE_SIZE; + } + + /* Inflate values until the end of the block. This is the + main loop of the inflation code. */ + + while (1) + { + uint16_t t; + unsigned int b; + uint16_t v; + unsigned int lit; + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + t = tlit[val & 0xff]; + b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; + v = t & HUFFMAN_VALUE_MASK; + + if ((t & (1U << HUFFMAN_SECONDARY_SHIFT)) == 0) + { + lit = v; + val >>= b + 1; + bits -= b + 1; + } + else + { + t = tlit[v + 0x100 + ((val >> 8) & ((1U << b) - 1))]; + b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; + lit = t & HUFFMAN_VALUE_MASK; + val >>= b + 8; + bits -= b + 8; + } + + if (lit < 256) + { + if (unlikely (pout == poutend)) + { + elf_uncompress_failed (); + return 0; + } + + *pout++ = lit; + + /* We will need to write the next byte soon. We ask + for high temporal locality because we will write + to the whole cache line soon. */ + __builtin_prefetch (pout, 1, 3); + } + else if (lit == 256) + { + /* The end of the block. */ + break; + } + else + { + unsigned int dist; + unsigned int len; + + /* Convert lit into a length. */ + + if (lit < 265) + len = lit - 257 + 3; + else if (lit == 285) + len = 258; + else if (unlikely (lit > 285)) + { + elf_uncompress_failed (); + return 0; + } + else + { + unsigned int extra; + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + /* This is an expression for the table of length + codes in RFC 1951 3.2.5. */ + lit -= 265; + extra = (lit >> 2) + 1; + len = (lit & 3) << extra; + len += 11; + len += ((1U << (extra - 1)) - 1) << 3; + len += val & ((1U << extra) - 1); + val >>= extra; + bits -= extra; + } + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + t = tdist[val & 0xff]; + b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; + v = t & HUFFMAN_VALUE_MASK; + + if ((t & (1U << HUFFMAN_SECONDARY_SHIFT)) == 0) + { + dist = v; + val >>= b + 1; + bits -= b + 1; + } + else + { + t = tdist[v + 0x100 + ((val >> 8) & ((1U << b) - 1))]; + b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; + dist = t & HUFFMAN_VALUE_MASK; + val >>= b + 8; + bits -= b + 8; + } + + /* Convert dist to a distance. */ + + if (dist == 0) + { + /* A distance of 1. A common case, meaning + repeat the last character LEN times. */ + + if (unlikely (pout == porigout)) + { + elf_uncompress_failed (); + return 0; + } + + if (unlikely ((unsigned int) (poutend - pout) < len)) + { + elf_uncompress_failed (); + return 0; + } + + memset (pout, pout[-1], len); + pout += len; + } + else if (unlikely (dist > 29)) + { + elf_uncompress_failed (); + return 0; + } + else + { + if (dist < 4) + dist = dist + 1; + else + { + unsigned int extra; + + if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + return 0; + + /* This is an expression for the table of + distance codes in RFC 1951 3.2.5. */ + dist -= 4; + extra = (dist >> 1) + 1; + dist = (dist & 1) << extra; + dist += 5; + dist += ((1U << (extra - 1)) - 1) << 2; + dist += val & ((1U << extra) - 1); + val >>= extra; + bits -= extra; + } + + /* Go back dist bytes, and copy len bytes from + there. */ + + if (unlikely ((unsigned int) (pout - porigout) < dist)) + { + elf_uncompress_failed (); + return 0; + } + + if (unlikely ((unsigned int) (poutend - pout) < len)) + { + elf_uncompress_failed (); + return 0; + } + + if (dist >= len) + { + memcpy (pout, pout - dist, len); + pout += len; + } + else + { + while (len > 0) + { + unsigned int copy; + + copy = len < dist ? len : dist; + memcpy (pout, pout - dist, copy); + len -= copy; + pout += copy; + } + } + } + } + } + } + } + + /* We should have filled the output buffer. */ + if (unlikely (pout != poutend)) + { + elf_uncompress_failed (); + return 0; + } + + return 1; +} + +/* Verify the zlib checksum. The checksum is in the 4 bytes at + CHECKBYTES, and the uncompressed data is at UNCOMPRESSED / + UNCOMPRESSED_SIZE. Returns 1 on success, 0 on failure. */ + +static int +elf_zlib_verify_checksum (const unsigned char *checkbytes, + const unsigned char *uncompressed, + size_t uncompressed_size) +{ + unsigned int i; + unsigned int cksum; + const unsigned char *p; + uint32_t s1; + uint32_t s2; + size_t hsz; + + cksum = 0; + for (i = 0; i < 4; i++) + cksum = (cksum << 8) | checkbytes[i]; + + s1 = 1; + s2 = 0; + + /* Minimize modulo operations. */ + + p = uncompressed; + hsz = uncompressed_size; + while (hsz >= 5552) + { + for (i = 0; i < 5552; i += 16) + { + /* Manually unroll loop 16 times. */ + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + } + hsz -= 5552; + s1 %= 65521; + s2 %= 65521; + } + + while (hsz >= 16) + { + /* Manually unroll loop 16 times. */ + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + s1 = s1 + *p++; + s2 = s2 + s1; + + hsz -= 16; + } + + for (i = 0; i < hsz; ++i) + { + s1 = s1 + *p++; + s2 = s2 + s1; + } + + s1 %= 65521; + s2 %= 65521; + + if (unlikely ((s2 << 16) + s1 != cksum)) + { + elf_uncompress_failed (); + return 0; + } + + return 1; +} + +/* Inflate a zlib stream from PIN/SIN to POUT/SOUT, and verify the + checksum. Return 1 on success, 0 on error. */ + +static int +elf_zlib_inflate_and_verify (const unsigned char *pin, size_t sin, + uint16_t *zdebug_table, unsigned char *pout, + size_t sout) +{ + if (!elf_zlib_inflate (pin, sin, zdebug_table, pout, sout)) + return 0; + if (!elf_zlib_verify_checksum (pin + sin - 4, pout, sout)) + return 0; + return 1; +} + +/* Uncompress the old compressed debug format, the one emitted by + --compress-debug-sections=zlib-gnu. The compressed data is in + COMPRESSED / COMPRESSED_SIZE, and the function writes to + *UNCOMPRESSED / *UNCOMPRESSED_SIZE. ZDEBUG_TABLE is work space to + hold Huffman tables. Returns 0 on error, 1 on successful + decompression or if something goes wrong. In general we try to + carry on, by returning 1, even if we can't decompress. */ + +static int +elf_uncompress_zdebug (struct backtrace_state *state, + const unsigned char *compressed, size_t compressed_size, + uint16_t *zdebug_table, + backtrace_error_callback error_callback, void *data, + unsigned char **uncompressed, size_t *uncompressed_size) +{ + size_t sz; + size_t i; + unsigned char *po; + + *uncompressed = NULL; + *uncompressed_size = 0; + + /* The format starts with the four bytes ZLIB, followed by the 8 + byte length of the uncompressed data in big-endian order, + followed by a zlib stream. */ + + if (compressed_size < 12 || memcmp (compressed, "ZLIB", 4) != 0) + return 1; + + sz = 0; + for (i = 0; i < 8; i++) + sz = (sz << 8) | compressed[i + 4]; + + if (*uncompressed != NULL && *uncompressed_size >= sz) + po = *uncompressed; + else + { + po = (unsigned char *) backtrace_alloc (state, sz, error_callback, data); + if (po == NULL) + return 0; + } + + if (!elf_zlib_inflate_and_verify (compressed + 12, compressed_size - 12, + zdebug_table, po, sz)) + return 1; + + *uncompressed = po; + *uncompressed_size = sz; + + return 1; +} + +/* Uncompress the new compressed debug format, the official standard + ELF approach emitted by --compress-debug-sections=zlib-gabi. The + compressed data is in COMPRESSED / COMPRESSED_SIZE, and the + function writes to *UNCOMPRESSED / *UNCOMPRESSED_SIZE. + ZDEBUG_TABLE is work space as for elf_uncompress_zdebug. Returns 0 + on error, 1 on successful decompression or if something goes wrong. + In general we try to carry on, by returning 1, even if we can't + decompress. */ + +static int +elf_uncompress_chdr (struct backtrace_state *state, + const unsigned char *compressed, size_t compressed_size, + uint16_t *zdebug_table, + backtrace_error_callback error_callback, void *data, + unsigned char **uncompressed, size_t *uncompressed_size) +{ + const b_elf_chdr *chdr; + unsigned char *po; + + *uncompressed = NULL; + *uncompressed_size = 0; + + /* The format starts with an ELF compression header. */ + if (compressed_size < sizeof (b_elf_chdr)) + return 1; + + chdr = (const b_elf_chdr *) compressed; + + if (chdr->ch_type != ELFCOMPRESS_ZLIB) + { + /* Unsupported compression algorithm. */ + return 1; + } + + if (*uncompressed != NULL && *uncompressed_size >= chdr->ch_size) + po = *uncompressed; + else + { + po = (unsigned char *) backtrace_alloc (state, chdr->ch_size, + error_callback, data); + if (po == NULL) + return 0; + } + + if (!elf_zlib_inflate_and_verify (compressed + sizeof (b_elf_chdr), + compressed_size - sizeof (b_elf_chdr), + zdebug_table, po, chdr->ch_size)) + return 1; + + *uncompressed = po; + *uncompressed_size = chdr->ch_size; + + return 1; +} + +/* This function is a hook for testing the zlib support. It is only + used by tests. */ + +int +backtrace_uncompress_zdebug (struct backtrace_state *state, + const unsigned char *compressed, + size_t compressed_size, + backtrace_error_callback error_callback, + void *data, unsigned char **uncompressed, + size_t *uncompressed_size) +{ + uint16_t *zdebug_table; + int ret; + + zdebug_table = ((uint16_t *) backtrace_alloc (state, ZDEBUG_TABLE_SIZE, + error_callback, data)); + if (zdebug_table == NULL) + return 0; + ret = elf_uncompress_zdebug (state, compressed, compressed_size, + zdebug_table, error_callback, data, + uncompressed, uncompressed_size); + backtrace_free (state, zdebug_table, ZDEBUG_TABLE_SIZE, + error_callback, data); + return ret; +} + +/* Number of LZMA states. */ +#define LZMA_STATES (12) + +/* Number of LZMA position states. The pb value of the property byte + is the number of bits to include in these states, and the maximum + value of pb is 4. */ +#define LZMA_POS_STATES (16) + +/* Number of LZMA distance states. These are used match distances + with a short match length: up to 4 bytes. */ +#define LZMA_DIST_STATES (4) + +/* Number of LZMA distance slots. LZMA uses six bits to encode larger + match lengths, so 1 << 6 possible probabilities. */ +#define LZMA_DIST_SLOTS (64) + +/* LZMA distances 0 to 3 are encoded directly, larger values use a + probability model. */ +#define LZMA_DIST_MODEL_START (4) + +/* The LZMA probability model ends at 14. */ +#define LZMA_DIST_MODEL_END (14) + +/* LZMA distance slots for distances less than 127. */ +#define LZMA_FULL_DISTANCES (128) + +/* LZMA uses four alignment bits. */ +#define LZMA_ALIGN_SIZE (16) + +/* LZMA match length is encoded with 4, 5, or 10 bits, some of which + are already known. */ +#define LZMA_LEN_LOW_SYMBOLS (8) +#define LZMA_LEN_MID_SYMBOLS (8) +#define LZMA_LEN_HIGH_SYMBOLS (256) + +/* LZMA literal encoding. */ +#define LZMA_LITERAL_CODERS_MAX (16) +#define LZMA_LITERAL_CODER_SIZE (0x300) + +/* LZMA is based on a large set of probabilities, each managed + independently. Each probability is an 11 bit number that we store + in a uint16_t. We use a single large array of probabilities. */ + +/* Lengths of entries in the LZMA probabilities array. The names used + here are copied from the Linux kernel implementation. */ + +#define LZMA_PROB_IS_MATCH_LEN (LZMA_STATES * LZMA_POS_STATES) +#define LZMA_PROB_IS_REP_LEN LZMA_STATES +#define LZMA_PROB_IS_REP0_LEN LZMA_STATES +#define LZMA_PROB_IS_REP1_LEN LZMA_STATES +#define LZMA_PROB_IS_REP2_LEN LZMA_STATES +#define LZMA_PROB_IS_REP0_LONG_LEN (LZMA_STATES * LZMA_POS_STATES) +#define LZMA_PROB_DIST_SLOT_LEN (LZMA_DIST_STATES * LZMA_DIST_SLOTS) +#define LZMA_PROB_DIST_SPECIAL_LEN (LZMA_FULL_DISTANCES - LZMA_DIST_MODEL_END) +#define LZMA_PROB_DIST_ALIGN_LEN LZMA_ALIGN_SIZE +#define LZMA_PROB_MATCH_LEN_CHOICE_LEN 1 +#define LZMA_PROB_MATCH_LEN_CHOICE2_LEN 1 +#define LZMA_PROB_MATCH_LEN_LOW_LEN (LZMA_POS_STATES * LZMA_LEN_LOW_SYMBOLS) +#define LZMA_PROB_MATCH_LEN_MID_LEN (LZMA_POS_STATES * LZMA_LEN_MID_SYMBOLS) +#define LZMA_PROB_MATCH_LEN_HIGH_LEN LZMA_LEN_HIGH_SYMBOLS +#define LZMA_PROB_REP_LEN_CHOICE_LEN 1 +#define LZMA_PROB_REP_LEN_CHOICE2_LEN 1 +#define LZMA_PROB_REP_LEN_LOW_LEN (LZMA_POS_STATES * LZMA_LEN_LOW_SYMBOLS) +#define LZMA_PROB_REP_LEN_MID_LEN (LZMA_POS_STATES * LZMA_LEN_MID_SYMBOLS) +#define LZMA_PROB_REP_LEN_HIGH_LEN LZMA_LEN_HIGH_SYMBOLS +#define LZMA_PROB_LITERAL_LEN \ + (LZMA_LITERAL_CODERS_MAX * LZMA_LITERAL_CODER_SIZE) + +/* Offsets into the LZMA probabilities array. This is mechanically + generated from the above lengths. */ + +#define LZMA_PROB_IS_MATCH_OFFSET 0 +#define LZMA_PROB_IS_REP_OFFSET \ + (LZMA_PROB_IS_MATCH_OFFSET + LZMA_PROB_IS_MATCH_LEN) +#define LZMA_PROB_IS_REP0_OFFSET \ + (LZMA_PROB_IS_REP_OFFSET + LZMA_PROB_IS_REP_LEN) +#define LZMA_PROB_IS_REP1_OFFSET \ + (LZMA_PROB_IS_REP0_OFFSET + LZMA_PROB_IS_REP0_LEN) +#define LZMA_PROB_IS_REP2_OFFSET \ + (LZMA_PROB_IS_REP1_OFFSET + LZMA_PROB_IS_REP1_LEN) +#define LZMA_PROB_IS_REP0_LONG_OFFSET \ + (LZMA_PROB_IS_REP2_OFFSET + LZMA_PROB_IS_REP2_LEN) +#define LZMA_PROB_DIST_SLOT_OFFSET \ + (LZMA_PROB_IS_REP0_LONG_OFFSET + LZMA_PROB_IS_REP0_LONG_LEN) +#define LZMA_PROB_DIST_SPECIAL_OFFSET \ + (LZMA_PROB_DIST_SLOT_OFFSET + LZMA_PROB_DIST_SLOT_LEN) +#define LZMA_PROB_DIST_ALIGN_OFFSET \ + (LZMA_PROB_DIST_SPECIAL_OFFSET + LZMA_PROB_DIST_SPECIAL_LEN) +#define LZMA_PROB_MATCH_LEN_CHOICE_OFFSET \ + (LZMA_PROB_DIST_ALIGN_OFFSET + LZMA_PROB_DIST_ALIGN_LEN) +#define LZMA_PROB_MATCH_LEN_CHOICE2_OFFSET \ + (LZMA_PROB_MATCH_LEN_CHOICE_OFFSET + LZMA_PROB_MATCH_LEN_CHOICE_LEN) +#define LZMA_PROB_MATCH_LEN_LOW_OFFSET \ + (LZMA_PROB_MATCH_LEN_CHOICE2_OFFSET + LZMA_PROB_MATCH_LEN_CHOICE2_LEN) +#define LZMA_PROB_MATCH_LEN_MID_OFFSET \ + (LZMA_PROB_MATCH_LEN_LOW_OFFSET + LZMA_PROB_MATCH_LEN_LOW_LEN) +#define LZMA_PROB_MATCH_LEN_HIGH_OFFSET \ + (LZMA_PROB_MATCH_LEN_MID_OFFSET + LZMA_PROB_MATCH_LEN_MID_LEN) +#define LZMA_PROB_REP_LEN_CHOICE_OFFSET \ + (LZMA_PROB_MATCH_LEN_HIGH_OFFSET + LZMA_PROB_MATCH_LEN_HIGH_LEN) +#define LZMA_PROB_REP_LEN_CHOICE2_OFFSET \ + (LZMA_PROB_REP_LEN_CHOICE_OFFSET + LZMA_PROB_REP_LEN_CHOICE_LEN) +#define LZMA_PROB_REP_LEN_LOW_OFFSET \ + (LZMA_PROB_REP_LEN_CHOICE2_OFFSET + LZMA_PROB_REP_LEN_CHOICE2_LEN) +#define LZMA_PROB_REP_LEN_MID_OFFSET \ + (LZMA_PROB_REP_LEN_LOW_OFFSET + LZMA_PROB_REP_LEN_LOW_LEN) +#define LZMA_PROB_REP_LEN_HIGH_OFFSET \ + (LZMA_PROB_REP_LEN_MID_OFFSET + LZMA_PROB_REP_LEN_MID_LEN) +#define LZMA_PROB_LITERAL_OFFSET \ + (LZMA_PROB_REP_LEN_HIGH_OFFSET + LZMA_PROB_REP_LEN_HIGH_LEN) + +#define LZMA_PROB_TOTAL_COUNT \ + (LZMA_PROB_LITERAL_OFFSET + LZMA_PROB_LITERAL_LEN) + +/* Check that the number of LZMA probabilities is the same as the + Linux kernel implementation. */ + +#if LZMA_PROB_TOTAL_COUNT != 1846 + (1 << 4) * 0x300 + #error Wrong number of LZMA probabilities +#endif + +/* Expressions for the offset in the LZMA probabilities array of a + specific probability. */ + +#define LZMA_IS_MATCH(state, pos) \ + (LZMA_PROB_IS_MATCH_OFFSET + (state) * LZMA_POS_STATES + (pos)) +#define LZMA_IS_REP(state) \ + (LZMA_PROB_IS_REP_OFFSET + (state)) +#define LZMA_IS_REP0(state) \ + (LZMA_PROB_IS_REP0_OFFSET + (state)) +#define LZMA_IS_REP1(state) \ + (LZMA_PROB_IS_REP1_OFFSET + (state)) +#define LZMA_IS_REP2(state) \ + (LZMA_PROB_IS_REP2_OFFSET + (state)) +#define LZMA_IS_REP0_LONG(state, pos) \ + (LZMA_PROB_IS_REP0_LONG_OFFSET + (state) * LZMA_POS_STATES + (pos)) +#define LZMA_DIST_SLOT(dist, slot) \ + (LZMA_PROB_DIST_SLOT_OFFSET + (dist) * LZMA_DIST_SLOTS + (slot)) +#define LZMA_DIST_SPECIAL(dist) \ + (LZMA_PROB_DIST_SPECIAL_OFFSET + (dist)) +#define LZMA_DIST_ALIGN(dist) \ + (LZMA_PROB_DIST_ALIGN_OFFSET + (dist)) +#define LZMA_MATCH_LEN_CHOICE \ + LZMA_PROB_MATCH_LEN_CHOICE_OFFSET +#define LZMA_MATCH_LEN_CHOICE2 \ + LZMA_PROB_MATCH_LEN_CHOICE2_OFFSET +#define LZMA_MATCH_LEN_LOW(pos, sym) \ + (LZMA_PROB_MATCH_LEN_LOW_OFFSET + (pos) * LZMA_LEN_LOW_SYMBOLS + (sym)) +#define LZMA_MATCH_LEN_MID(pos, sym) \ + (LZMA_PROB_MATCH_LEN_MID_OFFSET + (pos) * LZMA_LEN_MID_SYMBOLS + (sym)) +#define LZMA_MATCH_LEN_HIGH(sym) \ + (LZMA_PROB_MATCH_LEN_HIGH_OFFSET + (sym)) +#define LZMA_REP_LEN_CHOICE \ + LZMA_PROB_REP_LEN_CHOICE_OFFSET +#define LZMA_REP_LEN_CHOICE2 \ + LZMA_PROB_REP_LEN_CHOICE2_OFFSET +#define LZMA_REP_LEN_LOW(pos, sym) \ + (LZMA_PROB_REP_LEN_LOW_OFFSET + (pos) * LZMA_LEN_LOW_SYMBOLS + (sym)) +#define LZMA_REP_LEN_MID(pos, sym) \ + (LZMA_PROB_REP_LEN_MID_OFFSET + (pos) * LZMA_LEN_MID_SYMBOLS + (sym)) +#define LZMA_REP_LEN_HIGH(sym) \ + (LZMA_PROB_REP_LEN_HIGH_OFFSET + (sym)) +#define LZMA_LITERAL(code, size) \ + (LZMA_PROB_LITERAL_OFFSET + (code) * LZMA_LITERAL_CODER_SIZE + (size)) + +/* Read an LZMA varint from BUF, reading and updating *POFFSET, + setting *VAL. Returns 0 on error, 1 on success. */ + +static int +elf_lzma_varint (const unsigned char *compressed, size_t compressed_size, + size_t *poffset, uint64_t *val) +{ + size_t off; + int i; + uint64_t v; + unsigned char b; + + off = *poffset; + i = 0; + v = 0; + while (1) + { + if (unlikely (off >= compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + b = compressed[off]; + v |= (b & 0x7f) << (i * 7); + ++off; + if ((b & 0x80) == 0) + { + *poffset = off; + *val = v; + return 1; + } + ++i; + if (unlikely (i >= 9)) + { + elf_uncompress_failed (); + return 0; + } + } +} + +/* Normalize the LZMA range decoder, pulling in an extra input byte if + needed. */ + +static void +elf_lzma_range_normalize (const unsigned char *compressed, + size_t compressed_size, size_t *poffset, + uint32_t *prange, uint32_t *pcode) +{ + if (*prange < (1U << 24)) + { + if (unlikely (*poffset >= compressed_size)) + { + /* We assume this will be caught elsewhere. */ + elf_uncompress_failed (); + return; + } + *prange <<= 8; + *pcode <<= 8; + *pcode += compressed[*poffset]; + ++*poffset; + } +} + +/* Read and return a single bit from the LZMA stream, reading and + updating *PROB. Each bit comes from the range coder. */ + +static int +elf_lzma_bit (const unsigned char *compressed, size_t compressed_size, + uint16_t *prob, size_t *poffset, uint32_t *prange, + uint32_t *pcode) +{ + uint32_t bound; + + elf_lzma_range_normalize (compressed, compressed_size, poffset, + prange, pcode); + bound = (*prange >> 11) * (uint32_t) *prob; + if (*pcode < bound) + { + *prange = bound; + *prob += ((1U << 11) - *prob) >> 5; + return 0; + } + else + { + *prange -= bound; + *pcode -= bound; + *prob -= *prob >> 5; + return 1; + } +} + +/* Read an integer of size BITS from the LZMA stream, most significant + bit first. The bits are predicted using PROBS. */ + +static uint32_t +elf_lzma_integer (const unsigned char *compressed, size_t compressed_size, + uint16_t *probs, uint32_t bits, size_t *poffset, + uint32_t *prange, uint32_t *pcode) +{ + uint32_t sym; + uint32_t i; + + sym = 1; + for (i = 0; i < bits; i++) + { + int bit; + + bit = elf_lzma_bit (compressed, compressed_size, probs + sym, poffset, + prange, pcode); + sym <<= 1; + sym += bit; + } + return sym - (1 << bits); +} + +/* Read an integer of size BITS from the LZMA stream, least + significant bit first. The bits are predicted using PROBS. */ + +static uint32_t +elf_lzma_reverse_integer (const unsigned char *compressed, + size_t compressed_size, uint16_t *probs, + uint32_t bits, size_t *poffset, uint32_t *prange, + uint32_t *pcode) +{ + uint32_t sym; + uint32_t val; + uint32_t i; + + sym = 1; + val = 0; + for (i = 0; i < bits; i++) + { + int bit; + + bit = elf_lzma_bit (compressed, compressed_size, probs + sym, poffset, + prange, pcode); + sym <<= 1; + sym += bit; + val += bit << i; + } + return val; +} + +/* Read a length from the LZMA stream. IS_REP picks either LZMA_MATCH + or LZMA_REP probabilities. */ + +static uint32_t +elf_lzma_len (const unsigned char *compressed, size_t compressed_size, + uint16_t *probs, int is_rep, unsigned int pos_state, + size_t *poffset, uint32_t *prange, uint32_t *pcode) +{ + uint16_t *probs_choice; + uint16_t *probs_sym; + uint32_t bits; + uint32_t len; + + probs_choice = probs + (is_rep + ? LZMA_REP_LEN_CHOICE + : LZMA_MATCH_LEN_CHOICE); + if (elf_lzma_bit (compressed, compressed_size, probs_choice, poffset, + prange, pcode)) + { + probs_choice = probs + (is_rep + ? LZMA_REP_LEN_CHOICE2 + : LZMA_MATCH_LEN_CHOICE2); + if (elf_lzma_bit (compressed, compressed_size, probs_choice, + poffset, prange, pcode)) + { + probs_sym = probs + (is_rep + ? LZMA_REP_LEN_HIGH (0) + : LZMA_MATCH_LEN_HIGH (0)); + bits = 8; + len = 2 + 8 + 8; + } + else + { + probs_sym = probs + (is_rep + ? LZMA_REP_LEN_MID (pos_state, 0) + : LZMA_MATCH_LEN_MID (pos_state, 0)); + bits = 3; + len = 2 + 8; + } + } + else + { + probs_sym = probs + (is_rep + ? LZMA_REP_LEN_LOW (pos_state, 0) + : LZMA_MATCH_LEN_LOW (pos_state, 0)); + bits = 3; + len = 2; + } + + len += elf_lzma_integer (compressed, compressed_size, probs_sym, bits, + poffset, prange, pcode); + return len; +} + +/* Uncompress one LZMA block from a minidebug file. The compressed + data is at COMPRESSED + *POFFSET. Update *POFFSET. Store the data + into the memory at UNCOMPRESSED, size UNCOMPRESSED_SIZE. CHECK is + the stream flag from the xz header. Return 1 on successful + decompression. */ + +static int +elf_uncompress_lzma_block (const unsigned char *compressed, + size_t compressed_size, unsigned char check, + uint16_t *probs, unsigned char *uncompressed, + size_t uncompressed_size, size_t *poffset) +{ + size_t off; + size_t block_header_offset; + size_t block_header_size; + unsigned char block_flags; + uint64_t header_compressed_size; + uint64_t header_uncompressed_size; + unsigned char lzma2_properties; + uint32_t computed_crc; + uint32_t stream_crc; + size_t uncompressed_offset; + size_t dict_start_offset; + unsigned int lc; + unsigned int lp; + unsigned int pb; + uint32_t range; + uint32_t code; + uint32_t lstate; + uint32_t dist[4]; + + off = *poffset; + block_header_offset = off; + + /* Block header size is a single byte. */ + if (unlikely (off >= compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + block_header_size = (compressed[off] + 1) * 4; + if (unlikely (off + block_header_size > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + /* Block flags. */ + block_flags = compressed[off + 1]; + if (unlikely ((block_flags & 0x3c) != 0)) + { + elf_uncompress_failed (); + return 0; + } + + off += 2; + + /* Optional compressed size. */ + header_compressed_size = 0; + if ((block_flags & 0x40) != 0) + { + *poffset = off; + if (!elf_lzma_varint (compressed, compressed_size, poffset, + &header_compressed_size)) + return 0; + off = *poffset; + } + + /* Optional uncompressed size. */ + header_uncompressed_size = 0; + if ((block_flags & 0x80) != 0) + { + *poffset = off; + if (!elf_lzma_varint (compressed, compressed_size, poffset, + &header_uncompressed_size)) + return 0; + off = *poffset; + } + + /* The recipe for creating a minidebug file is to run the xz program + with no arguments, so we expect exactly one filter: lzma2. */ + + if (unlikely ((block_flags & 0x3) != 0)) + { + elf_uncompress_failed (); + return 0; + } + + if (unlikely (off + 2 >= block_header_offset + block_header_size)) + { + elf_uncompress_failed (); + return 0; + } + + /* The filter ID for LZMA2 is 0x21. */ + if (unlikely (compressed[off] != 0x21)) + { + elf_uncompress_failed (); + return 0; + } + ++off; + + /* The size of the filter properties for LZMA2 is 1. */ + if (unlikely (compressed[off] != 1)) + { + elf_uncompress_failed (); + return 0; + } + ++off; + + lzma2_properties = compressed[off]; + ++off; + + if (unlikely (lzma2_properties > 40)) + { + elf_uncompress_failed (); + return 0; + } + + /* The properties describe the dictionary size, but we don't care + what that is. */ + + /* Block header padding. */ + if (unlikely (off + 4 > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + off = (off + 3) &~ (size_t) 3; + + if (unlikely (off + 4 > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + /* Block header CRC. */ + computed_crc = elf_crc32 (0, compressed + block_header_offset, + block_header_size - 4); + stream_crc = (compressed[off] + | (compressed[off + 1] << 8) + | (compressed[off + 2] << 16) + | (compressed[off + 3] << 24)); + if (unlikely (computed_crc != stream_crc)) + { + elf_uncompress_failed (); + return 0; + } + off += 4; + + /* Read a sequence of LZMA2 packets. */ + + uncompressed_offset = 0; + dict_start_offset = 0; + lc = 0; + lp = 0; + pb = 0; + lstate = 0; + while (off < compressed_size) + { + unsigned char control; + + range = 0xffffffff; + code = 0; + + control = compressed[off]; + ++off; + if (unlikely (control == 0)) + { + /* End of packets. */ + break; + } + + if (control == 1 || control >= 0xe0) + { + /* Reset dictionary to empty. */ + dict_start_offset = uncompressed_offset; + } + + if (control < 0x80) + { + size_t chunk_size; + + /* The only valid values here are 1 or 2. A 1 means to + reset the dictionary (done above). Then we see an + uncompressed chunk. */ + + if (unlikely (control > 2)) + { + elf_uncompress_failed (); + return 0; + } + + /* An uncompressed chunk is a two byte size followed by + data. */ + + if (unlikely (off + 2 > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + chunk_size = compressed[off] << 8; + chunk_size += compressed[off + 1]; + ++chunk_size; + + off += 2; + + if (unlikely (off + chunk_size > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + if (unlikely (uncompressed_offset + chunk_size > uncompressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + memcpy (uncompressed + uncompressed_offset, compressed + off, + chunk_size); + uncompressed_offset += chunk_size; + off += chunk_size; + } + else + { + size_t uncompressed_chunk_start; + size_t uncompressed_chunk_size; + size_t compressed_chunk_size; + size_t limit; + + /* An LZMA chunk. This starts with an uncompressed size and + a compressed size. */ + + if (unlikely (off + 4 >= compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + uncompressed_chunk_start = uncompressed_offset; + + uncompressed_chunk_size = (control & 0x1f) << 16; + uncompressed_chunk_size += compressed[off] << 8; + uncompressed_chunk_size += compressed[off + 1]; + ++uncompressed_chunk_size; + + compressed_chunk_size = compressed[off + 2] << 8; + compressed_chunk_size += compressed[off + 3]; + ++compressed_chunk_size; + + off += 4; + + /* Bit 7 (0x80) is set. + Bits 6 and 5 (0x40 and 0x20) are as follows: + 0: don't reset anything + 1: reset state + 2: reset state, read properties + 3: reset state, read properties, reset dictionary (done above) */ + + if (control >= 0xc0) + { + unsigned char props; + + /* Bit 6 is set, read properties. */ + + if (unlikely (off >= compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + props = compressed[off]; + ++off; + if (unlikely (props > (4 * 5 + 4) * 9 + 8)) + { + elf_uncompress_failed (); + return 0; + } + pb = 0; + while (props >= 9 * 5) + { + props -= 9 * 5; + ++pb; + } + lp = 0; + while (props > 9) + { + props -= 9; + ++lp; + } + lc = props; + if (unlikely (lc + lp > 4)) + { + elf_uncompress_failed (); + return 0; + } + } + + if (control >= 0xa0) + { + size_t i; + + /* Bit 5 or 6 is set, reset LZMA state. */ + + lstate = 0; + memset (&dist, 0, sizeof dist); + for (i = 0; i < LZMA_PROB_TOTAL_COUNT; i++) + probs[i] = 1 << 10; + range = 0xffffffff; + code = 0; + } + + /* Read the range code. */ + + if (unlikely (off + 5 > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + /* The byte at compressed[off] is ignored for some + reason. */ + + code = ((compressed[off + 1] << 24) + + (compressed[off + 2] << 16) + + (compressed[off + 3] << 8) + + compressed[off + 4]); + off += 5; + + /* This is the main LZMA decode loop. */ + + limit = off + compressed_chunk_size; + *poffset = off; + while (*poffset < limit) + { + unsigned int pos_state; + + if (unlikely (uncompressed_offset + == (uncompressed_chunk_start + + uncompressed_chunk_size))) + { + /* We've decompressed all the expected bytes. */ + break; + } + + pos_state = ((uncompressed_offset - dict_start_offset) + & ((1 << pb) - 1)); + + if (elf_lzma_bit (compressed, compressed_size, + probs + LZMA_IS_MATCH (lstate, pos_state), + poffset, &range, &code)) + { + uint32_t len; + + if (elf_lzma_bit (compressed, compressed_size, + probs + LZMA_IS_REP (lstate), + poffset, &range, &code)) + { + int short_rep; + uint32_t next_dist; + + /* Repeated match. */ + + short_rep = 0; + if (elf_lzma_bit (compressed, compressed_size, + probs + LZMA_IS_REP0 (lstate), + poffset, &range, &code)) + { + if (elf_lzma_bit (compressed, compressed_size, + probs + LZMA_IS_REP1 (lstate), + poffset, &range, &code)) + { + if (elf_lzma_bit (compressed, compressed_size, + probs + LZMA_IS_REP2 (lstate), + poffset, &range, &code)) + { + next_dist = dist[3]; + dist[3] = dist[2]; + } + else + { + next_dist = dist[2]; + } + dist[2] = dist[1]; + } + else + { + next_dist = dist[1]; + } + + dist[1] = dist[0]; + dist[0] = next_dist; + } + else + { + if (!elf_lzma_bit (compressed, compressed_size, + (probs + + LZMA_IS_REP0_LONG (lstate, + pos_state)), + poffset, &range, &code)) + short_rep = 1; + } + + if (lstate < 7) + lstate = short_rep ? 9 : 8; + else + lstate = 11; + + if (short_rep) + len = 1; + else + len = elf_lzma_len (compressed, compressed_size, + probs, 1, pos_state, poffset, + &range, &code); + } + else + { + uint32_t dist_state; + uint32_t dist_slot; + uint16_t *probs_dist; + + /* Match. */ + + if (lstate < 7) + lstate = 7; + else + lstate = 10; + dist[3] = dist[2]; + dist[2] = dist[1]; + dist[1] = dist[0]; + len = elf_lzma_len (compressed, compressed_size, + probs, 0, pos_state, poffset, + &range, &code); + + if (len < 4 + 2) + dist_state = len - 2; + else + dist_state = 3; + probs_dist = probs + LZMA_DIST_SLOT (dist_state, 0); + dist_slot = elf_lzma_integer (compressed, + compressed_size, + probs_dist, 6, + poffset, &range, + &code); + if (dist_slot < LZMA_DIST_MODEL_START) + dist[0] = dist_slot; + else + { + uint32_t limit; + + limit = (dist_slot >> 1) - 1; + dist[0] = 2 + (dist_slot & 1); + if (dist_slot < LZMA_DIST_MODEL_END) + { + dist[0] <<= limit; + probs_dist = (probs + + LZMA_DIST_SPECIAL(dist[0] + - dist_slot + - 1)); + dist[0] += + elf_lzma_reverse_integer (compressed, + compressed_size, + probs_dist, + limit, poffset, + &range, &code); + } + else + { + uint32_t dist0; + uint32_t i; + + dist0 = dist[0]; + for (i = 0; i < limit - 4; i++) + { + uint32_t mask; + + elf_lzma_range_normalize (compressed, + compressed_size, + poffset, + &range, &code); + range >>= 1; + code -= range; + mask = -(code >> 31); + code += range & mask; + dist0 <<= 1; + dist0 += mask + 1; + } + dist0 <<= 4; + probs_dist = probs + LZMA_DIST_ALIGN (0); + dist0 += + elf_lzma_reverse_integer (compressed, + compressed_size, + probs_dist, 4, + poffset, + &range, &code); + dist[0] = dist0; + } + } + } + + if (unlikely (uncompressed_offset + - dict_start_offset < dist[0] + 1)) + { + elf_uncompress_failed (); + return 0; + } + if (unlikely (uncompressed_offset + len > uncompressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + if (dist[0] == 0) + { + /* A common case, meaning repeat the last + character LEN times. */ + memset (uncompressed + uncompressed_offset, + uncompressed[uncompressed_offset - 1], + len); + uncompressed_offset += len; + } + else if (dist[0] + 1 >= len) + { + memcpy (uncompressed + uncompressed_offset, + uncompressed + uncompressed_offset - dist[0] - 1, + len); + uncompressed_offset += len; + } + else + { + while (len > 0) + { + uint32_t copy; + + copy = len < dist[0] + 1 ? len : dist[0] + 1; + memcpy (uncompressed + uncompressed_offset, + (uncompressed + uncompressed_offset + - dist[0] - 1), + copy); + len -= copy; + uncompressed_offset += copy; + } + } + } + else + { + unsigned char prev; + unsigned char low; + size_t high; + uint16_t *lit_probs; + unsigned int sym; + + /* Literal value. */ + + if (uncompressed_offset > 0) + prev = uncompressed[uncompressed_offset - 1]; + else + prev = 0; + low = prev >> (8 - lc); + high = (((uncompressed_offset - dict_start_offset) + & ((1 << lp) - 1)) + << lc); + lit_probs = probs + LZMA_LITERAL (low + high, 0); + if (lstate < 7) + sym = elf_lzma_integer (compressed, compressed_size, + lit_probs, 8, poffset, &range, + &code); + else + { + unsigned int match; + unsigned int bit; + unsigned int match_bit; + unsigned int idx; + + sym = 1; + if (uncompressed_offset >= dist[0] + 1) + match = uncompressed[uncompressed_offset - dist[0] - 1]; + else + match = 0; + match <<= 1; + bit = 0x100; + do + { + match_bit = match & bit; + match <<= 1; + idx = bit + match_bit + sym; + sym <<= 1; + if (elf_lzma_bit (compressed, compressed_size, + lit_probs + idx, poffset, + &range, &code)) + { + ++sym; + bit &= match_bit; + } + else + { + bit &= ~ match_bit; + } + } + while (sym < 0x100); + } + + if (unlikely (uncompressed_offset >= uncompressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + uncompressed[uncompressed_offset] = (unsigned char) sym; + ++uncompressed_offset; + if (lstate <= 3) + lstate = 0; + else if (lstate <= 9) + lstate -= 3; + else + lstate -= 6; + } + } + + elf_lzma_range_normalize (compressed, compressed_size, poffset, + &range, &code); + + off = *poffset; + } + } + + /* We have reached the end of the block. Pad to four byte + boundary. */ + off = (off + 3) &~ (size_t) 3; + if (unlikely (off > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + + switch (check) + { + case 0: + /* No check. */ + break; + + case 1: + /* CRC32 */ + if (unlikely (off + 4 > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + computed_crc = elf_crc32 (0, uncompressed, uncompressed_offset); + stream_crc = (compressed[off] + | (compressed[off + 1] << 8) + | (compressed[off + 2] << 16) + | (compressed[off + 3] << 24)); + if (computed_crc != stream_crc) + { + elf_uncompress_failed (); + return 0; + } + off += 4; + break; + + case 4: + /* CRC64. We don't bother computing a CRC64 checksum. */ + if (unlikely (off + 8 > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + off += 8; + break; + + case 10: + /* SHA. We don't bother computing a SHA checksum. */ + if (unlikely (off + 32 > compressed_size)) + { + elf_uncompress_failed (); + return 0; + } + off += 32; + break; + + default: + elf_uncompress_failed (); + return 0; + } + + *poffset = off; + + return 1; +} + +/* Uncompress LZMA data found in a minidebug file. The minidebug + format is described at + https://sourceware.org/gdb/current/onlinedocs/gdb/MiniDebugInfo.html. + Returns 0 on error, 1 on successful decompression. For this + function we return 0 on failure to decompress, as the calling code + will carry on in that case. */ + +static int +elf_uncompress_lzma (struct backtrace_state *state, + const unsigned char *compressed, size_t compressed_size, + backtrace_error_callback error_callback, void *data, + unsigned char **uncompressed, size_t *uncompressed_size) +{ + size_t header_size; + size_t footer_size; + unsigned char check; + uint32_t computed_crc; + uint32_t stream_crc; + size_t offset; + size_t index_size; + size_t footer_offset; + size_t index_offset; + uint64_t index_compressed_size; + uint64_t index_uncompressed_size; + unsigned char *mem; + uint16_t *probs; + size_t compressed_block_size; + + /* The format starts with a stream header and ends with a stream + footer. */ + header_size = 12; + footer_size = 12; + if (unlikely (compressed_size < header_size + footer_size)) + { + elf_uncompress_failed (); + return 0; + } + + /* The stream header starts with a magic string. */ + if (unlikely (memcmp (compressed, "\375" "7zXZ\0", 6) != 0)) + { + elf_uncompress_failed (); + return 0; + } + + /* Next come stream flags. The first byte is zero, the second byte + is the check. */ + if (unlikely (compressed[6] != 0)) + { + elf_uncompress_failed (); + return 0; + } + check = compressed[7]; + if (unlikely ((check & 0xf8) != 0)) + { + elf_uncompress_failed (); + return 0; + } + + /* Next comes a CRC of the stream flags. */ + computed_crc = elf_crc32 (0, compressed + 6, 2); + stream_crc = (compressed[8] + | (compressed[9] << 8) + | (compressed[10] << 16) + | (compressed[11] << 24)); + if (unlikely (computed_crc != stream_crc)) + { + elf_uncompress_failed (); + return 0; + } + + /* Now that we've parsed the header, parse the footer, so that we + can get the uncompressed size. */ + + /* The footer ends with two magic bytes. */ + + offset = compressed_size; + if (unlikely (memcmp (compressed + offset - 2, "YZ", 2) != 0)) + { + elf_uncompress_failed (); + return 0; + } + offset -= 2; + + /* Before that are the stream flags, which should be the same as the + flags in the header. */ + if (unlikely (compressed[offset - 2] != 0 + || compressed[offset - 1] != check)) + { + elf_uncompress_failed (); + return 0; + } + offset -= 2; + + /* Before that is the size of the index field, which precedes the + footer. */ + index_size = (compressed[offset - 4] + | (compressed[offset - 3] << 8) + | (compressed[offset - 2] << 16) + | (compressed[offset - 1] << 24)); + index_size = (index_size + 1) * 4; + offset -= 4; + + /* Before that is a footer CRC. */ + computed_crc = elf_crc32 (0, compressed + offset, 6); + stream_crc = (compressed[offset - 4] + | (compressed[offset - 3] << 8) + | (compressed[offset - 2] << 16) + | (compressed[offset - 1] << 24)); + if (unlikely (computed_crc != stream_crc)) + { + elf_uncompress_failed (); + return 0; + } + offset -= 4; + + /* The index comes just before the footer. */ + if (unlikely (offset < index_size + header_size)) + { + elf_uncompress_failed (); + return 0; + } + + footer_offset = offset; + offset -= index_size; + index_offset = offset; + + /* The index starts with a zero byte. */ + if (unlikely (compressed[offset] != 0)) + { + elf_uncompress_failed (); + return 0; + } + ++offset; + + /* Next is the number of blocks. We expect zero blocks for an empty + stream, and otherwise a single block. */ + if (unlikely (compressed[offset] == 0)) + { + *uncompressed = NULL; + *uncompressed_size = 0; + return 1; + } + if (unlikely (compressed[offset] != 1)) + { + elf_uncompress_failed (); + return 0; + } + ++offset; + + /* Next is the compressed size and the uncompressed size. */ + if (!elf_lzma_varint (compressed, compressed_size, &offset, + &index_compressed_size)) + return 0; + if (!elf_lzma_varint (compressed, compressed_size, &offset, + &index_uncompressed_size)) + return 0; + + /* Pad to a four byte boundary. */ + offset = (offset + 3) &~ (size_t) 3; + + /* Next is a CRC of the index. */ + computed_crc = elf_crc32 (0, compressed + index_offset, + offset - index_offset); + stream_crc = (compressed[offset] + | (compressed[offset + 1] << 8) + | (compressed[offset + 2] << 16) + | (compressed[offset + 3] << 24)); + if (unlikely (computed_crc != stream_crc)) + { + elf_uncompress_failed (); + return 0; + } + offset += 4; + + /* We should now be back at the footer. */ + if (unlikely (offset != footer_offset)) + { + elf_uncompress_failed (); + return 0; + } + + /* Allocate space to hold the uncompressed data. If we succeed in + uncompressing the LZMA data, we never free this memory. */ + mem = (unsigned char *) backtrace_alloc (state, index_uncompressed_size, + error_callback, data); + if (unlikely (mem == NULL)) + return 0; + *uncompressed = mem; + *uncompressed_size = index_uncompressed_size; + + /* Allocate space for probabilities. */ + probs = ((uint16_t *) + backtrace_alloc (state, + LZMA_PROB_TOTAL_COUNT * sizeof (uint16_t), + error_callback, data)); + if (unlikely (probs == NULL)) + { + backtrace_free (state, mem, index_uncompressed_size, error_callback, + data); + return 0; + } + + /* Uncompress the block, which follows the header. */ + offset = 12; + if (!elf_uncompress_lzma_block (compressed, compressed_size, check, probs, + mem, index_uncompressed_size, &offset)) + { + backtrace_free (state, mem, index_uncompressed_size, error_callback, + data); + return 0; + } + + compressed_block_size = offset - 12; + if (unlikely (compressed_block_size + != ((index_compressed_size + 3) &~ (size_t) 3))) + { + elf_uncompress_failed (); + backtrace_free (state, mem, index_uncompressed_size, error_callback, + data); + return 0; + } + + offset = (offset + 3) &~ (size_t) 3; + if (unlikely (offset != index_offset)) + { + elf_uncompress_failed (); + backtrace_free (state, mem, index_uncompressed_size, error_callback, + data); + return 0; + } + + return 1; +} + +/* This function is a hook for testing the LZMA support. It is only + used by tests. */ + +int +backtrace_uncompress_lzma (struct backtrace_state *state, + const unsigned char *compressed, + size_t compressed_size, + backtrace_error_callback error_callback, + void *data, unsigned char **uncompressed, + size_t *uncompressed_size) +{ + return elf_uncompress_lzma (state, compressed, compressed_size, + error_callback, data, uncompressed, + uncompressed_size); +} + +/* Add the backtrace data for one ELF file. Returns 1 on success, + 0 on failure (in both cases descriptor is closed) or -1 if exe + is non-zero and the ELF file is ET_DYN, which tells the caller that + elf_add will need to be called on the descriptor again after + base_address is determined. */ + +static int +elf_add (struct backtrace_state *state, const char *filename, int descriptor, + const unsigned char *memory, size_t memory_size, + uintptr_t base_address, backtrace_error_callback error_callback, + void *data, fileline *fileline_fn, int *found_sym, int *found_dwarf, + struct dwarf_data **fileline_entry, int exe, int debuginfo, + const char *with_buildid_data, uint32_t with_buildid_size) +{ + struct elf_view ehdr_view; + b_elf_ehdr ehdr; + off_t shoff; + unsigned int shnum; + unsigned int shstrndx; + struct elf_view shdrs_view; + int shdrs_view_valid; + const b_elf_shdr *shdrs; + const b_elf_shdr *shstrhdr; + size_t shstr_size; + off_t shstr_off; + struct elf_view names_view; + int names_view_valid; + const char *names; + unsigned int symtab_shndx; + unsigned int dynsym_shndx; + unsigned int i; + struct debug_section_info sections[DEBUG_MAX]; + struct debug_section_info zsections[DEBUG_MAX]; + struct elf_view symtab_view; + int symtab_view_valid; + struct elf_view strtab_view; + int strtab_view_valid; + struct elf_view buildid_view; + int buildid_view_valid; + const char *buildid_data; + uint32_t buildid_size; + struct elf_view debuglink_view; + int debuglink_view_valid; + const char *debuglink_name; + uint32_t debuglink_crc; + struct elf_view debugaltlink_view; + int debugaltlink_view_valid; + const char *debugaltlink_name; + const char *debugaltlink_buildid_data; + uint32_t debugaltlink_buildid_size; + struct elf_view gnu_debugdata_view; + int gnu_debugdata_view_valid; + size_t gnu_debugdata_size; + unsigned char *gnu_debugdata_uncompressed; + size_t gnu_debugdata_uncompressed_size; + off_t min_offset; + off_t max_offset; + off_t debug_size; + struct elf_view debug_view; + int debug_view_valid; + unsigned int using_debug_view; + uint16_t *zdebug_table; + struct elf_view split_debug_view[DEBUG_MAX]; + unsigned char split_debug_view_valid[DEBUG_MAX]; + struct elf_ppc64_opd_data opd_data, *opd; + struct dwarf_sections dwarf_sections; + struct dwarf_data *fileline_altlink = NULL; + + if (!debuginfo) + { + *found_sym = 0; + *found_dwarf = 0; + } + + shdrs_view_valid = 0; + names_view_valid = 0; + symtab_view_valid = 0; + strtab_view_valid = 0; + buildid_view_valid = 0; + buildid_data = NULL; + buildid_size = 0; + debuglink_view_valid = 0; + debuglink_name = NULL; + debuglink_crc = 0; + debugaltlink_view_valid = 0; + debugaltlink_name = NULL; + debugaltlink_buildid_data = NULL; + debugaltlink_buildid_size = 0; + gnu_debugdata_view_valid = 0; + gnu_debugdata_size = 0; + debug_view_valid = 0; + memset (&split_debug_view_valid[0], 0, sizeof split_debug_view_valid); + opd = NULL; + + if (!elf_get_view (state, descriptor, memory, memory_size, 0, sizeof ehdr, + error_callback, data, &ehdr_view)) + goto fail; + + memcpy (&ehdr, ehdr_view.view.data, sizeof ehdr); + + elf_release_view (state, &ehdr_view, error_callback, data); + + if (ehdr.e_ident[EI_MAG0] != ELFMAG0 + || ehdr.e_ident[EI_MAG1] != ELFMAG1 + || ehdr.e_ident[EI_MAG2] != ELFMAG2 + || ehdr.e_ident[EI_MAG3] != ELFMAG3) + { + error_callback (data, "executable file is not ELF", 0); + goto fail; + } + if (ehdr.e_ident[EI_VERSION] != EV_CURRENT) + { + error_callback (data, "executable file is unrecognized ELF version", 0); + goto fail; + } + +#if BACKTRACE_ELF_SIZE == 32 +#define BACKTRACE_ELFCLASS ELFCLASS32 +#else +#define BACKTRACE_ELFCLASS ELFCLASS64 +#endif + + if (ehdr.e_ident[EI_CLASS] != BACKTRACE_ELFCLASS) + { + error_callback (data, "executable file is unexpected ELF class", 0); + goto fail; + } + + if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB + && ehdr.e_ident[EI_DATA] != ELFDATA2MSB) + { + error_callback (data, "executable file has unknown endianness", 0); + goto fail; + } + + /* If the executable is ET_DYN, it is either a PIE, or we are running + directly a shared library with .interp. We need to wait for + dl_iterate_phdr in that case to determine the actual base_address. */ + if (exe && ehdr.e_type == ET_DYN) + return -1; + + shoff = ehdr.e_shoff; + shnum = ehdr.e_shnum; + shstrndx = ehdr.e_shstrndx; + + if ((shnum == 0 || shstrndx == SHN_XINDEX) + && shoff != 0) + { + struct elf_view shdr_view; + const b_elf_shdr *shdr; + + if (!elf_get_view (state, descriptor, memory, memory_size, shoff, + sizeof shdr, error_callback, data, &shdr_view)) + goto fail; + + shdr = (const b_elf_shdr *) shdr_view.view.data; + + if (shnum == 0) + shnum = shdr->sh_size; + + if (shstrndx == SHN_XINDEX) + { + shstrndx = shdr->sh_link; + + /* Versions of the GNU binutils between 2.12 and 2.18 did + not handle objects with more than SHN_LORESERVE sections + correctly. All large section indexes were offset by + 0x100. There is more information at + http://sourceware.org/bugzilla/show_bug.cgi?id-5900 . + Fortunately these object files are easy to detect, as the + GNU binutils always put the section header string table + near the end of the list of sections. Thus if the + section header string table index is larger than the + number of sections, then we know we have to subtract + 0x100 to get the real section index. */ + if (shstrndx >= shnum && shstrndx >= SHN_LORESERVE + 0x100) + shstrndx -= 0x100; + } + + elf_release_view (state, &shdr_view, error_callback, data); + } + + if (shnum == 0 || shstrndx == 0) + goto fail; + + /* To translate PC to file/line when using DWARF, we need to find + the .debug_info and .debug_line sections. */ + + /* Read the section headers, skipping the first one. */ + + if (!elf_get_view (state, descriptor, memory, memory_size, + shoff + sizeof (b_elf_shdr), + (shnum - 1) * sizeof (b_elf_shdr), + error_callback, data, &shdrs_view)) + goto fail; + shdrs_view_valid = 1; + shdrs = (const b_elf_shdr *) shdrs_view.view.data; + + /* Read the section names. */ + + shstrhdr = &shdrs[shstrndx - 1]; + shstr_size = shstrhdr->sh_size; + shstr_off = shstrhdr->sh_offset; + + if (!elf_get_view (state, descriptor, memory, memory_size, shstr_off, + shstrhdr->sh_size, error_callback, data, &names_view)) + goto fail; + names_view_valid = 1; + names = (const char *) names_view.view.data; + + symtab_shndx = 0; + dynsym_shndx = 0; + + memset (sections, 0, sizeof sections); + memset (zsections, 0, sizeof zsections); + + /* Look for the symbol table. */ + for (i = 1; i < shnum; ++i) + { + const b_elf_shdr *shdr; + unsigned int sh_name; + const char *name; + int j; + + shdr = &shdrs[i - 1]; + + if (shdr->sh_type == SHT_SYMTAB) + symtab_shndx = i; + else if (shdr->sh_type == SHT_DYNSYM) + dynsym_shndx = i; + + sh_name = shdr->sh_name; + if (sh_name >= shstr_size) + { + error_callback (data, "ELF section name out of range", 0); + goto fail; + } + + name = names + sh_name; + + for (j = 0; j < (int) DEBUG_MAX; ++j) + { + if (strcmp (name, dwarf_section_names[j]) == 0) + { + sections[j].offset = shdr->sh_offset; + sections[j].size = shdr->sh_size; + sections[j].compressed = (shdr->sh_flags & SHF_COMPRESSED) != 0; + break; + } + } + + if (name[0] == '.' && name[1] == 'z') + { + for (j = 0; j < (int) DEBUG_MAX; ++j) + { + if (strcmp (name + 2, dwarf_section_names[j] + 1) == 0) + { + zsections[j].offset = shdr->sh_offset; + zsections[j].size = shdr->sh_size; + break; + } + } + } + + /* Read the build ID if present. This could check for any + SHT_NOTE section with the right note name and type, but gdb + looks for a specific section name. */ + if ((!debuginfo || with_buildid_data != NULL) + && !buildid_view_valid + && strcmp (name, ".note.gnu.build-id") == 0) + { + const b_elf_note *note; + + if (!elf_get_view (state, descriptor, memory, memory_size, + shdr->sh_offset, shdr->sh_size, error_callback, + data, &buildid_view)) + goto fail; + + buildid_view_valid = 1; + note = (const b_elf_note *) buildid_view.view.data; + if (note->type == NT_GNU_BUILD_ID + && note->namesz == 4 + && strncmp (note->name, "GNU", 4) == 0 + && shdr->sh_size <= 12 + ((note->namesz + 3) & ~ 3) + note->descsz) + { + buildid_data = ¬e->name[0] + ((note->namesz + 3) & ~ 3); + buildid_size = note->descsz; + } + + if (with_buildid_size != 0) + { + if (buildid_size != with_buildid_size) + goto fail; + + if (memcmp (buildid_data, with_buildid_data, buildid_size) != 0) + goto fail; + } + } + + /* Read the debuglink file if present. */ + if (!debuginfo + && !debuglink_view_valid + && strcmp (name, ".gnu_debuglink") == 0) + { + const char *debuglink_data; + size_t crc_offset; + + if (!elf_get_view (state, descriptor, memory, memory_size, + shdr->sh_offset, shdr->sh_size, error_callback, + data, &debuglink_view)) + goto fail; + + debuglink_view_valid = 1; + debuglink_data = (const char *) debuglink_view.view.data; + crc_offset = strnlen (debuglink_data, shdr->sh_size); + crc_offset = (crc_offset + 3) & ~3; + if (crc_offset + 4 <= shdr->sh_size) + { + debuglink_name = debuglink_data; + debuglink_crc = *(const uint32_t*)(debuglink_data + crc_offset); + } + } + + if (!debugaltlink_view_valid + && strcmp (name, ".gnu_debugaltlink") == 0) + { + const char *debugaltlink_data; + size_t debugaltlink_name_len; + + if (!elf_get_view (state, descriptor, memory, memory_size, + shdr->sh_offset, shdr->sh_size, error_callback, + data, &debugaltlink_view)) + goto fail; + + debugaltlink_view_valid = 1; + debugaltlink_data = (const char *) debugaltlink_view.view.data; + debugaltlink_name = debugaltlink_data; + debugaltlink_name_len = strnlen (debugaltlink_data, shdr->sh_size); + if (debugaltlink_name_len < shdr->sh_size) + { + /* Include terminating zero. */ + debugaltlink_name_len += 1; + + debugaltlink_buildid_data + = debugaltlink_data + debugaltlink_name_len; + debugaltlink_buildid_size = shdr->sh_size - debugaltlink_name_len; + } + } + + if (!gnu_debugdata_view_valid + && strcmp (name, ".gnu_debugdata") == 0) + { + if (!elf_get_view (state, descriptor, memory, memory_size, + shdr->sh_offset, shdr->sh_size, error_callback, + data, &gnu_debugdata_view)) + goto fail; + + gnu_debugdata_size = shdr->sh_size; + gnu_debugdata_view_valid = 1; + } + + /* Read the .opd section on PowerPC64 ELFv1. */ + if (ehdr.e_machine == EM_PPC64 + && (ehdr.e_flags & EF_PPC64_ABI) < 2 + && shdr->sh_type == SHT_PROGBITS + && strcmp (name, ".opd") == 0) + { + if (!elf_get_view (state, descriptor, memory, memory_size, + shdr->sh_offset, shdr->sh_size, error_callback, + data, &opd_data.view)) + goto fail; + + opd = &opd_data; + opd->addr = shdr->sh_addr; + opd->data = (const char *) opd_data.view.view.data; + opd->size = shdr->sh_size; + } + } + + if (symtab_shndx == 0) + symtab_shndx = dynsym_shndx; + if (symtab_shndx != 0 && !debuginfo) + { + const b_elf_shdr *symtab_shdr; + unsigned int strtab_shndx; + const b_elf_shdr *strtab_shdr; + struct elf_syminfo_data *sdata; + + symtab_shdr = &shdrs[symtab_shndx - 1]; + strtab_shndx = symtab_shdr->sh_link; + if (strtab_shndx >= shnum) + { + error_callback (data, + "ELF symbol table strtab link out of range", 0); + goto fail; + } + strtab_shdr = &shdrs[strtab_shndx - 1]; + + if (!elf_get_view (state, descriptor, memory, memory_size, + symtab_shdr->sh_offset, symtab_shdr->sh_size, + error_callback, data, &symtab_view)) + goto fail; + symtab_view_valid = 1; + + if (!elf_get_view (state, descriptor, memory, memory_size, + strtab_shdr->sh_offset, strtab_shdr->sh_size, + error_callback, data, &strtab_view)) + goto fail; + strtab_view_valid = 1; + + sdata = ((struct elf_syminfo_data *) + backtrace_alloc (state, sizeof *sdata, error_callback, data)); + if (sdata == NULL) + goto fail; + + if (!elf_initialize_syminfo (state, base_address, + (const unsigned char*)symtab_view.view.data, symtab_shdr->sh_size, + (const unsigned char*)strtab_view.view.data, strtab_shdr->sh_size, + error_callback, data, sdata, opd)) + { + backtrace_free (state, sdata, sizeof *sdata, error_callback, data); + goto fail; + } + + /* We no longer need the symbol table, but we hold on to the + string table permanently. */ + elf_release_view (state, &symtab_view, error_callback, data); + symtab_view_valid = 0; + strtab_view_valid = 0; + + *found_sym = 1; + + elf_add_syminfo_data (state, sdata); + } + + elf_release_view (state, &shdrs_view, error_callback, data); + shdrs_view_valid = 0; + elf_release_view (state, &names_view, error_callback, data); + names_view_valid = 0; + + /* If the debug info is in a separate file, read that one instead. */ + + if (buildid_data != NULL) + { + int d; + + d = elf_open_debugfile_by_buildid (state, buildid_data, buildid_size, + error_callback, data); + if (d >= 0) + { + int ret; + + elf_release_view (state, &buildid_view, error_callback, data); + if (debuglink_view_valid) + elf_release_view (state, &debuglink_view, error_callback, data); + if (debugaltlink_view_valid) + elf_release_view (state, &debugaltlink_view, error_callback, data); + ret = elf_add (state, "", d, NULL, 0, base_address, error_callback, + data, fileline_fn, found_sym, found_dwarf, NULL, 0, + 1, NULL, 0); + if (ret < 0) + backtrace_close (d, error_callback, data); + else if (descriptor >= 0) + backtrace_close (descriptor, error_callback, data); + return ret; + } + } + + if (buildid_view_valid) + { + elf_release_view (state, &buildid_view, error_callback, data); + buildid_view_valid = 0; + } + + if (opd) + { + elf_release_view (state, &opd->view, error_callback, data); + opd = NULL; + } + + if (debuglink_name != NULL) + { + int d; + + d = elf_open_debugfile_by_debuglink (state, filename, debuglink_name, + debuglink_crc, error_callback, + data); + if (d >= 0) + { + int ret; + + elf_release_view (state, &debuglink_view, error_callback, data); + if (debugaltlink_view_valid) + elf_release_view (state, &debugaltlink_view, error_callback, data); + ret = elf_add (state, "", d, NULL, 0, base_address, error_callback, + data, fileline_fn, found_sym, found_dwarf, NULL, 0, + 1, NULL, 0); + if (ret < 0) + backtrace_close (d, error_callback, data); + else if (descriptor >= 0) + backtrace_close(descriptor, error_callback, data); + return ret; + } + } + + if (debuglink_view_valid) + { + elf_release_view (state, &debuglink_view, error_callback, data); + debuglink_view_valid = 0; + } + + if (debugaltlink_name != NULL) + { + int d; + + d = elf_open_debugfile_by_debuglink (state, filename, debugaltlink_name, + 0, error_callback, data); + if (d >= 0) + { + int ret; + + ret = elf_add (state, filename, d, NULL, 0, base_address, + error_callback, data, fileline_fn, found_sym, + found_dwarf, &fileline_altlink, 0, 1, + debugaltlink_buildid_data, debugaltlink_buildid_size); + elf_release_view (state, &debugaltlink_view, error_callback, data); + debugaltlink_view_valid = 0; + if (ret < 0) + { + backtrace_close (d, error_callback, data); + return ret; + } + } + } + + if (debugaltlink_view_valid) + { + elf_release_view (state, &debugaltlink_view, error_callback, data); + debugaltlink_view_valid = 0; + } + + if (gnu_debugdata_view_valid) + { + int ret; + + ret = elf_uncompress_lzma (state, + ((const unsigned char *) + gnu_debugdata_view.view.data), + gnu_debugdata_size, error_callback, data, + &gnu_debugdata_uncompressed, + &gnu_debugdata_uncompressed_size); + + elf_release_view (state, &gnu_debugdata_view, error_callback, data); + gnu_debugdata_view_valid = 0; + + if (ret) + { + ret = elf_add (state, filename, -1, gnu_debugdata_uncompressed, + gnu_debugdata_uncompressed_size, base_address, + error_callback, data, fileline_fn, found_sym, + found_dwarf, NULL, 0, 0, NULL, 0); + if (ret >= 0 && descriptor >= 0) + backtrace_close(descriptor, error_callback, data); + return ret; + } + } + + /* Read all the debug sections in a single view, since they are + probably adjacent in the file. If any of sections are + uncompressed, we never release this view. */ + + min_offset = 0; + max_offset = 0; + debug_size = 0; + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + off_t end; + + if (sections[i].size != 0) + { + if (min_offset == 0 || sections[i].offset < min_offset) + min_offset = sections[i].offset; + end = sections[i].offset + sections[i].size; + if (end > max_offset) + max_offset = end; + debug_size += sections[i].size; + } + if (zsections[i].size != 0) + { + if (min_offset == 0 || zsections[i].offset < min_offset) + min_offset = zsections[i].offset; + end = zsections[i].offset + zsections[i].size; + if (end > max_offset) + max_offset = end; + debug_size += zsections[i].size; + } + } + if (min_offset == 0 || max_offset == 0) + { + if (descriptor >= 0) + { + if (!backtrace_close (descriptor, error_callback, data)) + goto fail; + } + return 1; + } + + /* If the total debug section size is large, assume that there are + gaps between the sections, and read them individually. */ + + if (max_offset - min_offset < 0x20000000 + || max_offset - min_offset < debug_size + 0x10000) + { + if (!elf_get_view (state, descriptor, memory, memory_size, min_offset, + max_offset - min_offset, error_callback, data, + &debug_view)) + goto fail; + debug_view_valid = 1; + } + else + { + memset (&split_debug_view[0], 0, sizeof split_debug_view); + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + struct debug_section_info *dsec; + + if (sections[i].size != 0) + dsec = §ions[i]; + else if (zsections[i].size != 0) + dsec = &zsections[i]; + else + continue; + + if (!elf_get_view (state, descriptor, memory, memory_size, + dsec->offset, dsec->size, error_callback, data, + &split_debug_view[i])) + goto fail; + split_debug_view_valid[i] = 1; + + if (sections[i].size != 0) + sections[i].data = ((const unsigned char *) + split_debug_view[i].view.data); + else + zsections[i].data = ((const unsigned char *) + split_debug_view[i].view.data); + } + } + + /* We've read all we need from the executable. */ + if (descriptor >= 0) + { + if (!backtrace_close (descriptor, error_callback, data)) + goto fail; + descriptor = -1; + } + + using_debug_view = 0; + if (debug_view_valid) + { + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + if (sections[i].size == 0) + sections[i].data = NULL; + else + { + sections[i].data = ((const unsigned char *) debug_view.view.data + + (sections[i].offset - min_offset)); + ++using_debug_view; + } + + if (zsections[i].size == 0) + zsections[i].data = NULL; + else + zsections[i].data = ((const unsigned char *) debug_view.view.data + + (zsections[i].offset - min_offset)); + } + } + + /* Uncompress the old format (--compress-debug-sections=zlib-gnu). */ + + zdebug_table = NULL; + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + if (sections[i].size == 0 && zsections[i].size > 0) + { + unsigned char *uncompressed_data; + size_t uncompressed_size; + + if (zdebug_table == NULL) + { + zdebug_table = ((uint16_t *) + backtrace_alloc (state, ZDEBUG_TABLE_SIZE, + error_callback, data)); + if (zdebug_table == NULL) + goto fail; + } + + uncompressed_data = NULL; + uncompressed_size = 0; + if (!elf_uncompress_zdebug (state, zsections[i].data, + zsections[i].size, zdebug_table, + error_callback, data, + &uncompressed_data, &uncompressed_size)) + goto fail; + sections[i].data = uncompressed_data; + sections[i].size = uncompressed_size; + sections[i].compressed = 0; + + if (split_debug_view_valid[i]) + { + elf_release_view (state, &split_debug_view[i], + error_callback, data); + split_debug_view_valid[i] = 0; + } + } + } + + /* Uncompress the official ELF format + (--compress-debug-sections=zlib-gabi). */ + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + unsigned char *uncompressed_data; + size_t uncompressed_size; + + if (sections[i].size == 0 || !sections[i].compressed) + continue; + + if (zdebug_table == NULL) + { + zdebug_table = ((uint16_t *) + backtrace_alloc (state, ZDEBUG_TABLE_SIZE, + error_callback, data)); + if (zdebug_table == NULL) + goto fail; + } + + uncompressed_data = NULL; + uncompressed_size = 0; + if (!elf_uncompress_chdr (state, sections[i].data, sections[i].size, + zdebug_table, error_callback, data, + &uncompressed_data, &uncompressed_size)) + goto fail; + sections[i].data = uncompressed_data; + sections[i].size = uncompressed_size; + sections[i].compressed = 0; + + if (debug_view_valid) + --using_debug_view; + else if (split_debug_view_valid[i]) + { + elf_release_view (state, &split_debug_view[i], error_callback, data); + split_debug_view_valid[i] = 0; + } + } + + if (zdebug_table != NULL) + backtrace_free (state, zdebug_table, ZDEBUG_TABLE_SIZE, + error_callback, data); + + if (debug_view_valid && using_debug_view == 0) + { + elf_release_view (state, &debug_view, error_callback, data); + debug_view_valid = 0; + } + + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + dwarf_sections.data[i] = sections[i].data; + dwarf_sections.size[i] = sections[i].size; + } + + if (!backtrace_dwarf_add (state, base_address, &dwarf_sections, + ehdr.e_ident[EI_DATA] == ELFDATA2MSB, + fileline_altlink, + error_callback, data, fileline_fn, + fileline_entry)) + goto fail; + + *found_dwarf = 1; + + return 1; + + fail: + if (shdrs_view_valid) + elf_release_view (state, &shdrs_view, error_callback, data); + if (names_view_valid) + elf_release_view (state, &names_view, error_callback, data); + if (symtab_view_valid) + elf_release_view (state, &symtab_view, error_callback, data); + if (strtab_view_valid) + elf_release_view (state, &strtab_view, error_callback, data); + if (debuglink_view_valid) + elf_release_view (state, &debuglink_view, error_callback, data); + if (debugaltlink_view_valid) + elf_release_view (state, &debugaltlink_view, error_callback, data); + if (gnu_debugdata_view_valid) + elf_release_view (state, &gnu_debugdata_view, error_callback, data); + if (buildid_view_valid) + elf_release_view (state, &buildid_view, error_callback, data); + if (debug_view_valid) + elf_release_view (state, &debug_view, error_callback, data); + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + if (split_debug_view_valid[i]) + elf_release_view (state, &split_debug_view[i], error_callback, data); + } + if (opd) + elf_release_view (state, &opd->view, error_callback, data); + if (descriptor >= 0) + backtrace_close (descriptor, error_callback, data); + return 0; +} + +/* Data passed to phdr_callback. */ + +struct phdr_data +{ + struct backtrace_state *state; + backtrace_error_callback error_callback; + void *data; + fileline *fileline_fn; + int *found_sym; + int *found_dwarf; + const char *exe_filename; + int exe_descriptor; +}; + +/* Callback passed to dl_iterate_phdr. Load debug info from shared + libraries. */ + +static int +#ifdef __i386__ +__attribute__ ((__force_align_arg_pointer__)) +#endif +phdr_callback (struct dl_phdr_info *info, size_t size ATTRIBUTE_UNUSED, + void *pdata) +{ + struct phdr_data *pd = (struct phdr_data *) pdata; + const char *filename; + int descriptor; + int does_not_exist; + fileline elf_fileline_fn; + int found_dwarf; + + /* There is not much we can do if we don't have the module name, + unless executable is ET_DYN, where we expect the very first + phdr_callback to be for the PIE. */ + if (info->dlpi_name == NULL || info->dlpi_name[0] == '\0') + { + if (pd->exe_descriptor == -1) + return 0; + filename = pd->exe_filename; + descriptor = pd->exe_descriptor; + pd->exe_descriptor = -1; + } + else + { + if (pd->exe_descriptor != -1) + { + backtrace_close (pd->exe_descriptor, pd->error_callback, pd->data); + pd->exe_descriptor = -1; + } + + filename = info->dlpi_name; + descriptor = backtrace_open (info->dlpi_name, pd->error_callback, + pd->data, &does_not_exist); + if (descriptor < 0) + return 0; + } + + if (elf_add (pd->state, filename, descriptor, NULL, 0, info->dlpi_addr, + pd->error_callback, pd->data, &elf_fileline_fn, pd->found_sym, + &found_dwarf, NULL, 0, 0, NULL, 0)) + { + if (found_dwarf) + { + *pd->found_dwarf = 1; + *pd->fileline_fn = elf_fileline_fn; + } + } + + return 0; +} + +/* Initialize the backtrace data we need from an ELF executable. At + the ELF level, all we need to do is find the debug info + sections. */ + +int +backtrace_initialize (struct backtrace_state *state, const char *filename, + int descriptor, backtrace_error_callback error_callback, + void *data, fileline *fileline_fn) +{ + int ret; + int found_sym; + int found_dwarf; + fileline elf_fileline_fn = elf_nodebug; + struct phdr_data pd; + + ret = elf_add (state, filename, descriptor, NULL, 0, 0, error_callback, data, + &elf_fileline_fn, &found_sym, &found_dwarf, NULL, 1, 0, NULL, + 0); + if (!ret) + return 0; + + pd.state = state; + pd.error_callback = error_callback; + pd.data = data; + pd.fileline_fn = &elf_fileline_fn; + pd.found_sym = &found_sym; + pd.found_dwarf = &found_dwarf; + pd.exe_filename = filename; + pd.exe_descriptor = ret < 0 ? descriptor : -1; + + dl_iterate_phdr (phdr_callback, (void *) &pd); + + if (!state->threaded) + { + if (found_sym) + state->syminfo_fn = elf_syminfo; + else if (state->syminfo_fn == NULL) + state->syminfo_fn = elf_nosyms; + } + else + { + if (found_sym) + backtrace_atomic_store_pointer (&state->syminfo_fn, &elf_syminfo); + else + (void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL, + elf_nosyms); + } + + if (!state->threaded) + *fileline_fn = state->fileline_fn; + else + *fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn); + + if (*fileline_fn == NULL || *fileline_fn == elf_nodebug) + *fileline_fn = elf_fileline_fn; + + return 1; +} + +} diff --git a/Source/ThirdParty/tracy/libbacktrace/fileline.cpp b/Source/ThirdParty/tracy/libbacktrace/fileline.cpp new file mode 100644 index 000000000..8645d754a --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/fileline.cpp @@ -0,0 +1,351 @@ +/* fileline.c -- Get file and line number information in a backtrace. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#if defined (HAVE_KERN_PROC_ARGS) || defined (HAVE_KERN_PROC) +#include +#endif + +#ifdef HAVE_MACH_O_DYLD_H +#include +#endif + +#include "backtrace.hpp" +#include "internal.hpp" + +#ifndef HAVE_GETEXECNAME +#define getexecname() NULL +#endif + +namespace tracy +{ + +#if !defined (HAVE_KERN_PROC_ARGS) && !defined (HAVE_KERN_PROC) + +#define sysctl_exec_name1(state, error_callback, data) NULL +#define sysctl_exec_name2(state, error_callback, data) NULL + +#else /* defined (HAVE_KERN_PROC_ARGS) || |defined (HAVE_KERN_PROC) */ + +static char * +sysctl_exec_name (struct backtrace_state *state, + int mib0, int mib1, int mib2, int mib3, + backtrace_error_callback error_callback, void *data) +{ + int mib[4]; + size_t len; + char *name; + size_t rlen; + + mib[0] = mib0; + mib[1] = mib1; + mib[2] = mib2; + mib[3] = mib3; + + if (sysctl (mib, 4, NULL, &len, NULL, 0) < 0) + return NULL; + name = (char *) backtrace_alloc (state, len, error_callback, data); + if (name == NULL) + return NULL; + rlen = len; + if (sysctl (mib, 4, name, &rlen, NULL, 0) < 0) + { + backtrace_free (state, name, len, error_callback, data); + return NULL; + } + return name; +} + +#ifdef HAVE_KERN_PROC_ARGS + +static char * +sysctl_exec_name1 (struct backtrace_state *state, + backtrace_error_callback error_callback, void *data) +{ + /* This variant is used on NetBSD. */ + return sysctl_exec_name (state, CTL_KERN, KERN_PROC_ARGS, -1, + KERN_PROC_PATHNAME, error_callback, data); +} + +#else + +#define sysctl_exec_name1(state, error_callback, data) NULL + +#endif + +#ifdef HAVE_KERN_PROC + +static char * +sysctl_exec_name2 (struct backtrace_state *state, + backtrace_error_callback error_callback, void *data) +{ + /* This variant is used on FreeBSD. */ + return sysctl_exec_name (state, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1, + error_callback, data); +} + +#else + +#define sysctl_exec_name2(state, error_callback, data) NULL + +#endif + +#endif /* defined (HAVE_KERN_PROC_ARGS) || |defined (HAVE_KERN_PROC) */ + +#ifdef HAVE_MACH_O_DYLD_H + +static char * +macho_get_executable_path (struct backtrace_state *state, + backtrace_error_callback error_callback, void *data) +{ + uint32_t len; + char *name; + + len = 0; + if (_NSGetExecutablePath (NULL, &len) == 0) + return NULL; + name = (char *) backtrace_alloc (state, len, error_callback, data); + if (name == NULL) + return NULL; + if (_NSGetExecutablePath (name, &len) != 0) + { + backtrace_free (state, name, len, error_callback, data); + return NULL; + } + return name; +} + +#else /* !defined (HAVE_MACH_O_DYLD_H) */ + +#define macho_get_executable_path(state, error_callback, data) NULL + +#endif /* !defined (HAVE_MACH_O_DYLD_H) */ + +/* Initialize the fileline information from the executable. Returns 1 + on success, 0 on failure. */ + +static int +fileline_initialize (struct backtrace_state *state, + backtrace_error_callback error_callback, void *data) +{ + int failed; + fileline fileline_fn; + int pass; + int called_error_callback; + int descriptor; + const char *filename; + char buf[64]; + + if (!state->threaded) + failed = state->fileline_initialization_failed; + else + failed = backtrace_atomic_load_int (&state->fileline_initialization_failed); + + if (failed) + { + error_callback (data, "failed to read executable information", -1); + return 0; + } + + if (!state->threaded) + fileline_fn = state->fileline_fn; + else + fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn); + if (fileline_fn != NULL) + return 1; + + /* We have not initialized the information. Do it now. */ + + descriptor = -1; + called_error_callback = 0; + for (pass = 0; pass < 8; ++pass) + { + int does_not_exist; + + switch (pass) + { + case 0: + filename = state->filename; + break; + case 1: + filename = getexecname (); + break; + case 2: + filename = "/proc/self/exe"; + break; + case 3: + filename = "/proc/curproc/file"; + break; + case 4: + snprintf (buf, sizeof (buf), "/proc/%ld/object/a.out", + (long) getpid ()); + filename = buf; + break; + case 5: + filename = sysctl_exec_name1 (state, error_callback, data); + break; + case 6: + filename = sysctl_exec_name2 (state, error_callback, data); + break; + case 7: + filename = macho_get_executable_path (state, error_callback, data); + break; + default: + abort (); + } + + if (filename == NULL) + continue; + + descriptor = backtrace_open (filename, error_callback, data, + &does_not_exist); + if (descriptor < 0 && !does_not_exist) + { + called_error_callback = 1; + break; + } + if (descriptor >= 0) + break; + } + + if (descriptor < 0) + { + if (!called_error_callback) + { + if (state->filename != NULL) + error_callback (data, state->filename, ENOENT); + else + error_callback (data, + "libbacktrace could not find executable to open", + 0); + } + failed = 1; + } + + if (!failed) + { + if (!backtrace_initialize (state, filename, descriptor, error_callback, + data, &fileline_fn)) + failed = 1; + } + + if (failed) + { + if (!state->threaded) + state->fileline_initialization_failed = 1; + else + backtrace_atomic_store_int (&state->fileline_initialization_failed, 1); + return 0; + } + + if (!state->threaded) + state->fileline_fn = fileline_fn; + else + { + backtrace_atomic_store_pointer (&state->fileline_fn, fileline_fn); + + /* Note that if two threads initialize at once, one of the data + sets may be leaked. */ + } + + return 1; +} + +/* Given a PC, find the file name, line number, and function name. */ + +int +backtrace_pcinfo (struct backtrace_state *state, uintptr_t pc, + backtrace_full_callback callback, + backtrace_error_callback error_callback, void *data) +{ + if (!fileline_initialize (state, error_callback, data)) + return 0; + + if (state->fileline_initialization_failed) + return 0; + + return state->fileline_fn (state, pc, callback, error_callback, data); +} + +/* Given a PC, find the symbol for it, and its value. */ + +int +backtrace_syminfo (struct backtrace_state *state, uintptr_t pc, + backtrace_syminfo_callback callback, + backtrace_error_callback error_callback, void *data) +{ + if (!fileline_initialize (state, error_callback, data)) + return 0; + + if (state->fileline_initialization_failed) + return 0; + + state->syminfo_fn (state, pc, callback, error_callback, data); + return 1; +} + +/* A backtrace_syminfo_callback that can call into a + backtrace_full_callback, used when we have a symbol table but no + debug info. */ + +void +backtrace_syminfo_to_full_callback (void *data, uintptr_t pc, + const char *symname, + uintptr_t symval ATTRIBUTE_UNUSED, + uintptr_t symsize ATTRIBUTE_UNUSED) +{ + struct backtrace_call_full *bdata = (struct backtrace_call_full *) data; + + bdata->ret = bdata->full_callback (bdata->full_data, pc, 0, NULL, 0, symname); +} + +/* An error callback that corresponds to + backtrace_syminfo_to_full_callback. */ + +void +backtrace_syminfo_to_full_error_callback (void *data, const char *msg, + int errnum) +{ + struct backtrace_call_full *bdata = (struct backtrace_call_full *) data; + + bdata->full_error_callback (bdata->full_data, msg, errnum); +} + +} diff --git a/Source/ThirdParty/tracy/libbacktrace/filenames.hpp b/Source/ThirdParty/tracy/libbacktrace/filenames.hpp new file mode 100644 index 000000000..aa7bd7adf --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/filenames.hpp @@ -0,0 +1,52 @@ +/* btest.c -- Filename header for libbacktrace library + Copyright (C) 2012-2018 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#ifndef GCC_VERSION +# define GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__) +#endif + +#if (GCC_VERSION < 2007) +# define __attribute__(x) +#endif + +#ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +#endif + +#if defined(__MSDOS__) || defined(_WIN32) || defined(__OS2__) || defined (__CYGWIN__) +# define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == '\\') +# define HAS_DRIVE_SPEC(f) ((f)[0] != '\0' && (f)[1] == ':') +# define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0]) || HAS_DRIVE_SPEC(f)) +#else +# define IS_DIR_SEPARATOR(c) ((c) == '/') +# define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0])) +#endif diff --git a/Source/ThirdParty/tracy/libbacktrace/internal.hpp b/Source/ThirdParty/tracy/libbacktrace/internal.hpp new file mode 100644 index 000000000..96c097e02 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/internal.hpp @@ -0,0 +1,385 @@ +/* internal.h -- Internal header file for stack backtrace library. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#ifndef BACKTRACE_INTERNAL_H +#define BACKTRACE_INTERNAL_H + +/* We assume that and "backtrace.h" have already been + included. */ + +#ifndef GCC_VERSION +# define GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__) +#endif + +#if (GCC_VERSION < 2007) +# define __attribute__(x) +#endif + +#ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +#endif + +#ifndef ATTRIBUTE_MALLOC +# if (GCC_VERSION >= 2096) +# define ATTRIBUTE_MALLOC __attribute__ ((__malloc__)) +# else +# define ATTRIBUTE_MALLOC +# endif +#endif + +#ifndef ATTRIBUTE_FALLTHROUGH +# if (GCC_VERSION >= 7000) +# define ATTRIBUTE_FALLTHROUGH __attribute__ ((__fallthrough__)) +# else +# define ATTRIBUTE_FALLTHROUGH +# endif +#endif + +#ifndef HAVE_SYNC_FUNCTIONS + +/* Define out the sync functions. These should never be called if + they are not available. */ + +#define __sync_bool_compare_and_swap(A, B, C) (abort(), 1) +#define __sync_lock_test_and_set(A, B) (abort(), 0) +#define __sync_lock_release(A) abort() + +#endif /* !defined (HAVE_SYNC_FUNCTIONS) */ + +#ifdef HAVE_ATOMIC_FUNCTIONS + +/* We have the atomic builtin functions. */ + +#define backtrace_atomic_load_pointer(p) \ + __atomic_load_n ((p), __ATOMIC_ACQUIRE) +#define backtrace_atomic_load_int(p) \ + __atomic_load_n ((p), __ATOMIC_ACQUIRE) +#define backtrace_atomic_store_pointer(p, v) \ + __atomic_store_n ((p), (v), __ATOMIC_RELEASE) +#define backtrace_atomic_store_size_t(p, v) \ + __atomic_store_n ((p), (v), __ATOMIC_RELEASE) +#define backtrace_atomic_store_int(p, v) \ + __atomic_store_n ((p), (v), __ATOMIC_RELEASE) + +#else /* !defined (HAVE_ATOMIC_FUNCTIONS) */ +#ifdef HAVE_SYNC_FUNCTIONS + +/* We have the sync functions but not the atomic functions. Define + the atomic ones in terms of the sync ones. */ + +extern void *backtrace_atomic_load_pointer (void *); +extern int backtrace_atomic_load_int (int *); +extern void backtrace_atomic_store_pointer (void *, void *); +extern void backtrace_atomic_store_size_t (size_t *, size_t); +extern void backtrace_atomic_store_int (int *, int); + +#else /* !defined (HAVE_SYNC_FUNCTIONS) */ + +/* We have neither the sync nor the atomic functions. These will + never be called. */ + +#define backtrace_atomic_load_pointer(p) (abort(), (void *) NULL) +#define backtrace_atomic_load_int(p) (abort(), 0) +#define backtrace_atomic_store_pointer(p, v) abort() +#define backtrace_atomic_store_size_t(p, v) abort() +#define backtrace_atomic_store_int(p, v) abort() + +#endif /* !defined (HAVE_SYNC_FUNCTIONS) */ +#endif /* !defined (HAVE_ATOMIC_FUNCTIONS) */ + +namespace tracy +{ + +/* The type of the function that collects file/line information. This + is like backtrace_pcinfo. */ + +typedef int (*fileline) (struct backtrace_state *state, uintptr_t pc, + backtrace_full_callback callback, + backtrace_error_callback error_callback, void *data); + +/* The type of the function that collects symbol information. This is + like backtrace_syminfo. */ + +typedef void (*syminfo) (struct backtrace_state *state, uintptr_t pc, + backtrace_syminfo_callback callback, + backtrace_error_callback error_callback, void *data); + +/* What the backtrace state pointer points to. */ + +struct backtrace_state +{ + /* The name of the executable. */ + const char *filename; + /* Non-zero if threaded. */ + int threaded; + /* The master lock for fileline_fn, fileline_data, syminfo_fn, + syminfo_data, fileline_initialization_failed and everything the + data pointers point to. */ + void *lock; + /* The function that returns file/line information. */ + fileline fileline_fn; + /* The data to pass to FILELINE_FN. */ + void *fileline_data; + /* The function that returns symbol information. */ + syminfo syminfo_fn; + /* The data to pass to SYMINFO_FN. */ + void *syminfo_data; + /* Whether initializing the file/line information failed. */ + int fileline_initialization_failed; + /* The lock for the freelist. */ + int lock_alloc; + /* The freelist when using mmap. */ + struct backtrace_freelist_struct *freelist; +}; + +/* Open a file for reading. Returns -1 on error. If DOES_NOT_EXIST + is not NULL, *DOES_NOT_EXIST will be set to 0 normally and set to 1 + if the file does not exist. If the file does not exist and + DOES_NOT_EXIST is not NULL, the function will return -1 and will + not call ERROR_CALLBACK. On other errors, or if DOES_NOT_EXIST is + NULL, the function will call ERROR_CALLBACK before returning. */ +extern int backtrace_open (const char *filename, + backtrace_error_callback error_callback, + void *data, + int *does_not_exist); + +/* A view of the contents of a file. This supports mmap when + available. A view will remain in memory even after backtrace_close + is called on the file descriptor from which the view was + obtained. */ + +struct backtrace_view +{ + /* The data that the caller requested. */ + const void *data; + /* The base of the view. */ + void *base; + /* The total length of the view. */ + size_t len; +}; + +/* Create a view of SIZE bytes from DESCRIPTOR at OFFSET. Store the + result in *VIEW. Returns 1 on success, 0 on error. */ +extern int backtrace_get_view (struct backtrace_state *state, int descriptor, + off_t offset, uint64_t size, + backtrace_error_callback error_callback, + void *data, struct backtrace_view *view); + +/* Release a view created by backtrace_get_view. */ +extern void backtrace_release_view (struct backtrace_state *state, + struct backtrace_view *view, + backtrace_error_callback error_callback, + void *data); + +/* Close a file opened by backtrace_open. Returns 1 on success, 0 on + error. */ + +extern int backtrace_close (int descriptor, + backtrace_error_callback error_callback, + void *data); + +/* Sort without using memory. */ + +extern void backtrace_qsort (void *base, size_t count, size_t size, + int (*compar) (const void *, const void *)); + +/* Allocate memory. This is like malloc. If ERROR_CALLBACK is NULL, + this does not report an error, it just returns NULL. */ + +extern void *backtrace_alloc (struct backtrace_state *state, size_t size, + backtrace_error_callback error_callback, + void *data) ATTRIBUTE_MALLOC; + +/* Free memory allocated by backtrace_alloc. If ERROR_CALLBACK is + NULL, this does not report an error. */ + +extern void backtrace_free (struct backtrace_state *state, void *mem, + size_t size, + backtrace_error_callback error_callback, + void *data); + +/* A growable vector of some struct. This is used for more efficient + allocation when we don't know the final size of some group of data + that we want to represent as an array. */ + +struct backtrace_vector +{ + /* The base of the vector. */ + void *base; + /* The number of bytes in the vector. */ + size_t size; + /* The number of bytes available at the current allocation. */ + size_t alc; +}; + +/* Grow VEC by SIZE bytes. Return a pointer to the newly allocated + bytes. Note that this may move the entire vector to a new memory + location. Returns NULL on failure. */ + +extern void *backtrace_vector_grow (struct backtrace_state *state, size_t size, + backtrace_error_callback error_callback, + void *data, + struct backtrace_vector *vec); + +/* Finish the current allocation on VEC. Prepare to start a new + allocation. The finished allocation will never be freed. Returns + a pointer to the base of the finished entries, or NULL on + failure. */ + +extern void* backtrace_vector_finish (struct backtrace_state *state, + struct backtrace_vector *vec, + backtrace_error_callback error_callback, + void *data); + +/* Release any extra space allocated for VEC. This may change + VEC->base. Returns 1 on success, 0 on failure. */ + +extern int backtrace_vector_release (struct backtrace_state *state, + struct backtrace_vector *vec, + backtrace_error_callback error_callback, + void *data); + +/* Free the space managed by VEC. This will reset VEC. */ + +static inline void +backtrace_vector_free (struct backtrace_state *state, + struct backtrace_vector *vec, + backtrace_error_callback error_callback, void *data) +{ + vec->alc += vec->size; + vec->size = 0; + backtrace_vector_release (state, vec, error_callback, data); +} + +/* Read initial debug data from a descriptor, and set the + fileline_data, syminfo_fn, and syminfo_data fields of STATE. + Return the fileln_fn field in *FILELN_FN--this is done this way so + that the synchronization code is only implemented once. This is + called after the descriptor has first been opened. It will close + the descriptor if it is no longer needed. Returns 1 on success, 0 + on error. There will be multiple implementations of this function, + for different file formats. Each system will compile the + appropriate one. */ + +extern int backtrace_initialize (struct backtrace_state *state, + const char *filename, + int descriptor, + backtrace_error_callback error_callback, + void *data, + fileline *fileline_fn); + +/* An enum for the DWARF sections we care about. */ + +enum dwarf_section +{ + DEBUG_INFO, + DEBUG_LINE, + DEBUG_ABBREV, + DEBUG_RANGES, + DEBUG_STR, + DEBUG_ADDR, + DEBUG_STR_OFFSETS, + DEBUG_LINE_STR, + DEBUG_RNGLISTS, + + DEBUG_MAX +}; + +/* Data for the DWARF sections we care about. */ + +struct dwarf_sections +{ + const unsigned char *data[DEBUG_MAX]; + size_t size[DEBUG_MAX]; +}; + +/* DWARF data read from a file, used for .gnu_debugaltlink. */ + +struct dwarf_data; + +/* Add file/line information for a DWARF module. */ + +extern int backtrace_dwarf_add (struct backtrace_state *state, + uintptr_t base_address, + const struct dwarf_sections *dwarf_sections, + int is_bigendian, + struct dwarf_data *fileline_altlink, + backtrace_error_callback error_callback, + void *data, fileline *fileline_fn, + struct dwarf_data **fileline_entry); + +/* A data structure to pass to backtrace_syminfo_to_full. */ + +struct backtrace_call_full +{ + backtrace_full_callback full_callback; + backtrace_error_callback full_error_callback; + void *full_data; + int ret; +}; + +/* A backtrace_syminfo_callback that can call into a + backtrace_full_callback, used when we have a symbol table but no + debug info. */ + +extern void backtrace_syminfo_to_full_callback (void *data, uintptr_t pc, + const char *symname, + uintptr_t symval, + uintptr_t symsize); + +/* An error callback that corresponds to + backtrace_syminfo_to_full_callback. */ + +extern void backtrace_syminfo_to_full_error_callback (void *, const char *, + int); + +/* A test-only hook for elf_uncompress_zdebug. */ + +extern int backtrace_uncompress_zdebug (struct backtrace_state *, + const unsigned char *compressed, + size_t compressed_size, + backtrace_error_callback, void *data, + unsigned char **uncompressed, + size_t *uncompressed_size); + +/* A test-only hook for elf_uncompress_lzma. */ + +extern int backtrace_uncompress_lzma (struct backtrace_state *, + const unsigned char *compressed, + size_t compressed_size, + backtrace_error_callback, void *data, + unsigned char **uncompressed, + size_t *uncompressed_size); + +} + +#endif diff --git a/Source/ThirdParty/tracy/libbacktrace/macho.cpp b/Source/ThirdParty/tracy/libbacktrace/macho.cpp new file mode 100644 index 000000000..cb50dc5f7 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/macho.cpp @@ -0,0 +1,1360 @@ +/* elf.c -- Get debug data from a Mach-O file for backtraces. + Copyright (C) 2020-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_MACH_O_DYLD_H +#include +#endif + +#include "backtrace.hpp" +#include "internal.hpp" + +namespace tracy +{ + +/* Mach-O file header for a 32-bit executable. */ + +struct macho_header_32 +{ + uint32_t magic; /* Magic number (MACH_O_MAGIC_32) */ + uint32_t cputype; /* CPU type */ + uint32_t cpusubtype; /* CPU subtype */ + uint32_t filetype; /* Type of file (object, executable) */ + uint32_t ncmds; /* Number of load commands */ + uint32_t sizeofcmds; /* Total size of load commands */ + uint32_t flags; /* Flags for special features */ +}; + +/* Mach-O file header for a 64-bit executable. */ + +struct macho_header_64 +{ + uint32_t magic; /* Magic number (MACH_O_MAGIC_64) */ + uint32_t cputype; /* CPU type */ + uint32_t cpusubtype; /* CPU subtype */ + uint32_t filetype; /* Type of file (object, executable) */ + uint32_t ncmds; /* Number of load commands */ + uint32_t sizeofcmds; /* Total size of load commands */ + uint32_t flags; /* Flags for special features */ + uint32_t reserved; /* Reserved */ +}; + +/* Mach-O file header for a fat executable. */ + +struct macho_header_fat +{ + uint32_t magic; /* Magic number (MACH_O_MH_(MAGIC|CIGAM)_FAT(_64)?) */ + uint32_t nfat_arch; /* Number of components */ +}; + +/* Values for the header magic field. */ + +#define MACH_O_MH_MAGIC_32 0xfeedface +#define MACH_O_MH_MAGIC_64 0xfeedfacf +#define MACH_O_MH_MAGIC_FAT 0xcafebabe +#define MACH_O_MH_CIGAM_FAT 0xbebafeca +#define MACH_O_MH_MAGIC_FAT_64 0xcafebabf +#define MACH_O_MH_CIGAM_FAT_64 0xbfbafeca + +/* Value for the header filetype field. */ + +#define MACH_O_MH_EXECUTE 0x02 +#define MACH_O_MH_DYLIB 0x06 +#define MACH_O_MH_DSYM 0x0a + +/* A component of a fat file. A fat file starts with a + macho_header_fat followed by nfat_arch instances of this + struct. */ + +struct macho_fat_arch +{ + uint32_t cputype; /* CPU type */ + uint32_t cpusubtype; /* CPU subtype */ + uint32_t offset; /* File offset of this entry */ + uint32_t size; /* Size of this entry */ + uint32_t align; /* Alignment of this entry */ +}; + +/* A component of a 64-bit fat file. This is used if the magic field + is MAGIC_FAT_64. This is only used when some file size or file + offset is too large to represent in the 32-bit format. */ + +struct macho_fat_arch_64 +{ + uint32_t cputype; /* CPU type */ + uint32_t cpusubtype; /* CPU subtype */ + uint64_t offset; /* File offset of this entry */ + uint64_t size; /* Size of this entry */ + uint32_t align; /* Alignment of this entry */ + uint32_t reserved; /* Reserved */ +}; + +/* Values for the fat_arch cputype field (and the header cputype + field). */ + +#define MACH_O_CPU_ARCH_ABI64 0x01000000 + +#define MACH_O_CPU_TYPE_X86 7 +#define MACH_O_CPU_TYPE_ARM 12 +#define MACH_O_CPU_TYPE_PPC 18 + +#define MACH_O_CPU_TYPE_X86_64 (MACH_O_CPU_TYPE_X86 | MACH_O_CPU_ARCH_ABI64) +#define MACH_O_CPU_TYPE_ARM64 (MACH_O_CPU_TYPE_ARM | MACH_O_CPU_ARCH_ABI64) +#define MACH_O_CPU_TYPE_PPC64 (MACH_O_CPU_TYPE_PPC | MACH_O_CPU_ARCH_ABI64) + +/* The header of a load command. */ + +struct macho_load_command +{ + uint32_t cmd; /* The type of load command */ + uint32_t cmdsize; /* Size in bytes of the entire command */ +}; + +/* Values for the load_command cmd field. */ + +#define MACH_O_LC_SEGMENT 0x01 +#define MACH_O_LC_SYMTAB 0x02 +#define MACH_O_LC_SEGMENT_64 0x19 +#define MACH_O_LC_UUID 0x1b + +/* The length of a section of segment name. */ + +#define MACH_O_NAMELEN (16) + +/* LC_SEGMENT load command. */ + +struct macho_segment_command +{ + uint32_t cmd; /* The type of load command (LC_SEGMENT) */ + uint32_t cmdsize; /* Size in bytes of the entire command */ + char segname[MACH_O_NAMELEN]; /* Segment name */ + uint32_t vmaddr; /* Virtual memory address */ + uint32_t vmsize; /* Virtual memory size */ + uint32_t fileoff; /* Offset of data to be mapped */ + uint32_t filesize; /* Size of data in file */ + uint32_t maxprot; /* Maximum permitted virtual protection */ + uint32_t initprot; /* Initial virtual memory protection */ + uint32_t nsects; /* Number of sections in this segment */ + uint32_t flags; /* Flags */ +}; + +/* LC_SEGMENT_64 load command. */ + +struct macho_segment_64_command +{ + uint32_t cmd; /* The type of load command (LC_SEGMENT) */ + uint32_t cmdsize; /* Size in bytes of the entire command */ + char segname[MACH_O_NAMELEN]; /* Segment name */ + uint64_t vmaddr; /* Virtual memory address */ + uint64_t vmsize; /* Virtual memory size */ + uint64_t fileoff; /* Offset of data to be mapped */ + uint64_t filesize; /* Size of data in file */ + uint32_t maxprot; /* Maximum permitted virtual protection */ + uint32_t initprot; /* Initial virtual memory protection */ + uint32_t nsects; /* Number of sections in this segment */ + uint32_t flags; /* Flags */ +}; + +/* LC_SYMTAB load command. */ + +struct macho_symtab_command +{ + uint32_t cmd; /* The type of load command (LC_SEGMENT) */ + uint32_t cmdsize; /* Size in bytes of the entire command */ + uint32_t symoff; /* File offset of symbol table */ + uint32_t nsyms; /* Number of symbols */ + uint32_t stroff; /* File offset of string table */ + uint32_t strsize; /* String table size */ +}; + +/* The length of a Mach-O uuid. */ + +#define MACH_O_UUID_LEN (16) + +/* LC_UUID load command. */ + +struct macho_uuid_command +{ + uint32_t cmd; /* Type of load command (LC_UUID) */ + uint32_t cmdsize; /* Size in bytes of command */ + unsigned char uuid[MACH_O_UUID_LEN]; /* UUID */ +}; + +/* 32-bit section header within a LC_SEGMENT segment. */ + +struct macho_section +{ + char sectname[MACH_O_NAMELEN]; /* Section name */ + char segment[MACH_O_NAMELEN]; /* Segment of this section */ + uint32_t addr; /* Address in memory */ + uint32_t size; /* Section size */ + uint32_t offset; /* File offset */ + uint32_t align; /* Log2 of section alignment */ + uint32_t reloff; /* File offset of relocations */ + uint32_t nreloc; /* Number of relocs for this section */ + uint32_t flags; /* Flags */ + uint32_t reserved1; + uint32_t reserved2; +}; + +/* 64-bit section header within a LC_SEGMENT_64 segment. */ + +struct macho_section_64 +{ + char sectname[MACH_O_NAMELEN]; /* Section name */ + char segment[MACH_O_NAMELEN]; /* Segment of this section */ + uint64_t addr; /* Address in memory */ + uint64_t size; /* Section size */ + uint32_t offset; /* File offset */ + uint32_t align; /* Log2 of section alignment */ + uint32_t reloff; /* File offset of section relocations */ + uint32_t nreloc; /* Number of relocs for this section */ + uint32_t flags; /* Flags */ + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; +}; + +/* 32-bit symbol data. */ + +struct macho_nlist +{ + uint32_t n_strx; /* Index of name in string table */ + uint8_t n_type; /* Type flag */ + uint8_t n_sect; /* Section number */ + uint16_t n_desc; /* Stabs description field */ + uint32_t n_value; /* Value */ +}; + +/* 64-bit symbol data. */ + +struct macho_nlist_64 +{ + uint32_t n_strx; /* Index of name in string table */ + uint8_t n_type; /* Type flag */ + uint8_t n_sect; /* Section number */ + uint16_t n_desc; /* Stabs description field */ + uint64_t n_value; /* Value */ +}; + +/* Value found in nlist n_type field. */ + +#define MACH_O_N_EXT 0x01 /* Extern symbol */ +#define MACH_O_N_ABS 0x02 /* Absolute symbol */ +#define MACH_O_N_SECT 0x0e /* Defined in section */ + +#define MACH_O_N_TYPE 0x0e /* Mask for type bits */ +#define MACH_O_N_STAB 0xe0 /* Stabs debugging symbol */ + +/* Information we keep for a Mach-O symbol. */ + +struct macho_symbol +{ + const char *name; /* Symbol name */ + uintptr_t address; /* Symbol address */ +}; + +/* Information to pass to macho_syminfo. */ + +struct macho_syminfo_data +{ + struct macho_syminfo_data *next; /* Next module */ + struct macho_symbol *symbols; /* Symbols sorted by address */ + size_t count; /* Number of symbols */ +}; + +/* Names of sections, indexed by enum dwarf_section in internal.h. */ + +static const char * const dwarf_section_names[DEBUG_MAX] = +{ + "__debug_info", + "__debug_line", + "__debug_abbrev", + "__debug_ranges", + "__debug_str", + "", /* DEBUG_ADDR */ + "__debug_str_offs", + "", /* DEBUG_LINE_STR */ + "__debug_rnglists" +}; + +/* Forward declaration. */ + +static int macho_add (struct backtrace_state *, const char *, int, off_t, + const unsigned char *, uintptr_t, int, + backtrace_error_callback, void *, fileline *, int *); + +/* A dummy callback function used when we can't find any debug info. */ + +static int +macho_nodebug (struct backtrace_state *state ATTRIBUTE_UNUSED, + uintptr_t pc ATTRIBUTE_UNUSED, + backtrace_full_callback callback ATTRIBUTE_UNUSED, + backtrace_error_callback error_callback, void *data) +{ + error_callback (data, "no debug info in Mach-O executable", -1); + return 0; +} + +/* A dummy callback function used when we can't find a symbol + table. */ + +static void +macho_nosyms (struct backtrace_state *state ATTRIBUTE_UNUSED, + uintptr_t addr ATTRIBUTE_UNUSED, + backtrace_syminfo_callback callback ATTRIBUTE_UNUSED, + backtrace_error_callback error_callback, void *data) +{ + error_callback (data, "no symbol table in Mach-O executable", -1); +} + +/* Add a single DWARF section to DWARF_SECTIONS, if we need the + section. Returns 1 on success, 0 on failure. */ + +static int +macho_add_dwarf_section (struct backtrace_state *state, int descriptor, + const char *sectname, uint32_t offset, uint64_t size, + backtrace_error_callback error_callback, void *data, + struct dwarf_sections *dwarf_sections) +{ + int i; + + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + if (dwarf_section_names[i][0] != '\0' + && strncmp (sectname, dwarf_section_names[i], MACH_O_NAMELEN) == 0) + { + struct backtrace_view section_view; + + /* FIXME: Perhaps it would be better to try to use a single + view to read all the DWARF data, as we try to do for + ELF. */ + + if (!backtrace_get_view (state, descriptor, offset, size, + error_callback, data, §ion_view)) + return 0; + dwarf_sections->data[i] = (const unsigned char *) section_view.data; + dwarf_sections->size[i] = size; + break; + } + } + return 1; +} + +/* Collect DWARF sections from a DWARF segment. Returns 1 on success, + 0 on failure. */ + +static int +macho_add_dwarf_segment (struct backtrace_state *state, int descriptor, + off_t offset, unsigned int cmd, const char *psecs, + size_t sizesecs, unsigned int nsects, + backtrace_error_callback error_callback, void *data, + struct dwarf_sections *dwarf_sections) +{ + size_t sec_header_size; + size_t secoffset; + unsigned int i; + + switch (cmd) + { + case MACH_O_LC_SEGMENT: + sec_header_size = sizeof (struct macho_section); + break; + case MACH_O_LC_SEGMENT_64: + sec_header_size = sizeof (struct macho_section_64); + break; + default: + abort (); + } + + secoffset = 0; + for (i = 0; i < nsects; ++i) + { + if (secoffset + sec_header_size > sizesecs) + { + error_callback (data, "section overflow withing segment", 0); + return 0; + } + + switch (cmd) + { + case MACH_O_LC_SEGMENT: + { + struct macho_section section; + + memcpy (§ion, psecs + secoffset, sizeof section); + macho_add_dwarf_section (state, descriptor, section.sectname, + offset + section.offset, section.size, + error_callback, data, dwarf_sections); + } + break; + + case MACH_O_LC_SEGMENT_64: + { + struct macho_section_64 section; + + memcpy (§ion, psecs + secoffset, sizeof section); + macho_add_dwarf_section (state, descriptor, section.sectname, + offset + section.offset, section.size, + error_callback, data, dwarf_sections); + } + break; + + default: + abort (); + } + + secoffset += sec_header_size; + } + + return 1; +} + +/* Compare struct macho_symbol for qsort. */ + +static int +macho_symbol_compare (const void *v1, const void *v2) +{ + const struct macho_symbol *m1 = (const struct macho_symbol *) v1; + const struct macho_symbol *m2 = (const struct macho_symbol *) v2; + + if (m1->address < m2->address) + return -1; + else if (m1->address > m2->address) + return 1; + else + return 0; +} + +/* Compare an address against a macho_symbol for bsearch. We allocate + one extra entry in the array so that this can safely look at the + next entry. */ + +static int +macho_symbol_search (const void *vkey, const void *ventry) +{ + const uintptr_t *key = (const uintptr_t *) vkey; + const struct macho_symbol *entry = (const struct macho_symbol *) ventry; + uintptr_t addr; + + addr = *key; + if (addr < entry->address) + return -1; + else if (entry->name[0] == '\0' + && entry->address == ~(uintptr_t) 0) + return -1; + else if ((entry + 1)->name[0] == '\0' + && (entry + 1)->address == ~(uintptr_t) 0) + return -1; + else if (addr >= (entry + 1)->address) + return 1; + else + return 0; +} + +/* Return whether the symbol type field indicates a symbol table entry + that we care about: a function or data symbol. */ + +static int +macho_defined_symbol (uint8_t type) +{ + if ((type & MACH_O_N_STAB) != 0) + return 0; + if ((type & MACH_O_N_EXT) != 0) + return 0; + switch (type & MACH_O_N_TYPE) + { + case MACH_O_N_ABS: + return 1; + case MACH_O_N_SECT: + return 1; + default: + return 0; + } +} + +/* Add symbol table information for a Mach-O file. */ + +static int +macho_add_symtab (struct backtrace_state *state, int descriptor, + uintptr_t base_address, int is_64, + off_t symoff, unsigned int nsyms, off_t stroff, + unsigned int strsize, + backtrace_error_callback error_callback, void *data) +{ + size_t symsize; + struct backtrace_view sym_view; + int sym_view_valid; + struct backtrace_view str_view; + int str_view_valid; + size_t ndefs; + size_t symtaboff; + unsigned int i; + size_t macho_symbol_size; + struct macho_symbol *macho_symbols; + unsigned int j; + struct macho_syminfo_data *sdata; + + sym_view_valid = 0; + str_view_valid = 0; + macho_symbol_size = 0; + macho_symbols = NULL; + + if (is_64) + symsize = sizeof (struct macho_nlist_64); + else + symsize = sizeof (struct macho_nlist); + + if (!backtrace_get_view (state, descriptor, symoff, nsyms * symsize, + error_callback, data, &sym_view)) + goto fail; + sym_view_valid = 1; + + if (!backtrace_get_view (state, descriptor, stroff, strsize, + error_callback, data, &str_view)) + return 0; + str_view_valid = 1; + + ndefs = 0; + symtaboff = 0; + for (i = 0; i < nsyms; ++i, symtaboff += symsize) + { + if (is_64) + { + struct macho_nlist_64 nlist; + + memcpy (&nlist, (const char *) sym_view.data + symtaboff, + sizeof nlist); + if (macho_defined_symbol (nlist.n_type)) + ++ndefs; + } + else + { + struct macho_nlist nlist; + + memcpy (&nlist, (const char *) sym_view.data + symtaboff, + sizeof nlist); + if (macho_defined_symbol (nlist.n_type)) + ++ndefs; + } + } + + /* Add 1 to ndefs to make room for a sentinel. */ + macho_symbol_size = (ndefs + 1) * sizeof (struct macho_symbol); + macho_symbols = ((struct macho_symbol *) + backtrace_alloc (state, macho_symbol_size, error_callback, + data)); + if (macho_symbols == NULL) + goto fail; + + j = 0; + symtaboff = 0; + for (i = 0; i < nsyms; ++i, symtaboff += symsize) + { + uint32_t strx; + uint64_t value; + const char *name; + + strx = 0; + value = 0; + if (is_64) + { + struct macho_nlist_64 nlist; + + memcpy (&nlist, (const char *) sym_view.data + symtaboff, + sizeof nlist); + if (!macho_defined_symbol (nlist.n_type)) + continue; + + strx = nlist.n_strx; + value = nlist.n_value; + } + else + { + struct macho_nlist nlist; + + memcpy (&nlist, (const char *) sym_view.data + symtaboff, + sizeof nlist); + if (!macho_defined_symbol (nlist.n_type)) + continue; + + strx = nlist.n_strx; + value = nlist.n_value; + } + + if (strx >= strsize) + { + error_callback (data, "symbol string index out of range", 0); + goto fail; + } + + name = (const char *) str_view.data + strx; + if (name[0] == '_') + ++name; + macho_symbols[j].name = name; + macho_symbols[j].address = value + base_address; + ++j; + } + + sdata = ((struct macho_syminfo_data *) + backtrace_alloc (state, sizeof *sdata, error_callback, data)); + if (sdata == NULL) + goto fail; + + /* We need to keep the string table since it holds the names, but we + can release the symbol table. */ + + backtrace_release_view (state, &sym_view, error_callback, data); + sym_view_valid = 0; + str_view_valid = 0; + + /* Add a trailing sentinel symbol. */ + macho_symbols[j].name = ""; + macho_symbols[j].address = ~(uintptr_t) 0; + + backtrace_qsort (macho_symbols, ndefs + 1, sizeof (struct macho_symbol), + macho_symbol_compare); + + sdata->next = NULL; + sdata->symbols = macho_symbols; + sdata->count = ndefs; + + if (!state->threaded) + { + struct macho_syminfo_data **pp; + + for (pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data; + *pp != NULL; + pp = &(*pp)->next) + ; + *pp = sdata; + } + else + { + while (1) + { + struct macho_syminfo_data **pp; + + pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data; + + while (1) + { + struct macho_syminfo_data *p; + + p = backtrace_atomic_load_pointer (pp); + + if (p == NULL) + break; + + pp = &p->next; + } + + if (__sync_bool_compare_and_swap (pp, NULL, sdata)) + break; + } + } + + return 1; + + fail: + if (macho_symbols != NULL) + backtrace_free (state, macho_symbols, macho_symbol_size, + error_callback, data); + if (sym_view_valid) + backtrace_release_view (state, &sym_view, error_callback, data); + if (str_view_valid) + backtrace_release_view (state, &str_view, error_callback, data); + return 0; +} + +/* Return the symbol name and value for an ADDR. */ + +static void +macho_syminfo (struct backtrace_state *state, uintptr_t addr, + backtrace_syminfo_callback callback, + backtrace_error_callback error_callback ATTRIBUTE_UNUSED, + void *data) +{ + struct macho_syminfo_data *sdata; + struct macho_symbol *sym; + + sym = NULL; + if (!state->threaded) + { + for (sdata = (struct macho_syminfo_data *) state->syminfo_data; + sdata != NULL; + sdata = sdata->next) + { + sym = ((struct macho_symbol *) + bsearch (&addr, sdata->symbols, sdata->count, + sizeof (struct macho_symbol), macho_symbol_search)); + if (sym != NULL) + break; + } + } + else + { + struct macho_syminfo_data **pp; + + pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data; + while (1) + { + sdata = backtrace_atomic_load_pointer (pp); + if (sdata == NULL) + break; + + sym = ((struct macho_symbol *) + bsearch (&addr, sdata->symbols, sdata->count, + sizeof (struct macho_symbol), macho_symbol_search)); + if (sym != NULL) + break; + + pp = &sdata->next; + } + } + + if (sym == NULL) + callback (data, addr, NULL, 0, 0); + else + callback (data, addr, sym->name, sym->address, 0); +} + +/* Look through a fat file to find the relevant executable. Returns 1 + on success, 0 on failure (in both cases descriptor is closed). */ + +static int +macho_add_fat (struct backtrace_state *state, const char *filename, + int descriptor, int swapped, off_t offset, + const unsigned char *match_uuid, uintptr_t base_address, + int skip_symtab, uint32_t nfat_arch, int is_64, + backtrace_error_callback error_callback, void *data, + fileline *fileline_fn, int *found_sym) +{ + int arch_view_valid; + unsigned int cputype; + size_t arch_size; + struct backtrace_view arch_view; + unsigned int i; + + arch_view_valid = 0; + +#if defined (__x86_64__) + cputype = MACH_O_CPU_TYPE_X86_64; +#elif defined (__i386__) + cputype = MACH_O_CPU_TYPE_X86; +#elif defined (__aarch64__) + cputype = MACH_O_CPU_TYPE_ARM64; +#elif defined (__arm__) + cputype = MACH_O_CPU_TYPE_ARM; +#elif defined (__ppc__) + cputype = MACH_O_CPU_TYPE_PPC; +#elif defined (__ppc64__) + cputype = MACH_O_CPU_TYPE_PPC64; +#else + error_callback (data, "unknown Mach-O architecture", 0); + goto fail; +#endif + + if (is_64) + arch_size = sizeof (struct macho_fat_arch_64); + else + arch_size = sizeof (struct macho_fat_arch); + + if (!backtrace_get_view (state, descriptor, offset, + nfat_arch * arch_size, + error_callback, data, &arch_view)) + goto fail; + + for (i = 0; i < nfat_arch; ++i) + { + uint32_t fcputype; + uint64_t foffset; + + if (is_64) + { + struct macho_fat_arch_64 fat_arch_64; + + memcpy (&fat_arch_64, + (const char *) arch_view.data + i * arch_size, + arch_size); + fcputype = fat_arch_64.cputype; + foffset = fat_arch_64.offset; + if (swapped) + { + fcputype = __builtin_bswap32 (fcputype); + foffset = __builtin_bswap64 (foffset); + } + } + else + { + struct macho_fat_arch fat_arch_32; + + memcpy (&fat_arch_32, + (const char *) arch_view.data + i * arch_size, + arch_size); + fcputype = fat_arch_32.cputype; + foffset = (uint64_t) fat_arch_32.offset; + if (swapped) + { + fcputype = __builtin_bswap32 (fcputype); + foffset = (uint64_t) __builtin_bswap32 ((uint32_t) foffset); + } + } + + if (fcputype == cputype) + { + /* FIXME: What about cpusubtype? */ + backtrace_release_view (state, &arch_view, error_callback, data); + return macho_add (state, filename, descriptor, foffset, match_uuid, + base_address, skip_symtab, error_callback, data, + fileline_fn, found_sym); + } + } + + error_callback (data, "could not find executable in fat file", 0); + + fail: + if (arch_view_valid) + backtrace_release_view (state, &arch_view, error_callback, data); + if (descriptor != -1) + backtrace_close (descriptor, error_callback, data); + return 0; +} + +/* Look for the dsym file for FILENAME. This is called if FILENAME + does not have debug info or a symbol table. Returns 1 on success, + 0 on failure. */ + +static int +macho_add_dsym (struct backtrace_state *state, const char *filename, + uintptr_t base_address, const unsigned char *uuid, + backtrace_error_callback error_callback, void *data, + fileline* fileline_fn) +{ + const char *p; + const char *dirname; + char *diralc; + size_t dirnamelen; + const char *basename; + size_t basenamelen; + const char *dsymsuffixdir; + size_t dsymsuffixdirlen; + size_t dsymlen; + char *dsym; + char *ps; + int d; + int does_not_exist; + int dummy_found_sym; + + diralc = NULL; + dirnamelen = 0; + dsym = NULL; + dsymlen = 0; + + p = strrchr (filename, '/'); + if (p == NULL) + { + dirname = "."; + dirnamelen = 1; + basename = filename; + basenamelen = strlen (basename); + diralc = NULL; + } + else + { + dirnamelen = p - filename; + diralc = (char*)backtrace_alloc (state, dirnamelen + 1, error_callback, data); + if (diralc == NULL) + goto fail; + memcpy (diralc, filename, dirnamelen); + diralc[dirnamelen] = '\0'; + dirname = diralc; + basename = p + 1; + basenamelen = strlen (basename); + } + + dsymsuffixdir = ".dSYM/Contents/Resources/DWARF/"; + dsymsuffixdirlen = strlen (dsymsuffixdir); + + dsymlen = (dirnamelen + + 1 + + basenamelen + + dsymsuffixdirlen + + basenamelen + + 1); + dsym = (char*)backtrace_alloc (state, dsymlen, error_callback, data); + if (dsym == NULL) + goto fail; + + ps = dsym; + memcpy (ps, dirname, dirnamelen); + ps += dirnamelen; + *ps++ = '/'; + memcpy (ps, basename, basenamelen); + ps += basenamelen; + memcpy (ps, dsymsuffixdir, dsymsuffixdirlen); + ps += dsymsuffixdirlen; + memcpy (ps, basename, basenamelen); + ps += basenamelen; + *ps = '\0'; + + if (diralc != NULL) + { + backtrace_free (state, diralc, dirnamelen + 1, error_callback, data); + diralc = NULL; + } + + d = backtrace_open (dsym, error_callback, data, &does_not_exist); + if (d < 0) + { + /* The file does not exist, so we can't read the debug info. + Just return success. */ + backtrace_free (state, dsym, dsymlen, error_callback, data); + return 1; + } + + if (!macho_add (state, dsym, d, 0, uuid, base_address, 1, + error_callback, data, fileline_fn, &dummy_found_sym)) + goto fail; + + backtrace_free (state, dsym, dsymlen, error_callback, data); + + return 1; + + fail: + if (dsym != NULL) + backtrace_free (state, dsym, dsymlen, error_callback, data); + if (diralc != NULL) + backtrace_free (state, diralc, dirnamelen, error_callback, data); + return 0; +} + +/* Add the backtrace data for a Macho-O file. Returns 1 on success, 0 + on failure (in both cases descriptor is closed). + + FILENAME: the name of the executable. + DESCRIPTOR: an open descriptor for the executable, closed here. + OFFSET: the offset within the file of this executable, for fat files. + MATCH_UUID: if not NULL, UUID that must match. + BASE_ADDRESS: the load address of the executable. + SKIP_SYMTAB: if non-zero, ignore the symbol table; used for dSYM files. + FILELINE_FN: set to the fileline function, by backtrace_dwarf_add. + FOUND_SYM: set to non-zero if we found the symbol table. +*/ + +static int +macho_add (struct backtrace_state *state, const char *filename, int descriptor, + off_t offset, const unsigned char *match_uuid, + uintptr_t base_address, int skip_symtab, + backtrace_error_callback error_callback, void *data, + fileline *fileline_fn, int *found_sym) +{ + struct backtrace_view header_view; + struct macho_header_32 header; + off_t hdroffset; + int is_64; + struct backtrace_view cmds_view; + int cmds_view_valid; + struct dwarf_sections dwarf_sections; + int have_dwarf; + unsigned char uuid[MACH_O_UUID_LEN]; + int have_uuid; + size_t cmdoffset; + unsigned int i; + + *found_sym = 0; + + cmds_view_valid = 0; + + /* The 32-bit and 64-bit file headers start out the same, so we can + just always read the 32-bit version. A fat header is shorter but + it will always be followed by data, so it's OK to read extra. */ + + if (!backtrace_get_view (state, descriptor, offset, + sizeof (struct macho_header_32), + error_callback, data, &header_view)) + goto fail; + + memcpy (&header, header_view.data, sizeof header); + + backtrace_release_view (state, &header_view, error_callback, data); + + switch (header.magic) + { + case MACH_O_MH_MAGIC_32: + is_64 = 0; + hdroffset = offset + sizeof (struct macho_header_32); + break; + case MACH_O_MH_MAGIC_64: + is_64 = 1; + hdroffset = offset + sizeof (struct macho_header_64); + break; + case MACH_O_MH_MAGIC_FAT: + case MACH_O_MH_MAGIC_FAT_64: + { + struct macho_header_fat fat_header; + + hdroffset = offset + sizeof (struct macho_header_fat); + memcpy (&fat_header, &header, sizeof fat_header); + return macho_add_fat (state, filename, descriptor, 0, hdroffset, + match_uuid, base_address, skip_symtab, + fat_header.nfat_arch, + header.magic == MACH_O_MH_MAGIC_FAT_64, + error_callback, data, fileline_fn, found_sym); + } + case MACH_O_MH_CIGAM_FAT: + case MACH_O_MH_CIGAM_FAT_64: + { + struct macho_header_fat fat_header; + uint32_t nfat_arch; + + hdroffset = offset + sizeof (struct macho_header_fat); + memcpy (&fat_header, &header, sizeof fat_header); + nfat_arch = __builtin_bswap32 (fat_header.nfat_arch); + return macho_add_fat (state, filename, descriptor, 1, hdroffset, + match_uuid, base_address, skip_symtab, + nfat_arch, + header.magic == MACH_O_MH_CIGAM_FAT_64, + error_callback, data, fileline_fn, found_sym); + } + default: + error_callback (data, "executable file is not in Mach-O format", 0); + goto fail; + } + + switch (header.filetype) + { + case MACH_O_MH_EXECUTE: + case MACH_O_MH_DYLIB: + case MACH_O_MH_DSYM: + break; + default: + error_callback (data, "executable file is not an executable", 0); + goto fail; + } + + if (!backtrace_get_view (state, descriptor, hdroffset, header.sizeofcmds, + error_callback, data, &cmds_view)) + goto fail; + cmds_view_valid = 1; + + memset (&dwarf_sections, 0, sizeof dwarf_sections); + have_dwarf = 0; + memset (&uuid, 0, sizeof uuid); + have_uuid = 0; + + cmdoffset = 0; + for (i = 0; i < header.ncmds; ++i) + { + const char *pcmd; + struct macho_load_command load_command; + + if (cmdoffset + sizeof load_command > header.sizeofcmds) + break; + + pcmd = (const char *) cmds_view.data + cmdoffset; + memcpy (&load_command, pcmd, sizeof load_command); + + switch (load_command.cmd) + { + case MACH_O_LC_SEGMENT: + { + struct macho_segment_command segcmd; + + memcpy (&segcmd, pcmd, sizeof segcmd); + if (memcmp (segcmd.segname, + "__DWARF\0\0\0\0\0\0\0\0\0", + MACH_O_NAMELEN) == 0) + { + if (!macho_add_dwarf_segment (state, descriptor, offset, + load_command.cmd, + pcmd + sizeof segcmd, + (load_command.cmdsize + - sizeof segcmd), + segcmd.nsects, error_callback, + data, &dwarf_sections)) + goto fail; + have_dwarf = 1; + } + } + break; + + case MACH_O_LC_SEGMENT_64: + { + struct macho_segment_64_command segcmd; + + memcpy (&segcmd, pcmd, sizeof segcmd); + if (memcmp (segcmd.segname, + "__DWARF\0\0\0\0\0\0\0\0\0", + MACH_O_NAMELEN) == 0) + { + if (!macho_add_dwarf_segment (state, descriptor, offset, + load_command.cmd, + pcmd + sizeof segcmd, + (load_command.cmdsize + - sizeof segcmd), + segcmd.nsects, error_callback, + data, &dwarf_sections)) + goto fail; + have_dwarf = 1; + } + } + break; + + case MACH_O_LC_SYMTAB: + if (!skip_symtab) + { + struct macho_symtab_command symcmd; + + memcpy (&symcmd, pcmd, sizeof symcmd); + if (!macho_add_symtab (state, descriptor, base_address, is_64, + offset + symcmd.symoff, symcmd.nsyms, + offset + symcmd.stroff, symcmd.strsize, + error_callback, data)) + goto fail; + + *found_sym = 1; + } + break; + + case MACH_O_LC_UUID: + { + struct macho_uuid_command uuidcmd; + + memcpy (&uuidcmd, pcmd, sizeof uuidcmd); + memcpy (&uuid[0], &uuidcmd.uuid[0], MACH_O_UUID_LEN); + have_uuid = 1; + } + break; + + default: + break; + } + + cmdoffset += load_command.cmdsize; + } + + if (!backtrace_close (descriptor, error_callback, data)) + goto fail; + descriptor = -1; + + backtrace_release_view (state, &cmds_view, error_callback, data); + cmds_view_valid = 0; + + if (match_uuid != NULL) + { + /* If we don't have a UUID, or it doesn't match, just ignore + this file. */ + if (!have_uuid + || memcmp (match_uuid, &uuid[0], MACH_O_UUID_LEN) != 0) + return 1; + } + + if (have_dwarf) + { + int is_big_endian; + + is_big_endian = 0; +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + is_big_endian = 1; +#endif +#endif + + if (!backtrace_dwarf_add (state, base_address, &dwarf_sections, + is_big_endian, NULL, error_callback, data, + fileline_fn, NULL)) + goto fail; + } + + if (!have_dwarf && have_uuid) + { + if (!macho_add_dsym (state, filename, base_address, &uuid[0], + error_callback, data, fileline_fn)) + goto fail; + } + + return 1; + + fail: + if (cmds_view_valid) + backtrace_release_view (state, &cmds_view, error_callback, data); + if (descriptor != -1) + backtrace_close (descriptor, error_callback, data); + return 0; +} + +#ifdef HAVE_MACH_O_DYLD_H + +/* Initialize the backtrace data we need from a Mach-O executable + using the dyld support functions. This closes descriptor. */ + +int +backtrace_initialize (struct backtrace_state *state, const char *filename, + int descriptor, backtrace_error_callback error_callback, + void *data, fileline *fileline_fn) +{ + uint32_t c; + uint32_t i; + int closed_descriptor; + int found_sym; + fileline macho_fileline_fn; + + closed_descriptor = 0; + found_sym = 0; + macho_fileline_fn = macho_nodebug; + + c = _dyld_image_count (); + for (i = 0; i < c; ++i) + { + uintptr_t base_address; + const char *name; + int d; + fileline mff; + int mfs; + + name = _dyld_get_image_name (i); + if (name == NULL) + continue; + + if (strcmp (name, filename) == 0 && !closed_descriptor) + { + d = descriptor; + closed_descriptor = 1; + } + else + { + int does_not_exist; + + d = backtrace_open (name, error_callback, data, &does_not_exist); + if (d < 0) + continue; + } + + base_address = _dyld_get_image_vmaddr_slide (i); + + mff = macho_nodebug; + if (!macho_add (state, name, d, 0, NULL, base_address, 0, + error_callback, data, &mff, &mfs)) + return 0; + + if (mff != macho_nodebug) + macho_fileline_fn = mff; + if (mfs) + found_sym = 1; + } + + if (!closed_descriptor) + backtrace_close (descriptor, error_callback, data); + + if (!state->threaded) + { + if (found_sym) + state->syminfo_fn = macho_syminfo; + else if (state->syminfo_fn == NULL) + state->syminfo_fn = macho_nosyms; + } + else + { + if (found_sym) + backtrace_atomic_store_pointer (&state->syminfo_fn, macho_syminfo); + else + (void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL, + macho_nosyms); + } + + if (!state->threaded) + *fileline_fn = state->fileline_fn; + else + *fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn); + + if (*fileline_fn == NULL || *fileline_fn == macho_nodebug) + *fileline_fn = macho_fileline_fn; + + return 1; +} + +#else /* !defined (HAVE_MACH_O_DYLD_H) */ + +/* Initialize the backtrace data we need from a Mach-O executable + without using the dyld support functions. This closes + descriptor. */ + +int +backtrace_initialize (struct backtrace_state *state, const char *filename, + int descriptor, backtrace_error_callback error_callback, + void *data, fileline *fileline_fn) +{ + fileline macho_fileline_fn; + int found_sym; + + macho_fileline_fn = macho_nodebug; + if (!macho_add (state, filename, descriptor, 0, NULL, 0, 0, + error_callback, data, &macho_fileline_fn, &found_sym)) + return 0; + + if (!state->threaded) + { + if (found_sym) + state->syminfo_fn = macho_syminfo; + else if (state->syminfo_fn == NULL) + state->syminfo_fn = macho_nosyms; + } + else + { + if (found_sym) + backtrace_atomic_store_pointer (&state->syminfo_fn, macho_syminfo); + else + (void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL, + macho_nosyms); + } + + if (!state->threaded) + *fileline_fn = state->fileline_fn; + else + *fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn); + + if (*fileline_fn == NULL || *fileline_fn == macho_nodebug) + *fileline_fn = macho_fileline_fn; + + return 1; +} + +#endif /* !defined (HAVE_MACH_O_DYLD_H) */ + +} diff --git a/Source/ThirdParty/tracy/libbacktrace/mmapio.cpp b/Source/ThirdParty/tracy/libbacktrace/mmapio.cpp new file mode 100644 index 000000000..0e8f599bb --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/mmapio.cpp @@ -0,0 +1,115 @@ +/* mmapio.c -- File views using mmap. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include +#include +#include + +#include "backtrace.hpp" +#include "internal.hpp" + +#ifndef HAVE_DECL_GETPAGESIZE +extern int getpagesize (void); +#endif + +#ifndef MAP_FAILED +#define MAP_FAILED ((void *)-1) +#endif + +namespace tracy +{ + +/* This file implements file views and memory allocation when mmap is + available. */ + +/* Create a view of SIZE bytes from DESCRIPTOR at OFFSET. */ + +int +backtrace_get_view (struct backtrace_state *state ATTRIBUTE_UNUSED, + int descriptor, off_t offset, uint64_t size, + backtrace_error_callback error_callback, + void *data, struct backtrace_view *view) +{ + size_t pagesize; + unsigned int inpage; + off_t pageoff; + void *map; + + if ((uint64_t) (size_t) size != size) + { + error_callback (data, "file size too large", 0); + return 0; + } + + pagesize = getpagesize (); + inpage = offset % pagesize; + pageoff = offset - inpage; + + size += inpage; + size = (size + (pagesize - 1)) & ~ (pagesize - 1); + + map = mmap (NULL, size, PROT_READ, MAP_PRIVATE, descriptor, pageoff); + if (map == MAP_FAILED) + { + error_callback (data, "mmap", errno); + return 0; + } + + view->data = (char *) map + inpage; + view->base = map; + view->len = size; + + return 1; +} + +/* Release a view read by backtrace_get_view. */ + +void +backtrace_release_view (struct backtrace_state *state ATTRIBUTE_UNUSED, + struct backtrace_view *view, + backtrace_error_callback error_callback, + void *data) +{ + union { + const void *cv; + void *v; + } cc; + + cc.cv = view->base; + if (munmap (cc.v, view->len) < 0) + error_callback (data, "munmap", errno); +} + +} diff --git a/Source/ThirdParty/tracy/libbacktrace/posix.cpp b/Source/ThirdParty/tracy/libbacktrace/posix.cpp new file mode 100644 index 000000000..8233a8ea3 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/posix.cpp @@ -0,0 +1,109 @@ +/* posix.c -- POSIX file I/O routines for the backtrace library. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "backtrace.hpp" +#include "internal.hpp" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +#ifndef FD_CLOEXEC +#define FD_CLOEXEC 1 +#endif + +namespace tracy +{ + +/* Open a file for reading. */ + +int +backtrace_open (const char *filename, backtrace_error_callback error_callback, + void *data, int *does_not_exist) +{ + int descriptor; + + if (does_not_exist != NULL) + *does_not_exist = 0; + + descriptor = open (filename, (int) (O_RDONLY | O_BINARY | O_CLOEXEC)); + if (descriptor < 0) + { + /* If DOES_NOT_EXIST is not NULL, then don't call ERROR_CALLBACK + if the file does not exist. We treat lacking permission to + open the file as the file not existing; this case arises when + running the libgo syscall package tests as root. */ + if (does_not_exist != NULL && (errno == ENOENT || errno == EACCES)) + *does_not_exist = 1; + else + error_callback (data, filename, errno); + return -1; + } + +#ifdef HAVE_FCNTL + /* Set FD_CLOEXEC just in case the kernel does not support + O_CLOEXEC. It doesn't matter if this fails for some reason. + FIXME: At some point it should be safe to only do this if + O_CLOEXEC == 0. */ + fcntl (descriptor, F_SETFD, FD_CLOEXEC); +#endif + + return descriptor; +} + +/* Close DESCRIPTOR. */ + +int +backtrace_close (int descriptor, backtrace_error_callback error_callback, + void *data) +{ + if (close (descriptor) < 0) + { + error_callback (data, "close", errno); + return 0; + } + return 1; +} + +} diff --git a/Source/ThirdParty/tracy/libbacktrace/sort.cpp b/Source/ThirdParty/tracy/libbacktrace/sort.cpp new file mode 100644 index 000000000..6daee0a64 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/sort.cpp @@ -0,0 +1,113 @@ +/* sort.c -- Sort without allocating memory + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include + +#include "backtrace.hpp" +#include "internal.hpp" + +namespace tracy +{ + +/* The GNU glibc version of qsort allocates memory, which we must not + do if we are invoked by a signal handler. So provide our own + sort. */ + +static void +swap (char *a, char *b, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++, a++, b++) + { + char t; + + t = *a; + *a = *b; + *b = t; + } +} + +void +backtrace_qsort (void *basearg, size_t count, size_t size, + int (*compar) (const void *, const void *)) +{ + char *base = (char *) basearg; + size_t i; + size_t mid; + + tail_recurse: + if (count < 2) + return; + + /* The symbol table and DWARF tables, which is all we use this + routine for, tend to be roughly sorted. Pick the middle element + in the array as our pivot point, so that we are more likely to + cut the array in half for each recursion step. */ + swap (base, base + (count / 2) * size, size); + + mid = 0; + for (i = 1; i < count; i++) + { + if ((*compar) (base, base + i * size) > 0) + { + ++mid; + if (i != mid) + swap (base + mid * size, base + i * size, size); + } + } + + if (mid > 0) + swap (base, base + mid * size, size); + + /* Recurse with the smaller array, loop with the larger one. That + ensures that our maximum stack depth is log count. */ + if (2 * mid < count) + { + backtrace_qsort (base, mid, size, compar); + base += (mid + 1) * size; + count -= mid + 1; + goto tail_recurse; + } + else + { + backtrace_qsort (base + (mid + 1) * size, count - (mid + 1), + size, compar); + count = mid; + goto tail_recurse; + } +} + +} diff --git a/Source/ThirdParty/tracy/libbacktrace/state.cpp b/Source/ThirdParty/tracy/libbacktrace/state.cpp new file mode 100644 index 000000000..ea3c137c5 --- /dev/null +++ b/Source/ThirdParty/tracy/libbacktrace/state.cpp @@ -0,0 +1,76 @@ +/* state.c -- Create the backtrace state. + Copyright (C) 2012-2021 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +#include +#include + +#include "backtrace.hpp" +#include "internal.hpp" + +namespace tracy +{ + +/* Create the backtrace state. This will then be passed to all the + other routines. */ + +struct backtrace_state * +backtrace_create_state (const char *filename, int threaded, + backtrace_error_callback error_callback, + void *data) +{ + struct backtrace_state init_state; + struct backtrace_state *state; + +#ifndef HAVE_SYNC_FUNCTIONS + if (threaded) + { + error_callback (data, "backtrace library does not support threads", 0); + return NULL; + } +#endif + + memset (&init_state, 0, sizeof init_state); + init_state.filename = filename; + init_state.threaded = threaded; + + state = ((struct backtrace_state *) + backtrace_alloc (&init_state, sizeof *state, error_callback, data)); + if (state == NULL) + return NULL; + *state = init_state; + + return state; +} + +} diff --git a/Source/ThirdParty/tracy/tracy.Build.cs b/Source/ThirdParty/tracy/tracy.Build.cs new file mode 100644 index 000000000..aa10c514f --- /dev/null +++ b/Source/ThirdParty/tracy/tracy.Build.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using System.IO; +using Flax.Build; +using Flax.Build.NativeCpp; + +/// +/// https://github.com/wolfpld/tracy +/// +public class tracy : ThirdPartyModule +{ + /// + public override void Init() + { + base.Init(); + + LicenseType = LicenseTypes.BSD3Clause; + LicenseFilePath = "LICENSE"; + + // Merge third-party modules into engine binary + BinaryModuleName = "FlaxEngine"; + } + + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.SourcePaths.Clear(); + options.SourceFiles.Clear(); + options.SourceFiles.Add(Path.Combine(FolderPath, "Tracy.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "TracyClient.cpp")); + + options.PublicDefinitions.Add("TRACY_ENABLE"); + } + + /// + public override void GetFilesToDeploy(List files) + { + base.GetFilesToDeploy(files); + + files.Add(Path.Combine(FolderPath, "Tracy.h")); + files.Add(Path.Combine(FolderPath, "common", "TracySystem.hpp")); + files.Add(Path.Combine(FolderPath, "client", "TracyCallstack.h")); + } +} diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index 82128eaa7..27c902a5c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -261,7 +261,10 @@ namespace Flax.Build.Bindings { if (typeInfo == null) return null; - if (buildData.TypeCache.TryGetValue(typeInfo, out var result)) + var result = FindApiTypeInfoInner(typeInfo, caller); + if (result != null) + return result; + if (buildData.TypeCache.TryGetValue(typeInfo, out result)) return result; // Find across in-build types diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 84236cfd2..26fa55231 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -11,6 +11,9 @@ namespace Flax.Build.Bindings { partial class BindingsGenerator { + private static readonly HashSet CSharpUsedNamespaces = new HashSet(); + private static readonly List CSharpUsedNamespacesSorted = new List(); + private static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary() { // Language types @@ -50,12 +53,52 @@ namespace Flax.Build.Bindings { "MonoArray", "Array" }, }; - private static string GenerateCSharpDefaultValueNativeToManaged(BuildData buildData, string value, ApiTypeInfo caller) + private static string GenerateCSharpDefaultValueNativeToManaged(BuildData buildData, string value, ApiTypeInfo caller, bool attribute = false) { if (string.IsNullOrEmpty(value)) return null; + + // Special case for Engine TEXT macro + if (value.StartsWith("TEXT(\"") && value.EndsWith("\")")) + return value.Substring(5, value.Length - 6); + value = value.Replace("::", "."); + if (attribute) + { + // Value constructors (eg. Vector2(1, 2)) + if (value.Contains('(') && value.Contains(')')) + { + // Support for in-built types + if (value.StartsWith("Vector2(")) + return $"typeof(Vector2), \"{value.Substring(8, value.Length - 9).Replace("f", "")}\""; + if (value.StartsWith("Vector3(")) + return $"typeof(Vector3), \"{value.Substring(8, value.Length - 9).Replace("f", "")}\""; + if (value.StartsWith("Vector4(")) + return $"typeof(Vector4), \"{value.Substring(8, value.Length - 9).Replace("f", "")}\""; + + return null; + } + + // Constants (eg. Vector2::Zero) + if (value.Contains('.')) + { + // Support for in-built constants + switch (value) + { + case "Vector2.Zero": return "typeof(Vector2), \"0,0\""; + case "Vector2.One": return "typeof(Vector2), \"1,1\""; + case "Vector3.Zero": return "typeof(Vector3), \"0,0,0\""; + case "Vector3.One": return "typeof(Vector3), \"1,1,1\""; + case "Vector4.Zero": return "typeof(Vector4), \"0,0,0,0\""; + case "Vector4.One": return "typeof(Vector4), \"1,1,1,1\""; + case "Quaternion.Identity": return "typeof(Quaternion), \"0,0,0,1\""; + } + + return null; + } + } + // Skip constants unsupported in C# var dot = value.LastIndexOf('.'); if (dot != -1) @@ -66,10 +109,6 @@ namespace Flax.Build.Bindings return null; } - // Special case for Engine TEXT macro - if (value.Contains("TEXT(\"")) - return value.Replace("TEXT(\"", "(\""); - // Special case for value constructors if (value.Contains('(') && value.Contains(')')) return "new " + value; @@ -100,6 +139,8 @@ namespace Flax.Build.Bindings private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) { string result; + if (typeInfo?.Type == null) + throw new ArgumentNullException(); // Use dynamic array as wrapper container for fixed-size native arrays if (typeInfo.IsArray) @@ -394,26 +435,6 @@ namespace Flax.Build.Bindings } } - private static bool IsDefaultValueSupported(string value) - { - // TEXT macro (eg. TEXT("text")) - // TODO: support string for default value attribute - if (value.Contains("TEXT(\"")) - return false; - - // Value constructors (eg. Vector2(1, 2)) - // TODO: support value constructors for default value attribute - if (value.Contains('(') && value.Contains(')')) - return false; - - // Constants (eg. Vector2::Zero) - // TODO: support constants for default value attribute - if (value.Contains("::")) - return false; - - return true; - } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged, string defaultValue = null) { var writeTooltip = true; @@ -438,21 +459,27 @@ namespace Flax.Build.Bindings if (comment.Length >= 3 && comment[0] == "/// " && comment[1].StartsWith("/// ") && - comment[2] == "/// ") + comment[comment.Length - 1] == "/// ") { var tooltip = comment[1].Substring(4); if (tooltip.StartsWith("Gets the ")) tooltip = "The " + tooltip.Substring(9); + for (int i = 3; i < comment.Length; i++) + { + if (comment[i - 1].StartsWith("/// ")) + tooltip += " " + comment[i - 1].Substring(4); + } if (tooltip.IndexOf('\"') != -1) tooltip = tooltip.Replace("\"", "\\\""); contents.Append(indent).Append("[Tooltip(\"").Append(tooltip).Append("\")]").AppendLine(); } } - if (!string.IsNullOrEmpty(defaultValue) && writeDefaultValue && IsDefaultValueSupported(defaultValue)) + if (writeDefaultValue) { // Write default value attribute - defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, defaultValue, apiTypeInfo); - contents.Append(indent).Append("[DefaultValue(").Append(defaultValue).Append(")]").AppendLine(); + defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, defaultValue, apiTypeInfo, true); + if (defaultValue != null) + contents.Append(indent).Append("[DefaultValue(").Append(defaultValue).Append(")]").AppendLine(); } } @@ -642,12 +669,9 @@ namespace Flax.Build.Bindings contents.Append(returnValueType).Append(' ').Append(fieldInfo.Name); if (!useUnmanaged) { - if (fieldInfo.DefaultValue != null) - { - var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, fieldInfo.DefaultValue, classInfo); - if (!string.IsNullOrEmpty(defaultValue)) - contents.Append(" = ").Append(defaultValue); - } + var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, fieldInfo.DefaultValue, classInfo); + if (!string.IsNullOrEmpty(defaultValue)) + contents.Append(" = ").Append(defaultValue); contents.AppendLine(";"); continue; } @@ -773,12 +797,9 @@ namespace Flax.Build.Bindings contents.Append(' '); contents.Append(parameterInfo.Name); - if (parameterInfo.DefaultValue != null) - { - var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, parameterInfo.DefaultValue, classInfo); - if (!string.IsNullOrEmpty(defaultValue)) - contents.Append(" = ").Append(defaultValue); - } + var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, parameterInfo.DefaultValue, classInfo); + if (!string.IsNullOrEmpty(defaultValue)) + contents.Append(" = ").Append(defaultValue); } contents.Append(')').AppendLine().AppendLine(indent + "{"); @@ -865,7 +886,7 @@ namespace Flax.Build.Bindings contents.Append("static "); string type; - if (fieldInfo.NoArray && fieldInfo.Type.IsArray) + if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { // Fixed-size array that needs to be inlined into structure instead of passing it as managed array fieldInfo.Type.IsArray = false; @@ -1027,6 +1048,30 @@ namespace Flax.Build.Bindings return true; } + private static void GenerateCSharpCollectNamespaces(BuildData buildData, ApiTypeInfo apiType, HashSet usedNamespaces) + { + if (apiType is ClassInfo classInfo) + { + foreach (var field in classInfo.Fields) + { + var fieldInfo = FindApiTypeInfo(buildData, field.Type, classInfo); + if (fieldInfo != null && !string.IsNullOrWhiteSpace(fieldInfo.Namespace) && fieldInfo.Namespace != apiType.Namespace) + usedNamespaces.Add(fieldInfo.Namespace); + } + } + else if (apiType is StructureInfo structureInfo) + { + foreach (var field in structureInfo.Fields) + { + var fieldInfo = FindApiTypeInfo(buildData, field.Type, structureInfo); + if (fieldInfo != null && !string.IsNullOrWhiteSpace(fieldInfo.Namespace) && fieldInfo.Namespace != apiType.Namespace) + usedNamespaces.Add(fieldInfo.Namespace); + } + } + foreach (var child in apiType.Children) + GenerateCSharpCollectNamespaces(buildData, child, usedNamespaces); + } + private static void GenerateCSharp(BuildData buildData, ModuleInfo moduleInfo, ref BindingsResult bindings) { var contents = new StringBuilder(); @@ -1041,25 +1086,25 @@ namespace Flax.Build.Bindings contents.AppendLine(); // Using declarations - contents.AppendLine("using System;"); - contents.AppendLine("using System.ComponentModel;"); - contents.AppendLine("using System.Runtime.CompilerServices;"); - contents.AppendLine("using System.Runtime.InteropServices;"); + CSharpUsedNamespaces.Clear(); + CSharpUsedNamespaces.Add("System"); + CSharpUsedNamespaces.Add("System.ComponentModel"); + CSharpUsedNamespaces.Add("System.Globalization"); + CSharpUsedNamespaces.Add("System.Runtime.CompilerServices"); + CSharpUsedNamespaces.Add("System.Runtime.InteropServices"); + CSharpUsedNamespaces.Add("FlaxEngine"); foreach (var e in moduleInfo.Children) { - bool tmp = false; foreach (var apiTypeInfo in e.Children) { - if (apiTypeInfo.Namespace != "FlaxEngine") - { - tmp = true; - contents.AppendLine("using FlaxEngine;"); - break; - } + GenerateCSharpCollectNamespaces(buildData, apiTypeInfo, CSharpUsedNamespaces); } - if (tmp) - break; } + CSharpUsedNamespacesSorted.Clear(); + CSharpUsedNamespacesSorted.AddRange(CSharpUsedNamespaces); + CSharpUsedNamespacesSorted.Sort(); + foreach (var e in CSharpUsedNamespacesSorted) + contents.AppendLine($"using {e};"); // TODO: custom using declarations support // TODO: generate using declarations based on references modules (eg. using FlaxEngine, using Plugin1 in game API) @@ -1092,7 +1137,7 @@ namespace Flax.Build.Bindings private static unsafe void GenerateCSharp(BuildData buildData, IGrouping binaryModule) { // Skip generating C# bindings code for native-only modules - if (binaryModule.Any(x => !x.BuildCSharp)) + if (binaryModule.All(x => !x.BuildCSharp)) return; var contents = new StringBuilder(); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index 8413917bb..8fc7d18f2 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -18,7 +18,8 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { - private static readonly Dictionary _typeCache = new Dictionary(); + private static readonly Dictionary TypeCache = new Dictionary(); + private const int CacheVersion = 7; internal static void Write(BinaryWriter writer, string e) { @@ -160,7 +161,7 @@ namespace Flax.Build.Bindings var typename = reader.ReadString(); if (string.IsNullOrEmpty(typename)) return e; - if (!_typeCache.TryGetValue(typename, out var type)) + if (!TypeCache.TryGetValue(typename, out var type)) { type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); if (type == null) @@ -169,7 +170,7 @@ namespace Flax.Build.Bindings Log.Error(msg); throw new Exception(msg); } - _typeCache.Add(typename, type); + TypeCache.Add(typename, type); } e = (T)Activator.CreateInstance(type); e.Read(reader); @@ -185,7 +186,7 @@ namespace Flax.Build.Bindings for (int i = 0; i < count; i++) { var typename = reader.ReadString(); - if (!_typeCache.TryGetValue(typename, out var type)) + if (!TypeCache.TryGetValue(typename, out var type)) { type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); if (type == null) @@ -194,7 +195,7 @@ namespace Flax.Build.Bindings Log.Error(msg); throw new Exception(msg); } - _typeCache.Add(typename, type); + TypeCache.Add(typename, type); } var e = (T)Activator.CreateInstance(type); e.Read(reader); @@ -218,7 +219,7 @@ namespace Flax.Build.Bindings using (var writer = new BinaryWriter(stream, Encoding.UTF8)) { // Version - writer.Write(5); + writer.Write(CacheVersion); writer.Write(File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks); // Build options @@ -249,66 +250,66 @@ namespace Flax.Build.Bindings var path = GetCachePath(moduleInfo.Module, moduleOptions); if (!File.Exists(path)) return false; - using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var reader = new BinaryReader(stream, Encoding.UTF8)) + try { - // Version - var version = reader.ReadInt32(); - if (version != 5) - return false; - if (File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks != reader.ReadInt64()) - return false; - - // Build options - if (reader.ReadString() != moduleOptions.IntermediateFolder || - reader.ReadInt32() != (int)moduleOptions.Platform.Target || - reader.ReadInt32() != (int)moduleOptions.Architecture || - reader.ReadInt32() != (int)moduleOptions.Configuration) - return false; - var publicDefinitions = Read(reader, Utilities.GetEmptyArray()); - if (publicDefinitions.Length != moduleOptions.PublicDefinitions.Count || publicDefinitions.Any(x => !moduleOptions.PublicDefinitions.Contains(x))) - return false; - var privateDefinitions = Read(reader, Utilities.GetEmptyArray()); - if (privateDefinitions.Length != moduleOptions.PrivateDefinitions.Count || privateDefinitions.Any(x => !moduleOptions.PrivateDefinitions.Contains(x))) - return false; - var preprocessorDefinitions = Read(reader, Utilities.GetEmptyArray()); - if (preprocessorDefinitions.Length != moduleOptions.CompileEnv.PreprocessorDefinitions.Count || preprocessorDefinitions.Any(x => !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(x))) - return false; - - // Header files - var headerFilesCount = reader.ReadInt32(); - if (headerFilesCount != headerFiles.Count) - return false; - for (int i = 0; i < headerFilesCount; i++) + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new BinaryReader(stream, Encoding.UTF8)) { - var headerFile = headerFiles[i]; - if (headerFile != reader.ReadString()) + // Version + var version = reader.ReadInt32(); + if (version != CacheVersion) return false; - if (File.GetLastWriteTime(headerFile).Ticks > reader.ReadInt64()) + if (File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks != reader.ReadInt64()) return false; - } - // Info - var newModuleInfo = new ModuleInfo - { - Module = moduleInfo.Module, - Name = moduleInfo.Name, - Namespace = moduleInfo.Namespace, - IsFromCache = true, - }; - try - { + // Build options + if (reader.ReadString() != moduleOptions.IntermediateFolder || + reader.ReadInt32() != (int)moduleOptions.Platform.Target || + reader.ReadInt32() != (int)moduleOptions.Architecture || + reader.ReadInt32() != (int)moduleOptions.Configuration) + return false; + var publicDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (publicDefinitions.Length != moduleOptions.PublicDefinitions.Count || publicDefinitions.Any(x => !moduleOptions.PublicDefinitions.Contains(x))) + return false; + var privateDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (privateDefinitions.Length != moduleOptions.PrivateDefinitions.Count || privateDefinitions.Any(x => !moduleOptions.PrivateDefinitions.Contains(x))) + return false; + var preprocessorDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (preprocessorDefinitions.Length != moduleOptions.CompileEnv.PreprocessorDefinitions.Count || preprocessorDefinitions.Any(x => !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(x))) + return false; + + // Header files + var headerFilesCount = reader.ReadInt32(); + if (headerFilesCount != headerFiles.Count) + return false; + for (int i = 0; i < headerFilesCount; i++) + { + var headerFile = headerFiles[i]; + if (headerFile != reader.ReadString()) + return false; + if (File.GetLastWriteTime(headerFile).Ticks > reader.ReadInt64()) + return false; + } + + // Info + var newModuleInfo = new ModuleInfo + { + Module = moduleInfo.Module, + Name = moduleInfo.Name, + Namespace = moduleInfo.Namespace, + IsFromCache = true, + }; newModuleInfo.Read(reader); // Skip parsing and use data loaded from cache moduleInfo = newModuleInfo; return true; } - catch - { - // Skip loading cache - return false; - } + } + catch + { + // Skip loading cache + return false; } } } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 35ca38077..5868bcd8f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -223,6 +223,7 @@ namespace Flax.Build.Bindings if (!apiType.IsInBuild && !apiType.IsEnum) { // Use declared type initializer + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); return $"{apiType.FullNameNative}::TypeInitializer.GetType().ManagedClass->GetNative()"; } } @@ -262,6 +263,67 @@ namespace Flax.Build.Bindings return "Scripting::FindClassNative(\"" + managedType + "\")"; } + private static string GenerateCppGetNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo) + { + // Optimal path for in-build types + var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + switch (managedType) + { + case "bool": + case "sbyte": + case "byte": + case "short": + case "ushort": + case "int": + case "uint": + case "long": + case "ulong": + case "float": + case "double": + case "string": + case "object": + case "void": + case "char": + case "IntPtr": + case "UIntPtr": return "mono_class_get_type(" + GenerateCppGetNativeClass(buildData, typeInfo, caller, null) + ')'; + } + + // Find API type + var apiType = FindApiTypeInfo(buildData, typeInfo, caller); + if (apiType != null) + { + CppReferencesFiles.Add(apiType.File); + if (apiType.IsStruct && !apiType.IsPod && !CppUsedNonPodTypes.Contains(apiType)) + CppUsedNonPodTypes.Add(apiType); + if (!apiType.IsInBuild && !apiType.IsEnum) + { + // Use declared type initializer + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); + return $"mono_class_get_type({apiType.FullNameNative}::TypeInitializer.GetType().ManagedClass->GetNative())"; + } + } + + // Pass it from C# in glue parameter if used inside the wrapper function + if (functionInfo != null) + { + var customParam = new FunctionInfo.ParameterInfo + { + Name = "resultArrayItemType" + functionInfo.Glue.CustomParameters.Count, + DefaultValue = "typeof(" + managedType + ')', + Type = new TypeInfo + { + Type = "MonoReflectionType", + IsPtr = true, + }, + }; + functionInfo.Glue.CustomParameters.Add(customParam); + return "mono_reflection_type_get_type(" + customParam.Name + ')'; + } + + // Convert MonoClass* into MonoType* + return "mono_class_get_type(" + GenerateCppGetNativeClass(buildData, typeInfo, caller, null) + ')'; + } + private static string GenerateCppWrapperNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out string type, FunctionInfo functionInfo) { // Use dynamic array as wrapper container for fixed-size native arrays @@ -307,40 +369,10 @@ namespace Flax.Build.Bindings case "MClass": type = "MonoReflectionType*"; return "MUtils::GetType({0})"; + case "CultureInfo": + type = "void*"; + return "MUtils::ToManaged({0})"; default: - var apiType = FindApiTypeInfo(buildData, typeInfo, caller); - if (apiType != null) - { - CppReferencesFiles.Add(apiType.File); - - // Scripting Object - if (apiType.IsScriptingObject) - { - type = "MonoObject*"; - return "ScriptingObject::ToManaged((ScriptingObject*){0})"; - } - - // Non-POD structure passed as value (eg. it contains string or array inside) - if (apiType.IsStruct && !apiType.IsPod) - { - // Use wrapper structure that represents the memory layout of the managed data - if (!CppUsedNonPodTypes.Contains(apiType)) - CppUsedNonPodTypes.Add(apiType); - if (functionInfo != null) - type = apiType.Name + "Managed*"; - else - type = apiType.Name + "Managed"; - return "ToManaged({0})"; - } - - // Nested type (namespace prefix is required) - if (!(apiType.Parent is FileInfo)) - { - type = apiType.FullNameNative; - return string.Empty; - } - } - // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { @@ -368,8 +400,8 @@ namespace Flax.Build.Bindings { CppIncludeFiles.Add("Engine/Scripting/InternalCalls/ManagedDictionary.h"); type = "MonoObject*"; - var keyClass = GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo); - var valueClass = GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[1], caller, functionInfo); + var keyClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller, functionInfo); + var valueClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller, functionInfo); return "ManagedDictionary::ToManaged({0}, " + keyClass + ", " + valueClass + ")"; } @@ -388,8 +420,54 @@ namespace Flax.Build.Bindings return "ManagedBitArray::ToManaged({0})"; } + var apiType = FindApiTypeInfo(buildData, typeInfo, caller); + if (apiType != null) + { + CppReferencesFiles.Add(apiType.File); + + // Scripting Object + if (apiType.IsScriptingObject) + { + type = "MonoObject*"; + return "ScriptingObject::ToManaged((ScriptingObject*){0})"; + } + + // Non-POD structure passed as value (eg. it contains string or array inside) + if (apiType.IsStruct && !apiType.IsPod) + { + // Use wrapper structure that represents the memory layout of the managed data + if (!CppUsedNonPodTypes.Contains(apiType)) + CppUsedNonPodTypes.Add(apiType); + if (functionInfo != null) + type = apiType.Name + "Managed*"; + else + type = apiType.Name + "Managed"; + return "ToManaged({0})"; + } + + // Managed class + if (apiType.IsClass) + { + // Use wrapper structure that represents the memory layout of the managed data + if (!CppUsedNonPodTypes.Contains(apiType)) + { + CppUsedNonPodTypes.Add(apiType); + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); + } + type = "MonoObject*"; + return "MConverter<" + apiType.Name + ">::Box({0})"; + } + + // Nested type (namespace prefix is required) + if (!(apiType.Parent is FileInfo)) + { + type = apiType.FullNameNative; + return string.Empty; + } + } + type = typeInfo.ToString(); - return "{0}"; + return string.Empty; } } @@ -448,6 +526,9 @@ namespace Flax.Build.Bindings case "VariantType": type = "MonoReflectionType*"; return "MUtils::UnboxVariantType({0})"; + case "CultureInfo": + type = "void*"; + return "MUtils::ToNative({0})"; default: // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) @@ -473,10 +554,11 @@ namespace Flax.Build.Bindings // Array if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { + var T = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); type = "MonoArray*"; if (typeInfo.GenericArgs.Count != 1) - return "MUtils::ToArray<" + typeInfo.GenericArgs[0] + ", " + typeInfo.GenericArgs[1] + ">({0})"; - return "MUtils::ToArray<" + typeInfo.GenericArgs[0] + ">({0})"; + return "MUtils::ToArray<" + T + ", " + typeInfo.GenericArgs[1] + ">({0})"; + return "MUtils::ToArray<" + T + ">({0})"; } // Span @@ -546,6 +628,19 @@ namespace Flax.Build.Bindings return "ToNative({0})"; } + // Managed class + if (apiType.IsClass && !apiType.IsScriptingObject) + { + // Use wrapper structure that represents the memory layout of the managed data + if (!CppUsedNonPodTypes.Contains(apiType)) + { + CppUsedNonPodTypes.Add(apiType); + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); + } + type = "MonoObject*"; + return "MConverter<" + apiType.Name + ">::Unbox({0})"; + } + // Nested type (namespace prefix is required) if (!(apiType.Parent is FileInfo)) { @@ -857,11 +952,13 @@ namespace Flax.Build.Bindings contents.Append(parameterInfo.Name); } + CppIncludeFiles.Add("Engine/Profiler/ProfilerCPU.h"); + contents.Append(')'); contents.AppendLine(); contents.AppendLine(" {"); contents.AppendLine($" auto object = ({classInfo.NativeName}*)this;"); - contents.AppendLine(" static THREADLOCAL bool IsDuringWrapperCall = false;"); + contents.AppendLine(" static THREADLOCAL void* WrapperCallInstance = nullptr;"); contents.AppendLine(" ScriptingTypeHandle managedTypeHandle = object->GetTypeHandle();"); contents.AppendLine(" const ScriptingType* managedTypePtr = &managedTypeHandle.GetType();"); @@ -871,7 +968,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" managedTypePtr = &managedTypeHandle.GetType();"); contents.AppendLine(" }"); - contents.AppendLine(" if (IsDuringWrapperCall)"); + contents.AppendLine(" if (WrapperCallInstance == object)"); contents.AppendLine(" {"); contents.AppendLine(" // Prevent stack overflow by calling native base method"); contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Script.ScriptVTableBase;"); @@ -890,9 +987,11 @@ 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(" MonoObject* exception = nullptr;"); - contents.AppendLine(" IsDuringWrapperCall = true;"); + contents.AppendLine(" auto prevWrapperCallInstance = WrapperCallInstance;"); + contents.AppendLine(" WrapperCallInstance = object;"); contents.AppendLine("#if USE_MONO_AOT"); if (functionInfo.Parameters.Count == 0) @@ -907,7 +1006,7 @@ namespace Flax.Build.Bindings contents.Append($" params[{i}] = {paramValue};").AppendLine(); } - contents.AppendLine(" auto __result = method->Invoke(object->GetOrCreateManagedInstance(), params, &exception);"); + contents.AppendLine(" auto __result = mono_runtime_invoke(method->GetNative(), object->GetOrCreateManagedInstance(), params, &exception);"); contents.AppendLine("#else"); var thunkParams = string.Empty; @@ -966,7 +1065,7 @@ namespace Flax.Build.Bindings } contents.AppendLine("#endif"); - contents.AppendLine(" IsDuringWrapperCall = false;"); + contents.AppendLine(" WrapperCallInstance = prevWrapperCallInstance;"); contents.AppendLine(" if (exception)"); contents.AppendLine(" DebugLog::LogException(exception);"); @@ -996,6 +1095,8 @@ namespace Flax.Build.Bindings var baseType = classInfo?.BaseType ?? structureInfo?.BaseType; if (classInfo != null && classInfo.IsBaseTypeHidden) baseType = null; + if (baseType != null && (baseType.Type == "PersistentScriptingObject" || baseType.Type == "ScriptingObject")) + baseType = null; CppAutoSerializeFields.Clear(); CppAutoSerializeProperties.Clear(); CppIncludeFiles.Add("Engine/Serialization/Serialization.h"); @@ -1128,7 +1229,10 @@ namespace Flax.Build.Bindings var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; // C# event invoking wrapper (calls C# event from C++ delegate) + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); + CppIncludeFiles.Add("Engine/Profiler/ProfilerCPU.h"); contents.Append(" "); if (eventInfo.IsStatic) contents.Append("static "); @@ -1143,8 +1247,9 @@ namespace Flax.Build.Bindings contents.Append(" {").AppendLine(); contents.Append(" static MMethod* mmethod = nullptr;").AppendLine(); contents.Append(" if (!mmethod)").AppendLine(); - contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).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(" MonoObject* exception = nullptr;").AppendLine(); if (paramsCount == 0) contents.AppendLine(" void** params = nullptr;"); @@ -1161,7 +1266,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" MonoObject* instance = nullptr;"); else contents.AppendLine($" MonoObject* instance = (({classTypeNameNative}*)this)->GetManagedInstance();"); - contents.Append(" mmethod->Invoke(instance, params, &exception);").AppendLine(); + contents.Append(" mono_runtime_invoke(mmethod->GetNative(), instance, params, &exception);").AppendLine(); contents.Append(" if (exception)").AppendLine(); contents.Append(" DebugLog::LogException(exception);").AppendLine(); contents.Append(" }").AppendLine().AppendLine(); @@ -1250,7 +1355,13 @@ namespace Flax.Build.Bindings if (fieldInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, fieldInfo.Getter, "{0}"); if (fieldInfo.Setter != null) - GenerateCppWrapperFunction(buildData, contents, classInfo, fieldInfo.Setter, "{0} = {1}"); + { + var callFormat = "{0} = {1}"; + var type = fieldInfo.Setter.Parameters[0].Type; + if (type.IsArray) + callFormat = $"auto __tmp = {{1}}; for (int32 i = 0; i < {type.ArraySize}; i++) {{0}}[i] = __tmp[i]"; + GenerateCppWrapperFunction(buildData, contents, classInfo, fieldInfo.Setter, callFormat); + } } // Properties @@ -1601,7 +1712,7 @@ namespace Flax.Build.Bindings for (var i = 0; i < structureInfo.Fields.Count; i++) { var fieldInfo = structureInfo.Fields[i]; - if (fieldInfo.IsReadOnly || fieldInfo.IsStatic) + if (fieldInfo.IsReadOnly || fieldInfo.IsStatic || fieldInfo.Access == AccessLevel.Private) continue; if (i == 0) contents.AppendLine($" if (name == TEXT(\"{fieldInfo.Name}\"))"); @@ -1617,7 +1728,7 @@ namespace Flax.Build.Bindings for (var i = 0; i < structureInfo.Fields.Count; i++) { var fieldInfo = structureInfo.Fields[i]; - if (fieldInfo.IsReadOnly || fieldInfo.IsStatic) + if (fieldInfo.IsReadOnly || fieldInfo.IsStatic || fieldInfo.Access == AccessLevel.Private) continue; if (i == 0) contents.AppendLine($" if (name == TEXT(\"{fieldInfo.Name}\"))"); @@ -1872,41 +1983,53 @@ namespace Flax.Build.Bindings foreach (var apiType in CppUsedNonPodTypesList) { header.AppendLine(); - if (apiType is StructureInfo structureInfo) + var structureInfo = apiType as StructureInfo; + var classInfo = apiType as ClassInfo; + List fields; + if (structureInfo != null) + fields = structureInfo.Fields; + else if (classInfo != null) + fields = classInfo.Fields; + else + throw new Exception("Not supported Non-POD type " + apiType); + + // Get the full typename with nested parent prefix + var fullName = apiType.FullNameNative; + + // Generate managed type memory layout + header.Append("struct ").Append(apiType.Name).Append("Managed").AppendLine(); + header.Append('{').AppendLine(); + if (classInfo != null) + header.AppendLine(" MonoObject obj;"); + for (var i = 0; i < fields.Count; i++) { - // Get the full typename with nested parent prefix - var fullName = apiType.FullNameNative; + var fieldInfo = fields[i]; + if (fieldInfo.IsStatic) + continue; + string type; - // Generate managed structure memory layout - header.Append("struct ").Append(apiType.Name).Append("Managed").AppendLine(); - header.Append('{').AppendLine(); - for (var i = 0; i < structureInfo.Fields.Count; i++) + if (fieldInfo.NoArray && fieldInfo.Type.IsArray) { - var fieldInfo = structureInfo.Fields[i]; - if (fieldInfo.IsStatic) - continue; - string type; - - if (fieldInfo.NoArray && fieldInfo.Type.IsArray) - { - // Fixed-size array that needs to be inlined into structure instead of passing it as managed array - fieldInfo.Type.IsArray = false; - CppParamsWrappersCache[i] = GenerateCppWrapperNativeToManaged(buildData, fieldInfo.Type, structureInfo, out type, null); - fieldInfo.Type.IsArray = true; - header.AppendFormat(" {0} {1}[{2}];", type, fieldInfo.Name, fieldInfo.Type.ArraySize).AppendLine(); - continue; - } - - CppParamsWrappersCache[i] = GenerateCppWrapperNativeToManaged(buildData, fieldInfo.Type, structureInfo, out type, null); - header.AppendFormat(" {0} {1};", type, fieldInfo.Name).AppendLine(); + // Fixed-size array that needs to be inlined into structure instead of passing it as managed array + fieldInfo.Type.IsArray = false; + CppParamsWrappersCache[i] = GenerateCppWrapperNativeToManaged(buildData, fieldInfo.Type, apiType, out type, null); + fieldInfo.Type.IsArray = true; + header.AppendFormat(" {0} {1}[{2}];", type, fieldInfo.Name, fieldInfo.Type.ArraySize).AppendLine(); + continue; } - header.Append('}').Append(';').AppendLine(); + CppParamsWrappersCache[i] = GenerateCppWrapperNativeToManaged(buildData, fieldInfo.Type, apiType, out type, null); + header.AppendFormat(" {0} {1};", type, fieldInfo.Name).AppendLine(); + } + header.Append('}').Append(';').AppendLine(); + + if (structureInfo != null) + { // Generate forward declarations of structure converting functions header.AppendLine(); header.AppendLine("namespace {"); - header.AppendFormat("{0}Managed ToManaged(const {1}& value);", structureInfo.Name, fullName).AppendLine(); - header.AppendFormat("{1} ToNative(const {0}Managed& value);", structureInfo.Name, fullName).AppendLine(); + header.AppendFormat("{0}Managed ToManaged(const {1}& value);", apiType.Name, fullName).AppendLine(); + header.AppendFormat("{1} ToNative(const {0}Managed& value);", apiType.Name, fullName).AppendLine(); header.AppendLine("}"); // Generate MConverter @@ -1937,19 +2060,19 @@ namespace Flax.Build.Bindings // Generate converting function native -> managed header.AppendLine(); header.AppendLine("namespace {"); - header.AppendFormat("{0}Managed ToManaged(const {1}& value)", structureInfo.Name, fullName).AppendLine(); + header.AppendFormat("{0}Managed ToManaged(const {1}& value)", apiType.Name, fullName).AppendLine(); header.Append('{').AppendLine(); - header.AppendFormat(" {0}Managed result;", structureInfo.Name).AppendLine(); - for (var i = 0; i < structureInfo.Fields.Count; i++) + header.AppendFormat(" {0}Managed result;", apiType.Name).AppendLine(); + for (var i = 0; i < fields.Count; i++) { - var fieldInfo = structureInfo.Fields[i]; + var fieldInfo = fields[i]; if (fieldInfo.IsStatic) continue; if (fieldInfo.NoArray && fieldInfo.Type.IsArray) { // Fixed-size array needs to unbox every item manually if not using managed array - if (fieldInfo.Type.IsPod(buildData, structureInfo)) + if (fieldInfo.Type.IsPod(buildData, apiType)) header.AppendFormat(" Platform::MemoryCopy(result.{0}, value.{0}, sizeof({2}) * {1});", fieldInfo.Name, fieldInfo.Type.ArraySize, fieldInfo.Type).AppendLine(); else header.AppendFormat(" for (int32 i = 0; i < {0}; i++)", fieldInfo.Type.ArraySize).AppendLine().AppendFormat(" result.{0}[i] = value.{0}[i];", fieldInfo.Name).AppendLine(); @@ -1969,17 +2092,17 @@ namespace Flax.Build.Bindings // Generate converting function managed -> native header.AppendLine(); - header.AppendFormat("{1} ToNative(const {0}Managed& value)", structureInfo.Name, fullName).AppendLine(); + header.AppendFormat("{1} ToNative(const {0}Managed& value)", apiType.Name, fullName).AppendLine(); header.Append('{').AppendLine(); header.AppendFormat(" {0} result;", fullName).AppendLine(); - for (var i = 0; i < structureInfo.Fields.Count; i++) + for (var i = 0; i < fields.Count; i++) { - var fieldInfo = structureInfo.Fields[i]; + var fieldInfo = fields[i]; if (fieldInfo.IsStatic) continue; CppNonPodTypesConvertingGeneration = true; - var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, structureInfo, out _, null, out _); + var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, null, out _); CppNonPodTypesConvertingGeneration = false; if (fieldInfo.Type.IsArray) @@ -1987,7 +2110,7 @@ namespace Flax.Build.Bindings // Fixed-size array needs to unbox every item manually if (fieldInfo.NoArray) { - if (fieldInfo.Type.IsPod(buildData, structureInfo)) + if (fieldInfo.Type.IsPod(buildData, apiType)) header.AppendFormat(" Platform::MemoryCopy(result.{0}, value.{0}, sizeof({2}) * {1});", fieldInfo.Name, fieldInfo.Type.ArraySize, fieldInfo.Type).AppendLine(); else header.AppendFormat(" for (int32 i = 0; i < {0}; i++)", fieldInfo.Type.ArraySize).AppendLine().AppendFormat(" result.{0}[i] = value.{0}[i];", fieldInfo.Name).AppendLine(); @@ -2011,6 +2134,110 @@ namespace Flax.Build.Bindings header.Append('}').AppendLine(); header.AppendLine("}"); } + else if (classInfo != null) + { + // Generate MConverter + header.Append("template<>").AppendLine(); + header.AppendFormat("struct MConverter<{0}>", fullName).AppendLine(); + header.Append('{').AppendLine(); + + header.AppendFormat(" static MonoObject* Box(const {0}& data, MonoClass* klass)", fullName).AppendLine(); + header.Append(" {").AppendLine(); + header.AppendFormat(" auto obj = ({0}Managed*)mono_object_new(mono_domain_get(), klass);", fullName).AppendLine(); + for (var i = 0; i < fields.Count; i++) + { + var fieldInfo = fields[i]; + if (fieldInfo.IsStatic) + continue; + + if (fieldInfo.NoArray && fieldInfo.Type.IsArray) + { + // Fixed-size array needs to unbox every item manually if not using managed array + if (fieldInfo.Type.IsPod(buildData, apiType)) + header.AppendFormat(" Platform::MemoryCopy(obj->{0}, data.{0}, sizeof({2}) * {1});", fieldInfo.Name, fieldInfo.Type.ArraySize, fieldInfo.Type).AppendLine(); + else + header.AppendFormat(" for (int32 i = 0; i < {0}; i++)", fieldInfo.Type.ArraySize).AppendLine().AppendFormat(" obj->{0}[i] = data.{0}[i];", fieldInfo.Name).AppendLine(); + continue; + } + + var wrapper = CppParamsWrappersCache[i]; + header.AppendFormat(" MONO_OBJECT_SETREF(obj, {0}, ", fieldInfo.Name); + if (string.IsNullOrEmpty(wrapper)) + header.Append("data." + fieldInfo.Name); + else + header.AppendFormat(wrapper, "data." + fieldInfo.Name); + header.Append(')').Append(';').AppendLine(); + } + header.Append(" return (MonoObject*)obj;").AppendLine(); + header.Append(" }").AppendLine(); + + header.AppendFormat(" static MonoObject* Box(const {0}& data)", fullName).AppendLine(); + header.Append(" {").AppendLine(); + header.AppendFormat(" MonoClass* klass = {0}::TypeInitializer.GetType().ManagedClass->GetNative();", fullName).AppendLine(); + header.Append(" return Box(data, klass);").AppendLine(); + header.Append(" }").AppendLine(); + + header.AppendFormat(" static void Unbox({0}& result, MonoObject* data)", fullName).AppendLine(); + header.Append(" {").AppendLine(); + header.AppendFormat(" auto obj = ({0}Managed*)data;", fullName).AppendLine(); + for (var i = 0; i < fields.Count; i++) + { + var fieldInfo = fields[i]; + if (fieldInfo.IsStatic) + continue; + + CppNonPodTypesConvertingGeneration = true; + var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, null, out _); + CppNonPodTypesConvertingGeneration = false; + + if (fieldInfo.Type.IsArray) + { + // Fixed-size array needs to unbox every item manually + if (fieldInfo.NoArray) + { + if (fieldInfo.Type.IsPod(buildData, apiType)) + header.AppendFormat(" Platform::MemoryCopy(result.{0}, obj->{0}, sizeof({2}) * {1});", fieldInfo.Name, fieldInfo.Type.ArraySize, fieldInfo.Type).AppendLine(); + else + header.AppendFormat(" for (int32 i = 0; i < {0}; i++)", fieldInfo.Type.ArraySize).AppendLine().AppendFormat(" result.{0}[i] = obj->{0}[i];", fieldInfo.Name).AppendLine(); + } + else + { + wrapper = string.Format(wrapper.Remove(wrapper.Length - 6), string.Format("obj->{0}", fieldInfo.Name)); + header.AppendFormat(" auto tmp{0} = {1};", fieldInfo.Name, wrapper).AppendLine(); + header.AppendFormat(" for (int32 i = 0; i < {0} && i < tmp{1}.Count(); i++)", fieldInfo.Type.ArraySize, fieldInfo.Name).AppendLine(); + header.AppendFormat(" result.{0}[i] = tmp{0}[i];", fieldInfo.Name).AppendLine(); + } + continue; + } + + if (string.IsNullOrEmpty(wrapper)) + header.AppendFormat(" result.{0} = obj->{0};", fieldInfo.Name).AppendLine(); + else + header.AppendFormat(" result.{0} = {1};", fieldInfo.Name, string.Format(wrapper, string.Format("obj->{0}", fieldInfo.Name))).AppendLine(); + } + header.Append(" }").AppendLine(); + + header.AppendFormat(" static {0} Unbox(MonoObject* data)", fullName).AppendLine(); + header.Append(" {").AppendLine(); + header.AppendFormat(" {0} result;", fullName).AppendLine(); + header.Append(" Unbox(result, data);").AppendLine(); + header.Append(" return result;").AppendLine(); + header.Append(" }").AppendLine(); + + header.AppendFormat(" void ToManagedArray(MonoArray* result, const Span<{0}>& data)", fullName).AppendLine(); + header.Append(" {").AppendLine(); + header.Append(" for (int32 i = 0; i < data.Length(); i++)").AppendLine(); + header.Append(" mono_array_setref(result, i, Box(data[i]));").AppendLine(); + header.Append(" }").AppendLine(); + + header.AppendFormat(" void ToNativeArray(Array<{0}>& result, MonoArray* data, int32 length)", fullName).AppendLine(); + header.Append(" {").AppendLine(); + header.Append(" for (int32 i = 0; i < length; i++)").AppendLine(); + header.AppendFormat(" Unbox(result[i], (MonoObject*)mono_array_addr_with_size(data, sizeof({0}Managed), length));", fullName).AppendLine(); + header.Append(" }").AppendLine(); + + header.Append('}').Append(';').AppendLine(); + } } contents.Insert(headerPos, header.ToString()); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 7714e6abd..67e90efa9 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -213,7 +213,9 @@ namespace Flax.Build.Bindings type.GenericArgs = new List(); do { - type.GenericArgs.Add(ParseType(ref context)); + var argType = ParseType(ref context); + if (argType.Type != null) + type.GenericArgs.Add(argType); token = context.Tokenizer.NextToken(); } while (token.Type != TokenType.RightAngleBracket); @@ -1079,7 +1081,14 @@ namespace Flax.Build.Bindings { // Read the fixed array length ParseTypeArray(ref context, desc.Type, desc); - context.Tokenizer.ExpectToken(TokenType.SemiColon); + token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal }); + if (token.Type == TokenType.Equal) + { + // Fixed array initializer + context.Tokenizer.ExpectToken(TokenType.LeftCurlyBrace); + context.Tokenizer.SkipUntil(TokenType.RightCurlyBrace); + context.Tokenizer.ExpectToken(TokenType.SemiColon); + } } else if (token.Type == TokenType.Colon) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 742aa6f63..f8d26314f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -623,9 +623,6 @@ namespace Flax.Build.Bindings if (!fieldInfo.IsReadOnly) { - if (fieldInfo.Type.IsArray) - throw new NotImplementedException("Use ReadOnly on field. TODO: add support for setter for fixed-array fields."); - fieldInfo.Setter = new FunctionInfo { Name = "Set" + fieldInfo.Name, diff --git a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs index d01cde5e7..4a71a6f6b 100644 --- a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs @@ -18,6 +18,18 @@ namespace Flax.Build.Bindings public bool HasDefaultValue => !string.IsNullOrEmpty(DefaultValue); + /// + /// Gets a value indicating whether this type is POD (plain old data). + /// + public bool IsPod(Builder.BuildData buildData, ApiTypeInfo caller) + { + // Fixed array in C++ is converted into managed array in C# by default (char Data[100] -> char[] Data) + if (Type.IsArray && !NoArray) + return false; + + return Type.IsPod(buildData, caller); + } + public override void Write(BinaryWriter writer) { BindingsGenerator.Write(writer, Type); diff --git a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs index 75ff73720..0769d6ce6 100644 --- a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs @@ -34,6 +34,8 @@ namespace Flax.Build.Bindings BindingsGenerator.Write(writer, Module.BinaryModuleName); writer.Write(Module.BuildNativeCode); writer.Write(Module.BuildCSharp); + writer.Write(Globals.Project.Name); + writer.Write(Globals.Project.Version.ToString()); base.Write(writer); } @@ -44,7 +46,9 @@ namespace Flax.Build.Bindings reader.ReadString() != Module.FilePath || BindingsGenerator.Read(reader, Module.BinaryModuleName) != Module.BinaryModuleName || reader.ReadBoolean() != Module.BuildNativeCode || - reader.ReadBoolean() != Module.BuildCSharp + reader.ReadBoolean() != Module.BuildCSharp || + reader.ReadString() != Globals.Project.Name || + reader.ReadString() != Globals.Project.Version.ToString() ) throw new Exception(); diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index 882ed6295..5b664cc09 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -37,7 +37,7 @@ namespace Flax.Build.Bindings for (int i = 0; _isPod && i < Fields.Count; i++) { var field = Fields[i]; - if (!field.IsStatic && !field.Type.IsPod(buildData, this)) + if (!field.IsStatic && !field.IsPod(buildData, this)) { _isPod = false; } diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 2d15f6651..443b0687a 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -80,35 +80,55 @@ namespace Flax.Build.Bindings GenericArgs = BindingsGenerator.Read(reader, GenericArgs); } - public override string ToString() + public string GetFullNameNative(Builder.BuildData buildData, ApiTypeInfo caller) { - var sb = new StringBuilder(64); + var type = BindingsGenerator.FindApiTypeInfo(buildData, this, caller); + if (type == null) + return ToString(); + var sb = new StringBuilder(64); if (IsConst) sb.Append("const "); - - sb.Append(Type); - + sb.Append(type.FullNameNative); if (GenericArgs != null) { sb.Append('<'); - for (var i = 0; i < GenericArgs.Count; i++) { if (i != 0) sb.Append(", "); sb.Append(GenericArgs[i]); } - sb.Append('>'); } - if (IsRef) sb.Append('&'); - if (IsPtr) sb.Append('*'); + return sb.ToString(); + } + public override string ToString() + { + var sb = new StringBuilder(64); + if (IsConst) + sb.Append("const "); + sb.Append(Type); + if (GenericArgs != null) + { + sb.Append('<'); + for (var i = 0; i < GenericArgs.Count; i++) + { + if (i != 0) + sb.Append(", "); + sb.Append(GenericArgs[i]); + } + sb.Append('>'); + } + if (IsRef) + sb.Append('&'); + if (IsPtr) + sb.Append('*'); return sb.ToString(); } diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index b9ae1e319..c91fd9f9c 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -187,6 +187,8 @@ namespace Flax.Build // Setup var rules = GenerateRulesAssembly(); var rootProject = Globals.Project; + if (rootProject == null) + throw new Exception("Missing project."); var projectFiles = rootProject.GetAllProjects(); var targetGroups = rules.Targets.GroupBy(x => x.ProjectName); var workspaceRoot = rootProject.ProjectFolderPath; @@ -246,7 +248,7 @@ namespace Flax.Build // Get all modules aggregated into all binary modules used in all configurations of this target foreach (var configurationData in mainProject.Configurations) { - var configurationBinaryModules = GetBinaryModules(rootProject, configurationData.Target, configurationData.Modules); + var configurationBinaryModules = GetBinaryModules(projectInfo, configurationData.Target, configurationData.Modules); foreach (var configurationBinaryModule in configurationBinaryModules) { // Skip if none of the included binary modules is inside the project workspace (eg. merged external binary modules from engine to game project) @@ -272,7 +274,7 @@ namespace Flax.Build { var referenceBuildOptions = GetBuildOptions(referenceTarget, configurationData.TargetBuildOptions.Platform, configurationData.TargetBuildOptions.Toolchain, configurationData.Architecture, configurationData.Configuration, reference.Project.ProjectFolderPath); var referenceModules = CollectModules(rules, referenceBuildOptions.Platform, referenceTarget, referenceBuildOptions, referenceBuildOptions.Toolchain, referenceBuildOptions.Architecture, referenceBuildOptions.Configuration); - var referenceBinaryModules = GetBinaryModules(rootProject, referenceTarget, referenceModules); + var referenceBinaryModules = GetBinaryModules(projectInfo, referenceTarget, referenceModules); foreach (var binaryModule in referenceBinaryModules) { project.Defines.Add(binaryModule.Key.ToUpperInvariant() + "_API="); diff --git a/Source/Tools/Flax.Build/Build/Builder.cs b/Source/Tools/Flax.Build/Build/Builder.cs index 187a59776..1bcfca18e 100644 --- a/Source/Tools/Flax.Build/Build/Builder.cs +++ b/Source/Tools/Flax.Build/Build/Builder.cs @@ -82,6 +82,17 @@ namespace Flax.Build return GetModuleProject(module, buildData.Project); } + /// + /// Checks if the project that contains a given module (checks for modules located in the given project Source folder). + /// + /// The module. + /// The project to check. + /// True if project contains that module inside, otherwise it's external or referenced. + public static bool IsModuleFromProject(Module module, ProjectInfo project) + { + return GetModuleProject(module, project) == project; + } + /// /// Builds the targets. /// diff --git a/Source/Tools/Flax.Build/Build/EngineTarget.cs b/Source/Tools/Flax.Build/Build/EngineTarget.cs index 575d0cdc5..76191943c 100644 --- a/Source/Tools/Flax.Build/Build/EngineTarget.cs +++ b/Source/Tools/Flax.Build/Build/EngineTarget.cs @@ -99,10 +99,29 @@ namespace Flax.Build // Restore state from PreBuild Modules.Add("Main"); } + + // Mono on Linux is using dynamic linking and needs additional link files + if (buildOptions.Platform.Target == TargetPlatform.Linux && Platform.BuildTargetPlatform == TargetPlatform.Linux && !IsPreBuilt) + { + var task = graph.Add(); + task.PrerequisiteFiles.Add(Path.Combine(buildOptions.OutputFolder, "libmonosgen-2.0.so")); + task.ProducedFiles.Add(Path.Combine(buildOptions.OutputFolder, "libmonosgen-2.0.so.1")); + task.WorkingDirectory = buildOptions.OutputFolder; + task.CommandPath = "ln"; + task.CommandArguments = "-s -f libmonosgen-2.0.so libmonosgen-2.0.so.1"; + task = graph.Add(); + task.PrerequisiteFiles.Add(Path.Combine(buildOptions.OutputFolder, "libmonosgen-2.0.so")); + task.ProducedFiles.Add(Path.Combine(buildOptions.OutputFolder, "libmonosgen-2.0.so.1.0.0")); + task.WorkingDirectory = buildOptions.OutputFolder; + task.CommandPath = "ln"; + task.CommandArguments = "-s -f libmonosgen-2.0.so libmonosgen-2.0.so.1.0.0"; + } } private void BuildMainExecutable(TaskGraph graph, BuildOptions buildOptions) { + if (IsPreBuilt) + return; var outputPath = Path.Combine(buildOptions.OutputFolder, buildOptions.Platform.GetLinkOutputFileName(OutputName, LinkerOutput.Executable)); var exeBuildOptions = Builder.GetBuildOptions(this, buildOptions.Platform, buildOptions.Toolchain, buildOptions.Architecture, buildOptions.Configuration, buildOptions.WorkingDirectory); exeBuildOptions.LinkEnv.Output = LinkerOutput.Executable; @@ -122,7 +141,7 @@ namespace Flax.Build // Build Main module var mainModule = rules.GetModule("Main"); var mainModuleOutputPath = Path.Combine(exeBuildOptions.IntermediateFolder, mainModule.Name); - if (!IsPreBuilt && !Directory.Exists(mainModuleOutputPath)) + if (!Directory.Exists(mainModuleOutputPath)) Directory.CreateDirectory(mainModuleOutputPath); var mainModuleOptions = new BuildOptions { diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 5b748a481..0c3eedd53 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -696,6 +696,26 @@ namespace Flax.Build { // Export symbols from binary module moduleOptions.CompileEnv.PreprocessorDefinitions.Add(binaryModuleNameUpper + (target.UseSymbolsExports ? "_API=" + toolchain.DllExport : "_API=")); + + // Import symbols from binary modules containing the referenced modules (from this project only, external ones are handled via ReferenceBuilds below) + foreach (var moduleName in moduleOptions.PrivateDependencies) + { + var dependencyModule = buildData.Rules.GetModule(moduleName); + if (dependencyModule != null && !string.IsNullOrEmpty(dependencyModule.BinaryModuleName) && dependencyModule.BinaryModuleName != binaryModule.Key && IsModuleFromProject(dependencyModule, project) && buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions)) + { + // Import symbols from referenced binary module + moduleOptions.CompileEnv.PreprocessorDefinitions.Add(dependencyModule.BinaryModuleName.ToUpperInvariant() + "_API=" + toolchain.DllImport); + } + } + foreach (var moduleName in moduleOptions.PublicDependencies) + { + var dependencyModule = buildData.Rules.GetModule(moduleName); + if (dependencyModule != null && !string.IsNullOrEmpty(dependencyModule.BinaryModuleName) && dependencyModule.BinaryModuleName != binaryModule.Key && IsModuleFromProject(dependencyModule, project) && buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions)) + { + // Import symbols from referenced binary module + moduleOptions.CompileEnv.PreprocessorDefinitions.Add(dependencyModule.BinaryModuleName.ToUpperInvariant() + "_API=" + toolchain.DllImport); + } + } } else { diff --git a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs index b2656ce0d..70c90c154 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs @@ -44,8 +44,8 @@ namespace Flax.Build.Plugins contents.AppendLine(); contents.AppendLine(" {"); contents.AppendLine($" auto object = ({classInfo.NativeName}*)this;"); - contents.AppendLine(" static THREADLOCAL bool IsDuringWrapperCall = false;"); - contents.AppendLine(" if (IsDuringWrapperCall)"); + contents.AppendLine(" static THREADLOCAL void* WrapperCallInstance = nullptr;"); + contents.AppendLine(" if (WrapperCallInstance == object)"); contents.AppendLine(" {"); contents.AppendLine(" // Prevent stack overflow by calling base method"); contents.AppendLine(" const auto scriptVTableBase = object->GetType().Script.ScriptVTableBase;"); @@ -78,9 +78,10 @@ namespace Flax.Build.Plugins contents.AppendLine(" Variant* parameters = nullptr;"); } - contents.AppendLine(" IsDuringWrapperCall = true;"); + contents.AppendLine(" auto prevWrapperCallInstance = WrapperCallInstance;"); + contents.AppendLine(" WrapperCallInstance = object;"); contents.AppendLine($" auto __result = VisualScripting::Invoke(scriptVTable[{scriptVTableIndex}], object, Span(parameters, {functionInfo.Parameters.Count}));"); - contents.AppendLine(" IsDuringWrapperCall = false;"); + contents.AppendLine(" WrapperCallInstance = prevWrapperCallInstance;"); if (!functionInfo.ReturnType.IsVoid) { diff --git a/Source/Tools/Flax.Build/Build/ProjectTarget.cs b/Source/Tools/Flax.Build/Build/ProjectTarget.cs index 19d23690f..790452b34 100644 --- a/Source/Tools/Flax.Build/Build/ProjectTarget.cs +++ b/Source/Tools/Flax.Build/Build/ProjectTarget.cs @@ -45,7 +45,7 @@ namespace Flax.Build throw new Exception($"Invalid or missing editor target {project.EditorTarget} specified in project {project.Name} (referenced by project {Project.Name})."); return result; } - if (!IsEditor && !string.IsNullOrEmpty(project.GameTarget)) + if (!string.IsNullOrEmpty(project.GameTarget)) { var result = projectTargets.FirstOrDefault(x => x.Name == project.GameTarget); if (result == null) diff --git a/Source/Tools/Flax.Build/Deploy/Configuration.cs b/Source/Tools/Flax.Build/Deploy/Configuration.cs index 387a009d8..7cf30ccd6 100644 --- a/Source/Tools/Flax.Build/Deploy/Configuration.cs +++ b/Source/Tools/Flax.Build/Deploy/Configuration.cs @@ -15,5 +15,17 @@ namespace Flax.Build /// [CommandLine("deployPlatforms", "Builds and packages the platforms data.")] public static bool DeployPlatforms; + + /// + /// Certificate file path for binaries signing. + /// + [CommandLine("deployCert", "Certificate file path for binaries signing.")] + public static string DeployCert; + + /// + /// Certificate file password for binaries signing. + /// + [CommandLine("deployCertPass", "Certificate file password for binaries signing.")] + public static string DeployCertPass; } } diff --git a/Source/Tools/Flax.Build/Deploy/Deployer.cs b/Source/Tools/Flax.Build/Deploy/Deployer.cs index 472a94c22..9b6ac7657 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployer.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployer.cs @@ -103,9 +103,10 @@ namespace Flax.Deploy private static void BuildEditor() { - FlaxBuild.Build(Globals.EngineRoot, "FlaxEditor", TargetPlatform.Windows, TargetArchitecture.x64, TargetConfiguration.Debug); - FlaxBuild.Build(Globals.EngineRoot, "FlaxEditor", TargetPlatform.Windows, TargetArchitecture.x64, TargetConfiguration.Development); - FlaxBuild.Build(Globals.EngineRoot, "FlaxEditor", TargetPlatform.Windows, TargetArchitecture.x64, TargetConfiguration.Release); + var targetPlatform = Platform.BuildPlatform.Target; + FlaxBuild.Build(Globals.EngineRoot, "FlaxEditor", targetPlatform, TargetArchitecture.x64, TargetConfiguration.Debug); + FlaxBuild.Build(Globals.EngineRoot, "FlaxEditor", targetPlatform, TargetArchitecture.x64, TargetConfiguration.Development); + FlaxBuild.Build(Globals.EngineRoot, "FlaxEditor", targetPlatform, TargetArchitecture.x64, TargetConfiguration.Release); } private static bool CannotBuildPlatform(TargetPlatform platform) diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index b545f844d..33dbdbb78 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -12,6 +12,18 @@ namespace Flax.Deploy { partial class Deployment { + private static void CodeSign(string file) + { + if (string.IsNullOrEmpty(Configuration.DeployCert)) + return; + switch (Platform.BuildTargetPlatform) + { + case TargetPlatform.Windows: + VCEnvironment.CodeSign(file, Configuration.DeployCert, Configuration.DeployCertPass); + break; + } + } + public class Editor { private static string RootPath; @@ -37,6 +49,7 @@ namespace Flax.Deploy var dst = Path.Combine(OutputPath, binariesSubDir); DeployFile(src, dst, "Flax.Build.exe"); + CodeSign(Path.Combine(dst, "Flax.Build.exe")); DeployFile(src, dst, "Flax.Build.xml"); DeployFile(src, dst, "Ionic.Zip.Reduced.dll"); DeployFile(src, dst, "Newtonsoft.Json.dll"); @@ -50,6 +63,10 @@ namespace Flax.Deploy { DeployFolder(RootPath, OutputPath, "Source/Platforms/Editor/Windows/Mono"); } + else if (Platform.BuildPlatform.Target == TargetPlatform.Linux) + { + DeployFolder(RootPath, OutputPath, "Source/Platforms/Editor/Linux/Mono"); + } else { throw new NotImplementedException(); @@ -108,30 +125,44 @@ namespace Flax.Deploy // Compress Log.Info(string.Empty); Log.Info("Compressing editor files..."); - var editorPackageZipPath = Path.Combine(Deployer.PackageOutputPath, "Editor.zip"); - using (ZipFile zip = new ZipFile()) + string editorPackageZipPath; + if (Platform.BuildPlatform.Target == TargetPlatform.Linux) { - zip.AddDirectory(OutputPath); + // Use system tool (preserves executable file attributes and link files) + editorPackageZipPath = Path.Combine(Deployer.PackageOutputPath, "FlaxEditorLinux.zip"); + Utilities.Run("zip", "Editor.zip -r .", null, OutputPath, Utilities.RunOptions.None); + File.Move(Path.Combine(OutputPath, "Editor.zip"), editorPackageZipPath); + } + else + { + editorPackageZipPath = Path.Combine(Deployer.PackageOutputPath, "Editor.zip"); + using (ZipFile zip = new ZipFile()) + { + zip.AddDirectory(OutputPath); - zip.CompressionLevel = CompressionLevel.BestCompression; - zip.Comment = string.Format("Flax Editor {0}.{1}.{2}\nDate: {3}", Deployer.VersionMajor, Deployer.VersionMinor, Deployer.VersionBuild, DateTime.UtcNow); + zip.CompressionLevel = CompressionLevel.BestCompression; + zip.Comment = string.Format("Flax Editor {0}.{1}.{2}\nDate: {3}", Deployer.VersionMajor, Deployer.VersionMinor, Deployer.VersionBuild, DateTime.UtcNow); - zip.Save(editorPackageZipPath); + zip.Save(editorPackageZipPath); + } } Log.Info("Compressed editor package size: " + Utilities.GetFileSize(editorPackageZipPath)); - Log.Info("Compressing editor debug symbols files..."); - editorPackageZipPath = Path.Combine(Deployer.PackageOutputPath, "EditorDebugSymbols.zip"); - using (ZipFile zip = new ZipFile()) + if (Platform.BuildPlatform.Target == TargetPlatform.Windows) { - zip.AddDirectory(Path.Combine(Deployer.PackageOutputPath, "EditorDebugSymbols")); + Log.Info("Compressing editor debug symbols files..."); + editorPackageZipPath = Path.Combine(Deployer.PackageOutputPath, "EditorDebugSymbols.zip"); + using (ZipFile zip = new ZipFile()) + { + zip.AddDirectory(Path.Combine(Deployer.PackageOutputPath, "EditorDebugSymbols")); - zip.CompressionLevel = CompressionLevel.BestCompression; - zip.Comment = string.Format("Flax Editor {0}.{1}.{2}\nDate: {3}", Deployer.VersionMajor, Deployer.VersionMinor, Deployer.VersionBuild, DateTime.UtcNow); + zip.CompressionLevel = CompressionLevel.BestCompression; + zip.Comment = string.Format("Flax Editor {0}.{1}.{2}\nDate: {3}", Deployer.VersionMajor, Deployer.VersionMinor, Deployer.VersionBuild, DateTime.UtcNow); - zip.Save(editorPackageZipPath); + zip.Save(editorPackageZipPath); + } + Log.Info("Compressed editor debug symbols package size: " + Utilities.GetFileSize(editorPackageZipPath)); } - Log.Info("Compressed editor debug symbols package size: " + Utilities.GetFileSize(editorPackageZipPath)); // Cleanup Utilities.DirectoryDelete(OutputPath); @@ -159,18 +190,44 @@ namespace Flax.Deploy // Deploy binaries DeployFile(src, dst, editorExeName); + CodeSign(Path.Combine(dst, editorExeName)); DeployFile(src, dst, "FlaxEditor.Build.json"); DeployFile(src, dst, "FlaxEditor.lib"); DeployFile(src, dst, "FlaxEngine.CSharp.pdb"); DeployFile(src, dst, "FlaxEngine.CSharp.xml"); DeployFile(src, dst, "Newtonsoft.Json.pdb"); DeployFiles(src, dst, "*.dll"); + CodeSign(Path.Combine(dst, "FlaxEngine.CSharp.dll")); // Deploy debug symbols files DeployFiles(src, dstDebug, "*.pdb"); File.Delete(Path.Combine(dstDebug, "FlaxEngine.CSharp.pdb")); File.Delete(Path.Combine(dstDebug, "Newtonsoft.Json.pdb")); } + else if (Platform.BuildPlatform.Target == TargetPlatform.Linux) + { + var binariesSubDir = "Binaries/Editor/Linux/" + configuration; + var src = Path.Combine(RootPath, binariesSubDir); + var dst = Path.Combine(OutputPath, binariesSubDir); + Directory.CreateDirectory(dst); + + // Deploy binaries + DeployFile(src, dst, "FlaxEditor"); + DeployFile(src, dst, "FlaxEditor.Build.json"); + DeployFile(src, dst, "FlaxEngine.CSharp.pdb"); + DeployFile(src, dst, "FlaxEngine.CSharp.xml"); + DeployFile(src, dst, "Newtonsoft.Json.pdb"); + DeployFiles(src, dst, "*.dll"); + DeployFiles(src, dst, "*.so"); + DeployFile(src, dst, "Logo.png"); + + // Optimize package size + Utilities.Run("strip", "FlaxEditor", null, dst, Utilities.RunOptions.None); + Utilities.Run("strip", "libFlaxEditor.so", null, dst, Utilities.RunOptions.None); + Utilities.Run("strip", "libmonosgen-2.0.so", null, dst, Utilities.RunOptions.None); + Utilities.Run("ln", "-s libmonosgen-2.0.so libmonosgen-2.0.so.1", null, dst, Utilities.RunOptions.None); + Utilities.Run("ln", "-s libmonosgen-2.0.so libmonosgen-2.0.so.1.0.0", null, dst, Utilities.RunOptions.None); + } else { throw new NotImplementedException(); diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs index 4ff1a3af3..e9fc98acc 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs @@ -37,6 +37,22 @@ namespace Flax.Deploy File.Delete(Path.Combine(dst, "Binaries", "Game", "x64", "Release", "FlaxGame.a")); } + // Sign binaries + if (platform == TargetPlatform.Windows && !string.IsNullOrEmpty(Configuration.DeployCert)) + { + var binaries = Path.Combine(dst, "Binaries", "Game", "x64", "Debug"); + CodeSign(Path.Combine(binaries, "FlaxGame.exe")); + CodeSign(Path.Combine(binaries, "FlaxEngine.CSharp.dll")); + + binaries = Path.Combine(dst, "Binaries", "Game", "x64", "Development"); + CodeSign(Path.Combine(binaries, "FlaxGame.exe")); + CodeSign(Path.Combine(binaries, "FlaxEngine.CSharp.dll")); + + binaries = Path.Combine(dst, "Binaries", "Game", "x64", "Release"); + CodeSign(Path.Combine(binaries, "FlaxGame.exe")); + CodeSign(Path.Combine(binaries, "FlaxEngine.CSharp.dll")); + } + // Don't distribute engine deps Utilities.DirectoryDelete(Path.Combine(dst, "Binaries", "ThirdParty")); diff --git a/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs b/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs index c8d01dea9..402e3e144 100644 --- a/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs +++ b/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs @@ -21,6 +21,8 @@ namespace Flax.Deploy format = format.Replace("-", "--"); } var cmdLine = string.Format(format, target, platform, architecture, configuration); + if (!string.IsNullOrEmpty(Configuration.Compiler)) + cmdLine += " -compiler=" + Configuration.Compiler; int result; if (buildPlatform == TargetPlatform.Windows) diff --git a/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs b/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs index 359c571ef..4cb28f221 100644 --- a/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs +++ b/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs @@ -2,6 +2,7 @@ using System; using System.IO; +using System.Linq; using Flax.Build; using Flax.Build.Platforms; using Flax.Build.Projects.VisualStudio; @@ -255,5 +256,22 @@ namespace Flax.Deploy string cmdLine = string.Format("\"{0}\" /t:Clean /verbosity:minimal /nologo", solutionFile); Utilities.Run(msBuild, cmdLine); } + + internal static void CodeSign(string file, string certificatePath, string certificatePass) + { + if (!File.Exists(file)) + throw new FileNotFoundException("Missing file to sign.", file); + if (!File.Exists(certificatePath)) + throw new FileNotFoundException("Missing certificate to sign with.", certificatePath); + var sdks = WindowsPlatformBase.GetSDKs(); + if (sdks.Count == 0) + throw new Exception("No Windows SDK found. Cannot sign file."); + var sdkKeys = sdks.Keys.ToList(); + sdkKeys.Sort(); + var sdk = sdks[sdkKeys.Last()]; + var signtool = Path.Combine(sdk, "bin", "x64", "signtool.exe"); + var cmdLine = string.Format("sign /debug /f \"{0}\" /p \"{1}\" /tr http://timestamp.comodoca.com /td sha256 /fd sha256 \"{2}\"", certificatePath, certificatePass, file); + Utilities.Run(signtool, cmdLine); + } } } diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs index 5e8346fe3..1c671f4b4 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs @@ -39,6 +39,46 @@ namespace Flax.Deps.Dependencies { var root = options.IntermediateFolder; var moduleFilename = "assimp.Build.cs"; + var configs = new string[] + { + "-DASSIMP_NO_EXPORT=ON", + "-DASSIMP_BUILD_ASSIMP_TOOLS=OFF", + "-DASSIMP_BUILD_TESTS=OFF", + "-DASSIMP_BUILD_AMF_IMPORTER=FALSE", + "-DASSIMP_BUILD_AC_IMPORTER=FALSE", + "-DASSIMP_BUILD_ASE_IMPORTER=FALSE", + "-DASSIMP_BUILD_ASSBIN_IMPORTER=FALSE", + "-DASSIMP_BUILD_ASSXML_IMPORTER=FALSE", + "-DASSIMP_BUILD_DXF_IMPORTER=FALSE", + "-DASSIMP_BUILD_CSM_IMPORTER=FALSE", + "-DASSIMP_BUILD_HMP_IMPORTER=FALSE", + "-DASSIMP_BUILD_NFF_IMPORTER=FALSE", + "-DASSIMP_BUILD_NDO_IMPORTER=FALSE", + "-DASSIMP_BUILD_IFC_IMPORTER=FALSE", + "-DASSIMP_BUILD_FBX_IMPORTER=FALSE", + "-DASSIMP_BUILD_RAW_IMPORTER=FALSE", + "-DASSIMP_BUILD_TERRAGEN_IMPORTER=FALSE", + "-DASSIMP_BUILD_3MF_IMPORTER=FALSE", + "-DASSIMP_BUILD_STEP_IMPORTER=FALSE", + "-DASSIMP_BUILD_3DS_IMPORTER=FALSE", + "-DASSIMP_BUILD_BVH_IMPORTER=FALSE", + "-DASSIMP_BUILD_IRRMESH_IMPORTER=FALSE", + "-DASSIMP_BUILD_IRR_IMPORTER=FALSE", + "-DASSIMP_BUILD_MD2_IMPORTER=FALSE", + "-DASSIMP_BUILD_MD3_IMPORTER=FALSE", + "-DASSIMP_BUILD_MD5_IMPORTER=FALSE", + "-DASSIMP_BUILD_MDC_IMPORTER=FALSE", + "-DASSIMP_BUILD_MDL_IMPORTER=FALSE", + "-DASSIMP_BUILD_OFF_IMPORTER=FALSE", + "-DASSIMP_BUILD_COB_IMPORTER=FALSE", + "-DASSIMP_BUILD_Q3D_IMPORTER=FALSE", + "-DASSIMP_BUILD_Q3BSP_IMPORTER=FALSE", + "-DASSIMP_BUILD_SIB_IMPORTER=FALSE", + "-DASSIMP_BUILD_SMD_IMPORTER=FALSE", + "-DASSIMP_BUILD_X3D_IMPORTER=FALSE", + "-DASSIMP_BUILD_MMD_IMPORTER=FALSE", + }; + var globalConfig = string.Join(" ", configs); // Get the source CloneGitRepo(root, "https://github.com/FlaxEngine/assimp.git"); @@ -73,12 +113,11 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Linux: { // Build for Linux - RunCmake(root, TargetPlatform.Linux, TargetArchitecture.x64, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DASSIMP_NO_EXPORT=ON -DASSIMP_BUILD_ASSIMP_TOOLS=OFF -DASSIMP_BUILD_TESTS=OFF"); + RunCmake(root, TargetPlatform.Linux, TargetArchitecture.x64, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " + globalConfig); Utilities.Run("make", null, null, root, Utilities.RunOptions.None); var depsFolder = GetThirdPartyFolder(options, TargetPlatform.Linux, TargetArchitecture.x64); - var libName = "libassimp.so.4.1.0"; - Utilities.FileCopy(Path.Combine(root, "lib", libName), Path.Combine(depsFolder, libName)); - Utilities.Run("strip", libName, null, depsFolder, Utilities.RunOptions.None); + Utilities.FileCopy(Path.Combine(root, "lib", "libassimp.a"), Path.Combine(depsFolder, "libassimp.a")); + Utilities.FileCopy(Path.Combine(root, "lib", "libIrrXML.a"), Path.Combine(depsFolder, "libIrrXML.a")); break; } } diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/NewtonsoftJson.cs b/Source/Tools/Flax.Build/Deps/Dependencies/NewtonsoftJson.cs index 69d53c990..5d3683ef3 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/NewtonsoftJson.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/NewtonsoftJson.cs @@ -27,6 +27,7 @@ namespace Flax.Deps.Dependencies TargetPlatform.XboxOne, TargetPlatform.XboxScarlett, TargetPlatform.PS4, + TargetPlatform.Switch, }; default: return new TargetPlatform[0]; } @@ -81,11 +82,10 @@ namespace Flax.Deps.Dependencies case TargetPlatform.XboxOne: case TargetPlatform.PS4: case TargetPlatform.XboxScarlett: + case TargetPlatform.Switch: { - foreach (var file in outputFileNames) - { - Utilities.FileCopy(Path.Combine(binFolder, file), Path.Combine(options.PlatformsFolder, platform.ToString(), file)); - } + var file = "Newtonsoft.Json.dll"; + Utilities.FileCopy(Path.Combine(binFolder, file), Path.Combine(options.PlatformsFolder, platform.ToString(), "Binaries", file)); break; } } diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs b/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs index 4fdc68067..6461706df 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; @@ -25,6 +26,11 @@ namespace Flax.Deps.Dependencies { TargetPlatform.Windows, }; + case TargetPlatform.Linux: + return new[] + { + TargetPlatform.Linux, + }; default: return new TargetPlatform[0]; } } @@ -76,7 +82,37 @@ namespace Flax.Deps.Dependencies foreach (var filename in binariesToCopyWin) Utilities.FileCopy(Path.Combine(root, "build", "Win64", vcVersion, configuration, filename), Path.Combine(depsFolder, Path.GetFileName(filename))); } - + break; + } + case TargetPlatform.Linux: + { + // Build for Linux + var settings = new [] + { + "-without-librtmp", + "--without-ssl", + "--with-gnutls", + "--disable-ipv6", + "--disable-manual", + "--disable-verbose", + "--disable-shared", + "--enable-static", + "-disable-ldap --disable-sspi --disable-ftp --disable-file --disable-dict --disable-telnet --disable-tftp --disable-rtsp --disable-pop3 --disable-imap --disable-smtp --disable-gopher --disable-smb", + }; + var envVars = new Dictionary + { + { "CC", "clang-7" }, + { "CC_FOR_BUILD", "clang-7" } + }; + var buildDir = Path.Combine(root, "build"); + SetupDirectory(buildDir, true); + Utilities.Run("chmod", "+x configure", null, root, Utilities.RunOptions.None); + Utilities.Run(Path.Combine(root, "configure"), string.Join(" ", settings) + " --prefix=\"" + buildDir + "\"", null, root, Utilities.RunOptions.None, envVars); + Utilities.Run("make", null, null, root, Utilities.RunOptions.None); + Utilities.Run("make", "install", null, root, Utilities.RunOptions.None); + var depsFolder = GetThirdPartyFolder(options, TargetPlatform.Linux, TargetArchitecture.x64); + var filename = "libcurl.a"; + Utilities.FileCopy(Path.Combine(buildDir, "lib", filename), Path.Combine(depsFolder, filename)); break; } } diff --git a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs index 221483536..bec8b583c 100644 --- a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs @@ -20,6 +20,13 @@ namespace Flax.Build.Platforms /// public UWPPlatform() { + // Skip if running on non-Windows system + if (Platform.BuildTargetPlatform != TargetPlatform.Windows) + { + _hasRequiredSDKsInstalled = false; + return; + } + // Visual Studio 2017+ supported only var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => x.Version == VisualStudioVersion.VisualStudio2017 || x.Version == VisualStudioVersion.VisualStudio2019); if (visualStudio == null) diff --git a/Source/Tools/Flax.Build/Platforms/UWP/UWPToolchain.cs b/Source/Tools/Flax.Build/Platforms/UWP/UWPToolchain.cs index 7a76d642d..237026b32 100644 --- a/Source/Tools/Flax.Build/Platforms/UWP/UWPToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/UWP/UWPToolchain.cs @@ -67,10 +67,10 @@ namespace Flax.Build.Platforms options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_UWP"); options.CompileEnv.PreprocessorDefinitions.Add("WINAPI_FAMILY=WINAPI_FAMILY_PC_APP"); options.CompileEnv.PreprocessorDefinitions.Add("_WINRT_DLL"); + options.CompileEnv.PreprocessorDefinitions.Add("_WINDLL"); options.CompileEnv.PreprocessorDefinitions.Add("__WRL_NO_DEFAULT_LIB__"); options.LinkEnv.InputLibraries.Add("WindowsApp.lib"); - options.LinkEnv.InputLibraries.Add("dloadhelper.lib"); } } } diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs index fca112e9c..7c1a78c81 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs @@ -243,9 +243,14 @@ namespace Flax.Build.Platforms { if (_toolsets != null) return _toolsets; - var vsInstances = VisualStudioInstance.GetInstances(); _toolsets = new Dictionary(); + // Skip if running on non-Windows system + if (BuildTargetPlatform != TargetPlatform.Windows) + return _toolsets; + + var vsInstances = VisualStudioInstance.GetInstances(); + // Visual Studio 2015 - single instance var vs2015 = vsInstances.FirstOrDefault(x => x.Version == VisualStudioVersion.VisualStudio2015); if (vs2015 != null) diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 38446ee17..b59bcae39 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -680,6 +680,8 @@ namespace Flax.Build.Platforms args.Add("/WINMD"); args.Add(string.Format("/WINMDFILE:\"{0}\"", Path.ChangeExtension(outputFilePath, "winmd"))); args.Add("/APPCONTAINER"); + if (linkEnvironment.Output == LinkerOutput.SharedLibrary) + args.Add("/DYNAMICBASE"); } if (linkEnvironment.LinkTimeCodeGeneration) @@ -937,7 +939,7 @@ namespace Flax.Build.Platforms xmlTextWriter.WriteStartElement("Properties"); // TODO: better logo handling - var logoSrcPath = Path.Combine(Environment.CurrentDirectory, "Source", "Logo.png"); + var logoSrcPath = Path.Combine(Globals.EngineRoot, "Source", "Logo.png"); var logoDstPath = Path.Combine(options.IntermediateFolder, "Logo.png"); if (!File.Exists(logoDstPath)) Utilities.FileCopy(logoSrcPath, logoDstPath); diff --git a/Source/Tools/Flax.Build/ProjectInfo.cs b/Source/Tools/Flax.Build/ProjectInfo.cs index 5f144b2fd..c47c87331 100644 --- a/Source/Tools/Flax.Build/ProjectInfo.cs +++ b/Source/Tools/Flax.Build/ProjectInfo.cs @@ -119,6 +119,8 @@ namespace Flax.Build private bool IsTargetCSharpOnly(string name) { + if (string.IsNullOrWhiteSpace(name)) + return true; var rules = Builder.GenerateRulesAssembly(); var target = rules.GetTarget(name); return target == null || target.Modules.TrueForAll(x => !rules.GetModule(x).BuildNativeCode); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs index 4c4250aab..6d39625f9 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs @@ -97,9 +97,13 @@ namespace Flax.Build.Projects.VisualStudio { _installDirs = new List(); + // Skip if running on non-Windows system + if (Platform.BuildTargetPlatform != TargetPlatform.Windows) + return _installDirs; + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - // Visual Studio 2017-2020 + // Visual Studio 2017-2019 List preReleaseInstallDirs = null; try { diff --git a/Source/flax.natvis b/Source/flax.natvis index 550d8a4d4..3337ccdd5 100644 --- a/Source/flax.natvis +++ b/Source/flax.natvis @@ -100,7 +100,7 @@ Empty Invalid - {_data,su} + {_data,[_length]su} _data _length @@ -130,7 +130,7 @@ Empty Invalid - {_data} + {_data,[_length]} _data _length @@ -143,9 +143,9 @@ - {_data_allocation.._data,su} + {_data._allocation._data,[_data._count]su} - _data._data + _data._allocation._data _data._count _data._count