Merge branch 'FlaxEngine:master' into flax-msdf-font
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FlaxEditor.SceneGraph
|
||||
/// <summary>
|
||||
/// The parent node.
|
||||
/// </summary>
|
||||
protected SceneGraphNode parentNode;
|
||||
internal SceneGraphNode parentNode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children list.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user