Merge branch 'master' into PressGToGameModeAndPToNavigate
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -93,6 +93,7 @@ void MultiBlendBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
||||
void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
||||
{
|
||||
bucket.BlendPose.TransitionPosition = 0.0f;
|
||||
bucket.BlendPose.BlendPoseIndex = -1;
|
||||
bucket.BlendPose.PreviousBlendPoseIndex = -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -239,7 +239,8 @@ public:
|
||||
struct BlendPoseBucket
|
||||
{
|
||||
float TransitionPosition;
|
||||
int32 PreviousBlendPoseIndex;
|
||||
int16 BlendPoseIndex;
|
||||
int16 PreviousBlendPoseIndex;
|
||||
};
|
||||
|
||||
struct StateMachineBucket
|
||||
@@ -810,6 +811,7 @@ public:
|
||||
{
|
||||
// Copy the node transformations
|
||||
Platform::MemoryCopy(dstNodes->Nodes.Get(), srcNodes->Nodes.Get(), sizeof(Transform) * _skeletonNodesCount);
|
||||
dstNodes->RootMotion = srcNodes->RootMotion;
|
||||
|
||||
// Copy the animation playback state
|
||||
dstNodes->Position = srcNodes->Position;
|
||||
|
||||
@@ -246,11 +246,19 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
||||
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
||||
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
|
||||
if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration
|
||||
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
|
||||
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration())
|
||||
&& Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
|
||||
// Handle the edge case of an event on 0 or on max animation duration during looping
|
||||
|| (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration())
|
||||
|| (!loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::NearEqual(k.Time, anim->GetDuration()))
|
||||
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration()))
|
||||
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration()))
|
||||
|| (Math::FloorToInt(animPos) == 1 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 1.0f)
|
||||
|| (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 1 && k.Time == 1.0f)
|
||||
|| (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::NearEqual(k.Time, anim->GetDuration() - 1.0f))
|
||||
|| (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration() - 1.0f))
|
||||
|| (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 0.0f)
|
||||
)
|
||||
{
|
||||
int32 stateIndex = -1;
|
||||
@@ -676,9 +684,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
|
||||
if (!ANIM_GRAPH_IS_VALID_PTR(poseB))
|
||||
nodesB = GetEmptyNodes();
|
||||
|
||||
const Transform* srcA = nodesA->Nodes.Get();
|
||||
const Transform* srcB = nodesB->Nodes.Get();
|
||||
Transform* dst = nodes->Nodes.Get();
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
|
||||
Transform::Lerp(srcA[i], srcB[i], alpha, dst[i]);
|
||||
}
|
||||
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
nodes->Position = Math::Lerp(nodesA->Position, nodesB->Position, alpha);
|
||||
@@ -1263,21 +1274,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
const auto valueA = tryGetValue(node->GetBox(1), Value::Null);
|
||||
const auto valueB = tryGetValue(node->GetBox(2), Value::Null);
|
||||
const auto nodes = node->GetNodes(this);
|
||||
|
||||
auto nodesA = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
|
||||
auto nodesB = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
|
||||
if (!ANIM_GRAPH_IS_VALID_PTR(valueA))
|
||||
nodesA = GetEmptyNodes();
|
||||
if (!ANIM_GRAPH_IS_VALID_PTR(valueB))
|
||||
nodesB = GetEmptyNodes();
|
||||
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
|
||||
}
|
||||
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
value = nodes;
|
||||
value = Blend(node, valueA, valueB, alpha, AlphaBlendMode::Linear);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1758,35 +1755,38 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// [2]: int Pose Count
|
||||
// [3]: AlphaBlendMode Mode
|
||||
|
||||
// Prepare
|
||||
auto& bucket = context.Data->State[node->BucketIndex].BlendPose;
|
||||
const int32 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]);
|
||||
const int16 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]);
|
||||
const float blendDuration = (float)tryGetValue(node->GetBox(2), node->Values[1]);
|
||||
const int32 poseCount = Math::Clamp(node->Values[2].AsInt, 0, MaxBlendPoses);
|
||||
const AlphaBlendMode mode = (AlphaBlendMode)node->Values[3].AsInt;
|
||||
|
||||
// Skip if nothing to blend
|
||||
if (poseCount == 0 || poseIndex < 0 || poseIndex >= poseCount)
|
||||
{
|
||||
break;
|
||||
|
||||
// Check if swap transition end points
|
||||
if (bucket.PreviousBlendPoseIndex == poseIndex && bucket.BlendPoseIndex != poseIndex && bucket.TransitionPosition >= ANIM_GRAPH_BLEND_THRESHOLD)
|
||||
{
|
||||
bucket.TransitionPosition = blendDuration - bucket.TransitionPosition;
|
||||
Swap(bucket.BlendPoseIndex, bucket.PreviousBlendPoseIndex);
|
||||
}
|
||||
|
||||
// Check if transition is not active (first update, pose not changing or transition ended)
|
||||
bucket.TransitionPosition += context.DeltaTime;
|
||||
bucket.BlendPoseIndex = poseIndex;
|
||||
if (bucket.PreviousBlendPoseIndex == -1 || bucket.PreviousBlendPoseIndex == poseIndex || bucket.TransitionPosition >= blendDuration || blendDuration <= ANIM_GRAPH_BLEND_THRESHOLD)
|
||||
{
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
bucket.BlendPoseIndex = poseIndex;
|
||||
bucket.PreviousBlendPoseIndex = poseIndex;
|
||||
value = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
|
||||
value = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.BlendPoseIndex), Value::Null);
|
||||
break;
|
||||
}
|
||||
ASSERT(bucket.PreviousBlendPoseIndex >= 0 && bucket.PreviousBlendPoseIndex < poseCount);
|
||||
|
||||
// Blend two animations
|
||||
{
|
||||
const float alpha = bucket.TransitionPosition / blendDuration;
|
||||
const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null);
|
||||
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
|
||||
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.BlendPoseIndex), Value::Null);
|
||||
value = Blend(node, valueA, valueB, alpha, mode);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
@@ -167,8 +168,8 @@ void AudioSource::Play()
|
||||
}
|
||||
else
|
||||
{
|
||||
// Source was nt properly added to the Audio Backend
|
||||
LOG(Warning, "Cannot play unitialized audio source.");
|
||||
// Source was not properly added to the Audio Backend
|
||||
LOG(Warning, "Cannot play uninitialized audio source.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -401,6 +408,9 @@ void AudioSource::Update()
|
||||
_startingToPlay = false;
|
||||
}
|
||||
|
||||
if (Math::NearEqual(GetTime(), _startTime) && _isActuallyPlayingSth && _startingToPlay)
|
||||
ClipStarted();
|
||||
|
||||
if (!UseStreaming() && Math::NearEqual(GetTime(), 0.0f) && _isActuallyPlayingSth && !_startingToPlay)
|
||||
{
|
||||
int32 queuedBuffers;
|
||||
@@ -416,6 +426,7 @@ void AudioSource::Update()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
ClipFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +497,7 @@ void AudioSource::Update()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
ClipFinished();
|
||||
}
|
||||
ASSERT(_streamingFirstChunk < clip->Buffers.Count());
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -76,6 +76,16 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Audio Source\")")
|
||||
AssetReference<AudioClip> Clip;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the audio clip starts.
|
||||
/// </summary>
|
||||
API_EVENT() Action ClipStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the audio clip finishes.
|
||||
/// </summary>
|
||||
API_EVENT() Action ClipFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the velocity of the source. Determines pitch in relation to AudioListener's position. Only relevant for spatial (3D) sources.
|
||||
/// </summary>
|
||||
@@ -293,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();
|
||||
|
||||
+129
-71
@@ -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();
|
||||
@@ -614,7 +666,7 @@ void Asset::onLoaded()
|
||||
{
|
||||
onLoaded_MainThread();
|
||||
}
|
||||
else if (OnLoaded.IsBinded())
|
||||
else if (OnLoaded.IsBinded() || _references.HasItems())
|
||||
{
|
||||
Function<void()> action;
|
||||
action.Bind<Asset, &Asset::onLoaded>(this);
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -90,9 +90,11 @@ void MaterialInstance::OnBaseParamsChanged()
|
||||
// Get the newest parameters
|
||||
baseParams->Clone(Params);
|
||||
|
||||
#if 0
|
||||
// Override all public parameters by default
|
||||
for (auto& param : Params)
|
||||
param.SetIsOverride(param.IsPublic());
|
||||
#endif
|
||||
|
||||
// Copy previous parameters values
|
||||
for (int32 i = 0; i < oldParams.Count(); i++)
|
||||
@@ -216,10 +218,14 @@ Asset::LoadResult MaterialInstance::load()
|
||||
Guid baseMaterialId;
|
||||
headerStream.Read(baseMaterialId);
|
||||
auto baseMaterial = Content::LoadAsync<MaterialBase>(baseMaterialId);
|
||||
if (baseMaterial)
|
||||
baseMaterial->AddReference();
|
||||
|
||||
// Load parameters
|
||||
if (Params.Load(&headerStream))
|
||||
{
|
||||
if (baseMaterial)
|
||||
baseMaterial->RemoveReference();
|
||||
LOG(Warning, "Cannot load material parameters.");
|
||||
return LoadResult::CannotLoadData;
|
||||
}
|
||||
@@ -237,6 +243,8 @@ Asset::LoadResult MaterialInstance::load()
|
||||
ParamsChanged();
|
||||
}
|
||||
|
||||
if (baseMaterial)
|
||||
baseMaterial->RemoveReference();
|
||||
return LoadResult::Ok;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -258,6 +262,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, f
|
||||
LOG(Warning, "Cannot generate SDF for virtual models on a main thread.");
|
||||
return true;
|
||||
}
|
||||
auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData();
|
||||
lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1);
|
||||
|
||||
// Generate SDF
|
||||
@@ -304,6 +309,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 +349,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 +516,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 +592,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 +623,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,13 +54,14 @@ public:
|
||||
AssetReference<ModelBase> model = _model.Get();
|
||||
if (model == nullptr)
|
||||
return true;
|
||||
PROFILE_MEM(GraphicsMeshes);
|
||||
|
||||
// Get data
|
||||
BytesContainer data;
|
||||
model->GetLODData(_lodIndex, data);
|
||||
if (data.IsInvalid())
|
||||
{
|
||||
LOG(Warning, "Missing data chunk");
|
||||
LOG(Warning, "Missing data chunk with LOD{} for model '{}'", _lodIndex, model->ToString());
|
||||
return true;
|
||||
}
|
||||
MemoryReadStream stream(data.Get(), data.Length());
|
||||
@@ -230,6 +234,7 @@ bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||
return true;
|
||||
}
|
||||
auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData();
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
@@ -334,6 +339,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 +668,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 +717,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;
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
/// <param name="id">The asset id.</param>
|
||||
/// <returns>Loaded asset of null.</returns>
|
||||
template<typename T>
|
||||
T* LoadAsync(const Guid& id)
|
||||
T* Load(const Guid& id)
|
||||
{
|
||||
for (auto& e : *this)
|
||||
{
|
||||
@@ -26,8 +26,10 @@ public:
|
||||
return (T*)e.Get();
|
||||
}
|
||||
auto asset = (T*)::LoadAsset(id, T::TypeInitializer);
|
||||
if (asset)
|
||||
if (asset && !asset->WaitForLoaded())
|
||||
Add(asset);
|
||||
else
|
||||
asset = nullptr;
|
||||
return asset;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 +28,9 @@
|
||||
#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/Internal/InternalCalls.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#if USE_EDITOR
|
||||
#include "Editor/Editor.h"
|
||||
@@ -117,6 +119,8 @@ ContentService ContentServiceInstance;
|
||||
|
||||
bool ContentService::Init()
|
||||
{
|
||||
PROFILE_MEM(Content);
|
||||
|
||||
// Load assets registry
|
||||
Cache.Init();
|
||||
|
||||
@@ -126,17 +130,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 +163,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 +329,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,21 +340,28 @@ 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;
|
||||
|
||||
MONO_THREAD_INFO_TYPE* monoThreadInfo = nullptr;
|
||||
while (Platform::AtomicRead(&_exitFlag) == 0)
|
||||
{
|
||||
if (LoadTasks.try_dequeue(task))
|
||||
{
|
||||
Run(task);
|
||||
MONO_THREAD_INFO_GET(monoThreadInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
MONO_ENTER_GC_SAFE_WITH_INFO(monoThreadInfo);
|
||||
LoadTasksMutex.Lock();
|
||||
LoadTasksSignal.Wait(LoadTasksMutex);
|
||||
LoadTasksMutex.Unlock();
|
||||
MONO_EXIT_GC_SAFE_WITH_INFO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,6 +429,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 +479,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 +526,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 +608,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 +651,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 +705,7 @@ Asset* Content::GetAsset(const StringView& outputPath)
|
||||
{
|
||||
if (outputPath.IsEmpty())
|
||||
return nullptr;
|
||||
|
||||
PROFILE_CPU();
|
||||
ScopeLock lock(AssetsLocker);
|
||||
for (auto i = Assets.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
@@ -737,6 +755,7 @@ void Content::DeleteAsset(const StringView& path)
|
||||
return;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
ScopeLock locker(AssetsLocker);
|
||||
|
||||
// Remove from registry
|
||||
@@ -753,6 +772,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 +811,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 +1014,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 +1060,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 +1083,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 +1094,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 +1118,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 +1149,8 @@ void Content::WaitForTask(ContentLoadTask* loadingTask, double timeoutInMillisec
|
||||
localQueue.Clear();
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Inline");
|
||||
ZoneColor(0xffaaaaaa);
|
||||
thread->Run(tmp);
|
||||
}
|
||||
else
|
||||
@@ -1209,6 +1263,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 +1332,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();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/// <summary>
|
||||
/// The asset soft reference. Asset gets referenced (loaded) on actual use (ID reference is resolving it).
|
||||
/// </summary>
|
||||
class FLAXENGINE_API SoftAssetReferenceBase
|
||||
class FLAXENGINE_API SoftAssetReferenceBase : public IAssetReference
|
||||
{
|
||||
protected:
|
||||
Asset* _asset = nullptr;
|
||||
@@ -46,11 +46,16 @@ 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 OnSet(const Guid& id);
|
||||
void OnResolve(const ScriptingTypeHandle& type);
|
||||
void OnUnloaded(Asset* asset);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -71,6 +76,13 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SoftAssetReference"/> class.
|
||||
/// </summary>
|
||||
explicit SoftAssetReference(decltype(__nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SoftAssetReference"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -15,14 +15,9 @@
|
||||
namespace
|
||||
{
|
||||
CriticalSection Locker;
|
||||
#if USE_EDITOR
|
||||
Array<FlaxFile*> Files(1024);
|
||||
Array<FlaxPackage*> Packages;
|
||||
#else
|
||||
Array<FlaxFile*> Files;
|
||||
Array<FlaxPackage*> Packages(64);
|
||||
#endif
|
||||
Dictionary<String, FlaxStorage*> StorageMap(2048);
|
||||
Array<FlaxPackage*> Packages;
|
||||
Dictionary<String, FlaxStorage*> StorageMap;
|
||||
}
|
||||
|
||||
class ContentStorageService : public EngineService
|
||||
@@ -231,6 +226,12 @@ void ContentStorageManager::GetStorage(Array<FlaxStorage*>& result)
|
||||
|
||||
bool ContentStorageService::Init()
|
||||
{
|
||||
#if USE_EDITOR
|
||||
Files.EnsureCapacity(1024);
|
||||
#else
|
||||
Packages.EnsureCapacity(64);
|
||||
#endif
|
||||
StorageMap.EnsureCapacity(2048);
|
||||
System = New<ContentStorageSystem>();
|
||||
Engine::UpdateGraph->AddSystem(System);
|
||||
return false;
|
||||
|
||||
@@ -87,6 +87,10 @@ public:
|
||||
/// </summary>
|
||||
double LastAccessTime = 0.0;
|
||||
|
||||
/// <summary>
|
||||
/// Flag set to indicate that chunk is during loading (atomic access to sync multiple reading threads).
|
||||
/// </summary>
|
||||
int64 IsLoading = 0;
|
||||
/// <summary>
|
||||
/// The chunk data.
|
||||
/// </summary>
|
||||
@@ -146,7 +150,7 @@ public:
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsLoaded() const
|
||||
{
|
||||
return Data.IsValid();
|
||||
return Data.IsValid() && Platform::AtomicRead(&IsLoading) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,7 +158,7 @@ public:
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsMissing() const
|
||||
{
|
||||
return Data.IsInvalid();
|
||||
return !IsLoaded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -182,10 +186,5 @@ public:
|
||||
/// Clones this chunk data (doesn't copy location in file).
|
||||
/// </summary>
|
||||
/// <returns>The cloned chunk.</returns>
|
||||
FlaxChunk* Clone() const
|
||||
{
|
||||
auto chunk = New<FlaxChunk>();
|
||||
chunk->Data.Copy(Data);
|
||||
return chunk;
|
||||
}
|
||||
FlaxChunk* Clone() const;
|
||||
};
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
#include "FlaxPackage.h"
|
||||
#include "ContentStorageManager.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/ScopeExit.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Serialization/FileWriteStream.h"
|
||||
#include "Engine/Content/Asset.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
@@ -63,9 +65,15 @@ void FlaxChunk::RegisterUsage()
|
||||
LastAccessTime = Platform::GetTimeSeconds();
|
||||
}
|
||||
|
||||
const int32 FlaxStorage::MagicCode = 1180124739;
|
||||
FlaxChunk* FlaxChunk::Clone() const
|
||||
{
|
||||
PROFILE_MEM(ContentFiles);
|
||||
auto chunk = New<FlaxChunk>();
|
||||
chunk->Data.Copy(Data);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
FlaxStorage::LockData FlaxStorage::LockData::Invalid(nullptr);
|
||||
const int32 FlaxStorage::MagicCode = 1180124739;
|
||||
|
||||
struct Header
|
||||
{
|
||||
@@ -237,6 +245,7 @@ FlaxStorage::~FlaxStorage()
|
||||
ASSERT(IsDisposed());
|
||||
CHECK(_chunksLock == 0);
|
||||
CHECK(_refCount == 0);
|
||||
CHECK(_isUnloadingData == 0);
|
||||
ASSERT(_chunks.IsEmpty());
|
||||
|
||||
#if USE_EDITOR
|
||||
@@ -252,6 +261,22 @@ FlaxStorage::~FlaxStorage()
|
||||
#endif
|
||||
}
|
||||
|
||||
void FlaxStorage::LockChunks()
|
||||
{
|
||||
RETRY:
|
||||
Platform::InterlockedIncrement(&_chunksLock);
|
||||
if (Platform::AtomicRead(&_isUnloadingData) != 0)
|
||||
{
|
||||
// Someone else is closing file handles or freeing chunks so wait for it to finish and retry
|
||||
Platform::InterlockedDecrement(&_chunksLock);
|
||||
do
|
||||
{
|
||||
Platform::Sleep(1);
|
||||
} while (Platform::AtomicRead(&_isUnloadingData) != 0);
|
||||
goto RETRY;
|
||||
}
|
||||
}
|
||||
|
||||
FlaxStorage::LockData FlaxStorage::LockSafe()
|
||||
{
|
||||
auto lock = LockData(this);
|
||||
@@ -281,19 +306,12 @@ uint32 FlaxStorage::GetMemoryUsage() const
|
||||
|
||||
bool FlaxStorage::Load()
|
||||
{
|
||||
// Check if was already loaded
|
||||
if (IsLoaded())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent loading by more than one thread
|
||||
PROFILE_MEM(ContentFiles);
|
||||
ScopeLock lock(_loadLocker);
|
||||
if (IsLoaded())
|
||||
{
|
||||
// Other thread loaded it
|
||||
return false;
|
||||
}
|
||||
ASSERT(GetEntriesCount() == 0);
|
||||
|
||||
// Open file
|
||||
@@ -687,16 +705,19 @@ bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load header
|
||||
return LoadAssetHeader(e, data);
|
||||
}
|
||||
|
||||
bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
|
||||
{
|
||||
PROFILE_MEM(ContentFiles);
|
||||
ASSERT(IsLoaded());
|
||||
ASSERT(chunk != nullptr && _chunks.Contains(chunk));
|
||||
|
||||
// Check if already loaded
|
||||
// Protect against loading the same chunk from multiple threads at once
|
||||
while (Platform::InterlockedCompareExchange(&chunk->IsLoading, 1, 0) != 0)
|
||||
Platform::Sleep(1);
|
||||
SCOPE_EXIT{ Platform::AtomicStore(&chunk->IsLoading, 0); };
|
||||
if (chunk->IsLoaded())
|
||||
return false;
|
||||
|
||||
@@ -773,12 +794,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
|
||||
// Raw data
|
||||
chunk->Data.Read(stream, size);
|
||||
}
|
||||
ASSERT(chunk->IsLoaded());
|
||||
chunk->RegisterUsage();
|
||||
}
|
||||
|
||||
UnlockChunks();
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
@@ -866,6 +885,7 @@ FlaxChunk* FlaxStorage::AllocateChunk()
|
||||
{
|
||||
if (AllowDataModifications())
|
||||
{
|
||||
PROFILE_MEM(ContentFiles);
|
||||
auto chunk = New<FlaxChunk>();
|
||||
_chunks.Add(chunk);
|
||||
return chunk;
|
||||
@@ -1125,6 +1145,7 @@ bool FlaxStorage::Save(const AssetInitData& data, bool silentMode)
|
||||
|
||||
bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data)
|
||||
{
|
||||
PROFILE_MEM(ContentFiles);
|
||||
ASSERT(IsLoaded());
|
||||
|
||||
auto lock = Lock();
|
||||
@@ -1396,6 +1417,8 @@ FileReadStream* FlaxStorage::OpenFile()
|
||||
auto& stream = _file.Get();
|
||||
if (stream == nullptr)
|
||||
{
|
||||
PROFILE_MEM(ContentFiles);
|
||||
|
||||
// Open file
|
||||
auto file = File::Open(_path, FileMode::OpenExisting, FileAccess::Read, FileShare::Read);
|
||||
if (file == nullptr)
|
||||
@@ -1413,11 +1436,14 @@ FileReadStream* FlaxStorage::OpenFile()
|
||||
|
||||
bool FlaxStorage::CloseFileHandles()
|
||||
{
|
||||
// Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
|
||||
Platform::InterlockedIncrement(&_isUnloadingData);
|
||||
SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
|
||||
|
||||
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false; // Early out when no files are opened
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(ContentFiles);
|
||||
|
||||
// Note: this is usually called by the content manager when this file is not used or on exit
|
||||
// In those situations all the async tasks using this storage should be cancelled externally
|
||||
@@ -1488,9 +1514,21 @@ void FlaxStorage::Tick(double time)
|
||||
{
|
||||
auto chunk = _chunks.Get()[i];
|
||||
const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime;
|
||||
if (!wasUsed && chunk->IsLoaded() && EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory))
|
||||
if (!wasUsed &&
|
||||
chunk->IsLoaded() &&
|
||||
EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory) &&
|
||||
Platform::AtomicRead(&chunk->IsLoading) == 0)
|
||||
{
|
||||
// Guard the unloading so if other thread wants to lock the chunks will need to wait for this to end
|
||||
Platform::InterlockedIncrement(&_isUnloadingData);
|
||||
if (Platform::AtomicRead(&_chunksLock) != 0 || Platform::AtomicRead(&chunk->IsLoading) != 0)
|
||||
{
|
||||
// Someone started loading so skip ticking
|
||||
Platform::InterlockedDecrement(&_isUnloadingData);
|
||||
return;
|
||||
}
|
||||
chunk->Unload();
|
||||
Platform::InterlockedDecrement(&_isUnloadingData);
|
||||
}
|
||||
wasAnyUsed |= wasUsed;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ protected:
|
||||
int64 _refCount = 0;
|
||||
int64 _chunksLock = 0;
|
||||
int64 _files = 0;
|
||||
int64 _isUnloadingData = 0;
|
||||
double _lastRefLostTime;
|
||||
CriticalSection _loadLocker;
|
||||
|
||||
@@ -129,10 +130,7 @@ public:
|
||||
/// <summary>
|
||||
/// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked.
|
||||
/// </summary>
|
||||
FORCE_INLINE void LockChunks()
|
||||
{
|
||||
Platform::InterlockedIncrement(&_chunksLock);
|
||||
}
|
||||
void LockChunks();
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks the storage chunks data.
|
||||
@@ -148,7 +146,6 @@ public:
|
||||
struct LockData
|
||||
{
|
||||
friend FlaxStorage;
|
||||
static LockData Invalid;
|
||||
|
||||
private:
|
||||
FlaxStorage* _storage;
|
||||
@@ -161,6 +158,11 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
LockData()
|
||||
: _storage(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
LockData(const LockData& other)
|
||||
: _storage(other._storage)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,11 @@ private:
|
||||
FlaxStorage* _storage;
|
||||
|
||||
public:
|
||||
FlaxStorageReference()
|
||||
: _storage(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
FlaxStorageReference(FlaxStorage* storage)
|
||||
: _storage(storage)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/// <summary>
|
||||
/// Asset reference utility that doesn't add reference to that asset. Handles asset unload event.
|
||||
/// </summary>
|
||||
API_CLASS(InBuild) class WeakAssetReferenceBase
|
||||
API_CLASS(InBuild) class WeakAssetReferenceBase : public IAssetReference
|
||||
{
|
||||
public:
|
||||
typedef Delegate<> EventType;
|
||||
@@ -56,9 +56,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 OnUnloaded(Asset* asset);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -72,7 +77,13 @@ public:
|
||||
/// Initializes a new instance of the <see cref="WeakAssetReference"/> class.
|
||||
/// </summary>
|
||||
WeakAssetReference()
|
||||
: WeakAssetReferenceBase()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WeakAssetReference"/> class.
|
||||
/// </summary>
|
||||
explicit WeakAssetReference(decltype(__nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -81,7 +92,6 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="asset">The asset to set.</param>
|
||||
WeakAssetReference(T* asset)
|
||||
: WeakAssetReferenceBase()
|
||||
{
|
||||
OnSet(asset);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "ImportTexture.h"
|
||||
#include "ImportModel.h"
|
||||
@@ -151,6 +152,7 @@ bool CreateAssetContext::AllocateChunk(int32 index)
|
||||
}
|
||||
|
||||
// Create new chunk
|
||||
PROFILE_MEM(ContentFiles);
|
||||
Data.Header.Chunks[index] = New<FlaxChunk>();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,14 +93,15 @@ namespace
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void AddInput(MaterialLayer* layer, Meta11 meta, MaterialGraphBoxes box, const Guid& texture, const T& value, const T& defaultValue, const Float2& pos, ShaderGraphNode<>** outTextureNode = nullptr)
|
||||
void AddInput(MaterialLayer* layer, Meta11 meta, MaterialGraphBoxes box, const Guid& texture, const T& value, const T& defaultValue, const Float2& pos, ShaderGraphNode<>** outTextureNode = nullptr, uint8 channel = MAX_uint8)
|
||||
{
|
||||
auto textureNode = AddTextureNode(layer, texture);
|
||||
auto valueNode = AddValueNode<T>(layer, value, defaultValue);
|
||||
auto textureNodeBox = channel == MAX_uint8 ? 1 : channel + 2; // Color or specific channel (RGBA)
|
||||
if (textureNode && valueNode)
|
||||
{
|
||||
auto diffuseMultiply = AddMultiplyNode(layer);
|
||||
CONNECT(diffuseMultiply->Boxes[0], textureNode->Boxes[1]);
|
||||
CONNECT(diffuseMultiply->Boxes[0], textureNode->Boxes[textureNodeBox]);
|
||||
CONNECT(diffuseMultiply->Boxes[1], valueNode->Boxes[0]);
|
||||
CONNECT(layer->Root->Boxes[static_cast<int32>(box)], diffuseMultiply->Boxes[2]);
|
||||
SET_POS(valueNode, pos + Float2(-467.7404, 91.41332));
|
||||
@@ -109,7 +110,7 @@ namespace
|
||||
}
|
||||
else if (textureNode)
|
||||
{
|
||||
CONNECT(layer->Root->Boxes[static_cast<int32>(box)], textureNode->Boxes[1]);
|
||||
CONNECT(layer->Root->Boxes[static_cast<int32>(box)], textureNode->Boxes[textureNodeBox]);
|
||||
SET_POS(textureNode, pos + Float2(-293.5272f, -2.926111f));
|
||||
}
|
||||
else if (valueNode)
|
||||
@@ -178,8 +179,9 @@ CreateAssetResult CreateMaterial::Create(CreateAssetContext& context)
|
||||
// Opacity
|
||||
AddInput(layer, meta, MaterialGraphBoxes::Opacity, options.Opacity.Texture, options.Opacity.Value, 1.0f, Float2(0, 400));
|
||||
|
||||
// Opacity
|
||||
AddInput(layer, meta, MaterialGraphBoxes::Roughness, options.Roughness.Texture, options.Roughness.Value, 0.5f, Float2(200, 400));
|
||||
// Roughness + Metalness
|
||||
AddInput(layer, meta, MaterialGraphBoxes::Roughness, options.Roughness.Texture, options.Roughness.Value, 0.5f, Float2(200, 400), nullptr, options.Roughness.Channel);
|
||||
AddInput(layer, meta, MaterialGraphBoxes::Metalness, options.Metalness.Texture, options.Metalness.Value, 0.0f, Float2(200, 600), nullptr, options.Metalness.Channel);
|
||||
|
||||
// Normal
|
||||
auto normalMap = AddTextureNode(layer, options.Normals.Texture, true);
|
||||
|
||||
@@ -40,9 +40,17 @@ public:
|
||||
struct
|
||||
{
|
||||
float Value = 0.5f;
|
||||
uint8 Channel = 0;
|
||||
Guid Texture = Guid::Empty;
|
||||
} Roughness;
|
||||
|
||||
struct
|
||||
{
|
||||
float Value = 0.0f;
|
||||
uint8 Channel = 0;
|
||||
Guid Texture = Guid::Empty;
|
||||
} Metalness;
|
||||
|
||||
struct
|
||||
{
|
||||
Guid Texture = Guid::Empty;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "Engine/Utilities/RectPack.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Editor/Utilities/EditorUtilities.h"
|
||||
#include "AssetsImportingManager.h"
|
||||
|
||||
bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
|
||||
@@ -281,13 +282,19 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
|
||||
// Import all of the objects recursive but use current model data to skip loading file again
|
||||
options.Cached = &cached;
|
||||
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)> splitImport = [&context, &autoImportOutput](Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)
|
||||
HashSet<String> objectNames;
|
||||
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)> splitImport = [&context, &autoImportOutput, &objectNames](Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)
|
||||
{
|
||||
// Recursive importing of the split object
|
||||
String postFix = objectName;
|
||||
const int32 splitPos = postFix.FindLast(TEXT('|'));
|
||||
if (splitPos != -1)
|
||||
if (splitPos != -1 && splitPos + 1 < postFix.Length())
|
||||
postFix = postFix.Substring(splitPos + 1);
|
||||
EditorUtilities::ValidatePathChars(postFix); // Ensure name is valid path
|
||||
int32 duplicate = 0;
|
||||
String postFixPre = postFix;
|
||||
while (!objectNames.Add(postFix)) // Ensure name is unique
|
||||
postFix = String::Format(TEXT("{} {}"), postFixPre, duplicate++);
|
||||
// TODO: check for name collisions with material/texture assets
|
||||
outputPath = autoImportOutput / String(StringUtils::GetFileNameWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
|
||||
splitOptions.SubAssetFolder = TEXT(" "); // Use the same folder as asset as they all are imported to the subdir for the prefab (see SubAssetFolder usage above)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "Cache.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback> Cache::ISerializeModifier;
|
||||
Cache::ISerializeModifierCache Cache::ISerializeModifier;
|
||||
|
||||
void Cache::ISerializeModifierClearCallback(::ISerializeModifier* obj)
|
||||
{
|
||||
|
||||
@@ -15,11 +15,12 @@ public:
|
||||
static void ISerializeModifierClearCallback(ISerializeModifier* obj);
|
||||
|
||||
public:
|
||||
typedef CollectionPoolCache<ISerializeModifier, ISerializeModifierClearCallback> ISerializeModifierCache;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ISerializeModifier lookup cache. Safe allocation, per thread, uses caching.
|
||||
/// </summary>
|
||||
static CollectionPoolCache<ISerializeModifier, ISerializeModifierClearCallback> ISerializeModifier;
|
||||
static ISerializeModifierCache ISerializeModifier;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ API_CLASS(InBuild) class Array
|
||||
public:
|
||||
using ItemType = T;
|
||||
using AllocationData = typename AllocationType::template Data<T>;
|
||||
using AllocationTag = typename AllocationType::Tag;
|
||||
|
||||
private:
|
||||
int32 _count;
|
||||
@@ -36,6 +37,17 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an empty <see cref="Array"/> without reserving any space.
|
||||
/// </summary>
|
||||
/// <param name="tag">The custom allocation tag.</param>
|
||||
Array(AllocationTag tag)
|
||||
: _count(0)
|
||||
, _capacity(0)
|
||||
, _allocation(tag)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes <see cref="Array"/> by reserving space.
|
||||
/// </summary>
|
||||
@@ -647,11 +659,8 @@ public:
|
||||
T* data = _allocation.Get();
|
||||
if (index < _count)
|
||||
{
|
||||
T* dst = data + index;
|
||||
T* src = data + (index + 1);
|
||||
const int32 count = _count - index;
|
||||
for (int32 i = 0; i < count; ++i)
|
||||
dst[i] = MoveTemp(src[i]);
|
||||
for (int32 i = index; i < _count; i++)
|
||||
data[i] = MoveTemp(data[i + 1]);
|
||||
}
|
||||
Memory::DestructItems(data + _count, 1);
|
||||
}
|
||||
|
||||
@@ -163,6 +163,15 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an empty <see cref="Dictionary"/> without reserving any space.
|
||||
/// </summary>
|
||||
/// <param name="tag">The custom allocation tag.</param>
|
||||
Dictionary(typename Base::AllocationTag tag)
|
||||
: Base(tag)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes <see cref="Dictionary"/> by reserving space.
|
||||
/// </summary>
|
||||
@@ -549,7 +558,7 @@ public:
|
||||
/// Removes element with a specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The element key to remove.</param>
|
||||
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
|
||||
/// <returns>True if item was removed from collection, otherwise false.</returns>
|
||||
template<typename KeyComparableType>
|
||||
bool Remove(const KeyComparableType& key)
|
||||
{
|
||||
@@ -569,7 +578,7 @@ public:
|
||||
/// Removes element at specified iterator.
|
||||
/// </summary>
|
||||
/// <param name="i">The element iterator to remove.</param>
|
||||
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
|
||||
/// <returns>True if item was removed from collection, otherwise false.</returns>
|
||||
bool Remove(const Iterator& i)
|
||||
{
|
||||
ASSERT(i._collection == this);
|
||||
|
||||
@@ -140,6 +140,15 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an empty <see cref="HashSet"/> without reserving any space.
|
||||
/// </summary>
|
||||
/// <param name="tag">The custom allocation tag.</param>
|
||||
HashSet(typename Base::AllocationTag tag)
|
||||
: Base(tag)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes <see cref="HashSet"/> by reserving space.
|
||||
/// </summary>
|
||||
@@ -442,7 +451,7 @@ public:
|
||||
/// Removes the specified element from the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The element to remove.</param>
|
||||
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
|
||||
/// <returns>True if item was removed from collection, otherwise false.</returns>
|
||||
template<typename ItemType>
|
||||
bool Remove(const ItemType& item)
|
||||
{
|
||||
@@ -462,7 +471,7 @@ public:
|
||||
/// Removes an element at specified iterator position.
|
||||
/// </summary>
|
||||
/// <param name="i">The element iterator to remove.</param>
|
||||
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
|
||||
/// <returns>True if item was removed from collection, otherwise false.</returns>
|
||||
bool Remove(const Iterator& i)
|
||||
{
|
||||
ASSERT(i._collection == this);
|
||||
|
||||
@@ -59,6 +59,7 @@ class HashSetBase
|
||||
public:
|
||||
// Type of allocation data used to store hash set buckets.
|
||||
using AllocationData = typename AllocationType::template Data<BucketType>;
|
||||
using AllocationTag = typename AllocationType::Tag;
|
||||
|
||||
protected:
|
||||
int32 _elementsCount = 0;
|
||||
@@ -70,6 +71,11 @@ protected:
|
||||
{
|
||||
}
|
||||
|
||||
HashSetBase(AllocationTag tag)
|
||||
: _allocation(tag)
|
||||
{
|
||||
}
|
||||
|
||||
void MoveToEmpty(HashSetBase&& other)
|
||||
{
|
||||
_elementsCount = other._elementsCount;
|
||||
|
||||
@@ -362,9 +362,10 @@ public:
|
||||
uint32 histogram[RADIXSORT_HISTOGRAM_SIZE];
|
||||
uint16 shift = 0;
|
||||
int32 pass = 0;
|
||||
for (; pass < 6; pass++)
|
||||
constexpr int32 passCount = sizeof(T) >= sizeof(uint64) ? 6 : 3;
|
||||
for (; pass < passCount; pass++)
|
||||
{
|
||||
Platform::MemoryClear(histogram, sizeof(uint32) * RADIXSORT_HISTOGRAM_SIZE);
|
||||
Platform::MemoryClear(histogram, sizeof(histogram));
|
||||
|
||||
bool sorted = true;
|
||||
T key = keys[0];
|
||||
@@ -372,16 +373,14 @@ public:
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
key = keys[i];
|
||||
const uint16 index = (key >> shift) & RADIXSORT_BIT_MASK;
|
||||
const uint16 index = (key >> (T)shift) & RADIXSORT_BIT_MASK;
|
||||
++histogram[index];
|
||||
sorted &= prevKey <= key;
|
||||
prevKey = key;
|
||||
}
|
||||
|
||||
if (sorted)
|
||||
{
|
||||
goto end;
|
||||
}
|
||||
|
||||
uint32 offset = 0;
|
||||
for (int32 i = 0; i < RADIXSORT_HISTOGRAM_SIZE; ++i)
|
||||
@@ -394,7 +393,7 @@ public:
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
const T k = keys[i];
|
||||
const uint16 index = (k >> shift) & RADIXSORT_BIT_MASK;
|
||||
const uint16 index = (k >> (T)shift) & RADIXSORT_BIT_MASK;
|
||||
const uint32 dest = histogram[index]++;
|
||||
tempKeys[dest] = k;
|
||||
tempValues[dest] = values[i];
|
||||
|
||||
@@ -30,13 +30,17 @@
|
||||
#endif
|
||||
|
||||
// Enable logging service (saving log to file, can be disabled using -nolog command line)
|
||||
#ifndef LOG_ENABLE
|
||||
#define LOG_ENABLE 1
|
||||
#endif
|
||||
|
||||
// Enable crash reporting service (stack trace and crash dump collecting)
|
||||
#define CRASH_LOG_ENABLE (!BUILD_RELEASE)
|
||||
|
||||
// Enable/disable assertion
|
||||
#ifndef ENABLE_ASSERTION
|
||||
#define ENABLE_ASSERTION (!BUILD_RELEASE)
|
||||
#endif
|
||||
|
||||
// Enable/disable assertion for Engine low layers
|
||||
#define ENABLE_ASSERTION_LOW_LAYERS ENABLE_ASSERTION && (BUILD_DEBUG || FLAX_TESTS)
|
||||
|
||||
@@ -89,6 +89,12 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")")
|
||||
bool SkipDefaultFonts = false;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum acceptable mesh vertex position error (in world units) for data quantization. Use 0 to disable this feature. Affects meshes during import (or reimpport).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2200), EditorDisplay(\"Content\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
float MaxMeshPositionError = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
|
||||
/// </summary>
|
||||
|
||||
@@ -221,6 +221,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
|
||||
ProductName = JsonTools::GetString(stream, "ProductName");
|
||||
CompanyName = JsonTools::GetString(stream, "CompanyName");
|
||||
CopyrightNotice = JsonTools::GetString(stream, "CopyrightNotice");
|
||||
Version = JsonTools::GetString(stream, "Version");
|
||||
Icon = JsonTools::GetGuid(stream, "Icon");
|
||||
FirstScene = JsonTools::GetGuid(stream, "FirstScene");
|
||||
NoSplashScreen = JsonTools::GetBool(stream, "NoSplashScreen", NoSplashScreen);
|
||||
|
||||
@@ -33,6 +33,12 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(15), EditorDisplay(\"General\")")
|
||||
String CopyrightNotice;
|
||||
|
||||
/// <summary>
|
||||
/// The game version number. Usually in format: MAJOR.MINOR.BUILD.REVISION
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"General\")")
|
||||
String Version;
|
||||
|
||||
/// <summary>
|
||||
/// The default application icon.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Core/Collections/HashSet.h"
|
||||
#endif
|
||||
#if COMPILE_WITH_PROFILER
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The function object that supports binding static, member and lambda functions.
|
||||
@@ -59,7 +62,7 @@ private:
|
||||
struct Lambda
|
||||
{
|
||||
int64 Refs;
|
||||
void (*Dtor)(void*);
|
||||
void (*Dtor)(void* callee, Lambda* lambda);
|
||||
};
|
||||
|
||||
void* _callee;
|
||||
@@ -75,8 +78,7 @@ private:
|
||||
{
|
||||
if (Platform::InterlockedDecrement(&_lambda->Refs) == 0)
|
||||
{
|
||||
((Lambda*)_lambda)->Dtor(_callee);
|
||||
Allocator::Free(_lambda);
|
||||
_lambda->Dtor(_callee, _lambda);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,9 +188,10 @@ public:
|
||||
LambdaDtor();
|
||||
_lambda = (Lambda*)Allocator::Allocate(sizeof(Lambda) + sizeof(T));
|
||||
_lambda->Refs = 1;
|
||||
_lambda->Dtor = [](void* callee) -> void
|
||||
_lambda->Dtor = [](void* callee, Lambda* lambda) -> void
|
||||
{
|
||||
static_cast<T*>(callee)->~T();
|
||||
Allocator::Free(lambda);
|
||||
};
|
||||
_function = [](void* callee, Params... params) -> ReturnType
|
||||
{
|
||||
@@ -198,6 +201,45 @@ public:
|
||||
new(_callee) T(lambda);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a lambda with a custom memory allocator.
|
||||
/// </summary>
|
||||
/// <param name="tag">The custom allocation tag.</param>
|
||||
/// <param name="lambda">The lambda.</param>
|
||||
template<typename AllocationType, typename T>
|
||||
void Bind(typename AllocationType::Tag tag, const T& lambda)
|
||||
{
|
||||
if (_lambda)
|
||||
LambdaDtor();
|
||||
using AllocationData = typename AllocationType::template Data<byte>;
|
||||
static_assert(AllocationType::HasSwap, "Function lambda binding supports only custom allocators with swap operation.");
|
||||
|
||||
// Allocate lambda (using temp data)
|
||||
AllocationData tempAlloc(tag);
|
||||
tempAlloc.Allocate(sizeof(Lambda) + sizeof(AllocationData) + sizeof(T));
|
||||
|
||||
// Move temp data into the one allocated
|
||||
AllocationData* dataAlloc = (AllocationData*)(tempAlloc.Get() + sizeof(Lambda));
|
||||
new(dataAlloc) AllocationData();
|
||||
dataAlloc->Swap(tempAlloc);
|
||||
|
||||
// Initialize lambda
|
||||
_lambda = (Lambda*)dataAlloc->Get();
|
||||
_lambda->Refs = 1;
|
||||
_lambda->Dtor = [](void* callee, Lambda* lambda) -> void
|
||||
{
|
||||
static_cast<T*>(callee)->~T();
|
||||
AllocationData* dataAlloc = (AllocationData*)((byte*)lambda + sizeof(Lambda));
|
||||
dataAlloc->Free();
|
||||
};
|
||||
_function = [](void* callee, Params... params) -> ReturnType
|
||||
{
|
||||
return (*static_cast<T*>(callee))(Forward<Params>(params)...);
|
||||
};
|
||||
_callee = (byte*)_lambda + sizeof(AllocationData) + sizeof(Lambda);
|
||||
new(_callee) T(lambda);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unbinds the function.
|
||||
/// </summary>
|
||||
@@ -457,6 +499,9 @@ public:
|
||||
/// <param name="f">The function to bind.</param>
|
||||
void Bind(const FunctionType& f)
|
||||
{
|
||||
#if COMPILE_WITH_PROFILER
|
||||
PROFILE_MEM(EngineDelegate);
|
||||
#endif
|
||||
#if DELEGATE_USE_ATOMIC
|
||||
const intptr size = Platform::AtomicRead(&_size);
|
||||
FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Log.h"
|
||||
#if LOG_ENABLE
|
||||
#include "Engine/Engine/CommandLine.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
@@ -8,6 +9,7 @@
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Serialization/FileWriteStream.h"
|
||||
#include "Engine/Debug/Exceptions/Exceptions.h"
|
||||
#if USE_EDITOR
|
||||
@@ -42,6 +44,7 @@ bool Log::Logger::Init()
|
||||
// Skip if disabled
|
||||
if (!IsLogEnabled())
|
||||
return false;
|
||||
PROFILE_MEM(Engine);
|
||||
|
||||
// Create logs directory (if is missing)
|
||||
#if USE_EDITOR
|
||||
@@ -119,6 +122,7 @@ void Log::Logger::Write(const StringView& msg)
|
||||
const auto length = msg.Length();
|
||||
if (length <= 0)
|
||||
return;
|
||||
PROFILE_MEM(Engine);
|
||||
|
||||
LogLocker.Lock();
|
||||
if (IsDuringLog)
|
||||
@@ -258,6 +262,7 @@ void Log::Logger::Write(LogType type, const StringView& msg)
|
||||
{
|
||||
if (msg.Length() <= 0)
|
||||
return;
|
||||
PROFILE_MEM(Engine);
|
||||
const bool isError = IsError(type);
|
||||
|
||||
// Create message for the log file
|
||||
@@ -306,3 +311,5 @@ const Char* ToString(LogType e)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+34
-21
@@ -7,27 +7,6 @@
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
|
||||
// Enable/disable auto flush function
|
||||
#define LOG_ENABLE_AUTO_FLUSH 1
|
||||
|
||||
/// <summary>
|
||||
/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum))
|
||||
/// </summary>
|
||||
#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__))
|
||||
|
||||
/// <summary>
|
||||
/// Sends a string message to the log file (message type - describes level of the log (see LogType enum))
|
||||
/// </summary>
|
||||
#define LOG_STR(messageType, str) Log::Logger::Write(LogType::messageType, str)
|
||||
|
||||
#if LOG_ENABLE_AUTO_FLUSH
|
||||
// Noop as log is auto-flushed on write
|
||||
#define LOG_FLUSH()
|
||||
#else
|
||||
// Flushes the log file buffer
|
||||
#define LOG_FLUSH() Log::Logger::Flush()
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The log message types.
|
||||
/// </summary>
|
||||
@@ -54,6 +33,31 @@ API_ENUM() enum class LogType
|
||||
Fatal = 8,
|
||||
};
|
||||
|
||||
#if LOG_ENABLE
|
||||
|
||||
// Enable/disable auto flush function
|
||||
#define LOG_ENABLE_AUTO_FLUSH 1
|
||||
|
||||
/// <summary>
|
||||
/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum))
|
||||
/// </summary>
|
||||
#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__))
|
||||
|
||||
/// <summary>
|
||||
/// Sends a string message to the log file (message type - describes level of the log (see LogType enum))
|
||||
/// </summary>
|
||||
#define LOG_STR(messageType, str) Log::Logger::Write(LogType::messageType, str)
|
||||
|
||||
#if LOG_ENABLE_AUTO_FLUSH
|
||||
// Noop as log is auto-flushed on write
|
||||
#define LOG_FLUSH()
|
||||
#else
|
||||
// Flushes the log file buffer
|
||||
#define LOG_FLUSH() Log::Logger::Flush()
|
||||
#endif
|
||||
|
||||
#define LOG_FLOOR() Log::Logger::WriteFloor()
|
||||
|
||||
extern const Char* ToString(LogType e);
|
||||
|
||||
namespace Log
|
||||
@@ -186,3 +190,12 @@ namespace Log
|
||||
static void ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w);
|
||||
};
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define LOG(messageType, format, ...) {}
|
||||
#define LOG_STR(messageType, str) {}
|
||||
#define LOG_FLUSH() {}
|
||||
#define LOG_FLOOR() {}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -47,6 +47,7 @@ ThreadLocal<LogContextThreadData> GlobalLogContexts;
|
||||
|
||||
void LogContext::Print(LogType verbosity)
|
||||
{
|
||||
#if LOG_ENABLE
|
||||
auto& stack = GlobalLogContexts.Get();
|
||||
if (stack.Count == 0)
|
||||
return;
|
||||
@@ -102,6 +103,7 @@ void LogContext::Print(LogType verbosity)
|
||||
// Print message
|
||||
Log::Logger::Write(verbosity, msg.ToStringView());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LogContext::Push(const Guid& id)
|
||||
|
||||
@@ -63,9 +63,16 @@ void BoundingFrustum::SetMatrix(const Matrix& matrix)
|
||||
|
||||
Plane BoundingFrustum::GetPlane(int32 index) const
|
||||
{
|
||||
if (index > 5)
|
||||
return Plane();
|
||||
return _planes[index];
|
||||
switch (index)
|
||||
{
|
||||
case 0: return _pLeft;
|
||||
case 1: return _pRight;
|
||||
case 2: return _pTop;
|
||||
case 3: return _pBottom;
|
||||
case 4: return _pNear;
|
||||
case 5: return _pFar;
|
||||
default: return Plane();
|
||||
}
|
||||
}
|
||||
|
||||
static Vector3 Get3PlanesInterPoint(const Plane& p1, const Plane& p2, const Plane& p3)
|
||||
|
||||
@@ -182,7 +182,7 @@ namespace FlaxEngine
|
||||
/// <summary>
|
||||
/// Returns one of the 6 planes related to this frustum.
|
||||
/// </summary>
|
||||
/// <param name="index">Plane index where 0 fro Left, 1 for Right, 2 for Top, 3 for Bottom, 4 for Near, 5 for Far</param>
|
||||
/// <param name="index">Plane index where 0 for Left, 1 for Right, 2 for Top, 3 for Bottom, 4 for Near, 5 for Far</param>
|
||||
/// <returns>The frustum plane.</returns>
|
||||
public Plane GetPlane(int index)
|
||||
{
|
||||
|
||||
@@ -148,13 +148,13 @@ public:
|
||||
Plane GetPlane(int32 index) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner).
|
||||
/// Gets the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner).
|
||||
/// </summary>
|
||||
/// <param name="corners">The corners.</param>
|
||||
void GetCorners(Float3 corners[8]) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner).
|
||||
/// Gets the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner).
|
||||
/// </summary>
|
||||
/// <param name="corners">The corners.</param>
|
||||
void GetCorners(Double3 corners[8]) const;
|
||||
|
||||
@@ -620,14 +620,9 @@ bool Collision::RayIntersectsTriangle(const Ray& ray, const Vector3& a, const Ve
|
||||
Real rayDistance = edge2.X * distanceCrossEdge1.X + edge2.Y * distanceCrossEdge1.Y + edge2.Z * distanceCrossEdge1.Z;
|
||||
rayDistance *= inverseDeterminant;
|
||||
|
||||
// Check if the triangle is behind the ray origin
|
||||
if (rayDistance < 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the triangle is in front the ray origin
|
||||
distance = rayDistance;
|
||||
return true;
|
||||
return rayDistance >= 0.0f;
|
||||
}
|
||||
|
||||
bool CollisionsHelper::RayIntersectsTriangle(const Ray& ray, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Real& distance, Vector3& normal)
|
||||
|
||||
@@ -12,11 +12,6 @@ String Ray::ToString() const
|
||||
return String::Format(TEXT("{}"), *this);
|
||||
}
|
||||
|
||||
Vector3 Ray::GetPoint(Real distance) const
|
||||
{
|
||||
return Position + Direction * distance;
|
||||
}
|
||||
|
||||
Ray Ray::GetPickRay(float x, float y, const Viewport& viewport, const Matrix& vp)
|
||||
{
|
||||
Vector3 nearPoint(x, y, 0.0f);
|
||||
|
||||
@@ -79,7 +79,10 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="distance">The distance from ray origin.</param>
|
||||
/// <returns>The calculated point.</returns>
|
||||
Vector3 GetPoint(Real distance) const;
|
||||
FORCE_INLINE Vector3 GetPoint(Real distance) const
|
||||
{
|
||||
return Position + Direction * distance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there is an intersection between ray and a point.
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ArenaAllocation.h"
|
||||
#include "../Math/Math.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
|
||||
void ArenaAllocator::Free()
|
||||
{
|
||||
// Free all pages
|
||||
Page* page = _first;
|
||||
while (page)
|
||||
{
|
||||
#if COMPILE_WITH_PROFILER
|
||||
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1);
|
||||
#endif
|
||||
Page* next = page->Next;
|
||||
Allocator::Free(page);
|
||||
page = next;
|
||||
}
|
||||
|
||||
// Unlink
|
||||
_first = nullptr;
|
||||
}
|
||||
|
||||
void* ArenaAllocator::Allocate(uint64 size, uint64 alignment)
|
||||
{
|
||||
if (size == 0)
|
||||
return nullptr;
|
||||
if (alignment < PLATFORM_MEMORY_ALIGNMENT)
|
||||
alignment = PLATFORM_MEMORY_ALIGNMENT;
|
||||
|
||||
// Find the first page that has some space left
|
||||
Page* page = _first;
|
||||
while (page && page->Offset + size + alignment > page->Size)
|
||||
page = page->Next;
|
||||
|
||||
// Create a new page if need to
|
||||
if (!page)
|
||||
{
|
||||
uint64 pageSize = Math::Max<uint64>(_pageSize, size + alignment + sizeof(Page));
|
||||
#if COMPILE_WITH_PROFILER
|
||||
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1);
|
||||
#endif
|
||||
page = (Page*)Allocator::Allocate(pageSize);
|
||||
page->Next = _first;
|
||||
page->Offset = sizeof(Page);
|
||||
page->Size = (uint32)pageSize;
|
||||
_first = page;
|
||||
}
|
||||
|
||||
// Allocate within a page
|
||||
page->Offset = Math::AlignUp(page->Offset, (uint32)alignment);
|
||||
void* mem = (byte*)page + page->Offset;
|
||||
page->Offset += (uint32)size;
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
void ConcurrentArenaAllocator::Free()
|
||||
{
|
||||
_locker.Lock();
|
||||
|
||||
// Free all pages
|
||||
Page* page = (Page*)_first;
|
||||
while (page)
|
||||
{
|
||||
#if COMPILE_WITH_PROFILER
|
||||
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1);
|
||||
#endif
|
||||
Page* next = page->Next;
|
||||
if (_free1)
|
||||
_free1(page);
|
||||
else
|
||||
_free2(page, page->Size);
|
||||
page = next;
|
||||
}
|
||||
|
||||
// Unlink
|
||||
_first = 0;
|
||||
_totalBytes = 0;
|
||||
|
||||
_locker.Unlock();
|
||||
}
|
||||
|
||||
void* ConcurrentArenaAllocator::Allocate(uint64 size, uint64 alignment)
|
||||
{
|
||||
if (size == 0)
|
||||
return nullptr;
|
||||
if (alignment < PLATFORM_MEMORY_ALIGNMENT)
|
||||
alignment = PLATFORM_MEMORY_ALIGNMENT;
|
||||
RETRY:
|
||||
|
||||
// Check if the current page has some space left
|
||||
Page* page = (Page*)Platform::AtomicRead(&_first);
|
||||
if (page)
|
||||
{
|
||||
int64 offset = Platform::AtomicRead(&page->Offset);
|
||||
int64 offsetAligned = Math::AlignUp(offset, (int64)alignment);
|
||||
int64 end = offsetAligned + size;
|
||||
if (end <= page->Size)
|
||||
{
|
||||
// Try to allocate within a page
|
||||
if (Platform::InterlockedCompareExchange(&page->Offset, end, offset) != offset)
|
||||
{
|
||||
// Someone else changed allocated so retry (new offset might mismatch alignment)
|
||||
goto RETRY;
|
||||
}
|
||||
Platform::InterlockedAdd(&_totalBytes, (int64)size);
|
||||
return (byte*)page + offsetAligned;
|
||||
}
|
||||
}
|
||||
|
||||
// Page allocation is thread-synced
|
||||
_locker.Lock();
|
||||
|
||||
// Check if page was unchanged by any other thread
|
||||
if ((Page*)Platform::AtomicRead(&_first) == page)
|
||||
{
|
||||
uint64 pageSize = Math::Max<uint64>(_pageSize, size + alignment + sizeof(Page));
|
||||
#if COMPILE_WITH_PROFILER
|
||||
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1);
|
||||
#endif
|
||||
page = (Page*)(_allocate1 ? _allocate1(pageSize, 16) : _allocate2(pageSize));
|
||||
page->Next = (Page*)_first;
|
||||
page->Offset = sizeof(Page);
|
||||
page->Size = (int64)pageSize;
|
||||
Platform::AtomicStore(&_first, (intptr)page);
|
||||
}
|
||||
|
||||
_locker.Unlock();
|
||||
|
||||
// Use a single code for allocation
|
||||
goto RETRY;
|
||||
}
|
||||
@@ -18,9 +18,7 @@ namespace AllocationUtils
|
||||
capacity |= capacity >> 8;
|
||||
capacity |= capacity >> 16;
|
||||
uint64 capacity64 = (uint64)(capacity + 1) * 2;
|
||||
if (capacity64 > MAX_int32)
|
||||
capacity64 = MAX_int32;
|
||||
return (int32)capacity64;
|
||||
return capacity64 >= MAX_int32 ? MAX_int32 : (int32)capacity64 / 2;
|
||||
}
|
||||
|
||||
// Aligns the input value to the next power of 2 to be used as bigger memory allocation block.
|
||||
@@ -36,6 +34,17 @@ namespace AllocationUtils
|
||||
capacity++;
|
||||
return capacity;
|
||||
}
|
||||
|
||||
inline int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity)
|
||||
{
|
||||
if (capacity < minCapacity)
|
||||
capacity = minCapacity;
|
||||
if (capacity < 8)
|
||||
capacity = 8;
|
||||
else
|
||||
capacity = RoundUpToPowerOf2(capacity);
|
||||
return capacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -46,6 +55,7 @@ class FixedAllocation
|
||||
{
|
||||
public:
|
||||
enum { HasSwap = false };
|
||||
typedef void* Tag;
|
||||
|
||||
template<typename T>
|
||||
class alignas(sizeof(void*)) Data
|
||||
@@ -58,6 +68,10 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE Data(Tag tag)
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE ~Data()
|
||||
{
|
||||
}
|
||||
@@ -106,6 +120,7 @@ class HeapAllocation
|
||||
{
|
||||
public:
|
||||
enum { HasSwap = true };
|
||||
typedef void* Tag;
|
||||
|
||||
template<typename T>
|
||||
class Data
|
||||
@@ -118,6 +133,10 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE Data(Tag tag)
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE ~Data()
|
||||
{
|
||||
Allocator::Free(_data);
|
||||
@@ -135,13 +154,7 @@ public:
|
||||
|
||||
FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, const int32 minCapacity) const
|
||||
{
|
||||
if (capacity < minCapacity)
|
||||
capacity = minCapacity;
|
||||
if (capacity < 8)
|
||||
capacity = 8;
|
||||
else
|
||||
capacity = AllocationUtils::RoundUpToPowerOf2(capacity);
|
||||
return capacity;
|
||||
return AllocationUtils::CalculateCapacityGrow(capacity, minCapacity);
|
||||
}
|
||||
|
||||
FORCE_INLINE void Allocate(const int32 capacity)
|
||||
@@ -184,6 +197,7 @@ class InlinedAllocation
|
||||
{
|
||||
public:
|
||||
enum { HasSwap = false };
|
||||
typedef void* Tag;
|
||||
|
||||
template<typename T>
|
||||
class alignas(sizeof(void*)) Data
|
||||
@@ -192,7 +206,7 @@ public:
|
||||
typedef typename FallbackAllocation::template Data<T> FallbackData;
|
||||
|
||||
bool _useFallback = false;
|
||||
byte _data[Capacity * sizeof(T)];
|
||||
alignas(sizeof(void*)) byte _data[Capacity * sizeof(T)];
|
||||
FallbackData _fallback;
|
||||
|
||||
public:
|
||||
@@ -200,6 +214,10 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE Data(Tag tag)
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE ~Data()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Allocation.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
|
||||
/// <summary>
|
||||
/// Allocator that uses pages for stack-based allocs without freeing memory during it's lifetime.
|
||||
/// </summary>
|
||||
class ArenaAllocator
|
||||
{
|
||||
private:
|
||||
struct Page
|
||||
{
|
||||
Page* Next;
|
||||
uint32 Offset, Size;
|
||||
};
|
||||
|
||||
int32 _pageSize;
|
||||
Page* _first = nullptr;
|
||||
|
||||
public:
|
||||
ArenaAllocator(int32 pageSizeBytes = 1024 * 1024) // 1 MB by default
|
||||
: _pageSize(pageSizeBytes)
|
||||
{
|
||||
}
|
||||
|
||||
~ArenaAllocator()
|
||||
{
|
||||
Free();
|
||||
}
|
||||
|
||||
// Allocates a chunk of unitialized memory.
|
||||
void* Allocate(uint64 size, uint64 alignment = 1);
|
||||
|
||||
// Frees all memory allocations within allocator.
|
||||
void Free();
|
||||
|
||||
// Creates a new object within the arena allocator.
|
||||
template<class T, class... Args>
|
||||
inline T* New(Args&&...args)
|
||||
{
|
||||
T* ptr = (T*)Allocate(sizeof(T));
|
||||
new(ptr) T(Forward<Args>(args)...);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Invokes destructor on values in an array and clears it.
|
||||
template<typename Value, typename Allocator>
|
||||
static void ClearDelete(Array<Value, Allocator>& collection)
|
||||
{
|
||||
Value* ptr = collection.Get();
|
||||
for (int32 i = 0; i < collection.Count(); i++)
|
||||
Memory::DestructItem(ptr[i]);
|
||||
collection.Clear();
|
||||
}
|
||||
|
||||
// Invokes destructor on values in a dictionary and clears it.
|
||||
template<typename Key, typename Value, typename Allocator>
|
||||
static void ClearDelete(Dictionary<Key, Value, Allocator>& collection)
|
||||
{
|
||||
for (auto it = collection.Begin(); it.IsNotEnd(); ++it)
|
||||
Memory::DestructItem(it->Value);
|
||||
collection.Clear();
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Allocator that uses pages for stack-based allocs without freeing memory during it's lifetime. Thread-safe to allocate memory from multiple threads at once.
|
||||
/// </summary>
|
||||
class ConcurrentArenaAllocator
|
||||
{
|
||||
private:
|
||||
struct Page
|
||||
{
|
||||
Page* Next;
|
||||
volatile int64 Offset;
|
||||
int64 Size;
|
||||
};
|
||||
|
||||
volatile int64 _first = 0;
|
||||
volatile int64 _totalBytes = 0;
|
||||
void*(*_allocate1)(uint64 size, uint64 alignment) = nullptr;
|
||||
void(*_free1)(void* ptr) = nullptr;
|
||||
void*(*_allocate2)(uint64 size) = nullptr;
|
||||
void(*_free2)(void* ptr, uint64 size) = nullptr;
|
||||
CriticalSection _locker;
|
||||
int32 _pageSize;
|
||||
|
||||
public:
|
||||
ConcurrentArenaAllocator(int32 pageSizeBytes, void* (*customAllocate)(uint64 size, uint64 alignment), void(*customFree)(void* ptr))
|
||||
: _allocate1(customAllocate)
|
||||
, _free1(customFree)
|
||||
, _pageSize(pageSizeBytes)
|
||||
{
|
||||
}
|
||||
|
||||
ConcurrentArenaAllocator(int32 pageSizeBytes, void* (*customAllocate)(uint64 size), void(*customFree)(void* ptr, uint64 size))
|
||||
: _allocate2(customAllocate)
|
||||
, _free2(customFree)
|
||||
, _pageSize(pageSizeBytes)
|
||||
{
|
||||
}
|
||||
|
||||
ConcurrentArenaAllocator(int32 pageSizeBytes = 1024 * 1024) // 1 MB by default
|
||||
: ConcurrentArenaAllocator(pageSizeBytes, Allocator::Allocate, Allocator::Free)
|
||||
{
|
||||
}
|
||||
|
||||
~ConcurrentArenaAllocator()
|
||||
{
|
||||
Free();
|
||||
}
|
||||
|
||||
// Gets the total amount of bytes allocated in arena (excluding alignment).
|
||||
int64 GetTotalBytes() const
|
||||
{
|
||||
return Platform::AtomicRead(&_totalBytes);
|
||||
}
|
||||
|
||||
// Allocates a chunk of unitialized memory.
|
||||
void* Allocate(uint64 size, uint64 alignment = PLATFORM_MEMORY_ALIGNMENT);
|
||||
|
||||
// Frees all memory allocations within allocator.
|
||||
void Free();
|
||||
|
||||
// Allocates a chunk of unitialized memory.
|
||||
template<class T>
|
||||
inline T* Allocate(uint64 count)
|
||||
{
|
||||
return (T*)Allocate(count * sizeof(T), alignof(T));
|
||||
}
|
||||
|
||||
// Creates a new object within the arena allocator.
|
||||
template<class T, class... Args>
|
||||
inline T* New(Args&&...args)
|
||||
{
|
||||
T* ptr = (T*)Allocate(sizeof(T));
|
||||
new(ptr) T(Forward<Args>(args)...);
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
|
||||
/// </summary>
|
||||
template<typename ArenaType>
|
||||
class ArenaAllocationBase
|
||||
{
|
||||
public:
|
||||
enum { HasSwap = true };
|
||||
typedef ArenaType* Tag;
|
||||
|
||||
template<typename T>
|
||||
class Data
|
||||
{
|
||||
private:
|
||||
T* _data = nullptr;
|
||||
ArenaType* _arena = nullptr;
|
||||
|
||||
public:
|
||||
FORCE_INLINE Data()
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE Data(Tag tag)
|
||||
{
|
||||
_arena = tag;
|
||||
}
|
||||
|
||||
FORCE_INLINE ~Data()
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE T* Get()
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
FORCE_INLINE const T* Get() const
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, const int32 minCapacity) const
|
||||
{
|
||||
return AllocationUtils::CalculateCapacityGrow(capacity, minCapacity);
|
||||
}
|
||||
|
||||
FORCE_INLINE void Allocate(const int32 capacity)
|
||||
{
|
||||
ASSERT_LOW_LAYER(!_data && _arena);
|
||||
_data = (T*)_arena->Allocate(capacity * sizeof(T), alignof(T));
|
||||
}
|
||||
|
||||
FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount)
|
||||
{
|
||||
ASSERT_LOW_LAYER(_arena);
|
||||
T* newData = capacity != 0 ? (T*)_arena->Allocate(capacity * sizeof(T), alignof(T)) : nullptr;
|
||||
if (oldCount)
|
||||
{
|
||||
if (newCount > 0)
|
||||
Memory::MoveItems(newData, _data, newCount);
|
||||
Memory::DestructItems(_data, oldCount);
|
||||
}
|
||||
_data = newData;
|
||||
}
|
||||
|
||||
FORCE_INLINE void Free()
|
||||
{
|
||||
_data = nullptr;
|
||||
}
|
||||
|
||||
FORCE_INLINE void Swap(Data& other)
|
||||
{
|
||||
::Swap(_data, other._data);
|
||||
_arena = other._arena; // TODO: find a better way to move allocation with AllocationUtils::MoveToEmpty to preserve/maintain allocation tag ownership
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
|
||||
/// </summary>
|
||||
typedef ArenaAllocationBase<ArenaAllocator> ArenaAllocation;
|
||||
|
||||
/// <summary>
|
||||
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
|
||||
/// </summary>
|
||||
typedef ArenaAllocationBase<ConcurrentArenaAllocator> ConcurrentArenaAllocation;
|
||||
@@ -11,14 +11,13 @@
|
||||
class CrtAllocator
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Allocates memory on a specified alignment boundary.
|
||||
/// </summary>
|
||||
/// <param name="size">The size of the allocation (in bytes).</param>
|
||||
/// <param name="alignment">The memory alignment (in bytes). Must be an integer power of 2.</param>
|
||||
/// <returns>The pointer to the allocated chunk of the memory. The pointer is a multiple of alignment.</returns>
|
||||
FORCE_INLINE static void* Allocate(uint64 size, uint64 alignment = 16)
|
||||
FORCE_INLINE static void* Allocate(uint64 size, uint64 alignment = PLATFORM_MEMORY_ALIGNMENT)
|
||||
{
|
||||
return Platform::Allocate(size, alignment);
|
||||
}
|
||||
|
||||
@@ -104,12 +104,6 @@ public:
|
||||
{
|
||||
new(dst) T();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the item in the memory.
|
||||
/// </summary>
|
||||
/// <remarks>The optimized version is noop.</remarks>
|
||||
/// <param name="dst">The address of the memory location to construct.</param>
|
||||
template<typename T>
|
||||
FORCE_INLINE static typename TEnableIf<TIsTriviallyConstructible<T>::Value>::Type ConstructItem(T* dst)
|
||||
{
|
||||
@@ -132,13 +126,6 @@ public:
|
||||
++(T*&)dst;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the range of items in the memory.
|
||||
/// </summary>
|
||||
/// <remarks>The optimized version is noop.</remarks>
|
||||
/// <param name="dst">The address of the first memory location to construct.</param>
|
||||
/// <param name="count">The number of element to construct. Can be equal 0.</param>
|
||||
template<typename T>
|
||||
FORCE_INLINE static typename TEnableIf<TIsTriviallyConstructible<T>::Value>::Type ConstructItems(T* dst, int32 count)
|
||||
{
|
||||
@@ -163,14 +150,6 @@ public:
|
||||
++src;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the range of items in the memory from the set of arguments.
|
||||
/// </summary>
|
||||
/// <remarks>The optimized version uses low-level memory copy.</remarks>
|
||||
/// <param name="dst">The address of the first memory location to construct.</param>
|
||||
/// <param name="src">The address of the first memory location to pass to the constructor.</param>
|
||||
/// <param name="count">The number of element to construct. Can be equal 0.</param>
|
||||
template<typename T, typename U>
|
||||
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type ConstructItems(T* dst, const U* src, int32 count)
|
||||
{
|
||||
@@ -187,12 +166,6 @@ public:
|
||||
{
|
||||
dst->~T();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the item in the memory.
|
||||
/// </summary>
|
||||
/// <remarks>The optimized version is noop.</remarks>
|
||||
/// <param name="dst">The address of the memory location to destruct.</param>
|
||||
template<typename T>
|
||||
FORCE_INLINE static typename TEnableIf<TIsTriviallyDestructible<T>::Value>::Type DestructItem(T* dst)
|
||||
{
|
||||
@@ -213,13 +186,6 @@ public:
|
||||
++dst;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the range of items in the memory.
|
||||
/// </summary>
|
||||
/// <remarks>The optimized version is noop.</remarks>
|
||||
/// <param name="dst">The address of the first memory location to destruct.</param>
|
||||
/// <param name="count">The number of element to destruct. Can be equal 0.</param>
|
||||
template<typename T>
|
||||
FORCE_INLINE static typename TEnableIf<TIsTriviallyDestructible<T>::Value>::Type DestructItems(T* dst, int32 count)
|
||||
{
|
||||
@@ -242,15 +208,7 @@ public:
|
||||
++src;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the range of items using the assignment operator.
|
||||
/// </summary>
|
||||
/// <remarks>The optimized version is low-level memory copy.</remarks>
|
||||
/// <param name="dst">The address of the first memory location to start assigning to.</param>
|
||||
/// <param name="src">The address of the first memory location to assign from.</param>
|
||||
/// <param name="count">The number of element to assign. Can be equal 0.</param>
|
||||
template<typename T>
|
||||
template<typename T, typename U>
|
||||
FORCE_INLINE static typename TEnableIf<TIsTriviallyCopyAssignable<T>::Value>::Type CopyItems(T* dst, const T* src, int32 count)
|
||||
{
|
||||
Platform::MemoryCopy(dst, src, count * sizeof(T));
|
||||
@@ -273,16 +231,31 @@ public:
|
||||
++src;
|
||||
}
|
||||
}
|
||||
template<typename T, typename U>
|
||||
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveItems(T* dst, U* src, int32 count)
|
||||
{
|
||||
Platform::MemoryCopy(dst, src, count * sizeof(U));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the range of items in the memory from the set of arguments.
|
||||
/// Moves the range of items using the assignment operator.
|
||||
/// </summary>
|
||||
/// <remarks>The optimized version uses low-level memory copy.</remarks>
|
||||
/// <param name="dst">The address of the first memory location to move.</param>
|
||||
/// <param name="src">The address of the first memory location to pass to the move constructor.</param>
|
||||
/// <param name="count">The number of element to move. Can be equal 0.</param>
|
||||
template<typename T, typename U>
|
||||
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveItems(T* dst, U* src, int32 count)
|
||||
FORCE_INLINE static typename TEnableIf<!TIsBitwiseConstructible<T, U>::Value>::Type MoveAssignItems(T* dst, U* src, int32 count)
|
||||
{
|
||||
while (count--)
|
||||
{
|
||||
*dst = MoveTemp(*src);
|
||||
++(T*&)dst;
|
||||
++src;
|
||||
}
|
||||
}
|
||||
template<typename T, typename U>
|
||||
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveAssignItems(T* dst, U* src, int32 count)
|
||||
{
|
||||
Platform::MemoryCopy(dst, src, count * sizeof(U));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class SimpleHeapAllocation
|
||||
{
|
||||
public:
|
||||
enum { HasSwap = true };
|
||||
typedef void* Tag;
|
||||
|
||||
template<typename T>
|
||||
class Data
|
||||
@@ -23,6 +24,10 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE Data(Tag tag)
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE ~Data()
|
||||
{
|
||||
if (_data)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace
|
||||
CriticalSection PoolLocker;
|
||||
double LastUpdate;
|
||||
float LastUpdateGameTime;
|
||||
Dictionary<Object*, float> Pool(8192);
|
||||
Dictionary<Object*, float> Pool;
|
||||
uint64 PoolCounter = 0;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
|
||||
|
||||
bool ObjectsRemoval::Init()
|
||||
{
|
||||
Pool.EnsureCapacity(8192);
|
||||
LastUpdate = Platform::GetTimeSeconds();
|
||||
LastUpdateGameTime = 0;
|
||||
return false;
|
||||
@@ -156,7 +157,7 @@ Object::~Object()
|
||||
{
|
||||
#if BUILD_DEBUG && 0
|
||||
// Prevent removing object that is still reverenced by the removal service
|
||||
ASSERT(!ObjectsRemovalService::IsInPool(this));
|
||||
//ASSERT(!ObjectsRemovalService::IsInPool(this));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the total number of milliseconds.
|
||||
/// </summary>
|
||||
FORCE_INLINE double GetTotalMilliseconds() const
|
||||
FORCE_INLINE float GetTotalMilliseconds() const
|
||||
{
|
||||
return (float)((_end - _start) * 1000.0);
|
||||
}
|
||||
|
||||
@@ -215,6 +215,11 @@ public:
|
||||
return String(_data.Get(), _data.Count());
|
||||
}
|
||||
|
||||
StringAnsi ToStringAnsi() const
|
||||
{
|
||||
return StringAnsi(_data.Get(), _data.Count());
|
||||
}
|
||||
|
||||
StringView ToStringView() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -208,6 +208,14 @@ public:
|
||||
return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
||||
return StringUtils::Compare(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
||||
}
|
||||
|
||||
bool Contains(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
|
||||
{
|
||||
const int32 length = Length();
|
||||
if (subStr == nullptr || length == 0)
|
||||
return false;
|
||||
return (searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(_data, subStr) : StringUtils::Find(_data, subStr)) != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -118,7 +118,7 @@ VariantType::VariantType(Types type, const MClass* klass)
|
||||
#if USE_CSHARP
|
||||
if (klass)
|
||||
{
|
||||
const StringAnsi& typeName = klass->GetFullName();
|
||||
const StringAnsiView typeName = klass->GetFullName();
|
||||
const int32 length = typeName.Length();
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), length);
|
||||
@@ -632,6 +632,7 @@ Variant::Variant(ScriptingObject* v)
|
||||
AsObject = v;
|
||||
if (v)
|
||||
{
|
||||
// TODO: optimize VariantType to support statically linked typename of ScriptingType (via 1 bit flag within Types enum, only in game as editor might hot-reload types)
|
||||
Type.SetTypeName(v->GetType().Fullname);
|
||||
v->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(this);
|
||||
}
|
||||
@@ -643,6 +644,7 @@ Variant::Variant(Asset* v)
|
||||
AsAsset = v;
|
||||
if (v)
|
||||
{
|
||||
// TODO: optimize VariantType to support statically linked typename of ScriptingType (via 1 bit flag within Types enum, only in game as editor might hot-reload types)
|
||||
Type.SetTypeName(v->GetType().Fullname);
|
||||
v->AddReference();
|
||||
v->OnUnloaded.Bind<Variant, &Variant::OnAssetUnloaded>(this);
|
||||
|
||||
@@ -186,6 +186,34 @@ namespace
|
||||
Task* AsyncTask = nullptr;
|
||||
Array<CommandData> Commands;
|
||||
|
||||
void BuildName(CommandData& cmd, const MClass* mclass, const StringAnsiView& itemName)
|
||||
{
|
||||
StringAnsiView mclassName = mclass->GetName();
|
||||
StringAnsiView mclassFullname = mclass->GetFullName();
|
||||
StringAnsiView mclassNamespace = mclass->GetNamespace();
|
||||
|
||||
Array<Char, InlinedAllocation<200>> buffer; // Stack-based but with option to alloc in case of very long commands
|
||||
buffer.Resize(mclassFullname.Length() - mclassNamespace.Length() + itemName.Length() + 1);
|
||||
Char* bufferPtr = buffer.Get();
|
||||
|
||||
// Check if class is nested, then include outer class name for hierarchy
|
||||
if (mclassNamespace.Length() + mclassName.Length() + 1 != mclassFullname.Length())
|
||||
{
|
||||
StringAnsiView outerName(mclassFullname.Get() + mclassNamespace.Length() + 1, mclassFullname.Length() - mclassNamespace.Length() - 2 - mclassName.Length());
|
||||
StringUtils::Copy(bufferPtr, outerName.Get(), outerName.Length());
|
||||
bufferPtr += outerName.Length();
|
||||
*bufferPtr++ = '.';
|
||||
}
|
||||
StringUtils::Copy(bufferPtr, mclassName.Get(), mclassName.Length());
|
||||
bufferPtr += mclassName.Length();
|
||||
*bufferPtr++ = '.';
|
||||
StringUtils::Copy(bufferPtr, itemName.Get(), itemName.Length());
|
||||
bufferPtr += itemName.Length();
|
||||
*bufferPtr = 0;
|
||||
|
||||
cmd.Name.Set(buffer.Get(), (int32)(bufferPtr - buffer.Get()));
|
||||
}
|
||||
|
||||
void FindDebugCommands(BinaryModule* module)
|
||||
{
|
||||
if (module == GetBinaryModuleCorlib())
|
||||
@@ -206,8 +234,6 @@ namespace
|
||||
mclass->IsEnum())
|
||||
continue;
|
||||
const bool useClass = mclass->HasAttribute(attribute);
|
||||
// TODO: optimize this via stack-based format buffer and then convert Ansi to UTF16
|
||||
#define BUILD_NAME(commandData, itemName) commandData.Name = String(mclass->GetName()) + TEXT(".") + String(itemName)
|
||||
|
||||
// Process methods
|
||||
const auto& methods = mclass->GetMethods();
|
||||
@@ -215,7 +241,7 @@ namespace
|
||||
{
|
||||
if (!method->IsStatic())
|
||||
continue;
|
||||
const StringAnsi& name = method->GetName();
|
||||
const StringAnsiView name = method->GetName();
|
||||
if (name.Contains("Internal_") ||
|
||||
mclass->GetFullName().Contains(".Interop."))
|
||||
continue;
|
||||
@@ -231,7 +257,7 @@ namespace
|
||||
continue;
|
||||
|
||||
auto& commandData = Commands.AddOne();
|
||||
BUILD_NAME(commandData, method->GetName());
|
||||
BuildName(commandData, mclass, method->GetName());
|
||||
commandData.Module = module;
|
||||
commandData.Method = method;
|
||||
}
|
||||
@@ -248,7 +274,7 @@ namespace
|
||||
continue;
|
||||
|
||||
auto& commandData = Commands.AddOne();
|
||||
BUILD_NAME(commandData, field->GetName());
|
||||
BuildName(commandData, mclass, field->GetName());
|
||||
commandData.Module = module;
|
||||
commandData.Field = field;
|
||||
}
|
||||
@@ -265,13 +291,12 @@ namespace
|
||||
continue;
|
||||
|
||||
auto& commandData = Commands.AddOne();
|
||||
BUILD_NAME(commandData, property->GetName());
|
||||
BuildName(commandData, mclass, property->GetName());
|
||||
commandData.Module = module;
|
||||
commandData.MethodGet = property->GetGetMethod();
|
||||
commandData.MethodSet = property->GetSetMethod();
|
||||
}
|
||||
}
|
||||
#undef BUILD_NAME
|
||||
}
|
||||
else
|
||||
#endif
|
||||
@@ -435,9 +460,19 @@ void DebugCommands::InitAsync()
|
||||
AsyncTask = Task::StartNew(InitCommands);
|
||||
}
|
||||
|
||||
void DebugCommands::GetAllCommands(Array<StringView>& commands)
|
||||
{
|
||||
EnsureInited();
|
||||
ScopeLock lock(Locker);
|
||||
for (const auto& command : Commands)
|
||||
commands.Add(command.Name);
|
||||
}
|
||||
|
||||
DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command)
|
||||
{
|
||||
CommandFlags result = CommandFlags::None;
|
||||
if (command.FindLast(' ') != -1)
|
||||
command = command.Left(command.Find(' '));
|
||||
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
|
||||
String commandCopy = command;
|
||||
command = commandCopy;
|
||||
|
||||
@@ -46,6 +46,12 @@ public:
|
||||
/// </summary>
|
||||
API_FUNCTION() static void InitAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available commands.
|
||||
/// </summary>
|
||||
/// <param name="commands">The output list of all commands (unsorted).</param>
|
||||
API_FUNCTION() static void GetAllCommands(API_PARAM(Out) Array<StringView, HeapAllocation>& commands);
|
||||
|
||||
/// <summary>
|
||||
/// Returns flags of the command.
|
||||
/// </summary>
|
||||
|
||||
@@ -358,7 +358,8 @@ struct DebugDrawContext
|
||||
DebugDrawData DebugDrawDefault;
|
||||
DebugDrawData DebugDrawDepthTest;
|
||||
Float3 LastViewPos = Float3::Zero;
|
||||
Matrix LastViewProj = Matrix::Identity;
|
||||
Matrix LastViewProjection = Matrix::Identity;
|
||||
BoundingFrustum LastViewFrustum;
|
||||
|
||||
inline int32 Count() const
|
||||
{
|
||||
@@ -526,6 +527,7 @@ DebugDrawService DebugDrawServiceInstance;
|
||||
|
||||
bool DebugDrawService::Init()
|
||||
{
|
||||
PROFILE_MEM(Graphics);
|
||||
Context = &GlobalContext;
|
||||
|
||||
// Init wireframe sphere cache
|
||||
@@ -644,6 +646,7 @@ void DebugDrawService::Update()
|
||||
}
|
||||
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Graphics);
|
||||
|
||||
// Update lists
|
||||
float deltaTime = Time::Update.DeltaTime.GetTotalSeconds();
|
||||
@@ -778,9 +781,23 @@ Vector3 DebugDraw::GetViewPos()
|
||||
return Context->LastViewPos;
|
||||
}
|
||||
|
||||
BoundingFrustum DebugDraw::GetViewFrustum()
|
||||
{
|
||||
return Context->LastViewFrustum;
|
||||
}
|
||||
|
||||
void DebugDraw::SetView(const RenderView& view)
|
||||
{
|
||||
Context->LastViewPos = view.Position;
|
||||
Context->LastViewProjection = view.Projection;
|
||||
Context->LastViewFrustum = view.Frustum;
|
||||
}
|
||||
|
||||
void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTextureView* depthBuffer, bool enableDepthTest)
|
||||
{
|
||||
PROFILE_GPU_CPU("Debug Draw");
|
||||
const RenderView& view = renderContext.View;
|
||||
SetView(view);
|
||||
|
||||
// Ensure to have shader loaded and any lines to render
|
||||
const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count();
|
||||
@@ -790,7 +807,6 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
|
||||
if (renderContext.Buffers == nullptr || !DebugDrawVB)
|
||||
return;
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
const RenderView& view = renderContext.View;
|
||||
if (Context->Origin != view.Origin)
|
||||
{
|
||||
// Teleport existing debug shapes to maintain their location
|
||||
@@ -799,8 +815,6 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
|
||||
Context->DebugDrawDepthTest.Teleport(delta);
|
||||
Context->Origin = view.Origin;
|
||||
}
|
||||
Context->LastViewPos = view.Position;
|
||||
Context->LastViewProj = view.Projection;
|
||||
TaaJitterRemoveContext taaJitterRemove(view);
|
||||
|
||||
// Fallback to task buffers
|
||||
@@ -839,7 +853,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
|
||||
auto vb = DebugDrawVB->GetBuffer();
|
||||
|
||||
// Draw with depth test
|
||||
if (depthTestLines.VertexCount + depthTestTriangles.VertexCount + depthTestWireTriangles.VertexCount > 0)
|
||||
if (depthTestLines.VertexCount + depthTestTriangles.VertexCount + depthTestWireTriangles.VertexCount + Context->DebugDrawDepthTest.GeometryBuffers.Count() > 0)
|
||||
{
|
||||
if (data.EnableDepthTest)
|
||||
context->BindSR(0, renderContext.Buffers->DepthBuffer);
|
||||
@@ -895,7 +909,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
|
||||
}
|
||||
|
||||
// Draw without depth
|
||||
if (defaultLines.VertexCount + defaultTriangles.VertexCount + defaultWireTriangles.VertexCount > 0)
|
||||
if (defaultLines.VertexCount + defaultTriangles.VertexCount + defaultWireTriangles.VertexCount + Context->DebugDrawDefault.GeometryBuffers.Count() > 0)
|
||||
{
|
||||
context->SetRenderTarget(target);
|
||||
|
||||
@@ -1383,7 +1397,7 @@ void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color,
|
||||
int32 index;
|
||||
const Float3 centerF = sphere.Center - Context->Origin;
|
||||
const float radiusF = (float)sphere.Radius;
|
||||
const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(centerF, radiusF, Context->LastViewPos, Context->LastViewProj);
|
||||
const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(centerF, radiusF, Context->LastViewPos, Context->LastViewProjection);
|
||||
if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * 0.25f)
|
||||
index = 0;
|
||||
else if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * 0.25f)
|
||||
|
||||
@@ -76,6 +76,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
|
||||
|
||||
// Gets the last view position when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
|
||||
static Vector3 GetViewPos();
|
||||
// Gets the last view frustum when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
|
||||
static BoundingFrustum GetViewFrustum();
|
||||
|
||||
// Sets the rendering view information beforehand.
|
||||
API_FUNCTION() static void SetView(API_PARAM(ref) const RenderView& view);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the collected debug shapes to the output.
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
Log::Exception::~Exception()
|
||||
{
|
||||
#if LOG_ENABLE
|
||||
// Always write exception to the log
|
||||
Logger::Write(_level, ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ void GameBaseImpl::OnPostRender(GPUContext* context, RenderContext& renderContex
|
||||
}
|
||||
|
||||
// Wait for texture loaded before showing splash screen
|
||||
if (!SplashScreen->IsLoaded() || SplashScreen.Get()->GetTexture()->MipLevels() != SplashScreen.Get()->StreamingTexture()->TotalMipLevels())
|
||||
if (!SplashScreen->IsLoaded() || SplashScreen.Get()->GetResidentMipLevels() != SplashScreen.Get()->GetMipLevels())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace FlaxEngine
|
||||
{
|
||||
if (message == null)
|
||||
return;
|
||||
#if BUILD_RELEASE
|
||||
#if BUILD_RELEASE || !FLAX_EDITOR
|
||||
string stackTrace = null;
|
||||
#else
|
||||
string stackTrace = Environment.StackTrace;
|
||||
|
||||
@@ -76,9 +76,15 @@ FatalErrorType Engine::FatalError = FatalErrorType::None;
|
||||
bool Engine::IsRequestingExit = false;
|
||||
int32 Engine::ExitCode = 0;
|
||||
Window* Engine::MainWindow = nullptr;
|
||||
double EngineIdleTime = 0;
|
||||
|
||||
int32 Engine::Main(const Char* cmdLine)
|
||||
{
|
||||
#if COMPILE_WITH_PROFILER
|
||||
extern void InitProfilerMemory(const Char* cmdLine, int32 stage);
|
||||
InitProfilerMemory(cmdLine, 0);
|
||||
#endif
|
||||
PROFILE_MEM_BEGIN(Engine);
|
||||
EngineImpl::CommandLine = cmdLine;
|
||||
Globals::MainThreadID = Platform::GetCurrentThreadID();
|
||||
StartupTime = DateTime::Now();
|
||||
@@ -104,6 +110,9 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
Platform::Fatal(TEXT("Cannot init platform."));
|
||||
return -1;
|
||||
}
|
||||
#if COMPILE_WITH_PROFILER
|
||||
InitProfilerMemory(cmdLine, 1);
|
||||
#endif
|
||||
|
||||
Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
|
||||
Time::StartupTime = DateTime::Now();
|
||||
@@ -142,7 +151,9 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
{
|
||||
// End
|
||||
LOG(Warning, "Loading project cancelled. Closing...");
|
||||
#if LOG_ENABLE
|
||||
Log::Logger::Dispose();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -160,10 +171,11 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
#if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC)
|
||||
EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground;
|
||||
#endif
|
||||
Log::Logger::WriteFloor();
|
||||
LOG_FLOOR();
|
||||
LOG_FLUSH();
|
||||
Time::Synchronize();
|
||||
EngineImpl::IsReady = true;
|
||||
PROFILE_MEM_END();
|
||||
|
||||
// Main engine loop
|
||||
const bool useSleep = true; // TODO: this should probably be a platform setting
|
||||
@@ -179,7 +191,10 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
if (timeToTick > 0.002)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Idle");
|
||||
auto sleepStart = Platform::GetTimeSeconds();
|
||||
Platform::Sleep(1);
|
||||
auto sleepEnd = Platform::GetTimeSeconds();
|
||||
EngineIdleTime += sleepEnd - sleepStart;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +219,10 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Platform.Tick");
|
||||
Platform::Tick();
|
||||
#if COMPILE_WITH_PROFILER
|
||||
extern void TickProfilerMemory();
|
||||
TickProfilerMemory();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Update game logic
|
||||
@@ -212,6 +231,7 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
OnUpdate();
|
||||
OnLateUpdate();
|
||||
Time::OnEndUpdate();
|
||||
EngineIdleTime = 0;
|
||||
}
|
||||
|
||||
// Start physics simulation
|
||||
@@ -227,7 +247,6 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
{
|
||||
OnDraw();
|
||||
Time::OnEndDraw();
|
||||
FrameMark;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,6 +396,11 @@ void Engine::OnLateUpdate()
|
||||
|
||||
void Engine::OnDraw()
|
||||
{
|
||||
#if COMPILE_WITH_PROFILER
|
||||
// Auto-enable GPU events when Tracy got connected
|
||||
if (!ProfilerGPU::EventsEnabled && TracyIsConnected)
|
||||
ProfilerGPU::EventsEnabled = true;
|
||||
#endif
|
||||
PROFILE_CPU_NAMED("Draw");
|
||||
|
||||
// Begin frame rendering
|
||||
@@ -391,6 +415,7 @@ void Engine::OnDraw()
|
||||
device->Draw();
|
||||
|
||||
// End frame rendering
|
||||
FrameMark;
|
||||
#if COMPILE_WITH_PROFILER
|
||||
ProfilerGPU::EndFrame();
|
||||
#endif
|
||||
@@ -540,16 +565,20 @@ void Engine::OnExit()
|
||||
#if COMPILE_WITH_PROFILER
|
||||
ProfilerCPU::Dispose();
|
||||
ProfilerGPU::Dispose();
|
||||
ProfilerMemory::Enabled = false;
|
||||
#endif
|
||||
|
||||
#if LOG_ENABLE
|
||||
// Close logging service
|
||||
Log::Logger::Dispose();
|
||||
#endif
|
||||
|
||||
Platform::Exit();
|
||||
}
|
||||
|
||||
void EngineImpl::InitLog()
|
||||
{
|
||||
#if LOG_ENABLE
|
||||
// Initialize logger
|
||||
Log::Logger::Init();
|
||||
|
||||
@@ -603,6 +632,7 @@ void EngineImpl::InitLog()
|
||||
Platform::LogInfo();
|
||||
|
||||
LOG_FLUSH();
|
||||
#endif
|
||||
}
|
||||
|
||||
void EngineImpl::InitPaths()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user