Merge branch 'FlaxEngine:master' into flax-msdf-font

This commit is contained in:
fibref
2025-10-26 14:37:20 +08:00
committed by GitHub
601 changed files with 58750 additions and 4422 deletions
@@ -174,7 +174,9 @@ void EditorAnalytics::StartSession()
// Bind events
GameCooker::OnEvent.Bind<RegisterGameCookingStart>();
ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Bind<RegisterLightmapsBuildingStart>();
#if LOG_ENABLE
Log::Logger::OnError.Bind<RegisterError>();
#endif
}
void EditorAnalytics::EndSession()
@@ -187,7 +189,9 @@ void EditorAnalytics::EndSession()
// Unbind events
GameCooker::OnEvent.Unbind<RegisterGameCookingStart>();
ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Unbind<RegisterLightmapsBuildingStart>();
#if LOG_ENABLE
Log::Logger::OnError.Unbind<RegisterError>();
#endif
// End session
{
+2 -7
View File
@@ -20,13 +20,6 @@ class PlatformTools;
#define GAME_BUILD_DOTNET_RUNTIME_MAX_VER 9
#endif
#if OFFICIAL_BUILD
// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=" MACRO_TO_STR(GAME_BUILD_DOTNET_RUNTIME_MIN_VER))
#else
#define GAME_BUILD_DOTNET_VER TEXT("")
#endif
/// <summary>
/// Game building options. Used as flags.
/// </summary>
@@ -374,6 +367,8 @@ public:
/// </summary>
void GetBuildPlatformName(const Char*& platform, const Char*& architecture) const;
String GetDotnetCommandArg() const;
public:
/// <summary>
+18 -3
View File
@@ -30,6 +30,7 @@
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Content/JsonAsset.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Profiler/ProfilerMemory.h"
#if PLATFORM_TOOLS_WINDOWS
#include "Platform/Windows/WindowsPlatformTools.h"
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
@@ -311,6 +312,14 @@ void CookingData::GetBuildPlatformName(const Char*& platform, const Char*& archi
}
}
String CookingData::GetDotnetCommandArg() const
{
int32 version = Tools->GetDotnetVersion();
if (version == 0)
return String::Empty;
return String::Format(TEXT("-dotnet={}"), version);
}
void CookingData::StepProgress(const String& info, const float stepProgress) const
{
const float singleStepProgress = 1.0f / (StepsCount + 1);
@@ -380,6 +389,7 @@ bool GameCooker::IsCancelRequested()
PlatformTools* GameCooker::GetTools(BuildPlatform platform)
{
PROFILE_MEM(Editor);
PlatformTools* result = nullptr;
if (!Tools.TryGet(platform, result))
{
@@ -471,6 +481,7 @@ bool GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration,
LOG(Error, "Build platform {0} is not supported.", ::ToString(platform));
return true;
}
PROFILE_MEM(Editor);
// Setup
CancelFlag = 0;
@@ -624,6 +635,7 @@ void GameCookerImpl::ReportProgress(const String& info, float totalProgress)
void GameCookerImpl::OnCollectAssets(HashSet<Guid>& assets)
{
PROFILE_MEM(Editor);
if (Internal_OnCollectAssets == nullptr)
{
auto c = GameCooker::GetStaticClass();
@@ -651,6 +663,7 @@ void GameCookerImpl::OnCollectAssets(HashSet<Guid>& assets)
bool GameCookerImpl::Build()
{
PROFILE_MEM(Editor);
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));
@@ -670,8 +683,7 @@ bool GameCookerImpl::Build()
MCore::Thread::Attach();
// Build Started
if (!EnumHasAnyFlags(data.Options, BuildOptions::NoCook))
// Build start
{
CallEvent(GameCooker::EventType::BuildStarted);
data.Tools->OnBuildStarted(data);
@@ -744,8 +756,8 @@ bool GameCookerImpl::Build()
}
IsRunning = false;
CancelFlag = 0;
if (!EnumHasAnyFlags(data.Options, BuildOptions::NoCook))
{
// Build end
for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++)
Steps[stepIndex]->OnBuildEnded(data, failed);
data.Tools->OnBuildEnded(data, failed);
@@ -778,6 +790,8 @@ int32 GameCookerImpl::ThreadFunction()
bool GameCookerService::Init()
{
PROFILE_MEM(Editor);
auto editorAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
editorAssembly->Unloading.Bind(OnEditorAssemblyUnloading);
GameCooker::OnCollectAssets.Bind(OnCollectAssets);
@@ -789,6 +803,7 @@ void GameCookerService::Update()
{
if (IsRunning)
{
PROFILE_MEM(Editor);
ScopeLock lock(ProgressLocker);
if (ProgressMsg.HasChars())
@@ -195,4 +195,9 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
return false;
}
int32 GDKPlatformTools::GetDotnetVersion() const
{
return GAME_BUILD_DOTNET_RUNTIME_MIN_VER;
}
#endif
@@ -26,6 +26,7 @@ public:
public:
// [PlatformTools]
int32 GetDotnetVersion() const override;
DotNetAOTModes UseAOT() const override;
bool OnDeployBinaries(CookingData& data) override;
};
@@ -186,7 +186,7 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
ADD_ENTRY("CFBundlePackageType", "APPL");
ADD_ENTRY("NSPrincipalClass", "NSApplication");
ADD_ENTRY("LSApplicationCategoryType", "public.app-category.games");
ADD_ENTRY("LSMinimumSystemVersion", "10.15");
ADD_ENTRY("LSMinimumSystemVersion", "13");
ADD_ENTRY("CFBundleIconFile", "icon.icns");
ADD_ENTRY_STR("CFBundleExecutable", executableName);
ADD_ENTRY_STR("CFBundleIdentifier", appIdentifier);
@@ -231,6 +231,8 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
LOG(Info, "Building app package...");
{
const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg");
if (FileSystem::FileExists(dmgPath))
FileSystem::DeleteFile(dmgPath);
CreateProcessSettings procSettings;
procSettings.HiddenWindow = true;
procSettings.WorkingDirectory = data.OriginalOutputPath;
@@ -528,6 +528,9 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
void WindowsPlatformTools::OnBuildStarted(CookingData& data)
{
if (EnumHasAllFlags(data.Options, BuildOptions::NoCook))
return;
// Remove old executable
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly);
+14
View File
@@ -70,6 +70,20 @@ public:
/// </summary>
virtual ArchitectureType GetArchitecture() const = 0;
/// <summary>
/// Gets the .Net version to use for the cooked game.
/// </summary>
virtual int32 GetDotnetVersion() const
{
#if OFFICIAL_BUILD
// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
return GAME_BUILD_DOTNET_RUNTIME_MIN_VER;
#else
// Use the highest version found on a system (Flax.Build will decide)
return 0;
#endif
}
/// <summary>
/// Gets the value indicating whenever platform requires AOT (needs C# assemblies to be precompiled).
/// </summary>
@@ -189,7 +189,7 @@ bool CompileScriptsStep::Perform(CookingData& data)
const String logFile = data.CacheDirectory / TEXT("CompileLog.txt");
auto args = String::Format(
TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5} {6}"),
target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()), GAME_BUILD_DOTNET_VER);
target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()), data.GetDotnetCommandArg());
#if PLATFORM_WINDOWS
if (data.Platform == BuildPlatform::LinuxX64)
#elif PLATFORM_LINUX
@@ -36,6 +36,7 @@
#include "Engine/Engine/Base/GameBase.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Enums.h"
#if PLATFORM_TOOLS_WINDOWS
@@ -88,7 +88,7 @@ bool DeployDataStep::Perform(CookingData& data)
{
// Ask Flax.Build to provide .NET SDK location for the current platform
String sdks;
bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory);
bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), data.GetDotnetCommandArg()), data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
int32 idx = sdks.Find(TEXT("DotNetSdk, "), StringSearchCase::CaseSensitive);
if (idx != -1)
@@ -200,7 +200,7 @@ bool DeployDataStep::Perform(CookingData& data)
String sdks;
const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName);
String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={} {}"), platformName, archName, GAME_BUILD_DOTNET_VER);
String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={} {}"), platformName, archName, data.GetDotnetCommandArg());
bool failed = ScriptsBuilder::RunBuildTool(args, data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
Array<String> parts;
@@ -244,10 +244,13 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (version.IsEmpty())
{
int32 minVer = GAME_BUILD_DOTNET_RUNTIME_MIN_VER, maxVer = GAME_BUILD_DOTNET_RUNTIME_MAX_VER;
if (srcDotnetFromEngine)
{
// Detect version from runtime files inside Engine Platform folder
for (int32 i = GAME_BUILD_DOTNET_RUNTIME_MAX_VER; i >= GAME_BUILD_DOTNET_RUNTIME_MIN_VER; i--)
if (data.Tools->GetDotnetVersion() != 0)
minVer = maxVer = data.Tools->GetDotnetVersion();
for (int32 i = maxVer; i >= minVer; i--)
{
// Check runtime files inside Engine Platform folder
String testPath1 = srcDotnet / String::Format(TEXT("lib/net{}.0"), i);
@@ -262,7 +265,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (version.IsEmpty())
{
data.Error(String::Format(TEXT("Failed to find supported .NET {} version for the current host platform."), GAME_BUILD_DOTNET_RUNTIME_MIN_VER));
data.Error(String::Format(TEXT("Failed to find supported .NET {} version (min {}) for the current host platform."), maxVer, minVer));
return true;
}
}
@@ -364,7 +367,7 @@ bool DeployDataStep::Perform(CookingData& data)
const String logFile = data.CacheDirectory / TEXT("StripDotnetLibs.txt");
String args = String::Format(
TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\" {}"),
logFile, data.DataOutputPath, GAME_BUILD_DOTNET_VER);
logFile, data.DataOutputPath, data.GetDotnetCommandArg());
for (const String& define : data.CustomDefines)
{
args += TEXT(" -D");
@@ -12,7 +12,7 @@
void PrecompileAssembliesStep::OnBuildStarted(CookingData& data)
{
const DotNetAOTModes aotMode = data.Tools->UseAOT();
if (aotMode == DotNetAOTModes::None)
if (aotMode == DotNetAOTModes::None || EnumHasAllFlags(data.Options, BuildOptions::NoCook))
return;
const auto& buildSettings = *BuildSettings::Get();
@@ -69,7 +69,7 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
const String logFile = data.CacheDirectory / TEXT("AOTLog.txt");
String args = String::Format(
TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\" {}"),
logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath, GAME_BUILD_DOTNET_VER);
logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath, data.GetDotnetCommandArg());
if (!buildSettings.SkipUnusedDotnetLibsPackaging)
args += TEXT(" -skipUnusedDotnetLibs=false"); // Run AOT on whole class library (not just used libs)
for (const String& define : data.CustomDefines)
@@ -6,6 +6,8 @@
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Types/Stopwatch.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
@@ -69,6 +71,7 @@ MTypeObject* CustomEditorsUtil::GetCustomEditor(MTypeObject* refType)
bool CustomEditorsUtilService::Init()
{
PROFILE_MEM(Editor);
TRACK_ASSEMBLY(((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly);
Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded);
@@ -77,6 +80,8 @@ bool CustomEditorsUtilService::Init()
void OnAssemblyLoaded(MAssembly* assembly)
{
PROFILE_CPU_NAMED("CustomEditors.OnAssemblyLoaded");
PROFILE_MEM(Editor);
Stopwatch stopwatch;
// Prepare FlaxEngine
@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
menu.AddButton("Copy", linkedEditor.Copy);
var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index));
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly && Editor._canResize;
b.Enabled = !Editor._readOnly && Editor._canResize;
b = menu.AddButton("Paste", linkedEditor.Paste);
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
@@ -407,7 +407,7 @@ namespace FlaxEditor.CustomEditors.Editors
menu.AddButton("Copy", linkedEditor.Copy);
var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index));
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly && Editor._canResize;
b.Enabled = !Editor._readOnly && Editor._canResize;
var paste = menu.AddButton("Paste", linkedEditor.Paste);
paste.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
@@ -22,7 +22,7 @@ internal class UIControlRefPickerControl : FlaxObjectRefPickerControl
/// <inheritdoc />
protected override bool IsValid(Object obj)
{
return obj == null || (obj is UIControl control && control.Control.GetType() == ControlType);
return obj == null || (obj is UIControl control && ControlType.IsAssignableFrom(control.Control.GetType()));
}
}
+14 -4
View File
@@ -20,6 +20,7 @@
#include "Engine/Engine/Engine.h"
#include "Engine/ShadowsOfMordor/Builder.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "FlaxEngine.Gen.h"
#if PLATFORM_LINUX
#include "Engine/Tools/TextureTool/TextureTool.h"
@@ -47,6 +48,7 @@ void Editor::CloseSplashScreen()
bool Editor::CheckProjectUpgrade()
{
PROFILE_MEM(Editor);
const auto versionFilePath = Globals::ProjectCacheFolder / TEXT("version");
// Load version cache file
@@ -266,8 +268,8 @@ bool Editor::CheckProjectUpgrade()
// Check if last version was older
else if (lastVersion.Major < FLAXENGINE_VERSION_MAJOR || (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor < FLAXENGINE_VERSION_MINOR))
{
LOG(Warning, "The project was opened with the older editor version last time");
const auto result = MessageBox::Show(TEXT("The project was opened with the older editor version last time. Loading it may modify existing data so older editor version won't open it. Do you want to perform a backup before or cancel operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question);
LOG(Warning, "The project was last opened with an older editor version");
const auto result = MessageBox::Show(TEXT("The project was last opened with an older editor version.\nLoading it may modify existing data, which can result in older editor versions being unable to open it.\n\nDo you want to perform a backup before or cancel the operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question);
if (result == DialogResult::Yes)
{
if (BackupProject())
@@ -289,8 +291,8 @@ bool Editor::CheckProjectUpgrade()
// Check if last version was newer
else if (lastVersion.Major > FLAXENGINE_VERSION_MAJOR || (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor > FLAXENGINE_VERSION_MINOR))
{
LOG(Warning, "The project was opened with the newer editor version last time");
const auto result = MessageBox::Show(TEXT("The project was opened with the newer editor version last time. Loading it may fail and corrupt existing data. Do you want to perform a backup before or cancel operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Warning);
LOG(Warning, "The project was last opened with a newer editor version");
const auto result = MessageBox::Show(TEXT("The project was last opened with a newer editor version.\nLoading it may fail and corrupt existing data.\n\nDo you want to perform a backup before loading or cancel the operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Warning);
if (result == DialogResult::Yes)
{
if (BackupProject())
@@ -366,6 +368,8 @@ bool Editor::BackupProject()
int32 Editor::LoadProduct()
{
PROFILE_MEM(Editor);
// Flax Editor product
Globals::ProductName = TEXT("Flax Editor");
Globals::CompanyName = TEXT("Flax");
@@ -626,6 +630,7 @@ int32 Editor::LoadProduct()
Window* Editor::CreateMainWindow()
{
PROFILE_MEM(Editor);
Window* window = Managed->GetMainWindow();
#if PLATFORM_LINUX
@@ -662,6 +667,7 @@ bool Editor::Init()
return true;
}
PROFILE_CPU();
PROFILE_MEM(Editor);
// If during last lightmaps baking engine crashed we could try to restore the progress
ShadowsOfMordor::Builder::Instance()->CheckIfRestoreState();
@@ -693,11 +699,13 @@ bool Editor::Init()
void Editor::BeforeRun()
{
PROFILE_MEM(Editor);
Managed->BeforeRun();
}
void Editor::BeforeExit()
{
PROFILE_MEM(Editor);
CloseSplashScreen();
Managed->Exit();
@@ -708,6 +716,8 @@ void Editor::BeforeExit()
void EditorImpl::OnUpdate()
{
PROFILE_MEM(Editor);
// Update c# editor
Editor::Managed->Update();
+1 -1
View File
@@ -51,7 +51,7 @@ namespace FlaxEditor.GUI
/// <summary>
/// The column title horizontal text alignment
/// </summary>
public TextAlignment TitleAlignment = TextAlignment.Near;
public TextAlignment TitleAlignment = TextAlignment.Center;
/// <summary>
/// The column title margin.
@@ -114,9 +114,10 @@ namespace FlaxEditor.GUI.ContextMenu
public ContextMenuBase()
: base(0, 0, 120, 32)
{
_direction = ContextMenuDirection.RightDown;
Visible = false;
AutoFocus = true;
_direction = ContextMenuDirection.RightDown;
_isSubMenu = true;
}
@@ -76,6 +76,8 @@ namespace FlaxEditor.GUI.Dialogs
public ColorSelector(float wheelSize)
: base(0, 0, wheelSize, wheelSize)
{
AutoFocus = true;
_colorWheelSprite = Editor.Instance.Icons.ColorWheel128;
_wheelRect = new Rectangle(0, 0, wheelSize, wheelSize);
}
@@ -70,8 +70,6 @@ namespace FlaxEditor.GUI.Docking
internal DockPanelProxy(DockPanel panel)
: base(0, 0, 64, 64)
{
AutoFocus = false;
_panel = panel;
AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero;
+32 -4
View File
@@ -129,11 +129,39 @@ namespace FlaxEditor.GUI.Input
{
base.Draw();
var style = Style.Current;
var r = new Rectangle(0, 0, Width, Height);
bool isTransparent = _value.A < 1;
Render2D.FillRectangle(r, _value);
Render2D.DrawRectangle(r, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
var style = Style.Current;
var fullRect = new Rectangle(0, 0, Width, Height);
var colorRect = new Rectangle(0, 0, isTransparent ? Width * 0.7f : Width, Height);
if (isTransparent)
{
var alphaRect = new Rectangle(colorRect.Right, 0, Width - colorRect.Right, Height);
// Draw checkerboard pattern to part of the color value box
Render2D.FillRectangle(alphaRect, Color.White);
var smallRectSize = 7.9f;
var numHor = Mathf.CeilToInt(alphaRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(alphaRect.Height / smallRectSize);
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0)
{
var rect = new Rectangle(alphaRect.X + smallRectSize * i, alphaRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.PushClip(alphaRect);
Render2D.FillRectangle(rect, Color.Gray);
Render2D.PopClip();
}
}
}
Render2D.FillRectangle(alphaRect, _value);
}
Render2D.FillRectangle(colorRect, _value with { A = 1 });
Render2D.DrawRectangle(fullRect, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
}
/// <inheritdoc />
+2
View File
@@ -368,6 +368,8 @@ namespace FlaxEditor.GUI.Input
public SliderControl(float value, float x = 0, float y = 0, float width = 120, float min = float.MinValue, float max = float.MaxValue)
: base(x, y, width, TextBox.DefaultHeight)
{
AutoFocus = true;
_min = min;
_max = max;
_value = Mathf.Clamp(value, min, max);
+2 -2
View File
@@ -447,8 +447,8 @@ namespace FlaxEditor.GUI.Tree
// Select previous parent child
var select = nodeParent.GetChild(myIndex - 1) as TreeNode;
// Select last child if is valid and expanded and has any children
if (select != null && select.IsExpanded && select.HasAnyVisibleChild)
// Get bottom most child node
while (select != null && select.IsExpanded && select.HasAnyVisibleChild)
{
select = select.GetChild(select.ChildrenCount - 1) as TreeNode;
}
+2
View File
@@ -319,6 +319,8 @@ namespace FlaxEditor.GUI.Tree
public TreeNode(bool canChangeOrder, SpriteHandle iconCollapsed, SpriteHandle iconOpened)
: base(0, 0, 64, 16)
{
AutoFocus = true;
_canChangeOrder = canChangeOrder;
_animationProgress = 1.0f;
_cachedHeight = _headerHeight;
+3
View File
@@ -155,6 +155,7 @@ namespace FlaxEditor.Gizmo
// Ensure player is not moving objects
if (ActiveAxis != Axis.None)
return;
Profiler.BeginEvent("Pick");
// Get mouse ray and try to hit any object
var ray = Owner.MouseRay;
@@ -243,6 +244,8 @@ namespace FlaxEditor.Gizmo
{
sceneEditing.Deselect();
}
Profiler.EndEvent();
}
/// <inheritdoc />
+9 -4
View File
@@ -14,6 +14,7 @@
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Content/Assets/VisualScript.h"
#include "Engine/Content/Content.h"
#include "Engine/Level/Actor.h"
#include "Engine/CSG/CSGBuilder.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Renderer/ProbesRenderer.h"
@@ -75,7 +76,7 @@ void OnLightmapsBuildFinished(bool failed)
OnLightmapsBake(ShadowsOfMordor::BuildProgressStep::GenerateLightmapCharts, 0, 0, false);
}
void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
void OnBakeEvent(bool started, Actor* e)
{
if (Internal_EnvProbeBake == nullptr)
{
@@ -83,7 +84,7 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
ASSERT(Internal_EnvProbeBake);
}
MObject* probeObj = e.Actor ? e.Actor->GetManagedInstance() : nullptr;
MObject* probeObj = e ? e->GetManagedInstance() : nullptr;
MainThreadManagedInvokeAction::ParamsBuilder params;
params.AddParam(started);
@@ -91,12 +92,12 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
MainThreadManagedInvokeAction::Invoke(Internal_EnvProbeBake, params);
}
void OnRegisterBake(const ProbesRenderer::Entry& e)
void OnRegisterBake(Actor* e)
{
OnBakeEvent(true, e);
}
void OnFinishBake(const ProbesRenderer::Entry& e)
void OnFinishBake(Actor* e)
{
OnBakeEvent(false, e);
}
@@ -157,7 +158,9 @@ ManagedEditor::ManagedEditor()
lightmapsBuilder->OnBuildProgress.Bind<OnLightmapsBuildProgress>();
lightmapsBuilder->OnBuildFinished.Bind<OnLightmapsBuildFinished>();
CSG::Builder::OnBrushModified.Bind<OnBrushModified>();
#if LOG_ENABLE
Log::Logger::OnMessage.Bind<OnLogMessage>();
#endif
VisualScripting::DebugFlow.Bind<OnVisualScriptingDebugFlow>();
}
@@ -173,7 +176,9 @@ ManagedEditor::~ManagedEditor()
lightmapsBuilder->OnBuildProgress.Unbind<OnLightmapsBuildProgress>();
lightmapsBuilder->OnBuildFinished.Unbind<OnLightmapsBuildFinished>();
CSG::Builder::OnBrushModified.Unbind<OnBrushModified>();
#if LOG_ENABLE
Log::Logger::OnMessage.Unbind<OnLogMessage>();
#endif
VisualScripting::DebugFlow.Unbind<OnVisualScriptingDebugFlow>();
}
@@ -712,7 +712,11 @@ namespace FlaxEditor.Modules
private void OnActorChildNodesDispose(ActorNode node)
{
if (Selection.Count == 0)
return;
// TODO: cache if selection contains any actor child node and skip this loop if no need to iterate
// TODO: or build a hash set with selected nodes for quick O(1) checks (cached until selection changes)
// Deselect child nodes
for (int i = 0; i < node.ChildNodes.Count; i++)
+45
View File
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEngine;
@@ -662,6 +663,48 @@ namespace FlaxEditor.Modules
//node?.TreeNode.OnActiveChanged();
}
private void OnActorDestroyChildren(Actor actor)
{
// Instead of doing OnActorParentChanged for every child lets remove all of them at once from that actor
ActorNode node = GetActorNode(actor);
if (node != null)
{
if (Editor.SceneEditing.HasSthSelected)
{
// Clear selection if one of the removed actors is selected
var selection = new HashSet<Actor>();
foreach (var e in Editor.SceneEditing.Selection)
{
if (e is ActorNode q && q.Actor)
selection.Add(q.Actor);
}
var count = actor.ChildrenCount;
for (int i = 0; i < count; i++)
{
var child = actor.GetChild(i);
if (selection.Contains(child))
{
Editor.SceneEditing.Deselect();
break;
}
}
}
// Remove all child nodes (upfront remove all nodes to run faster)
for (int i = 0; i < node.ChildNodes.Count; i++)
{
if (node.ChildNodes[i] is ActorNode child)
child.parentNode = null;
}
node.TreeNode.DisposeChildren();
for (int i = 0; i < node.ChildNodes.Count; i++)
{
node.ChildNodes[i].Dispose();
}
node.ChildNodes.Clear();
}
}
/// <summary>
/// Gets the actor node.
/// </summary>
@@ -713,6 +756,7 @@ namespace FlaxEditor.Modules
Level.ActorOrderInParentChanged += OnActorOrderInParentChanged;
Level.ActorNameChanged += OnActorNameChanged;
Level.ActorActiveChanged += OnActorActiveChanged;
Level.ActorDestroyChildren += OnActorDestroyChildren;
}
/// <inheritdoc />
@@ -730,6 +774,7 @@ namespace FlaxEditor.Modules
Level.ActorOrderInParentChanged -= OnActorOrderInParentChanged;
Level.ActorNameChanged -= OnActorNameChanged;
Level.ActorActiveChanged -= OnActorActiveChanged;
Level.ActorDestroyChildren -= OnActorDestroyChildren;
// Cleanup graph
Root.Dispose();
+1 -1
View File
@@ -669,7 +669,7 @@ namespace FlaxEditor.Modules
if (item != null)
Editor.ContentEditing.Open(item);
});
cm.AddButton("Editor Options", () => Editor.Windows.EditorOptionsWin.Show());
cm.AddButton("Editor Options", inputOptions.EditorOptionsWindow, () => Editor.Windows.EditorOptionsWin.Show());
// Scene
MenuScene = MainMenu.AddButton("Scene");
+4
View File
@@ -650,6 +650,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Windows"), EditorOrder(4020)]
public InputBinding VisualScriptDebuggerWindow = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "Control+Comma")]
[EditorDisplay("Windows"), EditorOrder(4030)]
public InputBinding EditorOptionsWindow = new InputBinding(KeyboardKeys.Comma, KeyboardKeys.Control);
#endregion
#region Node Editors
+2
View File
@@ -6,6 +6,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Core/Math/Quaternion.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Serialization/JsonTools.h"
#include <ThirdParty/pugixml/pugixml.hpp>
@@ -327,6 +328,7 @@ ProjectInfo* ProjectInfo::Load(const String& path)
}
// Load
PROFILE_MEM(Editor);
auto project = New<ProjectInfo>();
if (project->LoadProject(path))
{
+8 -2
View File
@@ -97,13 +97,16 @@ namespace FlaxEditor.SceneGraph
/// <returns>Hit object or null if there is no intersection at all.</returns>
public SceneGraphNode RayCast(ref Ray ray, ref Ray view, out Real distance, RayCastData.FlagTypes flags = RayCastData.FlagTypes.None)
{
Profiler.BeginEvent("RayCastScene");
var data = new RayCastData
{
Ray = ray,
View = view,
Flags = flags
};
return RayCast(ref data, out distance, out _);
var result = RayCast(ref data, out distance, out _);
Profiler.EndEvent();
return result;
}
/// <summary>
@@ -117,13 +120,16 @@ namespace FlaxEditor.SceneGraph
/// <returns>Hit object or null if there is no intersection at all.</returns>
public SceneGraphNode RayCast(ref Ray ray, ref Ray view, out Real distance, out Vector3 normal, RayCastData.FlagTypes flags = RayCastData.FlagTypes.None)
{
Profiler.BeginEvent("RayCastScene");
var data = new RayCastData
{
Ray = ray,
View = view,
Flags = flags
};
return RayCast(ref data, out distance, out normal);
var result = RayCast(ref data, out distance, out normal);
Profiler.EndEvent();
return result;
}
internal static Quaternion RaycastNormalRotation(ref Vector3 normal)
+1 -1
View File
@@ -27,7 +27,7 @@ namespace FlaxEditor.SceneGraph
/// <summary>
/// The parent node.
/// </summary>
protected SceneGraphNode parentNode;
internal SceneGraphNode parentNode;
/// <summary>
/// Gets the children list.
+3
View File
@@ -13,6 +13,7 @@
#include "Engine/Engine/EngineService.h"
#include "Engine/Platform/Thread.h"
#include "Engine/Threading/IRunnable.h"
#include "Engine/Profiler/ProfilerMemory.h"
void OnAsyncBegin(Thread* thread);
void OnAsyncEnd();
@@ -232,6 +233,8 @@ void OnAsyncEnd()
bool CodeEditingManagerService::Init()
{
PROFILE_MEM(Editor);
// Try get editors
#if USE_VISUAL_STUDIO_DTE
VisualStudioEditor::FindEditors(&CodeEditors);
+10 -2
View File
@@ -23,6 +23,7 @@
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Level/Level.h"
#include "FlaxEngine.Gen.h"
@@ -77,7 +78,7 @@ namespace ScriptsBuilderImpl
void onScriptsReloadEnd();
void onScriptsLoaded();
void GetClassName(const StringAnsi& fullname, StringAnsi& className);
void GetClassName(const StringAnsiView fullname, StringAnsi& className);
void onCodeEditorAsyncOpenBegin()
{
@@ -272,7 +273,7 @@ bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
return RunBuildTool(args);
}
void ScriptsBuilderImpl::GetClassName(const StringAnsi& fullname, StringAnsi& className)
void ScriptsBuilderImpl::GetClassName(const StringAnsiView fullname, StringAnsi& className)
{
const auto lastDotIndex = fullname.FindLast('.');
if (lastDotIndex != -1)
@@ -413,6 +414,7 @@ void ScriptsBuilder::GetBinariesConfiguration(const Char*& target, const Char*&
bool ScriptsBuilderImpl::compileGameScriptsAsyncInner()
{
PROFILE_MEM(Editor);
LOG(Info, "Starting scripts compilation...");
CallEvent(EventType::CompileStarted);
@@ -519,6 +521,8 @@ void ScriptsBuilderImpl::onEditorAssemblyUnloading(MAssembly* assembly)
bool ScriptsBuilderImpl::compileGameScriptsAsync()
{
PROFILE_MEM(Editor);
// Start
{
ScopeLock scopeLock(_locker);
@@ -562,6 +566,7 @@ bool ScriptsBuilderService::Init()
// Check flag
if (_isInited)
return false;
PROFILE_MEM(Editor);
_isInited = true;
// Link for Editor assembly unload event to clear cached Internal_OnCompilationEnd to prevent errors
@@ -659,6 +664,9 @@ bool ScriptsBuilderService::Init()
void ScriptsBuilderService::Update()
{
PROFILE_CPU();
PROFILE_MEM(Editor);
// Send compilation events
{
ScopeLock scopeLock(_compileEventsLocker);
@@ -233,6 +233,8 @@ namespace FlaxEditor.Surface.Archetypes
public BlendPointsEditor(Animation.MultiBlend node, bool is2D, float x, float y, float width, float height)
: base(x, y, width, height)
{
AutoFocus = true;
_node = node;
_is2D = is2D;
}
+215 -7
View File
@@ -6,6 +6,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Surface.Archetypes
{
@@ -123,7 +124,8 @@ namespace FlaxEditor.Surface.Archetypes
case MaterialDomain.Particle:
case MaterialDomain.Deformable:
{
bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit;
bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit && info.ShadingModel != MaterialShadingModel.CustomLit;
bool isOpaque = info.BlendMode == MaterialBlendMode.Opaque;
bool withTess = info.TessellationMode != TessellationMethod.None;
GetBox(MaterialNodeBoxes.Color).IsActive = isNotUnlit;
@@ -134,8 +136,8 @@ namespace FlaxEditor.Surface.Archetypes
GetBox(MaterialNodeBoxes.Roughness).IsActive = isNotUnlit;
GetBox(MaterialNodeBoxes.AmbientOcclusion).IsActive = isNotUnlit;
GetBox(MaterialNodeBoxes.Normal).IsActive = isNotUnlit;
GetBox(MaterialNodeBoxes.Opacity).IsActive = info.ShadingModel == MaterialShadingModel.Subsurface || info.ShadingModel == MaterialShadingModel.Foliage || info.BlendMode != MaterialBlendMode.Opaque;
GetBox(MaterialNodeBoxes.Refraction).IsActive = info.BlendMode != MaterialBlendMode.Opaque;
GetBox(MaterialNodeBoxes.Opacity).IsActive = info.ShadingModel == MaterialShadingModel.Subsurface || info.ShadingModel == MaterialShadingModel.Foliage || !isOpaque;
GetBox(MaterialNodeBoxes.Refraction).IsActive = !isOpaque;
GetBox(MaterialNodeBoxes.PositionOffset).IsActive = true;
GetBox(MaterialNodeBoxes.TessellationMultiplier).IsActive = withTess;
GetBox(MaterialNodeBoxes.WorldDisplacement).IsActive = withTess;
@@ -260,6 +262,211 @@ namespace FlaxEditor.Surface.Archetypes
}
}
#if false // TODO: finish code editor based on RichTextBoxBase with text block parsing for custom styling
internal sealed class CustomCodeTextBox : RichTextBoxBase
{
protected override void OnParseTextBlocks()
{
base.OnParseTextBlocks();
// Single block for a whole text
// TODO: implement code parsing with HLSL syntax
var font = Style.Current.FontMedium;
var style = new TextBlockStyle
{
Font = new FontReference(font),
Color = Style.Current.Foreground,
BackgroundSelectedBrush = new SolidColorBrush(Style.Current.BackgroundSelected),
};
_textBlocks.Clear();
_textBlocks.Add(new TextBlock
{
Range = new TextRange
{
StartIndex = 0,
EndIndex = TextLength,
},
Style = style,
Bounds = new Rectangle(Float2.Zero, font.MeasureText(Text)),
});
}
#else
internal sealed class CustomCodeTextBox : TextBox
{
#endif
public override void Draw()
{
base.Draw();
// Draw border
if (!IsFocused)
Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.BorderNormal);
}
}
internal sealed class CustomCodeNode : SurfaceNode
{
private Rectangle _resizeButtonRect;
private Float2 _startResizingSize;
private Float2 _startResizingCornerOffset;
private bool _isResizing;
private CustomCodeTextBox _textBox;
private int SizeValueIndex => Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
private Float2 SizeValue
{
get => (Float2)Values[SizeValueIndex];
set => SetValue(SizeValueIndex, value, false);
}
public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size;
if (nodeArch.TypeID == 8)
{
pos += new Float2(60, 0);
size = new Float2(172, 200);
}
else
{
pos += new Float2(0, 40);
size = new Float2(300, 200);
}
_textBox = new CustomCodeTextBox
{
IsMultiline = true,
Location = pos,
Size = size,
Parent = this,
AnchorMax = Float2.One,
};
_textBox.EditEnd += () => SetValue(0, _textBox.Text);
}
public override bool CanSelect(ref Float2 location)
{
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
}
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded(action);
_textBox.Text = (string)Values[0];
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Resize(size.X, size.Y);
}
public override void OnValuesChanged()
{
base.OnValuesChanged();
var size = SizeValue;
Resize(size.X, size.Y);
_textBox.Text = (string)Values[0];
}
protected override void UpdateRectangles()
{
base.UpdateRectangles();
const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
}
public override void Draw()
{
base.Draw();
var style = Style.Current;
if (_isResizing)
{
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
}
public override void OnLostFocus()
{
if (_isResizing)
EndResizing();
base.OnLostFocus();
}
public override void OnEndMouseCapture()
{
if (_isResizing)
EndResizing();
base.OnEndMouseCapture();
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start sliding
_isResizing = true;
_startResizingSize = Size;
_startResizingCornerOffset = Size - location;
StartMouseCapture();
Cursor = CursorType.SizeNWSE;
return true;
}
return false;
}
public override void OnMouseMove(Float2 location)
{
if (_isResizing)
{
var emptySize = CalculateNodeSize(0, 0);
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, new Float2(240, 160));
Resize(size.X, size.Y);
}
else
{
base.OnMouseMove(location);
}
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
return base.OnMouseUp(location, button);
}
private void EndResizing()
{
Cursor = CursorType.Default;
EndMouseCapture();
_isResizing = false;
if (_startResizingSize != Size)
{
var emptySize = CalculateNodeSize(0, 0);
SizeValue = Size - emptySize;
Surface.MarkAsEdited(false);
}
}
}
internal enum MaterialTemplateInputsMapping
{
/// <summary>
@@ -410,13 +617,15 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 8,
Create = (id, context, arch, groupArch) => new CustomCodeNode(id, context, arch, groupArch),
Title = "Custom Code",
Description = "Custom HLSL shader code expression",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(300, 200),
DefaultValues = new object[]
{
"// Here you can add HLSL code\nOutput0 = Input0;"
"// Here you can add HLSL code\nOutput0 = Input0;",
new Float2(300, 200),
},
Elements = new[]
{
@@ -433,8 +642,6 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(1, "Output1", typeof(Float4), 9),
NodeElementArchetype.Factory.Output(2, "Output2", typeof(Float4), 10),
NodeElementArchetype.Factory.Output(3, "Output3", typeof(Float4), 11),
NodeElementArchetype.Factory.TextBox(60, 0, 175, 200, 0),
}
},
new NodeArchetype
@@ -874,6 +1081,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 38,
Create = (id, context, arch, groupArch) => new CustomCodeNode(id, context, arch, groupArch),
Title = "Custom Global Code",
Description = "Custom global HLSL shader code expression (placed before material shader code). Can contain includes to shader utilities or declare functions to reuse later.",
Flags = NodeFlags.MaterialGraph,
@@ -883,6 +1091,7 @@ namespace FlaxEditor.Surface.Archetypes
"// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}",
true,
(int)MaterialTemplateInputsMapping.Utilities,
new Float2(300, 240),
},
Elements = new[]
{
@@ -890,7 +1099,6 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Text(20, 0, "Enabled"),
NodeElementArchetype.Factory.Text(0, 20, "Location"),
NodeElementArchetype.Factory.Enum(50, 20, 120, 2, typeof(MaterialTemplateInputsMapping)),
NodeElementArchetype.Factory.TextBox(0, 40, 300, 200, 0),
}
},
new NodeArchetype
+1 -1
View File
@@ -1390,7 +1390,7 @@ namespace FlaxEditor.Surface.Archetypes
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Time", typeof(float), 0),
NodeElementArchetype.Factory.Output(1, "Unscaled Time", typeof(float), 1),
NodeElementArchetype.Factory.Output(1, "Scaled Time", typeof(float), 1),
}
},
new NodeArchetype
@@ -412,7 +412,7 @@ namespace FlaxEditor.Surface.ContextMenu
{
group.UnlockChildrenRecursive();
// TODO: Improve filtering to be based on boxes with the most common things instead of first box
if (_contextSensitiveSearchEnabled && _selectedBoxes[0] != null)
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0 && _selectedBoxes[0] != null)
UpdateFilters();
else
SortGroups();
@@ -424,7 +424,7 @@ namespace FlaxEditor.Surface.ContextMenu
OnSearchFilterChanged();
}
}
else if (_contextSensitiveSearchEnabled)
else if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0)
{
// TODO: Filtering could be improved here as well
group.EvaluateVisibilityWithBox(_selectedBoxes[0]);
@@ -462,7 +462,7 @@ namespace FlaxEditor.Surface.ContextMenu
Parent = group
};
}
if (_contextSensitiveSearchEnabled)
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0)
group.EvaluateVisibilityWithBox(_selectedBoxes[0]);
group.SortChildren();
if (ShowExpanded)
@@ -476,7 +476,7 @@ namespace FlaxEditor.Surface.ContextMenu
if (!isLayoutLocked)
{
if (_contextSensitiveSearchEnabled && _selectedBoxes[0] != null)
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count != 0 && _selectedBoxes[0] != null)
UpdateFilters();
else
SortGroups();
@@ -64,6 +64,7 @@ namespace FlaxEditor.Surface.Elements
{
ParentNode = parentNode;
Archetype = archetype;
AutoFocus = true;
var back = Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f;
+1
View File
@@ -59,6 +59,7 @@ namespace FlaxEditor.Surface
protected SurfaceControl(VisjectSurfaceContext context, float width, float height)
: base(0, 0, width, height)
{
AutoFocus = true;
ClipChildren = false;
Surface = context.Surface;
@@ -176,10 +176,10 @@ namespace FlaxEditor.Surface
if (connectedNodes.Count == 0)
return;
for (int i = 0; i < connectedNodes.Count - 1; i++)
for (int i = 0; i < connectedNodes.Count; i++)
{
SurfaceNode nodeA = connectedNodes[i];
List<Box> connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.IsOutput && b.HasAnyConnection).ToList();
List<Box> connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.HasAnyConnection).ToList();
for (int j = 0; j < connectedOutputBoxes.Count; j++)
{
@@ -120,6 +120,8 @@ namespace FlaxEditor.Surface
private void UpdateSelectionRectangle()
{
if (Root == null)
return;
var p1 = _rootControl.PointFromParent(ref _leftMouseDownPos);
var p2 = _rootControl.PointFromParent(ref _mousePos);
var selectionRect = Rectangle.FromPoints(p1, p2);
@@ -5,6 +5,7 @@
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Assets/MaterialInstance.h"
#include "Engine/Content/Content.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/Actors/PointLight.h"
@@ -88,6 +89,7 @@ void ViewportIconsRenderer::DrawIcons(RenderContext& renderContext, Actor* actor
draw.Flags = StaticFlags::Transform;
draw.DrawModes = DrawPass::Forward;
draw.PerInstanceRandom = 0;
draw.StencilValue = 0;
draw.LODBias = 0;
draw.ForcedLOD = -1;
draw.SortOrder = 0;
@@ -264,6 +266,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
bool ViewportIconsRendererService::Init()
{
PROFILE_MEM(Editor);
QuadModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Quad"));
#define INIT(type, path) \
InstanceBuffers[static_cast<int32>(IconTypes::type)].Setup(1); \
@@ -340,6 +340,13 @@ namespace FlaxEditor.Viewport
{
_debugDrawData.Clear();
if (task is SceneRenderTask sceneRenderTask)
{
// Sync debug view to avoid lag on culling/LODing
var view = sceneRenderTask.View;
DebugDraw.SetView(ref view);
}
// Collect selected objects debug shapes and visuals
var selectedParents = TransformGizmo.SelectedParents;
if (selectedParents.Count > 0)
+18 -6
View File
@@ -74,7 +74,7 @@ namespace FlaxEditor.Viewport
private PrefabUIEditorRoot _uiRoot;
private bool _showUI = false;
private int _defaultScaleActiveIndex = 0;
private int _defaultScaleActiveIndex = -1;
private int _customScaleActiveIndex = -1;
private ContextMenuButton _uiModeButton;
private ContextMenuChildMenu _uiViewOptions;
@@ -257,16 +257,23 @@ namespace FlaxEditor.Viewport
/// </summary>
public void SaveActiveUIScalingOption()
{
var defaultKey = $"{Prefab.ID}:DefaultViewportScalingIndex";
if (!Prefab)
return;
var id = Prefab.ID;
var defaultKey = $"{id}:DefaultViewportScalingIndex";
Editor.Instance.ProjectCache.SetCustomData(defaultKey, _defaultScaleActiveIndex.ToString());
var customKey = $"{Prefab.ID}:CustomViewportScalingIndex";
var customKey = $"{id}:CustomViewportScalingIndex";
Editor.Instance.ProjectCache.SetCustomData(customKey, _customScaleActiveIndex.ToString());
}
private void LoadCustomUIScalingOption()
{
if (!Prefab)
return;
var id = Prefab.ID;
Prefab.WaitForLoaded();
var defaultKey = $"{Prefab.ID}:DefaultViewportScalingIndex";
var defaultKey = $"{id}:DefaultViewportScalingIndex";
if (Editor.Instance.ProjectCache.TryGetCustomData(defaultKey, out string defaultData))
{
if (int.TryParse(defaultData, out var index))
@@ -286,7 +293,7 @@ namespace FlaxEditor.Viewport
}
}
var customKey = $"{Prefab.ID}:CustomViewportScalingIndex";
var customKey = $"{id}:CustomViewportScalingIndex";
if (Editor.Instance.ProjectCache.TryGetCustomData(customKey, out string data))
{
if (int.TryParse(data, out var index))
@@ -329,7 +336,12 @@ namespace FlaxEditor.Viewport
_tempDebugDrawContext = DebugDraw.AllocateContext();
DebugDraw.SetContext(_tempDebugDrawContext);
DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f);
if (task is SceneRenderTask sceneRenderTask)
{
// Sync debug view to avoid lag on culling/LODing
var view = sceneRenderTask.View;
DebugDraw.SetView(ref view);
}
for (int i = 0; i < selectedParents.Count; i++)
{
if (selectedParents[i].IsActiveInHierarchy)
@@ -264,6 +264,7 @@ namespace FlaxEditor.Viewport.Previews
{
DebugDraw.SetContext(_debugDrawContext);
DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1));
DebugDraw.SetView(ref renderContext.View);
CustomDebugDraw?.Invoke(context, ref renderContext);
OnDebugDraw(context, ref renderContext);
DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true);
@@ -181,15 +181,8 @@ namespace FlaxEditor.Windows.Assets
private class CollisionDataPreview : ModelBasePreview
{
public bool ShowCollisionData = false;
private int _verticesCount = 0;
private int _trianglesCount = 0;
public void SetVerticesAndTriangleCount(int verticesCount, int triangleCount)
{
_verticesCount = verticesCount;
_trianglesCount = triangleCount;
}
public bool ShowInfo;
public string Info;
/// <inheritdoc />
public CollisionDataPreview(bool useWidgets)
@@ -204,13 +197,12 @@ namespace FlaxEditor.Windows.Assets
{
base.Draw();
if (ShowCollisionData)
if (ShowInfo)
{
var text = string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}\nMemory Size: {2}", _trianglesCount, _verticesCount, Utilities.Utils.FormatBytesCount(Asset.MemoryUsage));
var font = Style.Current.FontMedium;
var pos = new Float2(10, 50);
Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White);
Render2D.DrawText(font, Info, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, Info, new Rectangle(pos, Size), Color.White);
}
}
}
@@ -222,11 +214,11 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision");
var infoButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64).LinkTooltip("Show Collision Data");
var infoButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64).LinkTooltip("Show Collision Data info");
infoButton.Clicked += () =>
{
_preview.ShowCollisionData = !_preview.ShowCollisionData;
infoButton.Checked = _preview.ShowCollisionData;
_preview.ShowInfo = !_preview.ShowInfo;
infoButton.Checked = _preview.ShowInfo;
};
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more");
@@ -293,7 +285,7 @@ namespace FlaxEditor.Windows.Assets
}
_collisionWiresShowActor.Model = _collisionWiresModel;
_collisionWiresShowActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial));
_preview.SetVerticesAndTriangleCount(triangleCount, indicesCount / 3);
_preview.Info = string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}\nMemory Size: {2}", triangleCount, indicesCount / 3, Utilities.Utils.FormatBytesCount(Asset.MemoryUsage));
_preview.Asset = FlaxEngine.Content.LoadAsync<ModelBase>(_asset.Options.Model);
}
+5
View File
@@ -116,6 +116,11 @@ namespace FlaxEditor.Windows
if (InputOptions.WindowShortcutsAvaliable)
Editor.Windows.VisualScriptDebuggerWin.FocusOrShow();
});
InputActions.Add(options => options.EditorOptionsWindow, () =>
{
if (InputOptions.WindowShortcutsAvaliable)
Editor.Windows.EditorOptionsWin.FocusOrShow();
});
// Register
Editor.Windows.OnWindowAdd(this);
+20 -24
View File
@@ -59,17 +59,11 @@ namespace FlaxEditor.Windows
GameCookerWin = win;
Selector = platformSelector;
PerPlatformOptions[PlatformType.Windows].Init("Output/Windows", "Windows");
PerPlatformOptions[PlatformType.XboxOne].Init("Output/XboxOne", "XboxOne");
PerPlatformOptions[PlatformType.UWP].Init("Output/UWP", "UWP");
PerPlatformOptions[PlatformType.Linux].Init("Output/Linux", "Linux");
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");
PerPlatformOptions[PlatformType.PS5].Init("Output/PS5", "PS5");
PerPlatformOptions[PlatformType.Mac].Init("Output/Mac", "Mac");
PerPlatformOptions[PlatformType.iOS].Init("Output/iOS", "iOS");
foreach (var e in PerPlatformOptions)
{
var str = e.Key.ToString();
e.Value.Init("Output/" + str, str);
}
}
[HideInEditor]
@@ -196,12 +190,14 @@ namespace FlaxEditor.Windows
var label = layout.Label(text, TextAlignment.Center);
label.Label.AutoHeight = true;
}
/// <summary>
/// Used to add platform specific tools if available.
/// </summary>
/// <param name="layout">The layout to start the tools at.</param>
public virtual void OnCustomToolsLayout(LayoutElementsContainer layout) { }
public virtual void OnCustomToolsLayout(LayoutElementsContainer layout)
{
}
public virtual void Build()
{
@@ -256,7 +252,7 @@ namespace FlaxEditor.Windows
if (string.IsNullOrEmpty(sdkPath))
sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK");
emulatorGroup.Label($"SDK path: {sdkPath}");
// AVD and starting emulator
var avdGroup = emulatorGroup.Group("AVD Emulator");
avdGroup.Label("Note: Create AVDs using Android Studio.");
@@ -273,7 +269,7 @@ namespace FlaxEditor.Windows
{
if (avdListTree.Children.Count > 0)
avdListTree.DisposeChildren();
var processStartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
@@ -299,7 +295,7 @@ namespace FlaxEditor.Windows
};
//processSettings.ShellExecute = true;
FlaxEngine.Platform.CreateProcess(ref processSettings);
var output = new string(processSettings.Output);*/
if (output.Length == 0)
{
@@ -345,9 +341,9 @@ namespace FlaxEditor.Windows
processSettings.ShellExecute = true;
FlaxEngine.Platform.CreateProcess(ref processSettings);
};
emulatorGroup.Space(2);
// Device
var installGroup = emulatorGroup.Group("Install");
installGroup.Panel.IsClosed = false;
@@ -391,10 +387,10 @@ namespace FlaxEditor.Windows
processSettings.SaveOutput = true;
processSettings.ShellExecute = false;
FlaxEngine.Platform.CreateProcess(ref processSettings);
var output = new string(processSettings.Output);
*/
if (output.Length > 0 && !output.Equals("List of devices attached", StringComparison.Ordinal))
{
noDevicesLabel.Visible = false;
@@ -403,7 +399,7 @@ namespace FlaxEditor.Windows
{
if (line.Trim().Equals("List of devices attached", StringComparison.Ordinal) || string.IsNullOrEmpty(line.Trim()))
continue;
var tab = line.Split("device ");
if (tab.Length < 2)
continue;
@@ -430,7 +426,7 @@ namespace FlaxEditor.Windows
{
if (deviceListTree.Selection.Count == 0)
return;
// Get built APK at output path
string output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(Output));
if (!Directory.Exists(output))
@@ -438,7 +434,7 @@ namespace FlaxEditor.Windows
FlaxEditor.Editor.LogWarning("Can not copy APK because output folder does not exist.");
return;
}
var apkFiles = Directory.GetFiles(output, "*.apk");
if (apkFiles.Length == 0)
{
@@ -457,7 +453,7 @@ namespace FlaxEditor.Windows
}
apkFilesString += $" \"{file}\"";
}
CreateProcessSettings processSettings = new CreateProcessSettings
{
FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+6
View File
@@ -885,6 +885,12 @@ namespace FlaxEditor.Windows
// Restore cursor visibility (could be hidden by the game)
if (!_cursorVisible)
Screen.CursorVisible = true;
if (Editor.IsPlayMode && IsDocked && IsSelected && RootWindow.FocusedControl == null)
{
// Game UI cleared focus so regain it to maintain UI navigation just like game window does
FlaxEngine.Scripting.InvokeOnUpdate(Focus);
}
}
}
+8 -13
View File
@@ -233,11 +233,10 @@ namespace FlaxEditor.Windows
else
cm.ClearItems();
float longestItemWidth = 0.0f;
// Add items
var font = Style.Current.FontMedium;
ItemsListContextMenu.Item lastItem = null;
var itemFont = Style.Current.FontSmall;
var maxWidth = 0.0f;
foreach (var command in commands)
{
cm.AddItem(lastItem = new Item
@@ -255,10 +254,7 @@ namespace FlaxEditor.Windows
// Set command
Set(item.Name);
};
float width = font.MeasureText(command).X;
if (width > longestItemWidth)
longestItemWidth = width;
maxWidth = Mathf.Max(maxWidth, itemFont.MeasureText(command).X);
}
cm.ItemClicked += item =>
{
@@ -269,13 +265,12 @@ namespace FlaxEditor.Windows
// Setup popup
var count = commands.Count();
var totalHeight = count * lastItem.Height + cm.ItemsPanel.Margin.Height + cm.ItemsPanel.Spacing * (count - 1);
// Account for scroll bars taking up a part of the width
longestItemWidth += 25f;
cm.Width = longestItemWidth;
cm.Height = 220;
if (cm.Height > totalHeight)
cm.Height = totalHeight; // Limit popup height if list is small
maxWidth += 8.0f + ScrollBar.DefaultSize; // Margin
if (cm.Width < maxWidth)
cm.Width = maxWidth;
if (searchText != null)
{
cm.SortItems();
@@ -368,8 +363,8 @@ namespace FlaxEditor.Windows
// Update history buffer
if (_window._commandHistory == null)
_window._commandHistory = new List<string>();
else if (_window._commandHistory.Count != 0 && _window._commandHistory.Last() == command)
_window._commandHistory.RemoveAt(_window._commandHistory.Count - 1);
else if (_window._commandHistory.Count != 0 && _window._commandHistory.Contains(command))
_window._commandHistory.Remove(command);
_window._commandHistory.Add(command);
if (_window._commandHistory.Count > CommandHistoryLimit)
_window._commandHistory.RemoveAt(0);
+7 -4
View File
@@ -34,6 +34,7 @@ namespace FlaxEditor.Windows.Profiler
private List<ClickableRow> _tableRowsCache;
private Dictionary<Guid, Resource> _resourceCache;
private StringBuilder _stringBuilder;
private Asset[] _assetsCache;
public Assets()
: base("Assets")
@@ -138,12 +139,12 @@ namespace FlaxEditor.Windows.Profiler
_stringBuilder = new StringBuilder();
// Capture current assets usage info
var assets = FlaxEngine.Content.Assets;
var resources = new Resource[assets.Length];
FlaxEngine.Content.GetAssets(ref _assetsCache, out var count);
var resources = new Resource[count];
ulong totalMemoryUsage = 0;
for (int i = 0; i < resources.Length; i++)
for (int i = 0; i < count; i++)
{
var asset = assets[i];
var asset = _assetsCache[i];
ref var resource = ref resources[i];
if (!asset)
continue;
@@ -179,6 +180,7 @@ namespace FlaxEditor.Windows.Profiler
if (_resources == null)
_resources = new SamplesBuffer<Resource[]>();
_resources.Add(resources);
Array.Clear(_assetsCache);
}
/// <inheritdoc />
@@ -200,6 +202,7 @@ namespace FlaxEditor.Windows.Profiler
_resourceCache?.Clear();
_tableRowsCache?.Clear();
_stringBuilder?.Clear();
_assetsCache = null;
base.OnDestroy();
}
+200 -1
View File
@@ -2,6 +2,8 @@
#if USE_PROFILER
using System;
using System.Collections.Generic;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -13,9 +15,22 @@ namespace FlaxEditor.Windows.Profiler
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
internal sealed class Memory : ProfilerMode
{
private struct FrameData
{
public ProfilerMemory.GroupsArray Usage;
public ProfilerMemory.GroupsArray Peek;
public ProfilerMemory.GroupsArray Count;
}
private readonly SingleChart _nativeAllocationsChart;
private readonly SingleChart _managedAllocationsChart;
private readonly Table _table;
private SamplesBuffer<FrameData> _frames;
private List<Row> _tableRowsCache;
private string[] _groupNames;
private int[] _groupOrder;
private Label _warningText;
public Memory()
: base("Memory")
{
@@ -50,6 +65,70 @@ namespace FlaxEditor.Windows.Profiler
Parent = layout,
};
_managedAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Warning text
if (!ProfilerMemory.Enabled)
{
_warningText = new Label
{
Text = "Detailed memory profiling is disabled. Run with command line '-mem'",
TextColor = Color.Red,
Visible = false,
Parent = layout,
};
}
// Table
var style = Style.Current;
var headerColor = style.LightBackground;
var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
{
new ColumnDefinition
{
UseExpandCollapseMode = true,
CellAlignment = TextAlignment.Near,
Title = "Group",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Usage",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellBytes,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Peek",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellBytes,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Count",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
},
Parent = layout,
};
_table.Splits = new[]
{
0.5f,
0.2f,
0.2f,
0.1f,
};
}
private string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount(Convert.ToUInt64(x));
}
/// <inheritdoc />
@@ -57,6 +136,7 @@ namespace FlaxEditor.Windows.Profiler
{
_nativeAllocationsChart.Clear();
_managedAllocationsChart.Clear();
_frames?.Clear();
}
/// <inheritdoc />
@@ -84,6 +164,19 @@ namespace FlaxEditor.Windows.Profiler
_nativeAllocationsChart.AddSample(nativeMemoryAllocation);
_managedAllocationsChart.AddSample(managedMemoryAllocation);
// Gather memory profiler stats for groups
var frame = new FrameData
{
Usage = ProfilerMemory.GetGroups(0),
Peek = ProfilerMemory.GetGroups(1),
Count = ProfilerMemory.GetGroups(2),
};
if (_frames == null)
_frames = new SamplesBuffer<FrameData>();
if (_groupNames == null)
_groupNames = ProfilerMemory.GetGroupNames();
_frames.Add(frame);
}
/// <inheritdoc />
@@ -91,6 +184,112 @@ namespace FlaxEditor.Windows.Profiler
{
_nativeAllocationsChart.SelectedSampleIndex = selectedFrame;
_managedAllocationsChart.SelectedSampleIndex = selectedFrame;
UpdateTable(selectedFrame);
}
/// <inheritdoc />
public override void OnDestroy()
{
_tableRowsCache?.Clear();
_groupNames = null;
_groupOrder = null;
base.OnDestroy();
}
private void UpdateTable(int selectedFrame)
{
if (_frames == null)
return;
if (_tableRowsCache == null)
_tableRowsCache = new List<Row>();
_table.IsLayoutLocked = true;
RecycleTableRows(_table, _tableRowsCache);
UpdateTableInner(selectedFrame);
_table.UnlockChildrenRecursive();
_table.PerformLayout();
}
private unsafe void UpdateTableInner(int selectedFrame)
{
if (_frames.Count == 0)
return;
if (_warningText != null)
_warningText.Visible = true;
var frame = _frames.Get(selectedFrame);
var totalUage = frame.Usage.Values0[(int)ProfilerMemory.Groups.TotalTracked];
var totalPeek = frame.Peek.Values0[(int)ProfilerMemory.Groups.TotalTracked];
var totalCount = frame.Count.Values0[(int)ProfilerMemory.Groups.TotalTracked];
// Sort by memory size
if (_groupOrder == null)
_groupOrder = new int[(int)ProfilerMemory.Groups.MAX];
for (int i = 0; i < (int)ProfilerMemory.Groups.MAX; i++)
_groupOrder[i] = i;
Array.Sort(_groupOrder, (x, y) =>
{
var tmp = _frames.Get(selectedFrame);
return tmp.Usage.Values0[y].CompareTo(tmp.Usage.Values0[x]);
});
// Add rows
var rowColor2 = Style.Current.Background * 1.4f;
for (int i = 0; i < (int)ProfilerMemory.Groups.MAX; i++)
{
var group = _groupOrder[i];
var groupUsage = frame.Usage.Values0[group];
if (groupUsage <= 0)
continue;
var groupPeek = frame.Peek.Values0[group];
var groupCount = frame.Count.Values0[group];
Row row;
if (_tableRowsCache.Count != 0)
{
var last = _tableRowsCache.Count - 1;
row = _tableRowsCache[last];
_tableRowsCache.RemoveAt(last);
}
else
{
row = new Row
{
Values = new object[4],
BackgroundColors = new Color[4],
};
}
{
// Group
row.Values[0] = _groupNames[group];
// Usage
row.Values[1] = groupUsage;
row.BackgroundColors[1] = Color.Red.AlphaMultiplied(Mathf.Min(1, (float)groupUsage / totalUage) * 0.5f);
// Peek
row.Values[2] = groupPeek;
row.BackgroundColors[2] = Color.Red.AlphaMultiplied(Mathf.Min(1, (float)groupPeek / totalPeek) * 0.5f);
// Count
row.Values[3] = groupCount;
row.BackgroundColors[3] = Color.Red.AlphaMultiplied(Mathf.Min(1, (float)groupCount / totalCount) * 0.5f);
}
row.Width = _table.Width;
row.BackgroundColor = i % 2 == 1 ? rowColor2 : Color.Transparent;
row.Parent = _table;
var useBackground = group != (int)ProfilerMemory.Groups.Total &&
group != (int)ProfilerMemory.Groups.TotalTracked &&
group != (int)ProfilerMemory.Groups.Malloc;
if (!useBackground)
{
for (int k = 1; k < row.BackgroundColors.Length; k++)
row.BackgroundColors[k] = Color.Transparent;
}
}
}
}
}
+9 -4
View File
@@ -35,6 +35,7 @@ namespace FlaxEditor.Windows.Profiler
private Dictionary<string, Guid> _assetPathToId;
private Dictionary<Guid, Resource> _resourceCache;
private StringBuilder _stringBuilder;
private GPUResource[] _gpuResourcesCached;
public MemoryGPU()
: base("GPU Memory")
@@ -138,13 +139,15 @@ namespace FlaxEditor.Windows.Profiler
// Capture current GPU resources usage info
var contentDatabase = Editor.Instance.ContentDatabase;
var gpuResources = GPUDevice.Instance.Resources;
var resources = new Resource[gpuResources.Length];
GPUDevice.Instance.GetResources(ref _gpuResourcesCached, out var count);
var resources = new Resource[count];
var sb = _stringBuilder;
for (int i = 0; i < resources.Length; i++)
for (int i = 0; i < count; i++)
{
var gpuResource = gpuResources[i];
var gpuResource = _gpuResourcesCached[i];
ref var resource = ref resources[i];
if (!gpuResource)
continue;
// Try to reuse cached resource info
var gpuResourceId = gpuResource.ID;
@@ -219,6 +222,7 @@ namespace FlaxEditor.Windows.Profiler
if (_resources == null)
_resources = new SamplesBuffer<Resource[]>();
_resources.Add(resources);
Array.Clear(_gpuResourcesCached);
}
/// <inheritdoc />
@@ -255,6 +259,7 @@ namespace FlaxEditor.Windows.Profiler
_assetPathToId?.Clear();
_tableRowsCache?.Clear();
_stringBuilder?.Clear();
_gpuResourcesCached = null;
base.OnDestroy();
}
+9 -8
View File
@@ -319,22 +319,22 @@ namespace FlaxEditor.Windows
{
if (assetItem.IsOfType<SceneAsset>())
return true;
return assetItem.OnEditorDrag(this);
return assetItem.OnEditorDrag(this) && Level.IsAnySceneLoaded;
}
private static bool ValidateDragActorType(ScriptType actorType)
{
return Editor.Instance.CodeEditing.Actors.Get().Contains(actorType);
return Editor.Instance.CodeEditing.Actors.Get().Contains(actorType) && Level.IsAnySceneLoaded;
}
private static bool ValidateDragControlType(ScriptType controlType)
{
return Editor.Instance.CodeEditing.Controls.Get().Contains(controlType);
return Editor.Instance.CodeEditing.Controls.Get().Contains(controlType) && Level.IsAnySceneLoaded;
}
private static bool ValidateDragScriptItem(ScriptItem script)
{
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null && Level.IsAnySceneLoaded;
}
/// <inheritdoc />
@@ -490,6 +490,7 @@ namespace FlaxEditor.Windows
if (result == DragDropEffect.None)
{
_isDropping = true;
// Drag assets
if (_dragAssets != null && _dragAssets.HasValidDrag)
{
@@ -504,7 +505,7 @@ namespace FlaxEditor.Windows
}
var actor = item.OnEditorDrop(this);
actor.Name = item.ShortName;
Level.SpawnActor(actor);
Editor.SceneEditing.Spawn(actor);
var graphNode = Editor.Scene.GetActorNode(actor.ID);
if (graphNode != null)
graphNodes.Add(graphNode);
@@ -527,7 +528,7 @@ namespace FlaxEditor.Windows
continue;
}
actor.Name = item.Name;
Level.SpawnActor(actor);
Editor.SceneEditing.Spawn(actor);
Editor.Scene.MarkSceneEdited(actor.Scene);
}
result = DragDropEffect.Move;
@@ -549,7 +550,7 @@ namespace FlaxEditor.Windows
Control = control,
Name = item.Name,
};
Level.SpawnActor(uiControl);
Editor.SceneEditing.Spawn(uiControl);
Editor.Scene.MarkSceneEdited(uiControl.Scene);
}
result = DragDropEffect.Move;
@@ -571,7 +572,7 @@ namespace FlaxEditor.Windows
continue;
}
actor.Name = actorType.Name;
Level.SpawnActor(actor);
Editor.SceneEditing.Spawn(actor);
var graphNode = Editor.Scene.GetActorNode(actor.ID);
if (graphNode != null)
graphNodes.Add(graphNode);
+17 -1
View File
@@ -7,6 +7,7 @@
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Threading/TaskGraph.h"
class BehaviorSystem : public TaskGraphSystem
@@ -38,6 +39,7 @@ TaskGraphSystem* Behavior::System = nullptr;
void BehaviorSystem::Job(int32 index)
{
PROFILE_CPU_NAMED("Behavior.Job");
PROFILE_MEM(AI);
Behaviors[index]->UpdateAsync();
}
@@ -57,6 +59,7 @@ void BehaviorSystem::Execute(TaskGraph* graph)
bool BehaviorService::Init()
{
PROFILE_MEM(AI);
Behavior::System = New<BehaviorSystem>();
Engine::UpdateGraph->AddSystem(Behavior::System);
return false;
@@ -70,9 +73,9 @@ void BehaviorService::Dispose()
Behavior::Behavior(const SpawnParams& params)
: Script(params)
, Tree(this)
{
_knowledge.Behavior = this;
Tree.Changed.Bind<Behavior, &Behavior::ResetLogic>(this);
}
void Behavior::UpdateAsync()
@@ -172,6 +175,19 @@ void Behavior::OnDisable()
BehaviorServiceInstance.UpdateList.Remove(this);
}
void Behavior::OnAssetChanged(Asset* asset, void* caller)
{
ResetLogic();
}
void Behavior::OnAssetLoaded(Asset* asset, void* caller)
{
}
void Behavior::OnAssetUnloaded(Asset* asset, void* caller)
{
}
#if USE_EDITOR
bool Behavior::GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior)
+6 -1
View File
@@ -11,7 +11,7 @@
/// <summary>
/// Behavior instance script that runs Behavior Tree execution.
/// </summary>
API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script
API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script, private IAssetReference
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(Behavior);
@@ -92,6 +92,11 @@ public:
void OnDisable() override;
private:
// [IAssetReference]
void OnAssetChanged(Asset* asset, void* caller) override;
void OnAssetLoaded(Asset* asset, void* caller) override;
void OnAssetUnloaded(Asset* asset, void* caller) override;
#if USE_EDITOR
// Editor-only utilities to debug nodes state.
API_FUNCTION(Internal) static bool GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior);
+2
View File
@@ -4,6 +4,7 @@
#include "BehaviorTree.h"
#include "BehaviorTreeNodes.h"
#include "BehaviorKnowledgeSelector.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
@@ -144,6 +145,7 @@ BehaviorKnowledge::~BehaviorKnowledge()
void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
{
PROFILE_MEM(AI);
if (Tree)
FreeMemory();
if (!tree)
+2
View File
@@ -10,6 +10,7 @@
#include "Engine/Serialization/JsonSerializer.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "FlaxEngine.Gen.h"
#if USE_EDITOR
#include "Engine/Level/Level.h"
@@ -275,6 +276,7 @@ Asset::LoadResult BehaviorTree::load()
if (surfaceChunk == nullptr)
return LoadResult::MissingDataChunk;
MemoryReadStream surfaceStream(surfaceChunk->Get(), surfaceChunk->Size());
PROFILE_MEM(AI);
if (Graph.Load(&surfaceStream, true))
{
LOG(Warning, "Failed to load graph \'{0}\'", ToString());
+9 -3
View File
@@ -4,6 +4,7 @@
#include "AnimEvent.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Level/Actors/AnimatedModel.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
@@ -52,7 +53,7 @@ namespace
AnimationsService AnimationManagerInstance;
TaskGraphSystem* Animations::System = nullptr;
ConcurrentSystemLocker Animations::SystemLocker;
ReadWriteLock Animations::SystemLocker;
#if USE_EDITOR
Delegate<Animations::DebugFlowInfo> Animations::DebugFlow;
#endif
@@ -69,6 +70,7 @@ AnimContinuousEvent::AnimContinuousEvent(const SpawnParams& params)
bool AnimationsService::Init()
{
PROFILE_MEM(Animations);
Animations::System = New<AnimationsSystem>();
Engine::UpdateGraph->AddSystem(Animations::System);
return false;
@@ -83,6 +85,7 @@ void AnimationsService::Dispose()
void AnimationsSystem::Job(int32 index)
{
PROFILE_CPU_NAMED("Animations.Job");
PROFILE_MEM(Animations);
auto animatedModel = AnimationManagerInstance.UpdateList[index];
if (CanUpdateModel(animatedModel))
{
@@ -121,7 +124,7 @@ void AnimationsSystem::Execute(TaskGraph* graph)
Active = true;
// Ensure no animation assets can be reloaded/modified during async update
Animations::SystemLocker.Begin(false);
Animations::SystemLocker.ReadLock();
// Setup data for async update
const auto& tickData = Time::Update;
@@ -147,6 +150,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
if (!Active)
return;
PROFILE_CPU_NAMED("Animations.PostExecute");
PROFILE_MEM(Animations);
// Update gameplay
for (int32 index = 0; index < AnimationManagerInstance.UpdateList.Count(); index++)
@@ -161,16 +165,18 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
// Cleanup
AnimationManagerInstance.UpdateList.Clear();
Animations::SystemLocker.End(false);
Animations::SystemLocker.ReadUnlock();
Active = false;
}
void Animations::AddToUpdate(AnimatedModel* obj)
{
ScopeWriteLock lock(SystemLocker);
AnimationManagerInstance.UpdateList.Add(obj);
}
void Animations::RemoveFromUpdate(AnimatedModel* obj)
{
ScopeWriteLock lock(SystemLocker);
AnimationManagerInstance.UpdateList.Remove(obj);
}
+1 -2
View File
@@ -4,7 +4,6 @@
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Core/Delegate.h"
#include "Engine/Threading/ConcurrentSystemLocker.h"
class TaskGraphSystem;
class AnimatedModel;
@@ -23,7 +22,7 @@ API_CLASS(Static) class FLAXENGINE_API Animations
API_FIELD(ReadOnly) static TaskGraphSystem* System;
// Data access locker for animations data.
static ConcurrentSystemLocker SystemLocker;
static ReadWriteLock SystemLocker;
#if USE_EDITOR
// Data wrapper for the debug flow information.
@@ -6,6 +6,7 @@
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Threading/Threading.h"
extern void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes);
@@ -7,6 +7,7 @@
#include "Engine/Content/Content.h"
#include "Engine/Content/Deprecated.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Audio/AudioClip.h"
#include "Engine/Graphics/PostProcessSettings.h"
#if USE_EDITOR
@@ -249,6 +250,7 @@ bool SceneAnimation::Save(const StringView& path)
Asset::LoadResult SceneAnimation::load()
{
TrackStatesCount = 0;
PROFILE_MEM(AnimationsData);
// Get the data chunk
if (LoadChunk(0))
@@ -12,6 +12,7 @@
#include "Engine/Audio/AudioSource.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
@@ -151,6 +152,7 @@ void SceneAnimationPlayer::Tick(float dt)
SceneAnimation* anim = Animation.Get();
if (!anim || !anim->IsLoaded())
return;
PROFILE_MEM(Animations);
// Setup state
if (_tracks.Count() != anim->TrackStatesCount)
@@ -229,6 +231,7 @@ void SceneAnimationPlayer::MapTrack(const StringView& from, const Guid& to)
SceneAnimation* anim = Animation.Get();
if (!anim || !anim->IsLoaded())
return;
PROFILE_MEM(Animations);
for (int32 j = 0; j < anim->Tracks.Count(); j++)
{
const auto& track = anim->Tracks[j];
+3
View File
@@ -8,6 +8,7 @@
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Level/Level.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Core/Log.h"
@@ -151,6 +152,7 @@ void Audio::SetEnableHRTF(bool value)
bool AudioService::Init()
{
PROFILE_CPU_NAMED("Audio.Init");
PROFILE_MEM(Audio);
const auto settings = AudioSettings::Get();
const bool mute = CommandLine::Options.Mute.IsTrue() || settings->DisableAudio;
@@ -211,6 +213,7 @@ bool AudioService::Init()
void AudioService::Update()
{
PROFILE_CPU_NAMED("Audio.Update");
PROFILE_MEM(Audio);
// Update the master volume
float masterVolume = MasterVolume;
+44 -21
View File
@@ -7,50 +7,67 @@
#include "Engine/Core/Log.h"
#include "Engine/Content/Upgraders/AudioClipUpgrader.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Streaming/StreamingGroup.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Tools/AudioTool/OggVorbisDecoder.h"
#include "Engine/Tools/AudioTool/AudioTool.h"
#include "Engine/Threading/Threading.h"
REGISTER_BINARY_ASSET_WITH_UPGRADER(AudioClip, "FlaxEngine.AudioClip", AudioClipUpgrader, false);
AudioClip::StreamingTask::StreamingTask(AudioClip* asset)
: _asset(asset)
, _dataLock(asset->Storage->Lock())
{
}
bool AudioClip::StreamingTask::HasReference(Object* resource) const
{
return _asset == resource;
}
bool AudioClip::StreamingTask::Run()
{
AssetReference<AudioClip> ref = _asset.Get();
if (ref == nullptr || AudioBackend::Instance == nullptr)
PROFILE_CPU_NAMED("AudioStreaming");
PROFILE_MEM(Audio);
AssetReference<AudioClip> clip = _asset.Get();
if (clip == nullptr || AudioBackend::Instance == nullptr)
return true;
ScopeLock lock(ref->Locker);
const auto& queue = ref->StreamingQueue;
if (queue.Count() == 0)
return false;
auto clip = ref.Get();
#if TRACY_ENABLE
const StringView name(clip->GetPath());
ZoneName(*name, name.Length());
#endif
// Update the buffers
// Process the loading queue (hold the asset lock)
clip->Locker.Lock();
const auto& queue = clip->StreamingQueue;
Array<int32, FixedAllocation<ASSET_FILE_DATA_CHUNKS>> loadQueue;
for (int32 i = 0; i < queue.Count(); i++)
{
const auto idx = queue[i];
const int32 idx = queue[i];
uint32& bufferID = clip->Buffers[idx];
if (bufferID == 0)
{
bufferID = AudioBackend::Buffer::Create();
// Load buffers outside the asset lock to prevent lock contention
loadQueue.Add(idx);
}
else
{
// Release unused data
// Release unused buffer
AudioBackend::Buffer::Delete(bufferID);
bufferID = 0;
}
}
clip->Locker.Unlock();
// Load missing buffers data (from asset chunks)
for (int32 i = 0; i < queue.Count(); i++)
for (int32 i = 0; i < loadQueue.Count(); i++)
{
if (clip->WriteBuffer(queue[i]))
{
if (clip->WriteBuffer(loadQueue[i]))
return true;
}
}
// Update the sources
@@ -318,6 +335,7 @@ bool AudioClip::init(AssetInitData& initData)
Asset::LoadResult AudioClip::load()
{
PROFILE_MEM(Audio);
#if !COMPILE_WITH_OGG_VORBIS
if (AudioHeader.Format == AudioFormat::Vorbis)
{
@@ -410,14 +428,11 @@ void AudioClip::unload(bool isReloading)
bool AudioClip::WriteBuffer(int32 chunkIndex)
{
// Ignore if buffer is not created
const uint32 bufferID = Buffers[chunkIndex];
if (bufferID == 0)
return false;
// Ensure audio backend exists
if (AudioBackend::Instance == nullptr)
return true;
PROFILE_CPU();
PROFILE_MEM(Audio);
const auto chunk = GetChunk(chunkIndex);
if (chunk == nullptr || chunk->IsMissing())
@@ -429,6 +444,7 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
Array<byte> tmp1, tmp2;
AudioDataInfo info = AudioHeader.Info;
const uint32 bytesPerSample = info.BitDepth / 8;
ZoneValue(chunk->Size() / 1024); // Audio data size (in kB)
// Get raw data or decompress it
switch (Format())
@@ -436,6 +452,7 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
case AudioFormat::Vorbis:
{
#if COMPILE_WITH_OGG_VORBIS
PROFILE_CPU_NAMED("OggVorbisDecode");
OggVorbisDecoder decoder;
MemoryReadStream stream(chunk->Get(), chunk->Size());
AudioDataInfo tmpInfo;
@@ -472,7 +489,13 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
data = Span<byte>(tmp2.Get(), tmp2.Count());
}
// Write samples to the audio buffer
// Write samples to the audio buffer (create one if missing)
Locker.Lock(); // StreamingTask loads buffers without lock so do it here
uint32& bufferID = Buffers[chunkIndex];
if (bufferID == 0)
bufferID = AudioBackend::Buffer::Create();
AudioBackend::Buffer::Write(bufferID, data.Get(), info);
Locker.Unlock();
return false;
}
+2 -13
View File
@@ -44,22 +44,11 @@ public:
FlaxStorage::LockData _dataLock;
public:
/// <summary>
/// Init
/// </summary>
/// <param name="asset">Parent asset</param>
StreamingTask(AudioClip* asset)
: _asset(asset)
, _dataLock(asset->Storage->Lock())
{
}
StreamingTask(AudioClip* asset);
public:
// [ThreadPoolTask]
bool HasReference(Object* resource) const override
{
return _asset == resource;
}
bool HasReference(Object* resource) const override;
protected:
// [ThreadPoolTask]
+2 -2
View File
@@ -23,11 +23,11 @@ void AudioListener::Update()
{
// Update the velocity
const Vector3 pos = GetPosition();
const float dt = Time::Update.UnscaledDeltaTime.GetTotalSeconds();
const float dt = Math::Max(Time::Update.UnscaledDeltaTime.GetTotalSeconds(), 0.00001f);
const auto prevVelocity = _velocity;
_velocity = (pos - _prevPos) / dt;
_prevPos = pos;
if (_velocity != prevVelocity)
if (_velocity != prevVelocity && !_velocity.IsNanOrInfinity())
{
AudioBackend::Listener::VelocityChanged(_velocity);
}
+11 -4
View File
@@ -8,6 +8,7 @@
#include "Engine/Engine/Time.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "AudioBackend.h"
#include "Audio.h"
@@ -21,9 +22,8 @@ AudioSource::AudioSource(const SpawnParams& params)
, _playOnStart(false)
, _startTime(0.0f)
, _allowSpatialization(true)
, Clip(this)
{
Clip.Changed.Bind<AudioSource, &AudioSource::OnClipChanged>(this);
Clip.Loaded.Bind<AudioSource, &AudioSource::OnClipLoaded>(this);
}
void AudioSource::SetVolume(float value)
@@ -121,6 +121,7 @@ void AudioSource::Play()
auto state = _state;
if (state == States::Playing)
return;
PROFILE_CPU();
if (Clip == nullptr || Clip->WaitForLoaded())
{
LOG(Warning, "Cannot play audio source without a clip ({0})", GetNamePath());
@@ -189,6 +190,7 @@ void AudioSource::Stop()
{
if (_state == States::Stopped)
return;
PROFILE_CPU();
_state = States::Stopped;
_isActuallyPlayingSth = false;
@@ -264,7 +266,7 @@ void AudioSource::RequestStreamingBuffersUpdate()
_needToUpdateStreamingBuffers = true;
}
void AudioSource::OnClipChanged()
void AudioSource::OnAssetChanged(Asset* asset, void* caller)
{
Stop();
@@ -276,7 +278,7 @@ void AudioSource::OnClipChanged()
}
}
void AudioSource::OnClipLoaded()
void AudioSource::OnAssetLoaded(Asset* asset, void* caller)
{
if (!SourceID)
return;
@@ -302,6 +304,10 @@ void AudioSource::OnClipLoaded()
}
}
void AudioSource::OnAssetUnloaded(Asset* asset, void* caller)
{
}
bool AudioSource::UseStreaming() const
{
if (Clip == nullptr || Clip->WaitForLoaded())
@@ -383,6 +389,7 @@ bool AudioSource::IntersectsItself(const Ray& ray, Real& distance, Vector3& norm
void AudioSource::Update()
{
PROFILE_CPU();
PROFILE_MEM(Audio);
// Update the velocity
const Vector3 pos = GetPosition();
+5 -3
View File
@@ -13,7 +13,7 @@
/// Whether or not an audio source is spatial is controlled by the assigned AudioClip.The volume and the pitch of a spatial audio source is controlled by its position and the AudioListener's position/direction/velocity.
/// </remarks>
API_CLASS(Attributes="ActorContextMenu(\"New/Audio/Audio Source\"), ActorToolbox(\"Other\")")
class FLAXENGINE_API AudioSource : public Actor
class FLAXENGINE_API AudioSource : public Actor, IAssetReference
{
DECLARE_SCENE_OBJECT(AudioSource);
friend class AudioStreamingHandler;
@@ -303,8 +303,10 @@ public:
void RequestStreamingBuffersUpdate();
private:
void OnClipChanged();
void OnClipLoaded();
// [IAssetReference]
void OnAssetChanged(Asset* asset, void* caller) override;
void OnAssetLoaded(Asset* asset, void* caller) override;
void OnAssetUnloaded(Asset* asset, void* caller) override;
/// <summary>
/// Plays the audio source. Should have buffer(s) binded before.
@@ -9,6 +9,7 @@
#include "Engine/Tools/AudioTool/AudioTool.h"
#include "Engine/Engine/Units.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioListener.h"
#include "Engine/Audio/AudioSource.h"
@@ -321,6 +322,8 @@ void AudioBackendOAL::Listener_ReinitializeAll()
uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
PROFILE_MEM(Audio);
uint32 sourceID = 0;
ALC::Source::Rebuild(sourceID, position, orientation, volume, pitch, pan, loop, spatial, attenuation, minDistance, doppler);
@@ -516,6 +519,7 @@ void AudioBackendOAL::Buffer_Delete(uint32 bufferID)
void AudioBackendOAL::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{
PROFILE_CPU();
PROFILE_MEM(Audio);
// Pick the format for the audio data (it might not be supported natively)
ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
@@ -625,6 +629,8 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
void AudioBackendOAL::Base_OnActiveDeviceChanged()
{
PROFILE_MEM(Audio);
// Cleanup
Array<ALC::AudioSourceState> states;
states.EnsureCapacity(Audio::Sources.Count());
@@ -9,6 +9,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Audio/Audio.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerMemory.h"
#if PLATFORM_WINDOWS
// Tweak Win ver
@@ -232,6 +233,7 @@ void AudioBackendXAudio2::Listener_ReinitializeAll()
uint32 AudioBackendXAudio2::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
PROFILE_MEM(Audio);
ScopeLock lock(XAudio2::Locker);
// Get first free source
@@ -580,6 +582,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(uint32 sourceID)
uint32 AudioBackendXAudio2::Buffer_Create()
{
PROFILE_MEM(Audio);
uint32 bufferID;
ScopeLock lock(XAudio2::Locker);
@@ -618,6 +621,7 @@ void AudioBackendXAudio2::Buffer_Delete(uint32 bufferID)
void AudioBackendXAudio2::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{
PROFILE_MEM(Audio);
CHECK(info.NumChannels <= MAX_INPUT_CHANNELS);
XAudio2::Locker.Lock();
+128 -70
View File
@@ -9,6 +9,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadLocal.h"
@@ -34,15 +35,18 @@ bool ContentDeprecated::Clear(bool newValue)
#endif
AssetReferenceBase::AssetReferenceBase(IAssetReference* owner)
: _owner(owner)
{
}
AssetReferenceBase::~AssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
asset->RemoveReference(this);
}
}
@@ -51,52 +55,60 @@ String AssetReferenceBase::ToString() const
return _asset ? _asset->ToString() : TEXT("<null>");
}
void AssetReferenceBase::OnAssetChanged(Asset* asset, void* caller)
{
if (_owner)
_owner->OnAssetChanged(asset, this);
}
void AssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller)
{
if (_asset != asset)
return;
Loaded();
if (_owner)
_owner->OnAssetLoaded(asset, this);
}
void AssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller)
{
if (_asset != asset)
return;
Unload();
OnSet(nullptr);
if (_owner)
_owner->OnAssetUnloaded(asset, this);
}
void AssetReferenceBase::OnSet(Asset* asset)
{
auto e = _asset;
if (e != asset)
{
if (e)
{
e->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
e->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
e->RemoveReference();
}
e->RemoveReference(this);
_asset = e = asset;
if (e)
{
e->AddReference();
e->OnLoaded.Bind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
e->OnUnloaded.Bind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
}
e->AddReference(this);
Changed();
if (_owner)
_owner->OnAssetChanged(asset, this);
if (e && e->IsLoaded())
{
Loaded();
if (_owner)
_owner->OnAssetLoaded(asset, this);
}
}
}
void AssetReferenceBase::OnLoaded(Asset* asset)
{
if (_asset != asset)
return;
Loaded();
}
void AssetReferenceBase::OnUnloaded(Asset* asset)
{
if (_asset != asset)
return;
Unload();
OnSet(nullptr);
}
WeakAssetReferenceBase::~WeakAssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference(this, true);
}
}
@@ -105,36 +117,43 @@ String WeakAssetReferenceBase::ToString() const
return _asset ? _asset->ToString() : TEXT("<null>");
}
void WeakAssetReferenceBase::OnAssetChanged(Asset* asset, void* caller)
{
}
void WeakAssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller)
{
}
void WeakAssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller)
{
if (_asset != asset)
return;
Unload();
asset->RemoveReference(this, true);
_asset = nullptr;
}
void WeakAssetReferenceBase::OnSet(Asset* asset)
{
auto e = _asset;
if (e != asset)
{
if (e)
e->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
e->RemoveReference(this, true);
_asset = e = asset;
if (e)
e->OnUnloaded.Bind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
e->AddReference(this, true);
}
}
void WeakAssetReferenceBase::OnUnloaded(Asset* asset)
{
if (_asset != asset)
return;
Unload();
asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
_asset = nullptr;
}
SoftAssetReferenceBase::~SoftAssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
asset->RemoveReference(this);
}
#if !BUILD_RELEASE
_id = Guid::Empty;
@@ -146,22 +165,34 @@ String SoftAssetReferenceBase::ToString() const
return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("<null>"));
}
void SoftAssetReferenceBase::OnAssetChanged(Asset* asset, void* caller)
{
}
void SoftAssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller)
{
}
void SoftAssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller)
{
if (_asset != asset)
return;
_asset->RemoveReference(this);
_asset = nullptr;
_id = Guid::Empty;
Changed();
}
void SoftAssetReferenceBase::OnSet(Asset* asset)
{
if (_asset == asset)
return;
if (_asset)
{
_asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
_asset->RemoveReference();
}
_asset->RemoveReference(this);
_asset = asset;
_id = asset ? asset->GetID() : Guid::Empty;
if (asset)
{
asset->AddReference();
asset->OnUnloaded.Bind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
}
asset->AddReference(this);
Changed();
}
@@ -170,10 +201,7 @@ void SoftAssetReferenceBase::OnSet(const Guid& id)
if (_id == id)
return;
if (_asset)
{
_asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
_asset->RemoveReference();
}
_asset->RemoveReference(this);
_asset = nullptr;
_id = id;
Changed();
@@ -184,21 +212,7 @@ void SoftAssetReferenceBase::OnResolve(const ScriptingTypeHandle& type)
ASSERT(!_asset);
_asset = ::LoadAsset(_id, type);
if (_asset)
{
_asset->OnUnloaded.Bind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
_asset->AddReference();
}
}
void SoftAssetReferenceBase::OnUnloaded(Asset* asset)
{
if (_asset != asset)
return;
_asset->RemoveReference();
_asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
_asset = nullptr;
_id = Guid::Empty;
Changed();
_asset->AddReference(this);
}
Asset::Asset(const SpawnParams& params, const AssetInfo* info)
@@ -216,6 +230,39 @@ int32 Asset::GetReferencesCount() const
return (int32)Platform::AtomicRead(const_cast<int64 volatile*>(&_refCount));
}
void Asset::AddReference()
{
Platform::InterlockedIncrement(&_refCount);
}
void Asset::AddReference(IAssetReference* ref, bool week)
{
if (!week)
Platform::InterlockedIncrement(&_refCount);
if (ref)
{
//PROFILE_MEM(EngineDelegate); // Include references tracking memory within Delegate memory
ScopeLock lock(_referencesLocker);
_references.Add(ref);
}
}
void Asset::RemoveReference()
{
Platform::InterlockedDecrement(&_refCount);
}
void Asset::RemoveReference(IAssetReference* ref, bool week)
{
if (ref)
{
ScopeLock lock(_referencesLocker);
_references.Remove(ref);
}
if (!week)
Platform::InterlockedDecrement(&_refCount);
}
String Asset::ToString() const
{
return String::Format(TEXT("{0}, {1}, {2}"), GetTypeName(), GetID(), GetPath());
@@ -232,7 +279,7 @@ void Asset::OnDeleteObject()
const bool wasMarkedToDelete = _deleteFileOnUnload != 0;
#if USE_EDITOR
const String path = wasMarkedToDelete ? GetPath() : String::Empty;
const String path = wasMarkedToDelete ? String(GetPath()) : String::Empty;
#endif
const Guid id = GetID();
@@ -354,6 +401,7 @@ uint64 Asset::GetMemoryUsage() const
if (Platform::AtomicRead(&_loadingTask))
result += sizeof(ContentLoadTask);
result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
result += _references.Capacity() * sizeof(HashSet<IAssetReference*>::Bucket);
Locker.Unlock();
return result;
}
@@ -444,6 +492,9 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
}
PROFILE_CPU();
ZoneColor(TracyWaitZoneColor);
const StringView path(GetPath());
ZoneText(*path, path.Length());
Content::WaitForTask(loadingTask, timeoutInMilliseconds);
@@ -528,6 +579,7 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading()
{
PROFILE_MEM(ContentAssets);
ASSERT(!IsLoaded());
ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
auto loadingTask = createLoadingTask();
@@ -627,6 +679,9 @@ void Asset::onLoaded_MainThread()
ASSERT(IsInMainThread());
// Send event
ScopeLock lock(_referencesLocker);
for (const auto& e : _references)
e.Item->OnAssetLoaded(this, this);
OnLoaded(this);
}
@@ -640,6 +695,9 @@ void Asset::onUnload_MainThread()
CancelStreaming();
// Send event
ScopeLock lock(_referencesLocker);
for (const auto& e : _references)
e.Item->OnAssetUnloaded(this, this);
OnUnloaded(this);
}
+30 -9
View File
@@ -18,6 +18,20 @@
public: \
explicit type(const SpawnParams& params, const AssetInfo* info)
// Utility interface for objects that reference asset and want to get notified about asset reference changes.
class FLAXENGINE_API IAssetReference
{
public:
virtual ~IAssetReference() = default;
// Asset reference got changed.
virtual void OnAssetChanged(Asset* asset, void* caller) = 0;
// Asset got loaded.
virtual void OnAssetLoaded(Asset* asset, void* caller) = 0;
// Asset gets unloaded.
virtual void OnAssetUnloaded(Asset* asset, void* caller) = 0;
};
/// <summary>
/// Asset objects base class.
/// </summary>
@@ -48,6 +62,9 @@ protected:
int8 _deleteFileOnUnload : 1; // Indicates that asset source file should be removed on asset unload
int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved)
HashSet<IAssetReference*> _references;
CriticalSection _referencesLocker; // TODO: convert into a single interlocked exchange for the current thread owning lock
public:
/// <summary>
/// Initializes a new instance of the <see cref="Asset"/> class.
@@ -88,24 +105,28 @@ public:
/// <summary>
/// Adds reference to that asset.
/// </summary>
FORCE_INLINE void AddReference()
{
Platform::InterlockedIncrement(&_refCount);
}
void AddReference();
/// <summary>
/// Adds reference to that asset.
/// </summary>
void AddReference(IAssetReference* ref, bool week = false);
/// <summary>
/// Removes reference from that asset.
/// </summary>
FORCE_INLINE void RemoveReference()
{
Platform::InterlockedDecrement(&_refCount);
}
void RemoveReference();
/// <summary>
/// Removes reference from that asset.
/// </summary>
void RemoveReference(IAssetReference* ref, bool week = false);
public:
/// <summary>
/// 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.
/// </summary>
API_PROPERTY() virtual const String& GetPath() const = 0;
API_PROPERTY() virtual StringView GetPath() const = 0;
/// <summary>
/// Gets the asset type name.
+30 -3
View File
@@ -7,10 +7,11 @@
/// <summary>
/// Asset reference utility. Keeps reference to the linked asset object and handles load/unload events.
/// </summary>
class FLAXENGINE_API AssetReferenceBase
class FLAXENGINE_API AssetReferenceBase : public IAssetReference
{
protected:
Asset* _asset = nullptr;
IAssetReference* _owner = nullptr;
public:
/// <summary>
@@ -36,6 +37,12 @@ public:
/// </summary>
AssetReferenceBase() = default;
/// <summary>
/// Initializes a new instance of the <see cref="AssetReferenceBase"/> class.
/// </summary>
/// <param name="owner">The reference owner to keep notified about asset changes.</param>
AssetReferenceBase(IAssetReference* owner);
/// <summary>
/// Finalizes an instance of the <see cref="AssetReferenceBase"/> class.
/// </summary>
@@ -63,10 +70,14 @@ public:
/// </summary>
String ToString() const;
public:
// [IAssetReference]
void OnAssetChanged(Asset* asset, void* caller) override;
void OnAssetLoaded(Asset* asset, void* caller) override;
void OnAssetUnloaded(Asset* asset, void* caller) override;
protected:
void OnSet(Asset* asset);
void OnLoaded(Asset* asset);
void OnUnloaded(Asset* asset);
};
/// <summary>
@@ -87,6 +98,13 @@ public:
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetReference"/> class.
/// </summary>
explicit AssetReference(decltype(__nullptr))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetReference"/> class.
/// </summary>
@@ -96,6 +114,15 @@ public:
OnSet((Asset*)asset);
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetReference"/> class.
/// </summary>
/// <param name="owner">The reference owner to keep notified about asset changes.</param>
explicit AssetReference(IAssetReference* owner)
: AssetReferenceBase(owner)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetReference"/> class.
/// </summary>
+3 -2
View File
@@ -9,6 +9,7 @@
#include "Engine/Animations/Animations.h"
#include "Engine/Animations/SceneAnimations/SceneAnimation.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Serialization/MemoryReadStream.h"
#if USE_EDITOR
@@ -598,7 +599,7 @@ void Animation::OnScriptingDispose()
Asset::LoadResult Animation::load()
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
PROFILE_MEM(AnimationsData);
// Get stream with animations data
const auto dataChunk = GetChunk(0);
@@ -730,7 +731,7 @@ Asset::LoadResult Animation::load()
void Animation::unload(bool isReloading)
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeWriteLock systemScope(Animations::SystemLocker);
#if USE_EDITOR
if (_registeredForScriptingReload)
{
@@ -9,6 +9,7 @@
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Animations/Animations.h"
#include "Engine/Threading/Threading.h"
@@ -25,7 +26,7 @@ AnimationGraph::AnimationGraph(const SpawnParams& params, const AssetInfo* info)
Asset::LoadResult AnimationGraph::load()
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
PROFILE_MEM(AnimationsData);
// Get stream with graph data
const auto surfaceChunk = GetChunk(0);
@@ -51,7 +52,7 @@ Asset::LoadResult AnimationGraph::load()
void AnimationGraph::unload(bool isReloading)
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeWriteLock systemScope(Animations::SystemLocker);
Graph.Clear();
}
@@ -83,7 +84,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b
Log::ArgumentNullException();
return true;
}
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
PROFILE_MEM(AnimationsData);
// Create Graph data
MemoryWriteStream writeStream(512);
@@ -169,7 +170,6 @@ bool AnimationGraph::SaveSurface(const BytesContainer& data)
{
if (OnCheckSave())
return true;
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeLock lock(Locker);
if (IsVirtual())
@@ -8,6 +8,7 @@
#include "Engine/Serialization/MemoryWriteStream.h"
#endif
#include "Engine/Animations/Animations.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Threading/Threading.h"
@@ -20,7 +21,7 @@ AnimationGraphFunction::AnimationGraphFunction(const SpawnParams& params, const
Asset::LoadResult AnimationGraphFunction::load()
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
PROFILE_MEM(AnimationsData);
// Get graph data from chunk
const auto surfaceChunk = GetChunk(0);
@@ -47,7 +48,7 @@ Asset::LoadResult AnimationGraphFunction::load()
void AnimationGraphFunction::unload(bool isReloading)
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeWriteLock systemScope(Animations::SystemLocker);
GraphData.Release();
Inputs.Clear();
Outputs.Clear();
@@ -96,7 +97,6 @@ bool AnimationGraphFunction::SaveSurface(const BytesContainer& data) const
{
if (OnCheckSave())
return true;
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeLock lock(Locker);
// Set Visject Surface data
+53 -8
View File
@@ -165,9 +165,13 @@ Asset::LoadResult Material::load()
MaterialGenerator generator;
generator.Error.Bind(&OnGeneratorError);
if (_shaderHeader.Material.GraphVersion != MATERIAL_GRAPH_VERSION)
{
LOG(Info, "Converting material \'{0}\', from version {1} to {2}...", name, _shaderHeader.Material.GraphVersion, MATERIAL_GRAPH_VERSION);
}
else
{
LOG(Info, "Updating material \'{0}\'...", name);
}
// Load or create material surface
MaterialLayer* layer;
@@ -186,16 +190,55 @@ Asset::LoadResult Material::load()
// Load layer
layer = MaterialLayer::Load(GetID(), &stream, _shaderHeader.Material.Info, name);
if (ContentDeprecated::Clear())
const bool upgradeOldSpecular = _shaderHeader.Material.GraphVersion < 177;
if (ContentDeprecated::Clear() || upgradeOldSpecular)
{
// If encountered any deprecated data when loading graph then serialize it
MaterialGraph graph;
MemoryWriteStream writeStream(1024);
stream.SetPosition(0);
if (!graph.Load(&stream, true) && !graph.Save(&writeStream, true))
if (!graph.Load(&stream, true))
{
surfaceChunk->Data.Copy(ToSpan(writeStream));
ContentDeprecated::Clear();
if (upgradeOldSpecular)
{
// [Deprecated in 1.11]
// Specular calculations were changed to support up to 16% of reflectance via ^2 curve instead of linear up to 8%
// Insert Custom Code node that converts old materials into a new system to ensure they look the same
MaterialGraph::Node* rootNode = nullptr;
for (auto& e : graph.Nodes)
{
if (e.Type == ROOT_NODE_TYPE)
{
rootNode = &e;
break;
}
}
const auto& specularBoxInfo = MaterialGenerator::GetMaterialRootNodeBox(MaterialGraphBoxes::Specular);
auto specularBox = rootNode ? rootNode->GetBox(specularBoxInfo.ID) : nullptr;
if (specularBox && specularBox->HasConnection())
{
auto& customCodeNode = graph.Nodes.AddOne();
customCodeNode.ID = graph.Nodes.Count() + 1000;
customCodeNode.Type = GRAPH_NODE_MAKE_TYPE(1, 8);
customCodeNode.Boxes.Resize(2);
customCodeNode.Boxes[0] = MaterialGraphBox(&customCodeNode, 0, VariantType::Float4); // Input0
customCodeNode.Boxes[1] = MaterialGraphBox(&customCodeNode, 8, VariantType::Float4); // Output0
customCodeNode.Values.Resize(1);
customCodeNode.Values[0] = TEXT("// Convert old Specular value to a new range\nOutput0.x = min(Input0.x * 0.5f, 0.6f);");
auto specularSourceBox = specularBox->Connections[0];
specularBox->Connections.Clear();
specularSourceBox->Connections.Clear();
#define CONNECT(boxA, boxB) boxA->Connections.Add(boxB); boxB->Connections.Add(boxA)
CONNECT(specularSourceBox, (&customCodeNode.Boxes[0])); // Specular -> Input0
CONNECT((&customCodeNode.Boxes[1]), specularBox); // Output0 -> Specular
#undef CONNECT
}
}
if (!graph.Save(&writeStream, true))
{
surfaceChunk->Data.Copy(ToSpan(writeStream));
ContentDeprecated::Clear();
}
}
}
}
@@ -410,16 +453,18 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
// Prepare
auto& info = _shaderHeader.Material.Info;
const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable;
const bool isOpaque = info.BlendMode == MaterialBlendMode::Opaque;
const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage;
const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle;
const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && !isOpaque) || info.Domain == MaterialDomain::Particle;
const bool useTess =
info.TessellationMode != TessellationMethod::None &&
RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable;
const bool useDistortion =
(info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) &&
info.BlendMode != MaterialBlendMode::Opaque &&
!isOpaque &&
EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseRefraction) &&
(info.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None;
const MaterialShadingModel shadingModel = info.ShadingModel == MaterialShadingModel::CustomLit ? MaterialShadingModel::Unlit : info.ShadingModel;
// @formatter:off
static const char* Numbers[] =
@@ -431,7 +476,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
// Setup shader macros
options.Macros.Add({ "MATERIAL_DOMAIN", Numbers[(int32)info.Domain] });
options.Macros.Add({ "MATERIAL_BLEND", Numbers[(int32)info.BlendMode] });
options.Macros.Add({ "MATERIAL_SHADING_MODEL", Numbers[(int32)info.ShadingModel] });
options.Macros.Add({ "MATERIAL_SHADING_MODEL", Numbers[(int32)shadingModel] });
options.Macros.Add({ "MATERIAL_MASKED", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseMask) ? 1 : 0] });
options.Macros.Add({ "DECAL_BLEND_MODE", Numbers[(int32)info.DecalBlendingMode] });
options.Macros.Add({ "USE_EMISSIVE", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseEmissive) ? 1 : 0] });
@@ -488,7 +533,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] });
options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] });
options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] });
options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] });
options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && isOpaque ? 1 : 0] });
options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] });
#endif
}
+10 -1
View File
@@ -18,6 +18,7 @@
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
@@ -32,12 +33,14 @@ class StreamModelSDFTask : public GPUUploadTextureMipTask
{
private:
WeakAssetReference<Model> _asset;
FlaxStorageReference _dataRef;
FlaxStorage::LockData _dataLock;
public:
StreamModelSDFTask(Model* model, GPUTexture* texture, const Span<byte>& data, int32 mipIndex, int32 rowPitch, int32 slicePitch)
: GPUUploadTextureMipTask(texture, mipIndex, data, rowPitch, slicePitch, false)
, _asset(model)
, _dataRef(model->Storage)
, _dataLock(model->Storage->Lock())
{
}
@@ -58,6 +61,7 @@ public:
void OnEnd() override
{
_dataLock.Release();
_dataRef = FlaxStorageReference();
// Base
GPUUploadTextureMipTask::OnEnd();
@@ -304,6 +308,7 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
Log::ArgumentOutOfRangeException();
return true;
}
PROFILE_MEM(GraphicsMeshes);
// Dispose previous data and disable streaming (will start data uploading tasks manually)
StopStreaming();
@@ -343,6 +348,7 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
bool Model::LoadHeader(ReadStream& stream, byte& headerVersion)
{
PROFILE_MEM(GraphicsMeshes);
if (ModelBase::LoadHeader(stream, headerVersion))
return true;
@@ -509,6 +515,7 @@ bool Model::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk
void Model::SetupMaterialSlots(int32 slotsCount)
{
PROFILE_MEM(GraphicsMeshes);
ModelBase::SetupMaterialSlots(slotsCount);
// Adjust meshes indices for slots
@@ -584,6 +591,8 @@ int32 Model::GetAllocatedResidency() const
Asset::LoadResult Model::load()
{
PROFILE_MEM(GraphicsMeshes);
// Get header chunk
auto chunk0 = GetChunk(0);
if (chunk0 == nullptr || chunk0->IsMissing())
@@ -613,7 +622,7 @@ Asset::LoadResult Model::load()
{
String name;
#if !BUILD_RELEASE
name = GetPath() + TEXT(".SDF");
name = String(GetPath()) + TEXT(".SDF");
#endif
SDF.Texture = GPUDevice::Instance->CreateTexture(name);
}
+38 -2
View File
@@ -3,12 +3,15 @@
#include "ModelBase.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Core/Config/BuildSettings.h"
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Graphics/Config.h"
#include "Engine/Graphics/Models/MeshBase.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Threading/Threading.h"
#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
#include "Engine/Threading/ThreadPoolTask.h"
#define STREAM_TASK_BASE ThreadPoolTask
@@ -51,6 +54,7 @@ public:
AssetReference<ModelBase> model = _model.Get();
if (model == nullptr)
return true;
PROFILE_MEM(GraphicsMeshes);
// Get data
BytesContainer data;
@@ -334,6 +338,8 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion)
bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
{
PROFILE_MEM(GraphicsMeshes);
// Load descriptor
static_assert(MODEL_MESH_VERSION == 2, "Update code");
uint32 vertices, triangles;
@@ -661,11 +667,42 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
return true;
}
// Process mesh data if need to decide vertex buffer on format dynamically
auto positionFormat = modelData.PositionFormat;
if (positionFormat == ModelData::PositionFormats::Automatic)
{
const float maxPositionError = BuildSettings::Get()->MaxMeshPositionError; // In world-units
const float maxPositionErrorSq = maxPositionError * maxPositionError;
if (maxPositionErrorSq > 0.0f)
{
positionFormat = ModelData::PositionFormats::Float16;
const Float3* positions = mesh.Positions.Get();
for (int32 i = 0; i < mesh.Positions.Count(); i++)
{
// Encode to Half3 and decode back to see the position error
Float3 position = positions[i];
Half3 encoded(position);
Float3 decoded = encoded.ToFloat3();
if (Float3::DistanceSquared(position, decoded) > maxPositionErrorSq)
{
// Cannot use lower quality so go back to full precision
positionFormat = ModelData::PositionFormats::Float32;
break;
}
}
}
else
{
// Full precision as default
positionFormat = ModelData::PositionFormats::Float32;
}
}
// Define vertex buffers layout and packing
Array<GPUVertexLayout::Elements, FixedAllocation<MODEL_MAX_VB>> vbElements;
const bool useSeparatePositions = !isSkinned;
const bool useSeparateColors = !isSkinned;
PixelFormat positionsFormat = modelData.PositionFormat == ModelData::PositionFormats::Float32 ? PixelFormat::R32G32B32_Float : PixelFormat::R16G16B16A16_Float;
PixelFormat positionsFormat = positionFormat == ModelData::PositionFormats::Float32 ? PixelFormat::R32G32B32_Float : PixelFormat::R16G16B16A16_Float;
PixelFormat texCoordsFormat = modelData.TexCoordFormat == ModelData::TexCoordFormats::Float16 ? PixelFormat::R16G16_Float : PixelFormat::R8G8_UNorm;
PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt;
PixelFormat blendWeightsFormat = PixelFormat::R8G8B8A8_UNorm;
@@ -679,7 +716,6 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
}
{
byte vbIndex = 0;
// TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords)
// Position
if (useSeparatePositions)
+9 -1
View File
@@ -4,6 +4,10 @@
#include "Engine/Core/Log.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#if GPU_ENABLE_RESOURCE_NAMING && !USE_EDITOR
#include "Engine/Content/Content.h"
#include "Engine/Content/Cache/AssetsCache.h"
#endif
#include "Engine/Content/Upgraders/ShaderAssetUpgrader.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Serialization/MemoryReadStream.h"
@@ -14,7 +18,11 @@ Shader::Shader(const SpawnParams& params, const AssetInfo* info)
: ShaderAssetTypeBase<BinaryAsset>(params, info)
{
ASSERT(GPUDevice::Instance);
_shader = GPUDevice::Instance->CreateShader(info->Path);
StringView name = info->Path;
#if GPU_ENABLE_RESOURCE_NAMING && !USE_EDITOR
name = Content::GetRegistry()->GetEditorAssetPath(info->ID);
#endif
_shader = GPUDevice::Instance->CreateShader(name);
ASSERT(_shader);
GPU = _shader;
}
@@ -6,6 +6,7 @@
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Content/Upgraders/SkeletonMaskUpgrader.h"
#include "Engine/Threading/Threading.h"
REGISTER_BINARY_ASSET_WITH_UPGRADER(SkeletonMask, "FlaxEngine.SkeletonMask", SkeletonMaskUpgrader, true);
@@ -18,6 +18,7 @@
#include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Renderer/DrawCall.h"
#if USE_EDITOR
#include "Engine/Graphics/Models/ModelData.h"
@@ -458,6 +459,7 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
Log::ArgumentOutOfRangeException();
return true;
}
PROFILE_MEM(GraphicsMeshes);
// Dispose previous data and disable streaming (will start data uploading tasks manually)
StopStreaming();
@@ -501,6 +503,7 @@ void BlendShape::LoadHeader(ReadStream& stream, byte headerVersion)
void BlendShape::Load(ReadStream& stream, byte meshVersion)
{
PROFILE_MEM(GraphicsMeshes);
UseNormals = stream.ReadBool();
stream.ReadUint32(&MinVertexIndex);
stream.ReadUint32(&MaxVertexIndex);
@@ -531,6 +534,7 @@ void BlendShape::Save(WriteStream& stream) const
bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
{
PROFILE_MEM(GraphicsMeshes);
if (ModelBase::LoadMesh(stream, meshVersion, mesh, dataIfReadOnly))
return true;
static_assert(MODEL_MESH_VERSION == 2, "Update code");
@@ -560,6 +564,7 @@ bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase
bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
{
PROFILE_MEM(GraphicsMeshes);
if (ModelBase::LoadHeader(stream, headerVersion))
return true;
static_assert(MODEL_HEADER_VERSION == 2, "Update code");
@@ -861,6 +866,7 @@ uint64 SkinnedModel::GetMemoryUsage() const
void SkinnedModel::SetupMaterialSlots(int32 slotsCount)
{
PROFILE_MEM(GraphicsMeshes);
ModelBase::SetupMaterialSlots(slotsCount);
// Adjust meshes indices for slots
@@ -954,6 +960,7 @@ Asset::LoadResult SkinnedModel::load()
if (chunk0 == nullptr || chunk0->IsMissing())
return LoadResult::MissingDataChunk;
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
PROFILE_MEM(GraphicsMeshes);
// Load asset data (anything but mesh contents that use streaming)
byte headerVersion;
+1 -1
View File
@@ -36,7 +36,7 @@ bool Texture::Save(const StringView& path)
bool Texture::Save(const StringView& path, const InitData* customData)
{
if (OnCheckSave())
if (OnCheckSave(path))
return true;
ScopeLock lock(Locker);
@@ -18,6 +18,7 @@
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonWriter.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Level/SceneObject.h"
@@ -37,10 +38,12 @@ namespace
void PrintStack(LogType type)
{
#if LOG_ENABLE
const String stack = VisualScripting::GetStackTrace();
Log::Logger::Write(type, TEXT("Visual Script stack trace:"));
Log::Logger::Write(type, stack);
Log::Logger::Write(type, TEXT(""));
#endif
}
bool SerializeValue(const Variant& a, const Variant& b)
@@ -1340,6 +1343,8 @@ bool VisualScript::Save(const StringView& path)
Asset::LoadResult VisualScript::load()
{
PROFILE_MEM(ScriptingVisual);
// Build Visual Script typename that is based on asset id
String typeName = _id.ToString();
StringUtils::ConvertUTF162ANSI(typeName.Get(), _typenameChars, 32);
@@ -1532,6 +1537,7 @@ Asset::LoadResult VisualScript::load()
void VisualScript::unload(bool isReloading)
{
PROFILE_MEM(ScriptingVisual);
#if USE_EDITOR
if (isReloading)
{
@@ -1588,6 +1594,7 @@ AssetChunksFlag VisualScript::getChunksToPreload() const
void VisualScript::CacheScriptingType()
{
PROFILE_MEM(ScriptingVisual);
ScopeLock lock(VisualScriptingBinaryModule::Locker);
auto& binaryModule = VisualScriptingModule;
@@ -1723,6 +1730,7 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri
VisualScript* visualScript = VisualScriptingModule.Scripts[params.Type.TypeIndex];
// Initialize instance data
PROFILE_MEM(ScriptingVisual);
ScopeLock lock(visualScript->Locker);
auto& instanceParams = visualScript->_instances[object->GetID()].Params;
instanceParams.Resize(visualScript->Graph.Parameters.Count());
@@ -1747,6 +1755,8 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri
void VisualScriptingBinaryModule::OnScriptsReloading()
{
PROFILE_MEM(ScriptingVisual);
// Clear any cached types from that module across all loaded Visual Scripts
for (auto& script : Scripts)
{
@@ -1795,6 +1805,7 @@ void VisualScriptingBinaryModule::OnScriptsReloading()
void VisualScriptingBinaryModule::OnEvent(ScriptingObject* object, Span<Variant> parameters, ScriptingTypeHandle eventType, StringView eventName)
{
PROFILE_MEM(ScriptingVisual);
if (object)
{
// Object event
@@ -1900,9 +1911,13 @@ bool VisualScriptingBinaryModule::InvokeMethod(void* method, const Variant& inst
if (!instanceObject || instanceObject->GetTypeHandle() != vsMethod->Script->GetScriptingType())
{
if (!instanceObject)
{
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(vsMethod->Script->GetScriptTypeName()), String(vsMethod->Name), vsMethod->ParamNames.Count());
}
else
{
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid object instance of type '{3}'", String(vsMethod->Script->GetScriptTypeName()), String(vsMethod->Name), vsMethod->ParamNames.Count(), String(instanceObject->GetType().Fullname));
}
return true;
}
}
@@ -1952,6 +1967,7 @@ bool VisualScriptingBinaryModule::GetFieldValue(void* field, const Variant& inst
bool VisualScriptingBinaryModule::SetFieldValue(void* field, const Variant& instance, Variant& value)
{
PROFILE_MEM(ScriptingVisual);
const auto vsFiled = (VisualScript::Field*)field;
const auto instanceObject = (ScriptingObject*)instance;
if (!instanceObject)
@@ -2038,6 +2054,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO
void VisualScriptingBinaryModule::DeserializeObject(ISerializable::DeserializeStream& stream, ScriptingObject* object, ISerializeModifier* modifier)
{
PROFILE_MEM(ScriptingVisual);
ASSERT(stream.IsObject());
Locker.Lock();
const auto asset = Scripts[object->GetTypeHandle().TypeIndex].Get();
@@ -2161,6 +2178,7 @@ const Variant& VisualScript::GetScriptInstanceParameterValue(const StringView& n
void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, const Variant& value)
{
PROFILE_MEM(ScriptingVisual);
CHECK(instance);
for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++)
{
@@ -2182,6 +2200,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip
void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, Variant&& value)
{
PROFILE_MEM(ScriptingVisual);
CHECK(instance);
for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++)
{
@@ -2379,6 +2398,7 @@ VisualScriptingBinaryModule* VisualScripting::GetBinaryModule()
Variant VisualScripting::Invoke(VisualScript::Method* method, ScriptingObject* instance, Span<Variant> parameters)
{
PROFILE_MEM(ScriptingVisual);
CHECK_RETURN(method && method->Script->IsLoaded(), Variant::Zero);
PROFILE_CPU_SRC_LOC(method->ProfilerData);
@@ -2419,6 +2439,7 @@ bool VisualScripting::Evaluate(VisualScript* script, ScriptingObject* instance,
const auto box = node->GetBox(boxId);
if (!box)
return false;
PROFILE_MEM(ScriptingVisual);
// Add to the calling stack
ScopeContext scope;
+4 -2
View File
@@ -10,6 +10,7 @@
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Profiler/ProfilerMemory.h"
#if USE_EDITOR
#include "Engine/Platform/FileSystem.h"
#include "Engine/Threading/Threading.h"
@@ -463,10 +464,10 @@ void BinaryAsset::OnDeleteObject()
#endif
const String& BinaryAsset::GetPath() const
StringView BinaryAsset::GetPath() const
{
#if USE_EDITOR
return Storage ? Storage->GetPath() : String::Empty;
return Storage ? StringView(Storage->GetPath()) : StringView::Empty;
#else
// In build all assets are packed into packages so use ID for original path lookup
return Content::GetRegistry()->GetEditorAssetPath(_id);
@@ -527,6 +528,7 @@ protected:
auto storage = ref->Storage;
auto factory = (BinaryAssetFactoryBase*)Content::GetAssetFactory(ref->GetTypeName());
ASSERT(factory);
PROFILE_MEM(ContentAssets);
// Here we should open storage and extract AssetInitData
// This would also allow to convert/upgrade data
+1 -1
View File
@@ -292,7 +292,7 @@ public:
#if USE_EDITOR
void OnDeleteObject() override;
#endif
const String& GetPath() const final override;
StringView GetPath() const final override;
uint64 GetMemoryUsage() const override;
protected:
+47 -27
View File
@@ -10,15 +10,28 @@
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Serialization/FileReadStream.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Storage/JsonStorageProxy.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Engine/Globals.h"
#include "FlaxEngine.Gen.h"
#if ASSETS_CACHE_EDITABLE
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Storage/JsonStorageProxy.h"
#include "Engine/Threading/Threading.h"
#define ASSETS_CACHE_LOCK() ScopeLock lock(_locker)
#else
#define ASSETS_CACHE_LOCK()
#endif
int32 AssetsCache::Size() const
{
ASSETS_CACHE_LOCK();
const int32 result = _registry.Count();
return result;
}
void AssetsCache::Init()
{
PROFILE_CPU();
Entry e;
int32 count;
Stopwatch stopwatch;
@@ -71,7 +84,7 @@ void AssetsCache::Init()
return;
}
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
_isDirty = false;
// Load elements count
@@ -126,6 +139,16 @@ void AssetsCache::Init()
_pathsMapping.Add(mappedPath, id);
}
#if !USE_EDITOR && !BUILD_RELEASE
// Build inverse path mapping in development builds for faster GetEditorAssetPath (eg. used by PROFILE_CPU_ASSET)
_pathsMappingInv.Clear();
_pathsMappingInv.EnsureCapacity(count);
for (auto& mapping : _pathsMapping)
{
_pathsMappingInv.Add(mapping.Value, StringView(mapping.Key));
}
#endif
// Check errors
const bool hasError = stream->HasError();
deleteStream.Delete();
@@ -153,7 +176,7 @@ bool AssetsCache::Save()
if (!_isDirty && FileSystem::FileExists(_path))
return false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
if (Save(_path, _registry, _pathsMapping))
return true;
@@ -222,12 +245,16 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
return false;
}
const String& AssetsCache::GetEditorAssetPath(const Guid& id) const
StringView AssetsCache::GetEditorAssetPath(const Guid& id) const
{
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
#if USE_EDITOR
auto e = _registry.TryGet(id);
return e ? e->Info.Path : String::Empty;
#elif !BUILD_RELEASE
StringView result;
_pathsMappingInv.TryGet(id, result);
return result;
#else
for (auto& e : _pathsMapping)
{
@@ -241,10 +268,8 @@ const String& AssetsCache::GetEditorAssetPath(const Guid& id) const
bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info)
{
PROFILE_CPU();
bool result = false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
// Check if asset has direct mapping to id (used for some cooked assets)
Guid id;
@@ -293,7 +318,7 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
{
PROFILE_CPU();
bool result = false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
auto e = _registry.TryGet(id);
if (e != nullptr)
{
@@ -315,14 +340,14 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
void AssetsCache::GetAll(Array<Guid>& result) const
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
_registry.GetKeys(result);
}
void AssetsCache::GetAllByTypeName(const StringView& typeName, Array<Guid>& result) const
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.TypeName == typeName)
@@ -330,6 +355,8 @@ void AssetsCache::GetAllByTypeName(const StringView& typeName, Array<Guid>& resu
}
}
#if ASSETS_CACHE_EDITABLE
void AssetsCache::RegisterAssets(FlaxStorage* storage)
{
PROFILE_CPU();
@@ -341,7 +368,7 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage)
storage->GetEntries(entries);
ASSERT(entries.HasItems());
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
auto storagePath = storage->GetPath();
// Remove all old entries from that location
@@ -439,7 +466,7 @@ void AssetsCache::RegisterAssets(const FlaxStorageReference& storage)
void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const StringView& path)
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
// Check if asset has been already added to the registry
bool isMissing = true;
@@ -491,8 +518,7 @@ void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const St
bool AssetsCache::DeleteAsset(const StringView& path, AssetInfo* info)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.Path == path)
@@ -505,16 +531,13 @@ bool AssetsCache::DeleteAsset(const StringView& path, AssetInfo* info)
break;
}
}
_locker.Unlock();
return result;
}
bool AssetsCache::DeleteAsset(const Guid& id, AssetInfo* info)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
const auto e = _registry.TryGet(id);
if (e != nullptr)
{
@@ -524,16 +547,13 @@ bool AssetsCache::DeleteAsset(const Guid& id, AssetInfo* info)
_isDirty = true;
result = true;
}
_locker.Unlock();
return result;
}
bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPath)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.Path == oldPath)
@@ -544,11 +564,11 @@ bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPa
break;
}
}
_locker.Unlock();
return result;
}
#endif
bool AssetsCache::IsEntryValid(Entry& e)
{
#if ENABLE_ASSETS_DISCOVERY
+13 -9
View File
@@ -16,6 +16,9 @@ struct AssetHeader;
struct FlaxStorageReference;
class FlaxStorage;
// In cooked game all assets are there and all access to registry is read-only so can be multithreaded
#define ASSETS_CACHE_EDITABLE (USE_EDITOR)
/// <summary>
/// Assets cache flags.
/// </summary>
@@ -75,22 +78,21 @@ public:
private:
bool _isDirty = false;
#if ASSETS_CACHE_EDITABLE
CriticalSection _locker;
#endif
Registry _registry;
PathsMapping _pathsMapping;
#if !USE_EDITOR && !BUILD_RELEASE
Dictionary<Guid, StringView> _pathsMappingInv;
#endif
String _path;
public:
/// <summary>
/// Gets amount of registered assets.
/// </summary>
int32 Size() const
{
_locker.Lock();
const int32 result = _registry.Count();
_locker.Unlock();
return result;
}
int32 Size() const;
public:
/// <summary>
@@ -116,11 +118,11 @@ public:
public:
/// <summary>
/// Finds the asset path by id. In editor it returns the actual asset path, at runtime it returns the mapped asset path.
/// Finds the asset path by id. In editor, it returns the actual asset path, at runtime it returns the mapped asset path.
/// </summary>
/// <param name="id">The asset id.</param>
/// <returns>The asset path, or empty if failed to find.</returns>
const String& GetEditorAssetPath(const Guid& id) const;
StringView GetEditorAssetPath(const Guid& id) const;
/// <summary>
/// Finds the asset info by path.
@@ -173,6 +175,7 @@ public:
/// <param name="result">The result array.</param>
void GetAllByTypeName(const StringView& typeName, Array<Guid, HeapAllocation>& result) const;
#if ASSETS_CACHE_EDITABLE
/// <summary>
/// Register assets in the cache
/// </summary>
@@ -223,6 +226,7 @@ public:
/// <param name="newPath">New path</param>
/// <returns>True if has been deleted, otherwise false</returns>
bool RenameAsset(const StringView& oldPath, const StringView& newPath);
#endif
/// <summary>
/// Determines whether cached asset entry is valid.
+6 -1
View File
@@ -4,8 +4,13 @@
#include "Engine/Core/Config.h"
// Amount of content loading threads per single physical CPU core
// Amount of content loading threads per single logical CPU core
#ifndef LOADING_THREAD_PER_LOGICAL_CORE
#define LOADING_THREAD_PER_LOGICAL_CORE 0.5f
#endif
// Enables pinning loading threads to the logical CPU cores with affinity mask
//#define LOADING_THREAD_AFFINITY_MASK(thread) (1 << (thread + 1))
// Enables additional assets metadata verification
#define ASSETS_LOADING_EXTRA_VERIFICATION (BUILD_DEBUG || USE_EDITOR)
+56 -5
View File
@@ -28,6 +28,7 @@
#include "Engine/Engine/Globals.h"
#include "Engine/Level/Types.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/Scripting.h"
#if USE_EDITOR
@@ -117,6 +118,8 @@ ContentService ContentServiceInstance;
bool ContentService::Init()
{
PROFILE_MEM(Content);
// Load assets registry
Cache.Init();
@@ -126,17 +129,17 @@ bool ContentService::Init()
LOG(Info, "Creating {0} content loading threads...", count);
MainLoadThread = New<LoadingThread>();
ThisLoadThread = MainLoadThread;
LoadThreads.EnsureCapacity(count);
LoadThreads.Resize(count);
for (int32 i = 0; i < count; i++)
{
auto thread = New<LoadingThread>();
LoadThreads[i] = thread;
if (thread->Start(String::Format(TEXT("Load Thread {0}"), i)))
{
LOG(Fatal, "Cannot spawn content thread {0}/{1}", i, count);
Delete(thread);
return true;
}
LoadThreads.Add(thread);
}
return false;
@@ -159,6 +162,7 @@ void ContentService::Update()
void ContentService::LateUpdate()
{
PROFILE_CPU();
PROFILE_MEM(Content);
// Check if need to perform an update of unloading assets
const TimeSpan timeNow = Time::Update.UnscaledTime;
@@ -324,6 +328,7 @@ String LoadingThread::ToString() const
int32 LoadingThread::Run()
{
PROFILE_MEM(Content);
#if USE_EDITOR && PLATFORM_WINDOWS
// Initialize COM
// TODO: maybe add sth to Thread::Create to indicate that thread will use COM stuff
@@ -334,6 +339,9 @@ int32 LoadingThread::Run()
return -1;
}
#endif
#ifdef LOADING_THREAD_AFFINITY_MASK
Platform::SetThreadAffinityMask(LOADING_THREAD_AFFINITY_MASK(LoadThreads.Find(this)));
#endif
ContentLoadTask* task;
ThisLoadThread = this;
@@ -416,6 +424,7 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
if (Cache.FindAsset(id, info))
return true;
PROFILE_CPU();
PROFILE_MEM(Content);
// Locking injects some stalls but we need to make it safe (only one thread can pass though it at once)
ScopeLock lock(WorkspaceDiscoveryLocker);
@@ -465,6 +474,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
if (!FileSystem::FileExists(path))
return false;
PROFILE_CPU();
PROFILE_MEM(Content);
const auto extension = FileSystem::GetExtension(path).ToLower();
@@ -511,7 +521,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
#endif
}
String Content::GetEditorAssetPath(const Guid& id)
StringView Content::GetEditorAssetPath(const Guid& id)
{
return Cache.GetEditorAssetPath(id);
}
@@ -593,6 +603,7 @@ Asset* Content::LoadAsyncInternal(const StringView& internalPath, const MClass*
Asset* Content::LoadAsyncInternal(const StringView& internalPath, const ScriptingTypeHandle& type)
{
PROFILE_MEM(Content);
#if USE_EDITOR
const String path = Globals::EngineContentFolder / internalPath + ASSET_FILES_EXTENSION_WITH_DOT;
if (!FileSystem::FileExists(path))
@@ -635,6 +646,8 @@ Asset* Content::LoadAsync(const StringView& path, const MClass* type)
Asset* Content::LoadAsync(const StringView& path, const ScriptingTypeHandle& type)
{
PROFILE_MEM(Content);
// Ensure path is in a valid format
String pathNorm(path);
ContentStorageManager::FormatPath(pathNorm);
@@ -687,7 +700,6 @@ Asset* Content::GetAsset(const StringView& outputPath)
{
if (outputPath.IsEmpty())
return nullptr;
ScopeLock lock(AssetsLocker);
for (auto i = Assets.Begin(); i.IsNotEnd(); ++i)
{
@@ -737,6 +749,7 @@ void Content::DeleteAsset(const StringView& path)
return;
}
#if USE_EDITOR
ScopeLock locker(AssetsLocker);
// Remove from registry
@@ -753,6 +766,7 @@ void Content::DeleteAsset(const StringView& path)
// Delete file
deleteFileSafety(path, info.ID);
#endif
}
void Content::deleteFileSafety(const StringView& path, const Guid& id)
@@ -791,6 +805,23 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
#endif
}
#if !COMPILE_WITHOUT_CSHARP
#include "Engine/Scripting/ManagedCLR/MUtils.h"
void* Content::GetAssetsInternal()
{
AssetsLocker.Lock();
MArray* result = MCore::Array::New(Asset::TypeInitializer.GetClass(), Assets.Count());
int32 i = 0;
for (const auto& e : Assets)
MCore::GC::WriteArrayRef(result, e.Value->GetOrCreateManagedInstance(), i++);
AssetsLocker.Unlock();
return result;
}
#endif
#if USE_EDITOR
bool Content::RenameAsset(const StringView& oldPath, const StringView& newPath)
@@ -977,7 +1008,7 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath);
// Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath))
if (auto storage = ContentStorageManager::GetStorage(dstPath, false))
{
storage->Reload();
}
@@ -1023,6 +1054,7 @@ Asset* Content::CreateVirtualAsset(const MClass* type)
Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
{
PROFILE_CPU();
PROFILE_MEM(Content);
auto& assetType = type.GetType();
// Init mock asset info
@@ -1045,7 +1077,9 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
}
// Create asset object
PROFILE_MEM_BEGIN(ContentAssets);
auto asset = factory->NewVirtual(info);
PROFILE_MEM_END();
if (asset == nullptr)
{
LOG(Error, "Cannot create virtual asset object.");
@@ -1054,7 +1088,9 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
asset->RegisterObject();
// Call initializer function
PROFILE_MEM_BEGIN(ContentAssets);
asset->InitAsVirtual();
PROFILE_MEM_END();
// Register asset
AssetsLocker.Lock();
@@ -1076,11 +1112,21 @@ void Content::WaitForTask(ContentLoadTask* loadingTask, double timeoutInMillisec
const double timeoutInSeconds = timeoutInMilliseconds * 0.001;
const double startTime = Platform::GetTimeSeconds();
int32 loopCounter = 0;
Task* task = loadingTask;
Array<ContentLoadTask*, InlinedAllocation<64>> localQueue;
#define CHECK_CONDITIONS() (!Engine::ShouldExit() && (timeoutInSeconds <= 0.0 || Platform::GetTimeSeconds() - startTime < timeoutInSeconds))
do
{
// Give opportunity for other threads to use the current core
if (loopCounter == 0)
; // First run is fast
else if (loopCounter < 10)
Platform::Yield();
else
Platform::Sleep(1);
loopCounter++;
// Try to execute content tasks
while (task->IsQueued() && CHECK_CONDITIONS())
{
@@ -1097,6 +1143,8 @@ void Content::WaitForTask(ContentLoadTask* loadingTask, double timeoutInMillisec
localQueue.Clear();
}
PROFILE_CPU_NAMED("Inline");
ZoneColor(0xffaaaaaa);
thread->Run(tmp);
}
else
@@ -1209,6 +1257,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{
if (!id.IsValid())
return nullptr;
PROFILE_MEM(Content);
// Check if asset has been already loaded
Asset* result = nullptr;
@@ -1277,7 +1326,9 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
}
// Create asset object
PROFILE_MEM_BEGIN(ContentAssets);
result = factory->New(assetInfo);
PROFILE_MEM_END();
if (result == nullptr)
{
LOG(Error, "Cannot create asset object. Info: {0}", assetInfo.ToString());
+28
View File
@@ -1,5 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine.Interop;
using System;
using System.Runtime.CompilerServices;
@@ -7,6 +8,33 @@ namespace FlaxEngine
{
partial class Content
{
/// <summary>
/// Gets the assets (loaded or during load).
/// </summary>
public static Asset[] Assets
{
get
{
IntPtr ptr = Internal_GetAssetsInternal();
ManagedArray array = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(ptr).Target);
return NativeInterop.GCHandleArrayToManagedArray<Asset>(array);
}
}
/// <summary>
/// Gets the assets (loaded or during load).
/// </summary>
/// <param name="buffer">Output buffer to fill with asset pointers. Can be provided by a user to avoid memory allocation. Buffer might be larger than actual list size. Use <paramref name="count"/> for actual item count.></param>
/// <param name="count">Amount of valid items inside <paramref name="buffer"/>.</param>
public static void GetAssets(ref Asset[] buffer, out int count)
{
count = 0;
IntPtr ptr = Internal_GetAssetsInternal();
ManagedArray array = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(ptr).Target);
buffer = NativeInterop.GCHandleArrayToManagedArray<Asset>(array, buffer);
count = buffer.Length;
}
/// <summary>
/// Loads asset to the Content Pool and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async.
/// </summary>
+7 -2
View File
@@ -75,7 +75,7 @@ public:
/// </summary>
/// <param name="id">The asset id.</param>
/// <returns>The asset path, or empty if failed to find.</returns>
API_FUNCTION() static String GetEditorAssetPath(const Guid& id);
API_FUNCTION() static StringView GetEditorAssetPath(const Guid& id);
/// <summary>
/// Finds all the asset IDs. Uses asset registry.
@@ -122,7 +122,7 @@ public:
/// Gets the assets (loaded or during load).
/// </summary>
/// <returns>The collection of assets.</returns>
API_PROPERTY() static Array<Asset*, HeapAllocation> GetAssets();
static Array<Asset*, HeapAllocation> GetAssets();
/// <summary>
/// Gets the raw dictionary of assets (loaded or during load).
@@ -368,4 +368,9 @@ private:
static void onAssetUnload(Asset* asset);
static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId);
static void deleteFileSafety(const StringView& path, const Guid& id);
// Internal bindings
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) static void* GetAssetsInternal();
#endif
};
+9 -2
View File
@@ -20,6 +20,7 @@
#include "Engine/Core/Cache.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
@@ -39,6 +40,7 @@ String JsonAssetBase::GetData() const
if (Data == nullptr)
return String::Empty;
PROFILE_CPU_NAMED("JsonAsset.GetData");
PROFILE_MEM(ContentAssets);
rapidjson_flax::StringBuffer buffer;
OnGetData(buffer);
return String((const char*)buffer.GetString(), (int32)buffer.GetSize());
@@ -49,6 +51,7 @@ void JsonAssetBase::SetData(const StringView& value)
if (!IsLoaded())
return;
PROFILE_CPU_NAMED("JsonAsset.SetData");
PROFILE_MEM(ContentAssets);
const StringAnsi dataJson(value);
ScopeLock lock(Locker);
const StringView dataTypeName = DataTypeName;
@@ -60,6 +63,7 @@ void JsonAssetBase::SetData(const StringView& value)
bool JsonAssetBase::Init(const StringView& dataTypeName, const StringAnsiView& dataJson)
{
PROFILE_MEM(ContentAssets);
unload(true);
DataTypeName = dataTypeName;
DataEngineBuild = FLAXENGINE_VERSION_BUILD;
@@ -87,7 +91,7 @@ void JsonAssetBase::OnGetData(rapidjson_flax::StringBuffer& buffer) const
Data->Accept(writerObj.GetWriter());
}
const String& JsonAssetBase::GetPath() const
StringView JsonAssetBase::GetPath() const
{
#if USE_EDITOR
return _path;
@@ -176,7 +180,7 @@ bool JsonAssetBase::Save(const StringView& path)
_isResaving = false;
// Save json to file
if (File::WriteAllBytes(path.HasChars() ? path : StringView(GetPath()), (byte*)buffer.GetString(), (int32)buffer.GetSize()))
if (File::WriteAllBytes(path.HasChars() ? path : GetPath(), (byte*)buffer.GetString(), (int32)buffer.GetSize()))
{
LOG(Error, "Cannot save \'{0}\'", ToString());
return true;
@@ -239,6 +243,7 @@ Asset::LoadResult JsonAssetBase::loadAsset()
{
if (IsVirtual() || _isVirtualDocument)
return LoadResult::Ok;
PROFILE_MEM(ContentAssets);
// Load data (raw json file in editor, cooked asset in build game)
#if USE_EDITOR
@@ -305,6 +310,7 @@ Asset::LoadResult JsonAssetBase::loadAsset()
void JsonAssetBase::unload(bool isReloading)
{
PROFILE_MEM(ContentAssets);
ISerializable::SerializeDocument tmp;
Document.Swap(tmp);
Data = nullptr;
@@ -453,6 +459,7 @@ bool JsonAsset::CreateInstance()
ScopeLock lock(Locker);
if (Instance)
return false;
PROFILE_MEM(ContentAssets);
// Try to scripting type for this data
const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length());
+1 -1
View File
@@ -88,7 +88,7 @@ protected:
public:
// [Asset]
const String& GetPath() const override;
StringView GetPath() const override;
uint64 GetMemoryUsage() const override;
#if USE_EDITOR
void GetReferences(Array<Guid, HeapAllocation>& assets, Array<String, HeapAllocation>& files) const override;
@@ -19,6 +19,15 @@ API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference
OnSet(asset);
}
explicit JsonAssetReference(decltype(__nullptr))
{
}
explicit JsonAssetReference(IAssetReference* owner)
: AssetReference<JsonAsset>(owner)
{
}
/// <summary>
/// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type.
/// </summary>
@@ -36,7 +36,7 @@ public:
// [ContentLoadTask]
String ToString() const override
{
return String::Format(TEXT("Load Asset Data Task ({}, {}, {})"), (int32)GetState(), _chunks, _asset ? _asset->GetPath() : String::Empty);
return String::Format(TEXT("Load Asset Data Task ({}, {}, {})"), (int32)GetState(), _chunks, _asset ? _asset->GetPath() : StringView::Empty);
}
bool HasReference(Object* obj) const override
{
@@ -8,6 +8,7 @@
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Core/Log.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
/// <summary>
/// Asset loading task object.
@@ -44,6 +45,7 @@ protected:
Result run() override
{
PROFILE_CPU();
PROFILE_MEM(ContentAssets);
// Keep valid ref to the asset
AssetReference<::Asset> ref = Asset.Get();

Some files were not shown because too many files have changed in this diff Show More