Merge branch 'master' into visject_grid_snap
Merged master.
This commit is contained in:
@@ -2109,7 +2109,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
bucket.LoopsLeft--;
|
||||
bucket.LoopsDone++;
|
||||
}
|
||||
value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed);
|
||||
// Speed is accounted for in the new time pos, so keep sample speed at 1
|
||||
value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, 1);
|
||||
bucket.TimePosition = newTimePos;
|
||||
if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition)
|
||||
{
|
||||
|
||||
@@ -225,6 +225,7 @@ bool AudioClip::ExtractDataRaw(Array<byte>& resultData, AudioDataInfo& resultDat
|
||||
|
||||
void AudioClip::CancelStreaming()
|
||||
{
|
||||
Asset::CancelStreaming();
|
||||
CancelStreamingTasks();
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
int alError = alGetError(); \
|
||||
if (alError != 0) \
|
||||
{ \
|
||||
LOG(Error, "OpenAL method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), alError, __LINE__ - 1); \
|
||||
const Char* errorStr = GetOpenALErrorString(alError); \
|
||||
LOG(Error, "OpenAL method {0} failed with error 0x{1:X}:{2} (at line {3})", TEXT(#method), alError, errorStr, __LINE__ - 1); \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
@@ -290,6 +291,28 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Char* GetOpenALErrorString(int error)
|
||||
{
|
||||
switch (error)
|
||||
{
|
||||
case AL_NO_ERROR:
|
||||
return TEXT("AL_NO_ERROR");
|
||||
case AL_INVALID_NAME:
|
||||
return TEXT("AL_INVALID_NAME");
|
||||
case AL_INVALID_ENUM:
|
||||
return TEXT("AL_INVALID_ENUM");
|
||||
case AL_INVALID_VALUE:
|
||||
return TEXT("AL_INVALID_VALUE");
|
||||
case AL_INVALID_OPERATION:
|
||||
return TEXT("AL_INVALID_OPERATION");
|
||||
case AL_OUT_OF_MEMORY:
|
||||
return TEXT("AL_OUT_OF_MEMORY");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TEXT("???");
|
||||
}
|
||||
|
||||
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
|
||||
{
|
||||
#if ALC_MULTIPLE_LISTENERS
|
||||
@@ -838,7 +861,11 @@ bool AudioBackendOAL::Base_Init()
|
||||
// Init
|
||||
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
|
||||
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
|
||||
ALC::RebuildContexts(true);
|
||||
int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1);
|
||||
if (clampedIndex == Audio::GetActiveDeviceIndex())
|
||||
{
|
||||
ALC::RebuildContexts(true);
|
||||
}
|
||||
Audio::SetActiveDeviceIndex(activeDeviceIndex);
|
||||
#ifdef AL_SOFT_source_spatialize
|
||||
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
AssetReferenceBase::~AssetReferenceBase()
|
||||
{
|
||||
if (_asset)
|
||||
Asset* asset = _asset;
|
||||
if (asset)
|
||||
{
|
||||
_asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
|
||||
_asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
|
||||
_asset->RemoveReference();
|
||||
_asset = nullptr;
|
||||
asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
|
||||
asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
|
||||
asset->RemoveReference();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +72,12 @@ void AssetReferenceBase::OnUnloaded(Asset* asset)
|
||||
|
||||
WeakAssetReferenceBase::~WeakAssetReferenceBase()
|
||||
{
|
||||
if (_asset)
|
||||
_asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
|
||||
Asset* asset = _asset;
|
||||
if (asset)
|
||||
{
|
||||
_asset = nullptr;
|
||||
asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
|
||||
}
|
||||
}
|
||||
|
||||
String WeakAssetReferenceBase::ToString() const
|
||||
@@ -101,6 +107,20 @@ void WeakAssetReferenceBase::OnUnloaded(Asset* asset)
|
||||
_asset = nullptr;
|
||||
}
|
||||
|
||||
SoftAssetReferenceBase::~SoftAssetReferenceBase()
|
||||
{
|
||||
Asset* asset = _asset;
|
||||
if (asset)
|
||||
{
|
||||
_asset = nullptr;
|
||||
asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
|
||||
asset->RemoveReference();
|
||||
}
|
||||
#if !BUILD_RELEASE
|
||||
_id = Guid::Empty;
|
||||
#endif
|
||||
}
|
||||
|
||||
String SoftAssetReferenceBase::ToString() const
|
||||
{
|
||||
return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("<null>"));
|
||||
@@ -502,6 +522,14 @@ void Asset::InitAsVirtual()
|
||||
|
||||
void Asset::CancelStreaming()
|
||||
{
|
||||
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
|
||||
Locker.Lock();
|
||||
ContentLoadTask* loadTask = _loadingTask;
|
||||
Locker.Unlock();
|
||||
if (loadTask)
|
||||
{
|
||||
loadTask->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
@@ -538,11 +566,7 @@ ContentLoadTask* Asset::createLoadingTask()
|
||||
|
||||
void Asset::startLoading()
|
||||
{
|
||||
// Check if is already loaded
|
||||
if (IsLoaded())
|
||||
return;
|
||||
|
||||
// Start loading (using async tasks)
|
||||
ASSERT(!IsLoaded());
|
||||
ASSERT(_loadingTask == nullptr);
|
||||
_loadingTask = createLoadingTask();
|
||||
ASSERT(_loadingTask != nullptr);
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
/// </summary>
|
||||
class FLAXENGINE_API AssetReferenceBase
|
||||
{
|
||||
public:
|
||||
typedef Delegate<> EventType;
|
||||
|
||||
protected:
|
||||
Asset* _asset = nullptr;
|
||||
|
||||
@@ -19,17 +16,17 @@ public:
|
||||
/// <summary>
|
||||
/// The asset loaded event (fired when asset gets loaded or is already loaded after change).
|
||||
/// </summary>
|
||||
EventType Loaded;
|
||||
Action Loaded;
|
||||
|
||||
/// <summary>
|
||||
/// The asset unloading event (should cleanup refs to it).
|
||||
/// </summary>
|
||||
EventType Unload;
|
||||
Action Unload;
|
||||
|
||||
/// <summary>
|
||||
/// Action fired when field gets changed (link a new asset or change to the another value).
|
||||
/// </summary>
|
||||
EventType Changed;
|
||||
Action Changed;
|
||||
|
||||
public:
|
||||
NON_COPYABLE(AssetReferenceBase);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Engine/ShadersCompilation/Config.h"
|
||||
#if BUILD_DEBUG
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -256,7 +257,9 @@ Asset::LoadResult Material::load()
|
||||
|
||||
#if BUILD_DEBUG && USE_EDITOR
|
||||
// Dump generated material source to the temporary file
|
||||
BinaryModule::Locker.Lock();
|
||||
source.SaveToFile(Globals::ProjectCacheFolder / TEXT("material.txt"));
|
||||
BinaryModule::Locker.Unlock();
|
||||
#endif
|
||||
|
||||
// Encrypt source code
|
||||
|
||||
@@ -783,6 +783,7 @@ void Model::InitAsVirtual()
|
||||
|
||||
void Model::CancelStreaming()
|
||||
{
|
||||
Asset::CancelStreaming();
|
||||
CancelStreamingTasks();
|
||||
}
|
||||
|
||||
|
||||
@@ -969,6 +969,7 @@ void SkinnedModel::InitAsVirtual()
|
||||
|
||||
void SkinnedModel::CancelStreaming()
|
||||
{
|
||||
Asset::CancelStreaming();
|
||||
CancelStreamingTasks();
|
||||
}
|
||||
|
||||
|
||||
@@ -1428,6 +1428,10 @@ Asset::LoadResult VisualScript::load()
|
||||
#if USE_EDITOR
|
||||
if (_instances.HasItems())
|
||||
{
|
||||
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
|
||||
_loadFailed = false;
|
||||
_isLoaded = true;
|
||||
|
||||
// Setup scripting type
|
||||
CacheScriptingType();
|
||||
|
||||
@@ -1512,7 +1516,7 @@ void VisualScript::unload(bool isReloading)
|
||||
// Note: preserve the registered scripting type but invalidate the locally cached handle
|
||||
if (_scriptingTypeHandle)
|
||||
{
|
||||
VisualScriptingModule.Locker.Lock();
|
||||
VisualScriptingBinaryModule::Locker.Lock();
|
||||
auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex];
|
||||
if (type.Script.DefaultInstance)
|
||||
{
|
||||
@@ -1523,7 +1527,7 @@ void VisualScript::unload(bool isReloading)
|
||||
VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr;
|
||||
_scriptingTypeHandleCached = _scriptingTypeHandle;
|
||||
_scriptingTypeHandle = ScriptingTypeHandle();
|
||||
VisualScriptingModule.Locker.Unlock();
|
||||
VisualScriptingBinaryModule::Locker.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1534,8 +1538,8 @@ AssetChunksFlag VisualScript::getChunksToPreload() const
|
||||
|
||||
void VisualScript::CacheScriptingType()
|
||||
{
|
||||
ScopeLock lock(VisualScriptingBinaryModule::Locker);
|
||||
auto& binaryModule = VisualScriptingModule;
|
||||
ScopeLock lock(binaryModule.Locker);
|
||||
|
||||
// Find base type
|
||||
const StringAnsi baseTypename(Meta.BaseTypename);
|
||||
|
||||
@@ -154,7 +154,7 @@ void ContentService::LateUpdate()
|
||||
// Unload marked assets
|
||||
for (int32 i = 0; i < ToUnload.Count(); i++)
|
||||
{
|
||||
Asset* asset = ToUnload[i];
|
||||
Asset* asset = ToUnload[i];
|
||||
|
||||
// Check if has no references
|
||||
if (asset->GetReferencesCount() <= 0)
|
||||
@@ -521,37 +521,33 @@ Asset* Content::GetAsset(const Guid& id)
|
||||
|
||||
void Content::DeleteAsset(Asset* asset)
|
||||
{
|
||||
ScopeLock locker(AssetsLocker);
|
||||
|
||||
// Validate
|
||||
if (asset == nullptr || asset->_deleteFileOnUnload)
|
||||
{
|
||||
// Back
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(Info, "Deleting asset {0}...", asset->ToString());
|
||||
|
||||
// Ensure that asset is loaded (easier than cancel in-flight loading)
|
||||
asset->WaitForLoaded();
|
||||
|
||||
// Mark asset for delete queue (delete it after auto unload)
|
||||
asset->_deleteFileOnUnload = true;
|
||||
|
||||
// Unload
|
||||
UnloadAsset(asset);
|
||||
asset->DeleteObject();
|
||||
}
|
||||
|
||||
void Content::DeleteAsset(const StringView& path)
|
||||
{
|
||||
ScopeLock locker(AssetsLocker);
|
||||
|
||||
// Check if is loaded
|
||||
// Try to delete already loaded asset
|
||||
Asset* asset = GetAsset(path);
|
||||
if (asset != nullptr)
|
||||
{
|
||||
// Delete asset
|
||||
DeleteAsset(asset);
|
||||
return;
|
||||
}
|
||||
|
||||
ScopeLock locker(AssetsLocker);
|
||||
|
||||
// Remove from registry
|
||||
AssetInfo info;
|
||||
if (Cache.DeleteAsset(path, &info))
|
||||
@@ -573,7 +569,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
|
||||
// Check if given id is invalid
|
||||
if (!id.IsValid())
|
||||
{
|
||||
// Cancel operation
|
||||
LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path);
|
||||
return;
|
||||
}
|
||||
@@ -585,7 +580,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
|
||||
storage->CloseFileHandles(); // Close file handle to allow removing it
|
||||
if (!storage->HasAsset(id))
|
||||
{
|
||||
// Skip removing
|
||||
LOG(Warning, "Cannot remove file \'{0}\'. It doesn\'t contain asset {1}.", path, id);
|
||||
return;
|
||||
}
|
||||
@@ -703,13 +697,11 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (JsonStorageProxy::ChangeId(dstPath, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -774,12 +766,9 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
|
||||
FileSystem::DeleteFile(tmpPath);
|
||||
|
||||
// Reload storage
|
||||
if (auto storage = ContentStorageManager::GetStorage(dstPath))
|
||||
{
|
||||
auto storage = ContentStorageManager::GetStorage(dstPath);
|
||||
if (storage)
|
||||
{
|
||||
storage->Reload();
|
||||
}
|
||||
storage->Reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,10 +779,8 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
|
||||
|
||||
void Content::UnloadAsset(Asset* asset)
|
||||
{
|
||||
// Check input
|
||||
if (asset == nullptr)
|
||||
return;
|
||||
|
||||
asset->DeleteObject();
|
||||
}
|
||||
|
||||
@@ -919,12 +906,8 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script
|
||||
|
||||
Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
|
||||
{
|
||||
// Early out
|
||||
if (!id.IsValid())
|
||||
{
|
||||
// Back
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check if asset has been already loaded
|
||||
Asset* result = GetAsset(id);
|
||||
@@ -936,7 +919,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
|
||||
LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -954,12 +936,8 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
|
||||
LoadCallAssetsLocker.Lock();
|
||||
const bool contains = LoadCallAssets.Contains(id);
|
||||
LoadCallAssetsLocker.Unlock();
|
||||
|
||||
if (!contains)
|
||||
{
|
||||
return GetAsset(id);
|
||||
}
|
||||
|
||||
Platform::Sleep(1);
|
||||
}
|
||||
}
|
||||
@@ -967,7 +945,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
|
||||
{
|
||||
// Mark asset as loading
|
||||
LoadCallAssets.Add(id);
|
||||
|
||||
LoadCallAssetsLocker.Unlock();
|
||||
}
|
||||
|
||||
@@ -988,7 +965,7 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
|
||||
// Get cached asset info (from registry)
|
||||
if (!GetAssetInfo(id, assetInfo))
|
||||
{
|
||||
LOG(Warning, "Invalid or missing asset ({0}, {1}).", id.ToString(Guid::FormatType::N), type.ToString());
|
||||
LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1032,11 +1009,13 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
|
||||
ASSERT(!Assets.ContainsKey(id));
|
||||
#endif
|
||||
Assets.Add(id, result);
|
||||
AssetsLocker.Unlock();
|
||||
|
||||
// Start asset loading
|
||||
// TODO: refactor this to create asset loading task-chain before AssetsLocker.Lock() to allow better parallelization
|
||||
result->startLoading();
|
||||
|
||||
AssetsLocker.Unlock();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ protected:
|
||||
// [ContentLoadTask]
|
||||
Result run() override
|
||||
{
|
||||
if (IsCancelRequested())
|
||||
return Result::Ok;
|
||||
PROFILE_CPU();
|
||||
|
||||
AssetReference<BinaryAsset> ref = _asset.Get();
|
||||
@@ -67,8 +69,6 @@ protected:
|
||||
{
|
||||
if (IsCancelRequested())
|
||||
return Result::Ok;
|
||||
|
||||
// Load it
|
||||
#if TRACY_ENABLE
|
||||
ZoneScoped;
|
||||
ZoneName(*name, name.Length());
|
||||
|
||||
@@ -31,10 +31,13 @@ public:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
Asset->_loadFailed = true;
|
||||
Asset->_isLoaded = false;
|
||||
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
|
||||
Asset->_loadingTask = nullptr;
|
||||
if (Asset->_loadingTask == this)
|
||||
{
|
||||
Asset->_loadFailed = true;
|
||||
Asset->_isLoaded = false;
|
||||
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
|
||||
Asset->_loadingTask = nullptr;
|
||||
}
|
||||
Asset->Locker.Unlock();
|
||||
}
|
||||
}
|
||||
@@ -73,7 +76,10 @@ protected:
|
||||
{
|
||||
if (Asset)
|
||||
{
|
||||
Asset->_loadingTask = nullptr;
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
Asset->_loadingTask = nullptr;
|
||||
Asset->Locker.Unlock();
|
||||
Asset = nullptr;
|
||||
}
|
||||
|
||||
@@ -84,7 +90,10 @@ protected:
|
||||
{
|
||||
if (Asset)
|
||||
{
|
||||
Asset->_loadingTask = nullptr;
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
Asset->_loadingTask = nullptr;
|
||||
Asset->Locker.Unlock();
|
||||
Asset = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,7 @@ public:
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="SoftAssetReferenceBase"/> class.
|
||||
/// </summary>
|
||||
~SoftAssetReferenceBase()
|
||||
{
|
||||
}
|
||||
~SoftAssetReferenceBase();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -1302,15 +1302,15 @@ void FlaxStorage::CloseFileHandles()
|
||||
// In those situations all the async tasks using this storage should be cancelled externally
|
||||
|
||||
// Ensure that no one is using this resource
|
||||
int32 waitTime = 10;
|
||||
int32 waitTime = 100;
|
||||
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
|
||||
Platform::Sleep(10);
|
||||
Platform::Sleep(1);
|
||||
if (Platform::AtomicRead(&_chunksLock) != 0)
|
||||
{
|
||||
// File can be locked by some streaming tasks (eg. AudioClip::StreamingTask or StreamModelLODTask)
|
||||
Entry e;
|
||||
for (int32 i = 0; i < GetEntriesCount(); i++)
|
||||
{
|
||||
Entry e;
|
||||
GetEntry(i, e);
|
||||
Asset* asset = Content::GetAsset(e.ID);
|
||||
if (asset)
|
||||
@@ -1320,8 +1320,12 @@ void FlaxStorage::CloseFileHandles()
|
||||
}
|
||||
}
|
||||
}
|
||||
waitTime = 100;
|
||||
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
|
||||
Platform::Sleep(1);
|
||||
ASSERT(_chunksLock == 0);
|
||||
|
||||
// Close file handles (from all threads)
|
||||
_file.DeleteAll();
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,8 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
|
||||
// Import model file
|
||||
ModelData modelData;
|
||||
String errorMsg;
|
||||
String autoImportOutput = String(StringUtils::GetDirectoryName(context.TargetAssetPath)) / StringUtils::GetFileNameWithoutExtension(context.InputPath);
|
||||
String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath));
|
||||
autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
|
||||
if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput))
|
||||
{
|
||||
LOG(Error, "Cannot import model file. {0}", errorMsg);
|
||||
|
||||
@@ -22,6 +22,16 @@ private:
|
||||
int32 _capacity;
|
||||
AllocationData _allocation;
|
||||
|
||||
FORCE_INLINE static int32 ToItemCount(int32 size)
|
||||
{
|
||||
return Math::DivideAndRoundUp<int32>(size, sizeof(ItemType));
|
||||
}
|
||||
|
||||
FORCE_INLINE static int32 ToItemCapacity(int32 size)
|
||||
{
|
||||
return Math::Max<int32>(Math::DivideAndRoundUp<int32>(size, sizeof(ItemType)), 1);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitArray"/> class.
|
||||
@@ -41,7 +51,7 @@ public:
|
||||
, _capacity(capacity)
|
||||
{
|
||||
if (capacity > 0)
|
||||
_allocation.Allocate(Math::Max<ItemType>(capacity / sizeof(ItemType), 1));
|
||||
_allocation.Allocate(ToItemCapacity(capacity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,7 +63,7 @@ public:
|
||||
_count = _capacity = other.Count();
|
||||
if (_capacity > 0)
|
||||
{
|
||||
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
|
||||
const int32 itemsCapacity = ToItemCapacity(_capacity);
|
||||
_allocation.Allocate(itemsCapacity);
|
||||
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
|
||||
}
|
||||
@@ -69,7 +79,7 @@ public:
|
||||
_count = _capacity = other.Count();
|
||||
if (_capacity > 0)
|
||||
{
|
||||
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
|
||||
const int32 itemsCapacity = ToItemCapacity(_capacity);
|
||||
_allocation.Allocate(itemsCapacity);
|
||||
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
|
||||
}
|
||||
@@ -101,7 +111,7 @@ public:
|
||||
{
|
||||
_allocation.Free();
|
||||
_capacity = other._count;
|
||||
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
|
||||
const int32 itemsCapacity = ToItemCapacity(_capacity);
|
||||
_allocation.Allocate(itemsCapacity);
|
||||
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
|
||||
}
|
||||
@@ -246,7 +256,7 @@ public:
|
||||
return;
|
||||
ASSERT(capacity >= 0);
|
||||
const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0;
|
||||
_allocation.Relocate(Math::Max<ItemType>(capacity / sizeof(ItemType), 1), _count / sizeof(ItemType), count / sizeof(ItemType));
|
||||
_allocation.Relocate(ToItemCapacity(capacity), ToItemCount(_count), ToItemCount(count));
|
||||
_capacity = capacity;
|
||||
_count = count;
|
||||
}
|
||||
@@ -272,7 +282,7 @@ public:
|
||||
{
|
||||
if (_capacity < minCapacity)
|
||||
{
|
||||
const int32 capacity = _allocation.CalculateCapacityGrow(Math::Max<int32>(_capacity / sizeof(ItemType), 1), minCapacity);
|
||||
const int32 capacity = _allocation.CalculateCapacityGrow(ToItemCapacity(_capacity), minCapacity);
|
||||
SetCapacity(capacity, preserveContents);
|
||||
}
|
||||
}
|
||||
@@ -284,7 +294,7 @@ public:
|
||||
void SetAll(const bool value)
|
||||
{
|
||||
if (_count != 0)
|
||||
Platform::MemorySet(_allocation.Get(), Math::Max<ItemType>(_count / sizeof(ItemType), 1), value ? MAX_int32 : 0);
|
||||
Platform::MemorySet(_allocation.Get(), ToItemCount(_count) * sizeof(ItemType), value ? MAX_uint32 : 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -100,7 +100,7 @@ public:
|
||||
int32 _chunkIndex;
|
||||
int32 _index;
|
||||
|
||||
Iterator(ChunkedArray const* collection, const int32 index)
|
||||
Iterator(const ChunkedArray* collection, const int32 index)
|
||||
: _collection(const_cast<ChunkedArray*>(collection))
|
||||
, _chunkIndex(index / ChunkSize)
|
||||
, _index(index % ChunkSize)
|
||||
@@ -122,29 +122,29 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE ChunkedArray* GetChunkedArray() const
|
||||
Iterator(Iterator&& i)
|
||||
: _collection(i._collection)
|
||||
, _chunkIndex(i._chunkIndex)
|
||||
, _index(i._index)
|
||||
{
|
||||
return _collection;
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE int32 Index() const
|
||||
{
|
||||
return _chunkIndex * ChunkSize + _index;
|
||||
}
|
||||
|
||||
public:
|
||||
bool IsEnd() const
|
||||
FORCE_INLINE bool IsEnd() const
|
||||
{
|
||||
return Index() == _collection->Count();
|
||||
return (_chunkIndex * ChunkSize + _index) == _collection->_count;
|
||||
}
|
||||
|
||||
bool IsNotEnd() const
|
||||
FORCE_INLINE bool IsNotEnd() const
|
||||
{
|
||||
return Index() != _collection->Count();
|
||||
return (_chunkIndex * ChunkSize + _index) != _collection->_count;
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE T& operator*() const
|
||||
{
|
||||
return _collection->_chunks[_chunkIndex]->At(_index);
|
||||
@@ -155,7 +155,6 @@ public:
|
||||
return &_collection->_chunks[_chunkIndex]->At(_index);
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE bool operator==(const Iterator& v) const
|
||||
{
|
||||
return _collection == v._collection && _chunkIndex == v._chunkIndex && _index == v._index;
|
||||
@@ -166,17 +165,22 @@ public:
|
||||
return _collection != v._collection || _chunkIndex != v._chunkIndex || _index != v._index;
|
||||
}
|
||||
|
||||
public:
|
||||
Iterator& operator=(const Iterator& v)
|
||||
{
|
||||
_collection = v._collection;
|
||||
_chunkIndex = v._chunkIndex;
|
||||
_index = v._index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
// Check if it is not at end
|
||||
const int32 end = _collection->Count();
|
||||
if (Index() != end)
|
||||
if ((_chunkIndex * ChunkSize + _index) != _collection->_count)
|
||||
{
|
||||
// Move forward within chunk
|
||||
_index++;
|
||||
|
||||
// Check if need to change chunk
|
||||
if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1)
|
||||
{
|
||||
// Move to next chunk
|
||||
@@ -189,9 +193,9 @@ public:
|
||||
|
||||
Iterator operator++(int)
|
||||
{
|
||||
Iterator temp = *this;
|
||||
++temp;
|
||||
return temp;
|
||||
Iterator i = *this;
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
Iterator& operator--()
|
||||
@@ -199,7 +203,6 @@ public:
|
||||
// Check if it's not at beginning
|
||||
if (_index != 0 || _chunkIndex != 0)
|
||||
{
|
||||
// Check if need to change chunk
|
||||
if (_index == 0)
|
||||
{
|
||||
// Move to previous chunk
|
||||
@@ -217,9 +220,9 @@ public:
|
||||
|
||||
Iterator operator--(int)
|
||||
{
|
||||
Iterator temp = *this;
|
||||
--temp;
|
||||
return temp;
|
||||
Iterator i = *this;
|
||||
--i;
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -294,7 +297,7 @@ public:
|
||||
{
|
||||
if (IsEmpty())
|
||||
return;
|
||||
ASSERT(i.GetChunkedArray() == this);
|
||||
ASSERT(i._collection == this);
|
||||
ASSERT(i._chunkIndex < _chunks.Count() && i._index < ChunkSize);
|
||||
ASSERT(i.Index() < Count());
|
||||
|
||||
@@ -432,11 +435,31 @@ public:
|
||||
|
||||
Iterator End() const
|
||||
{
|
||||
return Iterator(this, Count());
|
||||
return Iterator(this, _count);
|
||||
}
|
||||
|
||||
Iterator IteratorAt(int32 index) const
|
||||
{
|
||||
return Iterator(this, index);
|
||||
}
|
||||
|
||||
FORCE_INLINE Iterator begin()
|
||||
{
|
||||
return Iterator(this, 0);
|
||||
}
|
||||
|
||||
FORCE_INLINE Iterator end()
|
||||
{
|
||||
return Iterator(this, _count);
|
||||
}
|
||||
|
||||
FORCE_INLINE const Iterator begin() const
|
||||
{
|
||||
return Iterator(this, 0);
|
||||
}
|
||||
|
||||
FORCE_INLINE const Iterator end() const
|
||||
{
|
||||
return Iterator(this, _count);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,13 +2,26 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// Default capacity for the dictionaries (amount of space for the elements)
|
||||
/// </summary>
|
||||
#define DICTIONARY_DEFAULT_CAPACITY 256
|
||||
#include "Engine/Platform/Defines.h"
|
||||
|
||||
/// <summary>
|
||||
/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size)
|
||||
/// Default capacity for the dictionaries (amount of space for the elements).
|
||||
/// </summary>
|
||||
#ifndef DICTIONARY_DEFAULT_CAPACITY
|
||||
#if PLATFORM_DESKTOP
|
||||
#define DICTIONARY_DEFAULT_CAPACITY 256
|
||||
#else
|
||||
#define DICTIONARY_DEFAULT_CAPACITY 64
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Default slack space divider for the dictionaries.
|
||||
/// </summary>
|
||||
#define DICTIONARY_DEFAULT_SLACK_SCALE 3
|
||||
|
||||
/// <summary>
|
||||
/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size).
|
||||
/// </summary>
|
||||
#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks)
|
||||
//#define DICTIONARY_PROB_FUNC(size, numChecks) (1)
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
private:
|
||||
State _state;
|
||||
|
||||
void Free()
|
||||
FORCE_INLINE void Free()
|
||||
{
|
||||
if (_state == Occupied)
|
||||
{
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
_state = Empty;
|
||||
}
|
||||
|
||||
void Delete()
|
||||
FORCE_INLINE void Delete()
|
||||
{
|
||||
_state = Deleted;
|
||||
Memory::DestructItem(&Key);
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
}
|
||||
|
||||
template<typename KeyComparableType>
|
||||
void Occupy(const KeyComparableType& key)
|
||||
FORCE_INLINE void Occupy(const KeyComparableType& key)
|
||||
{
|
||||
Memory::ConstructItems(&Key, &key, 1);
|
||||
Memory::ConstructItem(&Value);
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
}
|
||||
|
||||
template<typename KeyComparableType>
|
||||
void Occupy(const KeyComparableType& key, const ValueType& value)
|
||||
FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
|
||||
{
|
||||
Memory::ConstructItems(&Key, &key, 1);
|
||||
Memory::ConstructItems(&Value, &value, 1);
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
}
|
||||
|
||||
template<typename KeyComparableType>
|
||||
void Occupy(const KeyComparableType& key, ValueType&& value)
|
||||
FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
|
||||
{
|
||||
Memory::ConstructItems(&Key, &key, 1);
|
||||
Memory::MoveItems(&Value, &value, 1);
|
||||
@@ -132,9 +132,6 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="other">The other collection to move.</param>
|
||||
Dictionary(Dictionary&& other) noexcept
|
||||
: _elementsCount(other._elementsCount)
|
||||
, _deletedCount(other._deletedCount)
|
||||
, _size(other._size)
|
||||
{
|
||||
_elementsCount = other._elementsCount;
|
||||
_deletedCount = other._deletedCount;
|
||||
@@ -375,8 +372,12 @@ public:
|
||||
template<typename KeyComparableType>
|
||||
ValueType& At(const KeyComparableType& key)
|
||||
{
|
||||
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
|
||||
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
|
||||
Compact();
|
||||
|
||||
// Ensure to have enough memory for the next item (in case of new element insertion)
|
||||
EnsureCapacity(_elementsCount + _deletedCount + 1);
|
||||
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
|
||||
|
||||
// Find location of the item or place to insert it
|
||||
FindPositionResult pos;
|
||||
@@ -388,9 +389,9 @@ public:
|
||||
|
||||
// Insert
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
_elementsCount++;
|
||||
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
|
||||
bucket.Occupy(key);
|
||||
_elementsCount++;
|
||||
return bucket.Value;
|
||||
}
|
||||
|
||||
@@ -493,7 +494,7 @@ public:
|
||||
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (i->Value)
|
||||
Delete(i->Value);
|
||||
::Delete(i->Value);
|
||||
}
|
||||
Clear();
|
||||
}
|
||||
@@ -533,13 +534,22 @@ public:
|
||||
}
|
||||
_size = capacity;
|
||||
Bucket* oldData = oldAllocation.Get();
|
||||
if (oldElementsCount != 0 && preserveContents)
|
||||
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
|
||||
{
|
||||
// TODO; move keys and values on realloc
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < oldSize; i++)
|
||||
{
|
||||
if (oldData[i].IsOccupied())
|
||||
Add(oldData[i].Key, MoveTemp(oldData[i].Value));
|
||||
Bucket& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.Key, pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
|
||||
Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
|
||||
Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
|
||||
bucket->_state = Bucket::Occupied;
|
||||
_elementsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldElementsCount != 0)
|
||||
@@ -558,9 +568,9 @@ public:
|
||||
{
|
||||
if (_size >= minCapacity)
|
||||
return;
|
||||
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
|
||||
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
|
||||
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
|
||||
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
|
||||
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
|
||||
capacity = DICTIONARY_DEFAULT_CAPACITY;
|
||||
SetCapacity(capacity, preserveContents);
|
||||
}
|
||||
|
||||
@@ -584,24 +594,10 @@ public:
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>Weak reference to the stored bucket.</returns>
|
||||
template<typename KeyComparableType>
|
||||
Bucket* Add(const KeyComparableType& key, const ValueType& value)
|
||||
FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value)
|
||||
{
|
||||
// Ensure to have enough memory for the next item (in case of new element insertion)
|
||||
EnsureCapacity(_elementsCount + _deletedCount + 1);
|
||||
|
||||
// Find location of the item or place to insert it
|
||||
FindPositionResult pos;
|
||||
FindPosition(key, pos);
|
||||
|
||||
// Ensure key is unknown
|
||||
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
|
||||
|
||||
// Insert
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
|
||||
Bucket* bucket = OnAdd(key);
|
||||
bucket->Occupy(key, value);
|
||||
_elementsCount++;
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
@@ -612,24 +608,10 @@ public:
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>Weak reference to the stored bucket.</returns>
|
||||
template<typename KeyComparableType>
|
||||
Bucket* Add(const KeyComparableType& key, ValueType&& value)
|
||||
FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value)
|
||||
{
|
||||
// Ensure to have enough memory for the next item (in case of new element insertion)
|
||||
EnsureCapacity(_elementsCount + _deletedCount + 1);
|
||||
|
||||
// Find location of the item or place to insert it
|
||||
FindPositionResult pos;
|
||||
FindPosition(key, pos);
|
||||
|
||||
// Ensure key is unknown
|
||||
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
|
||||
|
||||
// Insert
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
|
||||
Bucket* bucket = OnAdd(key);
|
||||
bucket->Occupy(key, MoveTemp(value));
|
||||
_elementsCount++;
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
@@ -851,7 +833,7 @@ public:
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
protected:
|
||||
private:
|
||||
/// <summary>
|
||||
/// The result container of the dictionary item lookup searching.
|
||||
/// </summary>
|
||||
@@ -911,4 +893,66 @@ protected:
|
||||
result.ObjectIndex = -1;
|
||||
result.FreeSlotIndex = insertPos;
|
||||
}
|
||||
|
||||
template<typename KeyComparableType>
|
||||
Bucket* OnAdd(const KeyComparableType& key)
|
||||
{
|
||||
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
|
||||
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
|
||||
Compact();
|
||||
|
||||
// Ensure to have enough memory for the next item (in case of new element insertion)
|
||||
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
|
||||
|
||||
// Find location of the item or place to insert it
|
||||
FindPositionResult pos;
|
||||
FindPosition(key, pos);
|
||||
|
||||
// Ensure key is unknown
|
||||
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
|
||||
|
||||
// Insert
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
_elementsCount++;
|
||||
return &_allocation.Get()[pos.FreeSlotIndex];
|
||||
}
|
||||
|
||||
void Compact()
|
||||
{
|
||||
if (_elementsCount == 0)
|
||||
{
|
||||
// Fast path if it's empty
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
data[i]._state = Bucket::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rebuild entire table completely
|
||||
AllocationData oldAllocation;
|
||||
oldAllocation.Swap(_allocation);
|
||||
_allocation.Allocate(_size);
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
data[i]._state = Bucket::Empty;
|
||||
Bucket* oldData = oldAllocation.Get();
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
{
|
||||
Bucket& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.Key, pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
|
||||
Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
|
||||
Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
|
||||
bucket->_state = Bucket::Occupied;
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
oldData[i].Free();
|
||||
}
|
||||
_deletedCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,26 +37,33 @@ public:
|
||||
private:
|
||||
State _state;
|
||||
|
||||
void Free()
|
||||
FORCE_INLINE void Free()
|
||||
{
|
||||
if (_state == Occupied)
|
||||
Memory::DestructItem(&Item);
|
||||
_state = Empty;
|
||||
}
|
||||
|
||||
void Delete()
|
||||
FORCE_INLINE void Delete()
|
||||
{
|
||||
_state = Deleted;
|
||||
Memory::DestructItem(&Item);
|
||||
}
|
||||
|
||||
template<typename ItemType>
|
||||
void Occupy(const ItemType& item)
|
||||
FORCE_INLINE void Occupy(const ItemType& item)
|
||||
{
|
||||
Memory::ConstructItems(&Item, &item, 1);
|
||||
_state = Occupied;
|
||||
}
|
||||
|
||||
template<typename ItemType>
|
||||
FORCE_INLINE void Occupy(ItemType& item)
|
||||
{
|
||||
Memory::MoveItems(&Item, &item, 1);
|
||||
_state = Occupied;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsEmpty() const
|
||||
{
|
||||
return _state == Empty;
|
||||
@@ -108,9 +115,6 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="other">The other collection to move.</param>
|
||||
HashSet(HashSet&& other) noexcept
|
||||
: _elementsCount(other._elementsCount)
|
||||
, _deletedCount(other._deletedCount)
|
||||
, _size(other._size)
|
||||
{
|
||||
_elementsCount = other._elementsCount;
|
||||
_deletedCount = other._deletedCount;
|
||||
@@ -169,7 +173,7 @@ public:
|
||||
/// </summary>
|
||||
~HashSet()
|
||||
{
|
||||
SetCapacity(0, false);
|
||||
Clear();
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -216,6 +220,7 @@ public:
|
||||
HashSet* _collection;
|
||||
int32 _index;
|
||||
|
||||
public:
|
||||
Iterator(HashSet* collection, const int32 index)
|
||||
: _collection(collection)
|
||||
, _index(index)
|
||||
@@ -228,7 +233,12 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
Iterator()
|
||||
: _collection(nullptr)
|
||||
, _index(-1)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const Iterator& i)
|
||||
: _collection(i._collection)
|
||||
, _index(i._index)
|
||||
@@ -242,6 +252,11 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE int32 Index() const
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsEnd() const
|
||||
{
|
||||
return _index == _collection->_size;
|
||||
@@ -398,13 +413,21 @@ public:
|
||||
}
|
||||
_size = capacity;
|
||||
Bucket* oldData = oldAllocation.Get();
|
||||
if (oldElementsCount != 0 && preserveContents)
|
||||
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
|
||||
{
|
||||
// TODO; move keys and values on realloc
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < oldSize; i++)
|
||||
{
|
||||
if (oldData[i].IsOccupied())
|
||||
Add(oldData[i].Item);
|
||||
Bucket& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.Item, pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
|
||||
Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
|
||||
bucket->_state = Bucket::Occupied;
|
||||
_elementsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldElementsCount != 0)
|
||||
@@ -421,14 +444,26 @@ public:
|
||||
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
|
||||
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
|
||||
{
|
||||
if (Capacity() >= minCapacity)
|
||||
if (_size >= minCapacity)
|
||||
return;
|
||||
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
|
||||
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
|
||||
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
|
||||
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
|
||||
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
|
||||
capacity = DICTIONARY_DEFAULT_CAPACITY;
|
||||
SetCapacity(capacity, preserveContents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
|
||||
/// </summary>
|
||||
/// <param name="other">The other collection.</param>
|
||||
void Swap(HashSet& other)
|
||||
{
|
||||
::Swap(_elementsCount, other._elementsCount);
|
||||
::Swap(_deletedCount, other._deletedCount);
|
||||
::Swap(_size, other._size);
|
||||
_allocation.Swap(other._allocation);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Add element to the collection.
|
||||
@@ -438,24 +473,23 @@ public:
|
||||
template<typename ItemType>
|
||||
bool Add(const ItemType& item)
|
||||
{
|
||||
// Ensure to have enough memory for the next item (in case of new element insertion)
|
||||
EnsureCapacity(_elementsCount + _deletedCount + 1);
|
||||
Bucket* bucket = OnAdd(item);
|
||||
if (bucket)
|
||||
bucket->Occupy(item);
|
||||
return bucket != nullptr;
|
||||
}
|
||||
|
||||
// Find location of the item or place to insert it
|
||||
FindPositionResult pos;
|
||||
FindPosition(item, pos);
|
||||
|
||||
// Check if object has been already added
|
||||
if (pos.ObjectIndex != -1)
|
||||
return false;
|
||||
|
||||
// Insert
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
|
||||
bucket->Occupy(item);
|
||||
_elementsCount++;
|
||||
|
||||
return true;
|
||||
/// <summary>
|
||||
/// Add element to the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The element to add to the set.</param>
|
||||
/// <returns>True if element has been added to the collection, otherwise false if the element is already present.</returns>
|
||||
bool Add(T&& item)
|
||||
{
|
||||
Bucket* bucket = OnAdd(item);
|
||||
if (bucket)
|
||||
bucket->Occupy(MoveTemp(item));
|
||||
return bucket != nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -593,7 +627,7 @@ public:
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
protected:
|
||||
private:
|
||||
/// <summary>
|
||||
/// The result container of the set item lookup searching.
|
||||
/// </summary>
|
||||
@@ -654,4 +688,66 @@ protected:
|
||||
result.ObjectIndex = -1;
|
||||
result.FreeSlotIndex = insertPos;
|
||||
}
|
||||
|
||||
template<typename ItemType>
|
||||
Bucket* OnAdd(const ItemType& key)
|
||||
{
|
||||
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
|
||||
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
|
||||
Compact();
|
||||
|
||||
// Ensure to have enough memory for the next item (in case of new element insertion)
|
||||
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
|
||||
|
||||
// Find location of the item or place to insert it
|
||||
FindPositionResult pos;
|
||||
FindPosition(key, pos);
|
||||
|
||||
// Check if object has been already added
|
||||
if (pos.ObjectIndex != -1)
|
||||
return nullptr;
|
||||
|
||||
// Insert
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
_elementsCount++;
|
||||
return &_allocation.Get()[pos.FreeSlotIndex];
|
||||
}
|
||||
|
||||
void Compact()
|
||||
{
|
||||
if (_elementsCount == 0)
|
||||
{
|
||||
// Fast path if it's empty
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
data[i]._state = Bucket::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rebuild entire table completely
|
||||
AllocationData oldAllocation;
|
||||
oldAllocation.Swap(_allocation);
|
||||
_allocation.Allocate(_size);
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
data[i]._state = Bucket::Empty;
|
||||
Bucket* oldData = oldAllocation.Get();
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
{
|
||||
Bucket& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.Item, pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
|
||||
Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
|
||||
bucket->_state = Bucket::Occupied;
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
oldData[i].Free();
|
||||
}
|
||||
_deletedCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "Engine/Core/Config/Settings.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Content/Asset.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/SceneReference.h"
|
||||
@@ -76,6 +77,12 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")")
|
||||
bool ShadersGenerateDebugData = false;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, skips bundling default engine fonts for UI. Use if to reduce build size if you don't use default engine fonts but custom ones only.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")")
|
||||
bool SkipDefaultFonts = false;
|
||||
|
||||
/// <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>
|
||||
@@ -106,6 +113,7 @@ public:
|
||||
DESERIALIZE(AdditionalAssetFolders);
|
||||
DESERIALIZE(ShadersNoOptimize);
|
||||
DESERIALIZE(ShadersGenerateDebugData);
|
||||
DESERIALIZE(SkipDefaultFonts);
|
||||
DESERIALIZE(SkipDotnetPackaging);
|
||||
DESERIALIZE(SkipUnusedDotnetLibsPackaging);
|
||||
}
|
||||
|
||||
+101
-114
@@ -226,7 +226,7 @@ public:
|
||||
/// <returns>Function result</returns>
|
||||
FORCE_INLINE ReturnType operator()(Params... params) const
|
||||
{
|
||||
ASSERT(_function);
|
||||
ASSERT_LOW_LAYER(_function);
|
||||
return _function(_callee, Forward<Params>(params)...);
|
||||
}
|
||||
|
||||
@@ -289,8 +289,13 @@ protected:
|
||||
intptr volatile _ptr = 0;
|
||||
intptr volatile _size = 0;
|
||||
#else
|
||||
HashSet<FunctionType>* _functions = nullptr;
|
||||
CriticalSection* _locker = nullptr;
|
||||
struct Data
|
||||
{
|
||||
HashSet<FunctionType> Functions;
|
||||
CriticalSection Locker;
|
||||
};
|
||||
// Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations.
|
||||
intptr volatile _data = 0;
|
||||
#endif
|
||||
typedef void (*StubSignature)(void*, Params...);
|
||||
|
||||
@@ -314,15 +319,12 @@ public:
|
||||
_ptr = (intptr)newBindings;
|
||||
_size = newSize;
|
||||
#else
|
||||
if (other._functions == nullptr)
|
||||
Data* otherData = (Data*)Platform::AtomicRead(&_data);
|
||||
if (otherData == nullptr)
|
||||
return;
|
||||
_functions = New<HashSet<FunctionType>>(*other._functions);
|
||||
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (i->Item._function && i->Item._lambda)
|
||||
i->Item.LambdaCtor();
|
||||
}
|
||||
_locker = other._locker;
|
||||
ScopeLock lock(otherData->Locker);
|
||||
for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
|
||||
Bind(i->Item);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -334,10 +336,8 @@ public:
|
||||
other._ptr = 0;
|
||||
other._size = 0;
|
||||
#else
|
||||
_functions = other._functions;
|
||||
_locker = other._locker;
|
||||
other._functions = nullptr;
|
||||
other._locker = nullptr;
|
||||
_data = other._data;
|
||||
other._data = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -356,20 +356,11 @@ public:
|
||||
Allocator::Free((void*)_ptr);
|
||||
}
|
||||
#else
|
||||
if (_locker != nullptr)
|
||||
Data* data = (Data*)_data;
|
||||
if (data)
|
||||
{
|
||||
Allocator::Free(_locker);
|
||||
_locker = nullptr;
|
||||
}
|
||||
if (_functions != nullptr)
|
||||
{
|
||||
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (i->Item._lambda)
|
||||
i->Item.LambdaCtor();
|
||||
}
|
||||
Allocator::Free(_functions);
|
||||
_functions = nullptr;
|
||||
_data = 0;
|
||||
Delete(data);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -385,8 +376,13 @@ public:
|
||||
for (intptr i = 0; i < size; i++)
|
||||
Bind(bindings[i]);
|
||||
#else
|
||||
for (auto i = other._functions->Begin(); i.IsNotEnd(); ++i)
|
||||
Bind(i->Item);
|
||||
Data* otherData = (Data*)Platform::AtomicRead(&_data);
|
||||
if (otherData != nullptr)
|
||||
{
|
||||
ScopeLock lock(otherData->Locker);
|
||||
for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
|
||||
Bind(i->Item);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return *this;
|
||||
@@ -402,10 +398,8 @@ public:
|
||||
other._ptr = 0;
|
||||
other._size = 0;
|
||||
#else
|
||||
_functions = other._functions;
|
||||
_locker = other._locker;
|
||||
other._functions = nullptr;
|
||||
other._locker = nullptr;
|
||||
_data = other._data;
|
||||
other._data = 0;
|
||||
#endif
|
||||
}
|
||||
return *this;
|
||||
@@ -507,12 +501,20 @@ public:
|
||||
Allocator::Free(bindings);
|
||||
}
|
||||
#else
|
||||
if (_locker == nullptr)
|
||||
_locker = New<CriticalSection>();
|
||||
ScopeLock lock(*_locker);
|
||||
if (_functions == nullptr)
|
||||
_functions = New<HashSet<FunctionType>>(32);
|
||||
_functions->Add(f);
|
||||
Data* data = (Data*)Platform::AtomicRead(&_data);
|
||||
while (!data)
|
||||
{
|
||||
Data* newData = New<Data>();
|
||||
Data* oldData = (Data*)Platform::InterlockedCompareExchange(&_data, (intptr)newData, (intptr)data);
|
||||
if (oldData != data)
|
||||
{
|
||||
// Other thread already set the new data so free it and try again
|
||||
Delete(newData);
|
||||
}
|
||||
data = (Data*)Platform::AtomicRead(&_data);
|
||||
}
|
||||
ScopeLock lock(data->Locker);
|
||||
data->Functions.Add(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -568,13 +570,22 @@ public:
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (_locker == nullptr)
|
||||
_locker = New<CriticalSection>();
|
||||
ScopeLock lock(*_locker);
|
||||
if (_functions && _functions->Contains(f))
|
||||
return;
|
||||
Data* data = (Data*)Platform::AtomicRead(&_data);
|
||||
if (data)
|
||||
{
|
||||
data->Locker.Lock();
|
||||
if (data->Functions.Contains(f))
|
||||
{
|
||||
data->Locker.Unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Bind(f);
|
||||
#if !DELEGATE_USE_ATOMIC
|
||||
if (data)
|
||||
data->Locker.Unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -583,18 +594,9 @@ public:
|
||||
template<void(*Method)(Params...)>
|
||||
void Unbind()
|
||||
{
|
||||
#if DELEGATE_USE_ATOMIC
|
||||
FunctionType f;
|
||||
f.template Bind<Method>();
|
||||
Unbind(f);
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
return;
|
||||
FunctionType f;
|
||||
f.template Bind<Method>();
|
||||
ScopeLock lock(*_locker);
|
||||
_functions->Remove(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -604,18 +606,9 @@ public:
|
||||
template<class T, void(T::*Method)(Params...)>
|
||||
void Unbind(T* callee)
|
||||
{
|
||||
#if DELEGATE_USE_ATOMIC
|
||||
FunctionType f;
|
||||
f.template Bind<T, Method>(callee);
|
||||
Unbind(f);
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
return;
|
||||
FunctionType f;
|
||||
f.template Bind<T, Method>(callee);
|
||||
ScopeLock lock(*_locker);
|
||||
_functions->Remove(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -624,16 +617,8 @@ public:
|
||||
/// <param name="method">The method.</param>
|
||||
void Unbind(Signature method)
|
||||
{
|
||||
#if DELEGATE_USE_ATOMIC
|
||||
FunctionType f(method);
|
||||
Unbind(f);
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
return;
|
||||
FunctionType f(method);
|
||||
ScopeLock lock(*_locker);
|
||||
_functions->Remove(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -666,10 +651,11 @@ public:
|
||||
Unbind(f);
|
||||
}
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
Data* data = (Data*)Platform::AtomicRead(&_data);
|
||||
if (!data)
|
||||
return;
|
||||
ScopeLock lock(*_locker);
|
||||
_functions->Remove(f);
|
||||
ScopeLock lock(data->Locker);
|
||||
data->Functions.Remove(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -692,15 +678,11 @@ public:
|
||||
Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0);
|
||||
}
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
Data* data = (Data*)Platform::AtomicRead(&_data);
|
||||
if (!data)
|
||||
return;
|
||||
ScopeLock lock(*_locker);
|
||||
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (i->Item._lambda)
|
||||
i->Item.LambdaDtor();
|
||||
}
|
||||
_functions->Clear();
|
||||
ScopeLock lock(data->Locker);
|
||||
data->Functions.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -710,22 +692,24 @@ public:
|
||||
/// <returns>The bound functions count.</returns>
|
||||
int32 Count() const
|
||||
{
|
||||
int32 result = 0;
|
||||
#if DELEGATE_USE_ATOMIC
|
||||
int32 count = 0;
|
||||
const intptr size = Platform::AtomicRead((intptr volatile*)&_size);
|
||||
FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr);
|
||||
for (intptr i = 0; i < size; i++)
|
||||
{
|
||||
if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0)
|
||||
count++;
|
||||
result++;
|
||||
}
|
||||
return count;
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
return 0;
|
||||
ScopeLock lock(*_locker);
|
||||
return _functions->Count();
|
||||
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
|
||||
if (data)
|
||||
{
|
||||
ScopeLock lock(data->Locker);
|
||||
result = data->Functions.Count();
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -736,10 +720,14 @@ public:
|
||||
#if DELEGATE_USE_ATOMIC
|
||||
return (int32)Platform::AtomicRead((intptr volatile*)&_size);
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
return 0;
|
||||
ScopeLock lock(*_locker);
|
||||
return _functions->Capacity();
|
||||
int32 result = 0;
|
||||
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
|
||||
if (data)
|
||||
{
|
||||
ScopeLock lock(data->Locker);
|
||||
result = data->Functions.Capacity();
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -759,10 +747,14 @@ public:
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
return false;
|
||||
ScopeLock lock(*_locker);
|
||||
return _functions->Count() > 0;
|
||||
bool result = false;
|
||||
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
|
||||
if (data)
|
||||
{
|
||||
ScopeLock lock(data->Locker);
|
||||
result = data->Functions.Count() != 0;
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -791,18 +783,13 @@ public:
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
return 0;
|
||||
ScopeLock lock(*_locker);
|
||||
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
|
||||
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
|
||||
if (data)
|
||||
{
|
||||
if (i->Item._function != nullptr)
|
||||
ScopeLock lock(data->Locker);
|
||||
for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
buffer[count]._function = (StubSignature)i->Item._function;
|
||||
buffer[count]._callee = (void*)i->Item._callee;
|
||||
buffer[count]._lambda = (typename FunctionType::Lambda*)i->Item._lambda;
|
||||
if (buffer[count]._lambda)
|
||||
buffer[count].LambdaCtor();
|
||||
new(buffer + count) FunctionType((const FunctionType&)i->Item);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@@ -828,15 +815,15 @@ public:
|
||||
++bindings;
|
||||
}
|
||||
#else
|
||||
if (_functions == nullptr)
|
||||
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
|
||||
if (!data)
|
||||
return;
|
||||
ScopeLock lock(*_locker);
|
||||
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
|
||||
ScopeLock lock(data->Locker);
|
||||
for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
auto function = (StubSignature)(i->Item._function);
|
||||
auto callee = (void*)(i->Item._callee);
|
||||
if (function != nullptr)
|
||||
function(callee, Forward<Params>(params)...);
|
||||
const FunctionType& item = i->Item;
|
||||
ASSERT_LOW_LAYER(item._function);
|
||||
item._function(item._callee, Forward<Params>(params)...);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+21
-21
@@ -3,16 +3,15 @@
|
||||
#include "Log.h"
|
||||
#include "Engine/Engine/CommandLine.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
#include "Engine/Serialization/FileWriteStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Debug/Exceptions/Exceptions.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#endif
|
||||
#include <iostream>
|
||||
|
||||
@@ -199,35 +198,36 @@ void Log::Logger::WriteFloor()
|
||||
void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w)
|
||||
{
|
||||
const TimeSpan time = DateTime::Now() - LogStartTime;
|
||||
const int32 msgLength = msg.Length();
|
||||
|
||||
fmt_flax::format(w, TEXT("[ {0} ]: [{1}] "), *time.ToString('a'), ToString(type));
|
||||
|
||||
// On Windows convert all '\n' into '\r\n'
|
||||
#if PLATFORM_WINDOWS
|
||||
const int32 msgLength = msg.Length();
|
||||
if (msgLength > 1)
|
||||
bool hasWindowsNewLine = false;
|
||||
for (int32 i = 1; i < msgLength && !hasWindowsNewLine; i++)
|
||||
hasWindowsNewLine |= msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n';
|
||||
if (hasWindowsNewLine)
|
||||
{
|
||||
MemoryWriteStream msgStream(msgLength * sizeof(Char));
|
||||
msgStream.WriteChar(msg[0]);
|
||||
Array<Char> msgStream;
|
||||
msgStream.EnsureCapacity(msgLength);
|
||||
msgStream.Add(msg.Get()[0]);
|
||||
for (int32 i = 1; i < msgLength; i++)
|
||||
{
|
||||
if (msg[i - 1] != '\r' && msg[i] == '\n')
|
||||
msgStream.WriteChar(TEXT('\r'));
|
||||
msgStream.WriteChar(msg[i]);
|
||||
if (msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n')
|
||||
msgStream.Add(TEXT('\r'));
|
||||
msgStream.Add(msg.Get()[i]);
|
||||
}
|
||||
msgStream.WriteChar(msg[msgLength]);
|
||||
msgStream.WriteChar(TEXT('\0'));
|
||||
fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.GetHandle());
|
||||
//w.append(msgStream.GetHandle(), msgStream.GetHandle() + msgStream.GetPosition());
|
||||
msgStream.Add(TEXT('\0'));
|
||||
w.append(msgStream.Get(), (const Char*)(msgStream.Get() + msgStream.Count()));
|
||||
//fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.Get());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//w.append(msg.Get(), msg.Get() + msg.Length());
|
||||
fmt_flax::format(w, TEXT("{}"), msg);
|
||||
}
|
||||
#else
|
||||
fmt_flax::format(w, TEXT("{}"), msg);
|
||||
#endif
|
||||
|
||||
// Output raw message to the log
|
||||
w.append(msg.Get(), msg.Get() + msg.Length());
|
||||
//fmt_flax::format(w, TEXT("{}"), msg);
|
||||
}
|
||||
|
||||
void Log::Logger::Write(LogType type, const StringView& msg)
|
||||
|
||||
@@ -382,9 +382,8 @@ void Quaternion::GetRotationFromTo(const Float3& from, const Float3& to, Quatern
|
||||
v0.Normalize();
|
||||
v1.Normalize();
|
||||
|
||||
const float d = Float3::Dot(v0, v1);
|
||||
|
||||
// If dot == 1, vectors are the same
|
||||
const float d = Float3::Dot(v0, v1);
|
||||
if (d >= 1.0f)
|
||||
{
|
||||
result = Identity;
|
||||
|
||||
@@ -1077,6 +1077,115 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shortest arc quaternion to rotate this vector to the destination vector.
|
||||
/// </summary>
|
||||
/// <param name="from">The source vector.</param>
|
||||
/// <param name="to">The destination vector.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="fallbackAxis">The fallback axis.</param>
|
||||
public static void GetRotationFromTo(ref Float3 from, ref Float3 to, out Quaternion result, ref Float3 fallbackAxis)
|
||||
{
|
||||
// Based on Stan Melax's article in Game Programming Gems
|
||||
|
||||
Float3 v0 = from;
|
||||
Float3 v1 = to;
|
||||
v0.Normalize();
|
||||
v1.Normalize();
|
||||
|
||||
// If dot == 1, vectors are the same
|
||||
float d = Float3.Dot(ref v0, ref v1);
|
||||
if (d >= 1.0f)
|
||||
{
|
||||
result = Identity;
|
||||
return;
|
||||
}
|
||||
|
||||
if (d < 1e-6f - 1.0f)
|
||||
{
|
||||
if (fallbackAxis != Float3.Zero)
|
||||
{
|
||||
// Rotate 180 degrees about the fallback axis
|
||||
RotationAxis(ref fallbackAxis, Mathf.Pi, out result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generate an axis
|
||||
Float3 axis = Float3.Cross(Float3.UnitX, from);
|
||||
if (axis.LengthSquared < Mathf.Epsilon) // Pick another if colinear
|
||||
axis = Float3.Cross(Float3.UnitY, from);
|
||||
axis.Normalize();
|
||||
RotationAxis(ref axis, Mathf.Pi, out result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float s = Mathf.Sqrt((1 + d) * 2);
|
||||
float invS = 1 / s;
|
||||
Float3.Cross(ref v0, ref v1, out var c);
|
||||
result.X = c.X * invS;
|
||||
result.Y = c.Y * invS;
|
||||
result.Z = c.Z * invS;
|
||||
result.W = s * 0.5f;
|
||||
result.Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shortest arc quaternion to rotate this vector to the destination vector.
|
||||
/// </summary>
|
||||
/// <param name="from">The source vector.</param>
|
||||
/// <param name="to">The destination vector.</param>
|
||||
/// <param name="fallbackAxis">The fallback axis.</param>
|
||||
/// <returns>The rotation.</returns>
|
||||
public static Quaternion GetRotationFromTo(Float3 from, Float3 to, Float3 fallbackAxis)
|
||||
{
|
||||
GetRotationFromTo(ref from, ref to, out var result, ref fallbackAxis);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
|
||||
/// </summary>
|
||||
/// <param name="from">The source vector.</param>
|
||||
/// <param name="to">The destination vector.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
public static void FindBetween(ref Float3 from, ref Float3 to, out Quaternion result)
|
||||
{
|
||||
// http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
|
||||
float normFromNormTo = Mathf.Sqrt(from.LengthSquared * to.LengthSquared);
|
||||
if (normFromNormTo < Mathf.Epsilon)
|
||||
{
|
||||
result = Identity;
|
||||
return;
|
||||
}
|
||||
float w = normFromNormTo + Float3.Dot(from, to);
|
||||
if (w < 1.0-6f * normFromNormTo)
|
||||
{
|
||||
result = Mathf.Abs(from.X) > Mathf.Abs(from.Z)
|
||||
? new Quaternion(-from.Y, from.X, 0.0f, 0.0f)
|
||||
: new Quaternion(0.0f, -from.Z, from.Y, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Float3 cross = Float3.Cross(from, to);
|
||||
result = new Quaternion(cross.X, cross.Y, cross.Z, w);
|
||||
}
|
||||
result.Normalize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
|
||||
/// </summary>
|
||||
/// <param name="from">The source vector.</param>
|
||||
/// <param name="to">The destination vector.</param>
|
||||
/// <returns>The rotation.</returns>
|
||||
public static Quaternion FindBetween(Float3 from, Float3 to)
|
||||
{
|
||||
FindBetween(ref from, ref to, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a left-handed spherical billboard that rotates around a specified object position.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,9 +13,7 @@ namespace FlaxEngine.TypeConverters
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
@@ -23,9 +21,7 @@ namespace FlaxEngine.TypeConverters
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Double2Converter : TypeConverter
|
||||
internal class Double2Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Double3Converter : TypeConverter
|
||||
internal class Double3Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Double4Converter : TypeConverter
|
||||
internal class Double4Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Float2Converter : TypeConverter
|
||||
internal class Float2Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Float3Converter : TypeConverter
|
||||
internal class Float3Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,15 +7,13 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Float4Converter : TypeConverter
|
||||
internal class VectorConverter : TypeConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
@@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
internal static string[] GetParts(string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
if (v.Length == 1)
|
||||
{
|
||||
// When converting from ToString()
|
||||
v = str.Split(' ');
|
||||
for (int i = 0; i < v.Length; i++)
|
||||
v[i] = v[i].Substring(v[i].IndexOf(':') + 1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Float4Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Int2Converter : TypeConverter
|
||||
internal class Int2Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Int3Converter : TypeConverter
|
||||
internal class Int3Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Int4Converter : TypeConverter
|
||||
internal class Int4Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class QuaternionConverter : TypeConverter
|
||||
internal class QuaternionConverter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Vector2Converter : TypeConverter
|
||||
internal class Vector2Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Vector3Converter : TypeConverter
|
||||
internal class Vector3Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -7,34 +7,14 @@ using System.Globalization;
|
||||
|
||||
namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
internal class Vector4Converter : TypeConverter
|
||||
internal class Vector4Converter : VectorConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
string[] v = GetParts(str);
|
||||
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
|
||||
@@ -646,6 +646,12 @@ inline Vector2Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector2Ba
|
||||
return Vector2Base<T>(a) / b;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline uint32 GetHash(const Vector2Base<T>& key)
|
||||
{
|
||||
return (*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y;
|
||||
}
|
||||
|
||||
namespace Math
|
||||
{
|
||||
template<typename T>
|
||||
|
||||
@@ -977,6 +977,12 @@ inline Vector3Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector3Ba
|
||||
return Vector3Base<T>(a) / b;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline uint32 GetHash(const Vector3Base<T>& key)
|
||||
{
|
||||
return (((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z;
|
||||
}
|
||||
|
||||
namespace Math
|
||||
{
|
||||
template<typename T>
|
||||
|
||||
@@ -552,6 +552,12 @@ inline Vector4Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector4Ba
|
||||
return Vector4Base<T>(a) / b;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline uint32 GetHash(const Vector4Base<T>& key)
|
||||
{
|
||||
return (((((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z) * 397) ^*(uint32*)&key.W;
|
||||
}
|
||||
|
||||
namespace Math
|
||||
{
|
||||
template<typename T>
|
||||
|
||||
@@ -43,14 +43,14 @@ public:
|
||||
return Capacity;
|
||||
}
|
||||
|
||||
FORCE_INLINE void Allocate(uint64 capacity)
|
||||
FORCE_INLINE void Allocate(int32 capacity)
|
||||
{
|
||||
#if ENABLE_ASSERTION_LOW_LAYERS
|
||||
ASSERT(capacity <= Capacity);
|
||||
#endif
|
||||
}
|
||||
|
||||
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
|
||||
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
|
||||
{
|
||||
#if ENABLE_ASSERTION_LOW_LAYERS
|
||||
ASSERT(capacity <= Capacity);
|
||||
@@ -120,12 +120,15 @@ public:
|
||||
capacity |= capacity >> 4;
|
||||
capacity |= capacity >> 8;
|
||||
capacity |= capacity >> 16;
|
||||
capacity = (capacity + 1) * 2;
|
||||
uint64 capacity64 = (uint64)(capacity + 1) * 2;
|
||||
if (capacity64 > MAX_int32)
|
||||
capacity64 = MAX_int32;
|
||||
capacity = (int32)capacity64;
|
||||
}
|
||||
return capacity;
|
||||
}
|
||||
|
||||
FORCE_INLINE void Allocate(uint64 capacity)
|
||||
FORCE_INLINE void Allocate(int32 capacity)
|
||||
{
|
||||
#if ENABLE_ASSERTION_LOW_LAYERS
|
||||
ASSERT(!_data);
|
||||
@@ -137,7 +140,7 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
|
||||
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
|
||||
{
|
||||
T* newData = capacity != 0 ? (T*)Allocator::Allocate(capacity * sizeof(T)) : nullptr;
|
||||
#if !BUILD_RELEASE
|
||||
@@ -210,7 +213,7 @@ public:
|
||||
return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity);
|
||||
}
|
||||
|
||||
FORCE_INLINE void Allocate(uint64 capacity)
|
||||
FORCE_INLINE void Allocate(int32 capacity)
|
||||
{
|
||||
if (capacity > Capacity)
|
||||
{
|
||||
@@ -219,7 +222,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
|
||||
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
|
||||
{
|
||||
// Check if the new allocation will fit into inlined storage
|
||||
if (capacity <= Capacity)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Collections/Dictionary.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
|
||||
|
||||
@@ -107,6 +107,26 @@ public:
|
||||
ASSERT(index >= 0 && index < _length);
|
||||
return _data[index];
|
||||
}
|
||||
|
||||
FORCE_INLINE T* begin()
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
FORCE_INLINE T* end()
|
||||
{
|
||||
return _data + _length;
|
||||
}
|
||||
|
||||
FORCE_INLINE const T* begin() const
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
FORCE_INLINE const T* end() const
|
||||
{
|
||||
return _data + _length;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -327,6 +327,18 @@ public:
|
||||
bool operator!=(const String& other) const;
|
||||
|
||||
public:
|
||||
using StringViewBase::StartsWith;
|
||||
FORCE_INLINE bool StartsWith(const StringView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
|
||||
{
|
||||
return StringViewBase::StartsWith(prefix, searchCase);
|
||||
}
|
||||
|
||||
using StringViewBase::EndsWith;
|
||||
FORCE_INLINE bool EndsWith(const StringView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
|
||||
{
|
||||
return StringViewBase::EndsWith(suffix, searchCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the left most given number of characters.
|
||||
/// </summary>
|
||||
@@ -511,6 +523,18 @@ public:
|
||||
bool operator!=(const StringAnsi& other) const;
|
||||
|
||||
public:
|
||||
using StringViewBase::StartsWith;
|
||||
FORCE_INLINE bool StartsWith(const StringAnsiView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
|
||||
{
|
||||
return StringViewBase::StartsWith(prefix, searchCase);
|
||||
}
|
||||
|
||||
using StringViewBase::EndsWith;
|
||||
FORCE_INLINE bool EndsWith(const StringAnsiView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
|
||||
{
|
||||
return StringViewBase::EndsWith(suffix, searchCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves substring created from characters starting from startIndex to the String end.
|
||||
/// </summary>
|
||||
|
||||
@@ -2821,7 +2821,10 @@ void Variant::Inline()
|
||||
type = VariantType::Types::Vector4;
|
||||
}
|
||||
if (type != VariantType::Null)
|
||||
{
|
||||
ASSERT(sizeof(data) >= AsBlob.Length);
|
||||
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
|
||||
}
|
||||
}
|
||||
if (type != VariantType::Null)
|
||||
{
|
||||
@@ -2912,6 +2915,60 @@ void Variant::Inline()
|
||||
}
|
||||
}
|
||||
|
||||
void Variant::InvertInline()
|
||||
{
|
||||
byte data[sizeof(Matrix)];
|
||||
switch (Type.Type)
|
||||
{
|
||||
case VariantType::Bool:
|
||||
case VariantType::Int:
|
||||
case VariantType::Uint:
|
||||
case VariantType::Int64:
|
||||
case VariantType::Uint64:
|
||||
case VariantType::Float:
|
||||
case VariantType::Double:
|
||||
case VariantType::Pointer:
|
||||
case VariantType::String:
|
||||
case VariantType::Float2:
|
||||
case VariantType::Float3:
|
||||
case VariantType::Float4:
|
||||
case VariantType::Color:
|
||||
#if !USE_LARGE_WORLDS
|
||||
case VariantType::BoundingSphere:
|
||||
case VariantType::BoundingBox:
|
||||
case VariantType::Ray:
|
||||
#endif
|
||||
case VariantType::Guid:
|
||||
case VariantType::Quaternion:
|
||||
case VariantType::Rectangle:
|
||||
case VariantType::Int2:
|
||||
case VariantType::Int3:
|
||||
case VariantType::Int4:
|
||||
case VariantType::Int16:
|
||||
case VariantType::Uint16:
|
||||
case VariantType::Double2:
|
||||
case VariantType::Double3:
|
||||
case VariantType::Double4:
|
||||
static_assert(sizeof(data) >= sizeof(AsData), "Invalid memory size.");
|
||||
Platform::MemoryCopy(data, AsData, sizeof(AsData));
|
||||
break;
|
||||
#if USE_LARGE_WORLDS
|
||||
case VariantType::BoundingSphere:
|
||||
case VariantType::BoundingBox:
|
||||
case VariantType::Ray:
|
||||
#endif
|
||||
case VariantType::Transform:
|
||||
case VariantType::Matrix:
|
||||
ASSERT(sizeof(data) >= AsBlob.Length);
|
||||
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
|
||||
break;
|
||||
default:
|
||||
return; // Not used
|
||||
}
|
||||
SetType(VariantType(VariantType::Structure, InBuiltTypesTypeNames[Type.Type]));
|
||||
CopyStructure(data);
|
||||
}
|
||||
|
||||
Variant Variant::NewValue(const StringAnsiView& typeName)
|
||||
{
|
||||
Variant v;
|
||||
|
||||
@@ -372,6 +372,9 @@ public:
|
||||
// Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
|
||||
void Inline();
|
||||
|
||||
// Inverts the inlined value from in-built format into generic storage (eg. Float3 from inlined format into Structure).
|
||||
void InvertInline();
|
||||
|
||||
// Allocates the Variant of the specific type (eg. structure or object or value).
|
||||
static Variant NewValue(const StringAnsiView& typeName);
|
||||
|
||||
|
||||
@@ -7,15 +7,15 @@ Version::Version(int32 major, int32 minor, int32 build, int32 revision)
|
||||
{
|
||||
_major = Math::Max(major, 0);
|
||||
_minor = Math::Max(minor, 0);
|
||||
_build = Math::Max(build, 0);
|
||||
_revision = Math::Max(revision, 0);
|
||||
_build = Math::Max(build, -1);
|
||||
_revision = Math::Max(revision, -1);
|
||||
}
|
||||
|
||||
Version::Version(int32 major, int32 minor, int32 build)
|
||||
{
|
||||
_major = Math::Max(major, 0);
|
||||
_minor = Math::Max(minor, 0);
|
||||
_build = Math::Max(build, 0);
|
||||
_build = Math::Max(build, -1);
|
||||
_revision = -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,10 +51,14 @@ namespace Utilities
|
||||
int32 i = 0;
|
||||
double dblSUnits = static_cast<double>(units);
|
||||
for (; static_cast<uint64>(units / static_cast<double>(divider)) > 0; i++, units /= divider)
|
||||
dblSUnits = units / static_cast<double>(divider);
|
||||
dblSUnits = (double)units / (double)divider;
|
||||
if (i >= sizes.Length())
|
||||
i = 0;
|
||||
return String::Format(TEXT("{0} {1}"), RoundTo2DecimalPlaces(dblSUnits), sizes[i]);
|
||||
String text = String::Format(TEXT("{}"), RoundTo2DecimalPlaces(dblSUnits));
|
||||
const int32 dot = text.FindLast('.');
|
||||
if (dot != -1)
|
||||
text = text.Left(dot + 3);
|
||||
return String::Format(TEXT("{0} {1}"), text, sizes[i]);
|
||||
}
|
||||
|
||||
// Converts size of the file (in bytes) to the best fitting string
|
||||
|
||||
@@ -696,12 +696,15 @@ void* DebugDraw::AllocateContext()
|
||||
|
||||
void DebugDraw::FreeContext(void* context)
|
||||
{
|
||||
ASSERT(context);
|
||||
Memory::DestructItem((DebugDrawContext*)context);
|
||||
Allocator::Free(context);
|
||||
}
|
||||
|
||||
void DebugDraw::UpdateContext(void* context, float deltaTime)
|
||||
{
|
||||
if (!context)
|
||||
context = &GlobalContext;
|
||||
((DebugDrawContext*)context)->DebugDrawDefault.Update(deltaTime);
|
||||
((DebugDrawContext*)context)->DebugDrawDepthTest.Update(deltaTime);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#include "Engine/Threading/ThreadRegistry.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/JsonAsset.h"
|
||||
@@ -327,14 +326,6 @@ void Engine::OnUpdate()
|
||||
|
||||
// Update services
|
||||
EngineService::OnUpdate();
|
||||
|
||||
#ifdef USE_NETCORE
|
||||
// Force GC to run in background periodically to avoid large blocking collections causing hitches
|
||||
if (Time::Update.TicksCount % 60 == 0)
|
||||
{
|
||||
MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Engine::OnLateUpdate()
|
||||
@@ -596,11 +587,13 @@ void EngineImpl::InitPaths()
|
||||
Globals::ProjectCacheFolder = Globals::ProjectFolder / TEXT("Cache");
|
||||
#endif
|
||||
|
||||
#if USE_MONO
|
||||
// We must ensure that engine is located in folder which path contains only ANSI characters
|
||||
// Why? Mono lib must have etc and lib folders at ANSI path
|
||||
// But project can be located on Unicode path
|
||||
if (!Globals::StartupFolder.IsANSI())
|
||||
Platform::Fatal(TEXT("Cannot start application in directory which name contains non-ANSI characters."));
|
||||
#endif
|
||||
|
||||
#if !PLATFORM_SWITCH && !FLAX_TESTS
|
||||
// Setup directories
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace FlaxEngine.Interop
|
||||
internal static void RegisterNativeLibrary(IntPtr moduleNamePtr, IntPtr modulePathPtr)
|
||||
{
|
||||
string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr);
|
||||
string modulePath = Marshal.PtrToStringAnsi(modulePathPtr);
|
||||
string modulePath = Marshal.PtrToStringUni(modulePathPtr);
|
||||
libraryPaths[moduleName] = modulePath;
|
||||
}
|
||||
|
||||
|
||||
@@ -1302,7 +1302,8 @@ namespace FlaxEngine.Interop
|
||||
#if !USE_AOT
|
||||
internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke)
|
||||
{
|
||||
if (invokeDelegate == null)
|
||||
// Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
|
||||
if (invokeDelegate == null && !method.DeclaringType.IsValueType)
|
||||
{
|
||||
List<Type> methodTypes = new List<Type>();
|
||||
if (!method.IsStatic)
|
||||
|
||||
@@ -22,7 +22,7 @@ class ScreenService : public EngineService
|
||||
{
|
||||
public:
|
||||
ScreenService()
|
||||
: EngineService(TEXT("Screen"), 120)
|
||||
: EngineService(TEXT("Screen"), 500)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -634,15 +634,12 @@ void Foliage::RemoveFoliageType(int32 index)
|
||||
int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
int32 result = 0;
|
||||
|
||||
for (auto i = Instances.Begin(); i.IsNotEnd(); i++)
|
||||
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (i->Type == index)
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ public:
|
||||
, _srcResource(src)
|
||||
, _dstResource(dst)
|
||||
{
|
||||
_srcResource.OnUnload.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceUnload>(this);
|
||||
_dstResource.OnUnload.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceUnload>(this);
|
||||
_srcResource.Released.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceReleased>(this);
|
||||
_dstResource.Released.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceReleased>(this);
|
||||
}
|
||||
|
||||
private:
|
||||
void OnResourceUnload(GPUResourceReference* ref)
|
||||
void OnResourceReleased()
|
||||
{
|
||||
Cancel();
|
||||
}
|
||||
@@ -47,14 +47,11 @@ protected:
|
||||
// [GPUTask]
|
||||
Result run(GPUTasksContext* context) override
|
||||
{
|
||||
if (_srcResource.IsMissing() || _dstResource.IsMissing())
|
||||
if (!_srcResource || !_dstResource)
|
||||
return Result::MissingResources;
|
||||
|
||||
context->GPU->CopyResource(_dstResource, _srcResource);
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
void OnEnd() override
|
||||
{
|
||||
_srcResource.Unlink();
|
||||
|
||||
@@ -31,12 +31,12 @@ public:
|
||||
, _srcSubresource(srcSubresource)
|
||||
, _dstSubresource(dstSubresource)
|
||||
{
|
||||
_srcResource.OnUnload.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceUnload>(this);
|
||||
_dstResource.OnUnload.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceUnload>(this);
|
||||
_srcResource.Released.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceReleased>(this);
|
||||
_dstResource.Released.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceReleased>(this);
|
||||
}
|
||||
|
||||
private:
|
||||
void OnResourceUnload(GPUResourceReference* ref)
|
||||
void OnResourceReleased()
|
||||
{
|
||||
Cancel();
|
||||
}
|
||||
@@ -52,14 +52,11 @@ protected:
|
||||
// [GPUTask]
|
||||
Result run(GPUTasksContext* context) override
|
||||
{
|
||||
if (_srcResource.IsMissing() || _dstResource.IsMissing())
|
||||
if (!_srcResource || !_dstResource)
|
||||
return Result::MissingResources;
|
||||
|
||||
context->GPU->CopySubresource(_dstResource, _dstSubresource, _srcResource, _srcSubresource);
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
void OnEnd() override
|
||||
{
|
||||
_srcResource.Unlink();
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
, _buffer(buffer)
|
||||
, _offset(offset)
|
||||
{
|
||||
_buffer.OnUnload.Bind<GPUUploadBufferTask, &GPUUploadBufferTask::OnResourceUnload>(this);
|
||||
_buffer.Released.Bind<GPUUploadBufferTask, &GPUUploadBufferTask::OnResourceReleased>(this);
|
||||
|
||||
if (copyData)
|
||||
_data.Copy(data);
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void OnResourceUnload(BufferReference* ref)
|
||||
void OnResourceReleased()
|
||||
{
|
||||
Cancel();
|
||||
}
|
||||
@@ -56,14 +56,11 @@ protected:
|
||||
// [GPUTask]
|
||||
Result run(GPUTasksContext* context) override
|
||||
{
|
||||
if (_buffer.IsMissing())
|
||||
if (!_buffer)
|
||||
return Result::MissingResources;
|
||||
|
||||
context->GPU->UpdateBuffer(_buffer, _data.Get(), _data.Length(), _offset);
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
void OnEnd() override
|
||||
{
|
||||
_buffer.Unlink();
|
||||
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
, _rowPitch(rowPitch)
|
||||
, _slicePitch(slicePitch)
|
||||
{
|
||||
_texture.OnUnload.Bind<GPUUploadTextureMipTask, &GPUUploadTextureMipTask::OnResourceUnload>(this);
|
||||
_texture.Released.Bind<GPUUploadTextureMipTask, &GPUUploadTextureMipTask::OnResourceReleased>(this);
|
||||
|
||||
if (copyData)
|
||||
_data.Copy(data);
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void OnResourceUnload(GPUTextureReference* ref)
|
||||
void OnResourceReleased()
|
||||
{
|
||||
Cancel();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "GPUDevice.h"
|
||||
#include "RenderTargetPool.h"
|
||||
#include "GPUPipelineState.h"
|
||||
#include "GPUResourceProperty.h"
|
||||
#include "GPUSwapChain.h"
|
||||
#include "RenderTask.h"
|
||||
#include "RenderTools.h"
|
||||
@@ -25,6 +26,39 @@
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Scripting/Enums.h"
|
||||
|
||||
GPUResourcePropertyBase::~GPUResourcePropertyBase()
|
||||
{
|
||||
const auto e = _resource;
|
||||
if (e)
|
||||
{
|
||||
_resource = nullptr;
|
||||
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
|
||||
}
|
||||
}
|
||||
|
||||
void GPUResourcePropertyBase::OnSet(GPUResource* resource)
|
||||
{
|
||||
auto e = _resource;
|
||||
if (e != resource)
|
||||
{
|
||||
if (e)
|
||||
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
|
||||
_resource = e = resource;
|
||||
if (e)
|
||||
e->Releasing.Bind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
|
||||
}
|
||||
}
|
||||
|
||||
void GPUResourcePropertyBase::OnReleased()
|
||||
{
|
||||
auto e = _resource;
|
||||
if (e)
|
||||
{
|
||||
_resource = nullptr;
|
||||
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
|
||||
}
|
||||
}
|
||||
|
||||
GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params)
|
||||
{
|
||||
return GPUDevice::Instance->CreatePipelineState();
|
||||
@@ -313,6 +347,8 @@ bool GPUDevice::Init()
|
||||
|
||||
_res->TasksManager.SetExecutor(CreateTasksExecutor());
|
||||
LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory));
|
||||
if (!Limits.HasCompute)
|
||||
LOG(Warning, "Compute Shaders are not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -503,6 +539,9 @@ void GPUDevice::DrawEnd()
|
||||
// Call present on all used tasks
|
||||
int32 presentCount = 0;
|
||||
bool anyVSync = false;
|
||||
#if COMPILE_WITH_PROFILER
|
||||
const double presentStart = Platform::GetTimeSeconds();
|
||||
#endif
|
||||
for (int32 i = 0; i < RenderTask::Tasks.Count(); i++)
|
||||
{
|
||||
const auto task = RenderTask::Tasks[i];
|
||||
@@ -537,6 +576,10 @@ void GPUDevice::DrawEnd()
|
||||
#endif
|
||||
GetMainContext()->Flush();
|
||||
}
|
||||
#if COMPILE_WITH_PROFILER
|
||||
const double presentEnd = Platform::GetTimeSeconds();
|
||||
ProfilerGPU::OnPresentTime((float)((presentEnd - presentStart) * 1000.0));
|
||||
#endif
|
||||
|
||||
_wasVSyncUsed = anyVSync;
|
||||
_isRendering = false;
|
||||
|
||||
@@ -8,28 +8,39 @@
|
||||
/// <summary>
|
||||
/// GPU Resource container utility object.
|
||||
/// </summary>
|
||||
template<typename T = GPUResource>
|
||||
class GPUResourceProperty
|
||||
class FLAXENGINE_API GPUResourcePropertyBase
|
||||
{
|
||||
private:
|
||||
T* _resource;
|
||||
protected:
|
||||
GPUResource* _resource = nullptr;
|
||||
|
||||
private:
|
||||
// Disable copy actions
|
||||
GPUResourceProperty(const GPUResourceProperty& other) = delete;
|
||||
public:
|
||||
NON_COPYABLE(GPUResourcePropertyBase);
|
||||
|
||||
GPUResourcePropertyBase() = default;
|
||||
~GPUResourcePropertyBase();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Action fired when resource gets unloaded (reference gets cleared bu async tasks should stop execution).
|
||||
/// Action fired when resource gets released (reference gets cleared bu async tasks should stop execution).
|
||||
/// </summary>
|
||||
Delegate<GPUResourceProperty*> OnUnload;
|
||||
Action Released;
|
||||
|
||||
protected:
|
||||
void OnSet(GPUResource* resource);
|
||||
void OnReleased();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// GPU Resource container utility object.
|
||||
/// </summary>
|
||||
template<typename T = GPUResource>
|
||||
class GPUResourceProperty : public GPUResourcePropertyBase
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
|
||||
/// </summary>
|
||||
GPUResourceProperty()
|
||||
: _resource(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -38,9 +49,37 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource.</param>
|
||||
GPUResourceProperty(T* resource)
|
||||
: _resource(nullptr)
|
||||
{
|
||||
Set(resource);
|
||||
OnSet(resource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
|
||||
/// </summary>
|
||||
/// <param name="other">The other value.</param>
|
||||
GPUResourceProperty(const GPUResourceProperty& other)
|
||||
{
|
||||
OnSet(other.Get());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
|
||||
/// </summary>
|
||||
/// <param name="other">The other value.</param>
|
||||
GPUResourceProperty(GPUResourceProperty&& other)
|
||||
{
|
||||
OnSet(other.Get());
|
||||
other.OnSet(nullptr);
|
||||
}
|
||||
|
||||
GPUResourceProperty& operator=(GPUResourceProperty&& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
OnSet(other._resource);
|
||||
other.OnSet(nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,13 +87,6 @@ public:
|
||||
/// </summary>
|
||||
~GPUResourceProperty()
|
||||
{
|
||||
// Check if object has been binded
|
||||
if (_resource)
|
||||
{
|
||||
// Unlink
|
||||
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
|
||||
_resource = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -63,43 +95,34 @@ public:
|
||||
return Get() == other;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator==(GPUResourceProperty& other) const
|
||||
FORCE_INLINE bool operator==(const GPUResourceProperty& other) const
|
||||
{
|
||||
return Get() == other.Get();
|
||||
}
|
||||
|
||||
GPUResourceProperty& operator=(const GPUResourceProperty& other)
|
||||
{
|
||||
if (this != &other)
|
||||
Set(other.Get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
FORCE_INLINE GPUResourceProperty& operator=(T& other)
|
||||
{
|
||||
Set(&other);
|
||||
OnSet(&other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FORCE_INLINE GPUResourceProperty& operator=(T* other)
|
||||
{
|
||||
Set(other);
|
||||
OnSet(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicit conversion to GPU Resource
|
||||
/// </summary>
|
||||
/// <returns>Resource</returns>
|
||||
FORCE_INLINE operator T*() const
|
||||
{
|
||||
return _resource;
|
||||
return (T*)_resource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicit conversion to resource
|
||||
/// </summary>
|
||||
/// <returns>True if resource has been binded, otherwise false</returns>
|
||||
FORCE_INLINE operator bool() const
|
||||
{
|
||||
return _resource != nullptr;
|
||||
@@ -108,37 +131,17 @@ public:
|
||||
/// <summary>
|
||||
/// Implicit conversion to resource
|
||||
/// </summary>
|
||||
/// <returns>Resource</returns>
|
||||
FORCE_INLINE T* operator->() const
|
||||
{
|
||||
return _resource;
|
||||
return (T*)_resource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets linked resource
|
||||
/// </summary>
|
||||
/// <returns>Resource</returns>
|
||||
FORCE_INLINE T* Get() const
|
||||
{
|
||||
return _resource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if resource has been binded
|
||||
/// </summary>
|
||||
/// <returns>True if resource has been binded, otherwise false</returns>
|
||||
FORCE_INLINE bool IsBinded() const
|
||||
{
|
||||
return _resource != nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if resource is missing
|
||||
/// </summary>
|
||||
/// <returns>True if resource is missing, otherwise false</returns>
|
||||
FORCE_INLINE bool IsMissing() const
|
||||
{
|
||||
return _resource == nullptr;
|
||||
return (T*)_resource;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -148,19 +151,7 @@ public:
|
||||
/// <param name="value">Value to assign</param>
|
||||
void Set(T* value)
|
||||
{
|
||||
if (_resource != value)
|
||||
{
|
||||
// Remove reference from the old one
|
||||
if (_resource)
|
||||
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
|
||||
|
||||
// Change referenced object
|
||||
_resource = value;
|
||||
|
||||
// Add reference to the new one
|
||||
if (_resource)
|
||||
_resource->Releasing.template Bind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
|
||||
}
|
||||
OnSet(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -168,22 +159,7 @@ public:
|
||||
/// </summary>
|
||||
void Unlink()
|
||||
{
|
||||
if (_resource)
|
||||
{
|
||||
// Remove reference from the old one
|
||||
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
|
||||
_resource = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void onResourceUnload()
|
||||
{
|
||||
if (_resource)
|
||||
{
|
||||
_resource = nullptr;
|
||||
OnUnload(this);
|
||||
}
|
||||
OnSet(nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ bool DecalMaterialShader::Load()
|
||||
{
|
||||
GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc0.VS = _shader->GetVS("VS_Decal");
|
||||
if (psDesc0.VS == nullptr)
|
||||
return true;
|
||||
psDesc0.PS = _shader->GetPS("PS_Decal");
|
||||
psDesc0.CullMode = CullMode::Normal;
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ void DeferredMaterialShader::Unload()
|
||||
|
||||
bool DeferredMaterialShader::Load()
|
||||
{
|
||||
bool failed = false;
|
||||
auto psDesc = GPUPipelineState::Description::Default;
|
||||
psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None;
|
||||
if (EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::DisableDepthTest))
|
||||
@@ -155,16 +156,20 @@ bool DeferredMaterialShader::Load()
|
||||
|
||||
// GBuffer Pass
|
||||
psDesc.VS = _shader->GetVS("VS");
|
||||
failed |= psDesc.VS == nullptr;
|
||||
psDesc.PS = _shader->GetPS("PS_GBuffer");
|
||||
_cache.Default.Init(psDesc);
|
||||
psDesc.VS = _shader->GetVS("VS", 1);
|
||||
failed |= psDesc.VS == nullptr;
|
||||
_cacheInstanced.Default.Init(psDesc);
|
||||
|
||||
// GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1)
|
||||
psDesc.VS = _shader->GetVS("VS");
|
||||
failed |= psDesc.VS == nullptr;
|
||||
psDesc.PS = _shader->GetPS("PS_GBuffer", 1);
|
||||
_cache.DefaultLightmap.Init(psDesc);
|
||||
psDesc.VS = _shader->GetVS("VS", 1);
|
||||
failed |= psDesc.VS == nullptr;
|
||||
_cacheInstanced.DefaultLightmap.Init(psDesc);
|
||||
|
||||
// GBuffer Pass with skinning
|
||||
@@ -233,5 +238,5 @@ bool DeferredMaterialShader::Load()
|
||||
psDesc.VS = _shader->GetVS("VS_Skinned");
|
||||
_cache.DepthSkinned.Init(psDesc);
|
||||
|
||||
return false;
|
||||
return failed;
|
||||
}
|
||||
|
||||
@@ -174,6 +174,8 @@ bool ForwardMaterialShader::Load()
|
||||
|
||||
// Forward Pass
|
||||
psDesc.VS = _shader->GetVS("VS");
|
||||
if (psDesc.VS == nullptr)
|
||||
return true;
|
||||
psDesc.PS = _shader->GetPS("PS_Forward");
|
||||
psDesc.DepthWriteEnable = false;
|
||||
psDesc.BlendMode = BlendingMode::AlphaBlend;
|
||||
|
||||
@@ -86,7 +86,7 @@ API_ENUM() enum class MaterialBlendMode : byte
|
||||
API_ENUM() enum class MaterialShadingModel : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The unlit material. Emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
|
||||
/// The unlit material. The emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
|
||||
/// </summary>
|
||||
Unlit = 0,
|
||||
|
||||
@@ -96,7 +96,7 @@ API_ENUM() enum class MaterialShadingModel : byte
|
||||
Lit = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The subsurface material. Intended for materials like vax or skin that need light scattering to transport simulation through the object.
|
||||
/// The subsurface material. Intended for materials like wax or skin that need light scattering to transport simulation through the object.
|
||||
/// </summary>
|
||||
Subsurface = 2,
|
||||
|
||||
@@ -366,12 +366,12 @@ API_ENUM() enum class MaterialDecalBlendingMode : byte
|
||||
API_ENUM() enum class MaterialTransparentLightingMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting component active.
|
||||
/// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting components active.
|
||||
/// </summary>
|
||||
Surface = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only diffuse lighting term is active (no specular highlights).
|
||||
/// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only the diffuse lighting term is active (no specular highlights).
|
||||
/// </summary>
|
||||
SurfaceNonDirectional = 1,
|
||||
};
|
||||
|
||||
@@ -609,7 +609,7 @@ bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& c
|
||||
ScopeLock lock(model->Locker);
|
||||
if (model->IsVirtual())
|
||||
{
|
||||
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download");
|
||||
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -318,7 +318,9 @@ void MeshData::BuildIndexBuffer()
|
||||
}
|
||||
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
LOG(Info, "Generated index buffer for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), Positions.Count(), Indices.Count());
|
||||
const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
|
||||
if (time > 0.5f) // Don't log if generation was fast enough
|
||||
LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, Indices.Count(), TEXT("indices"));
|
||||
}
|
||||
|
||||
void MeshData::FindPositions(const Float3& position, float epsilon, Array<int32>& result)
|
||||
@@ -449,7 +451,9 @@ bool MeshData::GenerateNormals(float smoothingAngle)
|
||||
}
|
||||
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
|
||||
const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
|
||||
if (time > 0.5f) // Don't log if generation was fast enough
|
||||
LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("normals"));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -685,7 +689,10 @@ bool MeshData::GenerateTangents(float smoothingAngle)
|
||||
#endif
|
||||
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
|
||||
const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
|
||||
if (time > 0.5f) // Don't log if generation was fast enough
|
||||
LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("tangents"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -872,7 +879,9 @@ void MeshData::ImproveCacheLocality()
|
||||
Allocator::Free(piCandidates);
|
||||
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
LOG(Info, "Cache relevant optimize for {0} vertices and {1} indices. Average output ACMR is {2}. Time: {3}s", vertexCount, indexCount, (float)iCacheMisses / indexCount / 3, Utilities::RoundTo2DecimalPlaces(endTime - startTime));
|
||||
const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
|
||||
if (time > 0.5f) // Don't log if generation was fast enough
|
||||
LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("optimized indices"));
|
||||
}
|
||||
|
||||
float MeshData::CalculateTrianglesArea() const
|
||||
|
||||
@@ -90,6 +90,21 @@ public:
|
||||
/// </summary>
|
||||
Array<BlendShape> BlendShapes;
|
||||
|
||||
/// <summary>
|
||||
/// Global translation for this mesh to be at it's local origin.
|
||||
/// </summary>
|
||||
Vector3 OriginTranslation = Vector3::Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Orientation for this mesh at it's local origin.
|
||||
/// </summary>
|
||||
Quaternion OriginOrientation = Quaternion::Identity;
|
||||
|
||||
/// <summary>
|
||||
/// Meshes scaling.
|
||||
/// </summary>
|
||||
Vector3 Scaling = Vector3::One;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Determines whether this instance has any mesh data.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Math/Vector4.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/SoftAssetReference.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Content/Assets/Texture.h"
|
||||
#include "Engine/Content/Assets/MaterialBase.h"
|
||||
@@ -850,7 +851,7 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable
|
||||
/// The Lookup Table (LUT) used to perform color correction.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="DefaultValue(null), EditorOrder(22), PostProcessSetting((int)ColorGradingSettingsOverride.LutTexture)")
|
||||
AssetReference<Texture> LutTexture;
|
||||
SoftAssetReference<Texture> LutTexture;
|
||||
|
||||
/// <summary>
|
||||
/// The LUT blending weight (normalized to range 0-1). Default is 1.0.
|
||||
@@ -1277,7 +1278,7 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
|
||||
/// Fullscreen lens dirt texture.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="DefaultValue(null), EditorOrder(8), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirt)")
|
||||
AssetReference<Texture> LensDirt;
|
||||
SoftAssetReference<Texture> LensDirt;
|
||||
|
||||
/// <summary>
|
||||
/// Fullscreen lens dirt intensity parameter. Allows to tune dirt visibility.
|
||||
@@ -1289,13 +1290,13 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
|
||||
/// Custom lens color texture (1D) used for lens color spectrum.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="DefaultValue(null), EditorOrder(10), PostProcessSetting((int)LensFlaresSettingsOverride.LensColor)")
|
||||
AssetReference<Texture> LensColor;
|
||||
SoftAssetReference<Texture> LensColor;
|
||||
|
||||
/// <summary>
|
||||
/// Custom lens star texture sampled by lens flares.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)LensFlaresSettingsOverride.LensStar)")
|
||||
AssetReference<Texture> LensStar;
|
||||
SoftAssetReference<Texture> LensStar;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -1487,7 +1488,7 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
|
||||
/// If BokehShape is set to Custom, then this texture will be used for the bokeh shapes. For best performance, use small, compressed, grayscale textures (for instance 32px).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShapeCustom)")
|
||||
AssetReference<Texture> BokehShapeCustom;
|
||||
SoftAssetReference<Texture> BokehShapeCustom;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum pixel brightness to create bokeh. Pixels with lower brightness will be skipped.
|
||||
|
||||
@@ -87,7 +87,11 @@ bool GPUShader::Create(MemoryReadStream& stream)
|
||||
GPUShaderProgramInitializer initializer;
|
||||
#if !BUILD_RELEASE
|
||||
initializer.Owner = this;
|
||||
const StringView name = GetName();
|
||||
#else
|
||||
const StringView name;
|
||||
#endif
|
||||
const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
|
||||
for (int32 i = 0; i < shadersCount; i++)
|
||||
{
|
||||
const ShaderStage type = static_cast<ShaderStage>(stream.ReadByte());
|
||||
@@ -117,10 +121,15 @@ bool GPUShader::Create(MemoryReadStream& stream)
|
||||
stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));
|
||||
|
||||
// Create shader program
|
||||
if (type == ShaderStage::Compute && !hasCompute)
|
||||
{
|
||||
LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
|
||||
continue;
|
||||
}
|
||||
GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream);
|
||||
if (shader == nullptr)
|
||||
{
|
||||
LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name));
|
||||
LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,19 @@ public:
|
||||
/// </summary>
|
||||
class GPUShaderProgramVS : public GPUShaderProgram
|
||||
{
|
||||
public:
|
||||
// Input element run-time data (see VertexShaderMeta::InputElement for compile-time data)
|
||||
PACK_STRUCT(struct InputElement
|
||||
{
|
||||
byte Type; // VertexShaderMeta::InputType
|
||||
byte Index;
|
||||
byte Format; // PixelFormat
|
||||
byte InputSlot;
|
||||
uint32 AlignedByteOffset; // Fixed value or INPUT_LAYOUT_ELEMENT_ALIGN if auto
|
||||
byte InputSlotClass; // INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA or INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA
|
||||
uint32 InstanceDataStepRate; // 0 if per-vertex
|
||||
});
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets input layout description handle (platform dependent).
|
||||
|
||||
@@ -22,10 +22,10 @@ TextureHeader::TextureHeader()
|
||||
TextureGroup = -1;
|
||||
}
|
||||
|
||||
TextureHeader::TextureHeader(TextureHeader_Deprecated& old)
|
||||
TextureHeader::TextureHeader(const TextureHeader_Deprecated& old)
|
||||
{
|
||||
Platform::MemoryClear(this, sizeof(*this));
|
||||
Width = old.Width;;
|
||||
Width = old.Width;
|
||||
Height = old.Height;
|
||||
MipLevels = old.MipLevels;
|
||||
Format = old.Format;
|
||||
@@ -49,7 +49,7 @@ StreamingTexture::StreamingTexture(ITextureOwner* parent, const String& name)
|
||||
, _texture(nullptr)
|
||||
, _isBlockCompressed(false)
|
||||
{
|
||||
ASSERT(_owner != nullptr);
|
||||
ASSERT(parent != nullptr);
|
||||
|
||||
// Always have created texture object
|
||||
ASSERT(GPUDevice::Instance);
|
||||
@@ -329,11 +329,11 @@ public:
|
||||
, _dataLock(_streamingTexture->GetOwner()->LockData())
|
||||
{
|
||||
_streamingTexture->_streamingTasks.Add(this);
|
||||
_texture.OnUnload.Bind<StreamTextureMipTask, &StreamTextureMipTask::onResourceUnload2>(this);
|
||||
_texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this);
|
||||
}
|
||||
|
||||
private:
|
||||
void onResourceUnload2(GPUTextureReference* ref)
|
||||
void OnResourceReleased2()
|
||||
{
|
||||
// Unlink texture
|
||||
if (_streamingTexture)
|
||||
|
||||
@@ -660,6 +660,7 @@ uint64 TextureBase::GetMemoryUsage() const
|
||||
|
||||
void TextureBase::CancelStreaming()
|
||||
{
|
||||
Asset::CancelStreaming();
|
||||
_texture.CancelStreamingTasks();
|
||||
}
|
||||
|
||||
|
||||
@@ -106,5 +106,5 @@ struct FLAXENGINE_API TextureHeader
|
||||
byte CustomData[10];
|
||||
|
||||
TextureHeader();
|
||||
TextureHeader(TextureHeader_Deprecated& old);
|
||||
TextureHeader(const TextureHeader_Deprecated& old);
|
||||
};
|
||||
|
||||
@@ -15,32 +15,21 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
|
||||
{
|
||||
case ShaderStage::Vertex:
|
||||
{
|
||||
D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
|
||||
|
||||
// Temporary variables
|
||||
byte Type, Format, Index, InputSlot, InputSlotClass;
|
||||
uint32 AlignedByteOffset, InstanceDataStepRate;
|
||||
|
||||
// Load Input Layout (it may be empty)
|
||||
// Load Input Layout
|
||||
byte inputLayoutSize;
|
||||
stream.ReadByte(&inputLayoutSize);
|
||||
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
|
||||
D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
|
||||
for (int32 a = 0; a < inputLayoutSize; a++)
|
||||
{
|
||||
// Read description
|
||||
// TODO: maybe use struct and load at once?
|
||||
stream.ReadByte(&Type);
|
||||
stream.ReadByte(&Index);
|
||||
stream.ReadByte(&Format);
|
||||
stream.ReadByte(&InputSlot);
|
||||
stream.ReadUint32(&AlignedByteOffset);
|
||||
stream.ReadByte(&InputSlotClass);
|
||||
stream.ReadUint32(&InstanceDataStepRate);
|
||||
GPUShaderProgramVS::InputElement inputElement;
|
||||
stream.Read(inputElement);
|
||||
|
||||
// Get semantic name
|
||||
const char* semanticName = nullptr;
|
||||
// TODO: maybe use enum+mapping ?
|
||||
switch (Type)
|
||||
switch (inputElement.Type)
|
||||
{
|
||||
case 1:
|
||||
semanticName = "POSITION";
|
||||
@@ -70,7 +59,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
|
||||
semanticName = "BLENDWEIGHT";
|
||||
break;
|
||||
default:
|
||||
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
|
||||
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -78,12 +67,12 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
|
||||
inputLayoutDesc[a] =
|
||||
{
|
||||
semanticName,
|
||||
static_cast<UINT>(Index),
|
||||
static_cast<DXGI_FORMAT>(Format),
|
||||
static_cast<UINT>(InputSlot),
|
||||
static_cast<UINT>(AlignedByteOffset),
|
||||
static_cast<D3D11_INPUT_CLASSIFICATION>(InputSlotClass),
|
||||
static_cast<UINT>(InstanceDataStepRate)
|
||||
static_cast<UINT>(inputElement.Index),
|
||||
static_cast<DXGI_FORMAT>(inputElement.Format),
|
||||
static_cast<UINT>(inputElement.InputSlot),
|
||||
static_cast<UINT>(inputElement.AlignedByteOffset),
|
||||
static_cast<D3D11_INPUT_CLASSIFICATION>(inputElement.InputSlotClass),
|
||||
static_cast<UINT>(inputElement.InstanceDataStepRate)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,32 +20,21 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
|
||||
{
|
||||
case ShaderStage::Vertex:
|
||||
{
|
||||
D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
|
||||
|
||||
// Temporary variables
|
||||
byte Type, Format, Index, InputSlot, InputSlotClass;
|
||||
uint32 AlignedByteOffset, InstanceDataStepRate;
|
||||
|
||||
// Load Input Layout (it may be empty)
|
||||
byte inputLayoutSize;
|
||||
stream.ReadByte(&inputLayoutSize);
|
||||
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
|
||||
D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
|
||||
for (int32 a = 0; a < inputLayoutSize; a++)
|
||||
{
|
||||
// Read description
|
||||
// TODO: maybe use struct and load at once?
|
||||
stream.ReadByte(&Type);
|
||||
stream.ReadByte(&Index);
|
||||
stream.ReadByte(&Format);
|
||||
stream.ReadByte(&InputSlot);
|
||||
stream.ReadUint32(&AlignedByteOffset);
|
||||
stream.ReadByte(&InputSlotClass);
|
||||
stream.ReadUint32(&InstanceDataStepRate);
|
||||
GPUShaderProgramVS::InputElement inputElement;
|
||||
stream.Read(inputElement);
|
||||
|
||||
// Get semantic name
|
||||
const char* semanticName = nullptr;
|
||||
// TODO: maybe use enum+mapping ?
|
||||
switch (Type)
|
||||
switch (inputElement.Type)
|
||||
{
|
||||
case 1:
|
||||
semanticName = "POSITION";
|
||||
@@ -75,7 +64,7 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
|
||||
semanticName = "BLENDWEIGHT";
|
||||
break;
|
||||
default:
|
||||
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
|
||||
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -83,12 +72,12 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
|
||||
inputLayout[a] =
|
||||
{
|
||||
semanticName,
|
||||
static_cast<UINT>(Index),
|
||||
static_cast<DXGI_FORMAT>(Format),
|
||||
static_cast<UINT>(InputSlot),
|
||||
static_cast<UINT>(AlignedByteOffset),
|
||||
static_cast<D3D12_INPUT_CLASSIFICATION>(InputSlotClass),
|
||||
static_cast<UINT>(InstanceDataStepRate)
|
||||
static_cast<UINT>(inputElement.Index),
|
||||
static_cast<DXGI_FORMAT>(inputElement.Format),
|
||||
static_cast<UINT>(inputElement.InputSlot),
|
||||
static_cast<UINT>(inputElement.AlignedByteOffset),
|
||||
static_cast<D3D12_INPUT_CLASSIFICATION>(inputElement.InputSlotClass),
|
||||
static_cast<UINT>(inputElement.InstanceDataStepRate)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
|
||||
#if PLATFORM_DESKTOP
|
||||
#define VULKAN_UNIFORM_RING_BUFFER_SIZE 24 * 1024 * 1024
|
||||
#define VULKAN_UNIFORM_RING_BUFFER_SIZE (24 * 1024 * 1024)
|
||||
#else
|
||||
#define VULKAN_UNIFORM_RING_BUFFER_SIZE 8 * 1024 * 1024
|
||||
#define VULKAN_UNIFORM_RING_BUFFER_SIZE (8 * 1024 * 1024)
|
||||
#endif
|
||||
|
||||
UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device)
|
||||
@@ -153,10 +153,6 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
|
||||
vertexBindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
}
|
||||
|
||||
// Temporary variables
|
||||
byte Type, Format, Index, InputSlot, InputSlotClass;
|
||||
uint32 AlignedByteOffset, InstanceDataStepRate;
|
||||
|
||||
// Load Input Layout (it may be empty)
|
||||
byte inputLayoutSize;
|
||||
stream.ReadByte(&inputLayoutSize);
|
||||
@@ -167,32 +163,26 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
|
||||
for (int32 a = 0; a < inputLayoutSize; a++)
|
||||
{
|
||||
// Read description
|
||||
// TODO: maybe use struct and load at once?
|
||||
stream.ReadByte(&Type);
|
||||
stream.ReadByte(&Index);
|
||||
stream.ReadByte(&Format);
|
||||
stream.ReadByte(&InputSlot);
|
||||
stream.ReadUint32(&AlignedByteOffset);
|
||||
stream.ReadByte(&InputSlotClass);
|
||||
stream.ReadUint32(&InstanceDataStepRate);
|
||||
GPUShaderProgramVS::InputElement inputElement;
|
||||
stream.Read(inputElement);
|
||||
|
||||
const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)Format);
|
||||
if (AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
|
||||
offset = AlignedByteOffset;
|
||||
const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)inputElement.Format);
|
||||
if (inputElement.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
|
||||
offset = inputElement.AlignedByteOffset;
|
||||
|
||||
auto& vertexBindingDescription = vertexBindingDescriptions[InputSlot];
|
||||
vertexBindingDescription.binding = InputSlot;
|
||||
auto& vertexBindingDescription = vertexBindingDescriptions[inputElement.InputSlot];
|
||||
vertexBindingDescription.binding = inputElement.InputSlot;
|
||||
vertexBindingDescription.stride = Math::Max(vertexBindingDescription.stride, (uint32_t)(offset + size));
|
||||
vertexBindingDescription.inputRate = InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
||||
ASSERT(InstanceDataStepRate == 0 || InstanceDataStepRate == 1);
|
||||
vertexBindingDescription.inputRate = inputElement.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
||||
ASSERT(inputElement.InstanceDataStepRate == 0 || inputElement.InstanceDataStepRate == 1);
|
||||
|
||||
auto& vertexAttributeDescription = vertexAttributeDescriptions[a];
|
||||
vertexAttributeDescription.location = a;
|
||||
vertexAttributeDescription.binding = InputSlot;
|
||||
vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)Format);
|
||||
vertexAttributeDescription.binding = inputElement.InputSlot;
|
||||
vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)inputElement.Format);
|
||||
vertexAttributeDescription.offset = offset;
|
||||
|
||||
bindingsCount = Math::Max(bindingsCount, (uint32)InputSlot + 1);
|
||||
bindingsCount = Math::Max(bindingsCount, (uint32)inputElement.InputSlot + 1);
|
||||
offset += size;
|
||||
}
|
||||
|
||||
|
||||
@@ -1406,7 +1406,7 @@ Script* Actor::FindScript(const MClass* type) const
|
||||
CHECK_RETURN(type, nullptr);
|
||||
for (auto script : Scripts)
|
||||
{
|
||||
if (script->GetClass()->IsSubClassOf(type))
|
||||
if (script->GetClass()->IsSubClassOf(type) || script->GetClass()->HasInterface(type))
|
||||
return script;
|
||||
}
|
||||
for (auto child : Children)
|
||||
|
||||
@@ -706,13 +706,12 @@ void AnimatedModel::UpdateBounds()
|
||||
const int32 bonesCount = skeleton.Bones.Count();
|
||||
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
|
||||
box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation()));
|
||||
_box = box;
|
||||
}
|
||||
|
||||
// Apply margin based on model dimensions
|
||||
const Vector3 modelBoxSize = modelBox.GetSize();
|
||||
const Vector3 center = _box.GetCenter();
|
||||
const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
|
||||
const Vector3 center = box.GetCenter();
|
||||
const Vector3 sizeHalf = Vector3::Max(box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
|
||||
_box = BoundingBox(center - sizeHalf, center + sizeHalf);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// <summary>
|
||||
/// Performs an animation and renders a skinned model.
|
||||
/// </summary>
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Other/Animated Model\"), ActorToolbox(\"Other\")")
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Other/Animated Model\"), ActorToolbox(\"Visuals\")")
|
||||
class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(AnimatedModel);
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the value indicating if camera should use perspective rendering mode, otherwise it will use orthographic projection.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Camera\"), Tooltip(\"Enables perspective projection mode, otherwise uses orthographic.\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Camera\")")
|
||||
bool GetUsePerspective() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the camera's field of view (in degrees).
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), Tooltip(\"Field of view angle in degrees.\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))")
|
||||
float GetFieldOfView() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -88,7 +88,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the custom aspect ratio. 0 if not use custom value.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Custom aspect ratio to use. Set to 0 to disable.\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective))")
|
||||
float GetCustomAspectRatio() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets camera's near plane distance.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), Tooltip(\"Near clipping plane distance\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")")
|
||||
float GetNearPlane() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets camera's far plane distance.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), Tooltip(\"Far clipping plane distance\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")")
|
||||
float GetFarPlane() const;
|
||||
|
||||
/// <summary>
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the orthographic projection scale.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Orthographic projection scale\")")
|
||||
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective), true)")
|
||||
float GetOrthographicScale() const;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,7 +24,7 @@ private:
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Light source bulb radius
|
||||
/// Light source bulb radius.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
|
||||
float SourceRadius = 0.0f;
|
||||
@@ -42,54 +42,51 @@ public:
|
||||
float FallOffExponent = 8.0f;
|
||||
|
||||
/// <summary>
|
||||
/// IES texture (light profiles from real world measured data)
|
||||
/// IES texture (light profiles from real world measured data).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(211), DefaultValue(null), EditorDisplay(\"IES Profile\", \"IES Texture\")")
|
||||
AssetReference<IESProfile> IESTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable using light brightness from IES profile
|
||||
/// Enable/disable using light brightness from IES profile.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(212), DefaultValue(false), EditorDisplay(\"IES Profile\", \"Use IES Brightness\")")
|
||||
bool UseIESBrightness = false;
|
||||
|
||||
/// <summary>
|
||||
/// Global scale for IES brightness contribution
|
||||
/// Global scale for IES brightness contribution.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(213), DefaultValue(1.0f), Limit(0, 10000, 0.01f), EditorDisplay(\"IES Profile\", \"Brightness Scale\")")
|
||||
float IESBrightnessScale = 1.0f;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Computes light brightness value
|
||||
/// Computes light brightness value.
|
||||
/// </summary>
|
||||
/// <returns>Brightness</returns>
|
||||
float ComputeBrightness() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets scaled light radius
|
||||
/// Gets scaled light radius.
|
||||
/// </summary>
|
||||
float GetScaledRadius() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets light radius
|
||||
/// Gets light radius.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Tooltip(\"Light radius\"), Limit(0, 10000, 0.1f)")
|
||||
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Limit(0, 10000, 0.1f)")
|
||||
FORCE_INLINE float GetRadius() const
|
||||
{
|
||||
return _radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets light radius
|
||||
/// Sets light radius.
|
||||
/// </summary>
|
||||
/// <param name="value">New radius</param>
|
||||
API_PROPERTY() void SetRadius(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spot light's outer cone angle (in degrees)
|
||||
/// Gets the spot light's outer cone angle (in degrees).
|
||||
/// </summary>
|
||||
/// <returns>Outer angle (in degrees)</returns>
|
||||
API_PROPERTY(Attributes="EditorOrder(22), DefaultValue(43.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
|
||||
FORCE_INLINE float GetOuterConeAngle() const
|
||||
{
|
||||
@@ -97,15 +94,13 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the spot light's outer cone angle (in degrees)
|
||||
/// Sets the spot light's outer cone angle (in degrees).
|
||||
/// </summary>
|
||||
/// <param name="value">Value to assign</param>
|
||||
API_PROPERTY() void SetOuterConeAngle(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the spot light's inner cone angle (in degrees)
|
||||
/// Sets the spot light's inner cone angle (in degrees).
|
||||
/// </summary>
|
||||
/// <returns>Inner angle (in degrees)</returns>
|
||||
API_PROPERTY(Attributes="EditorOrder(21), DefaultValue(10.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
|
||||
FORCE_INLINE float GetInnerConeAngle() const
|
||||
{
|
||||
@@ -113,9 +108,8 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the spot light's inner cone angle (in degrees)
|
||||
/// Sets the spot light's inner cone angle (in degrees).
|
||||
/// </summary>
|
||||
/// <param name="value">Value to assign</param>
|
||||
API_PROPERTY() void SetInnerConeAngle(float value);
|
||||
|
||||
private:
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
/// <summary>
|
||||
/// Renders model on the screen.
|
||||
/// </summary>
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Model\")") class FLAXENGINE_API StaticModel : public ModelInstanceActor
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Model\"), ActorToolbox(\"Visuals\")")
|
||||
class FLAXENGINE_API StaticModel : public ModelInstanceActor
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(StaticModel);
|
||||
private:
|
||||
|
||||
@@ -19,7 +19,7 @@ API_CLASS(Static) class FLAXENGINE_API LargeWorlds
|
||||
/// <summary>
|
||||
/// Defines the size of a single chunk. Large world (64-bit) gets divided into smaller chunks so all the math operations (32-bit) can be performed relative to the chunk origin without precision loss.
|
||||
/// </summary>
|
||||
API_FIELD() static constexpr Real ChunkSize = 262144;
|
||||
API_FIELD() static constexpr Real ChunkSize = 8192;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the large world origin to match the input position. The origin is snapped to the best matching chunk location.
|
||||
|
||||
@@ -132,7 +132,7 @@ class LevelService : public EngineService
|
||||
{
|
||||
public:
|
||||
LevelService()
|
||||
: EngineService(TEXT("Scene Manager"), 30)
|
||||
: EngineService(TEXT("Scene Manager"), 200)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -416,7 +416,7 @@ public:
|
||||
}
|
||||
|
||||
// Load scene
|
||||
if (Level::loadScene(SceneAsset.Get()))
|
||||
if (Level::loadScene(SceneAsset))
|
||||
{
|
||||
LOG(Error, "Failed to deserialize scene {0}", SceneId);
|
||||
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
|
||||
@@ -816,40 +816,10 @@ bool LevelImpl::unloadScenes()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Level::loadScene(const Guid& sceneId)
|
||||
{
|
||||
const auto sceneAsset = Content::LoadAsync<JsonAsset>(sceneId);
|
||||
return loadScene(sceneAsset);
|
||||
}
|
||||
|
||||
bool Level::loadScene(const String& scenePath)
|
||||
{
|
||||
LOG(Info, "Loading scene from file. Path: \'{0}\'", scenePath);
|
||||
|
||||
// Check for missing file
|
||||
if (!FileSystem::FileExists(scenePath))
|
||||
{
|
||||
LOG(Error, "Missing scene file.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load file
|
||||
BytesContainer sceneData;
|
||||
if (File::ReadAllBytes(scenePath, sceneData))
|
||||
{
|
||||
LOG(Error, "Cannot load data from file.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return loadScene(sceneData);
|
||||
}
|
||||
|
||||
bool Level::loadScene(JsonAsset* sceneAsset)
|
||||
{
|
||||
// Keep reference to the asset (prevent unloading during action)
|
||||
AssetReference<JsonAsset> ref = sceneAsset;
|
||||
|
||||
// Wait for loaded
|
||||
if (sceneAsset == nullptr || sceneAsset->WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Cannot load scene asset.");
|
||||
@@ -879,6 +849,7 @@ bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene)
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(ScenesLock);
|
||||
return loadScene(document, outScene);
|
||||
}
|
||||
|
||||
@@ -975,6 +946,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
|
||||
SceneObject** objects = sceneObjects->Get();
|
||||
if (context.Async)
|
||||
{
|
||||
ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize)
|
||||
JobSystem::Execute([&](int32 i)
|
||||
{
|
||||
i++; // Start from 1. at index [0] was scene
|
||||
@@ -992,6 +964,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
|
||||
else
|
||||
SceneObjectsFactory::HandleObjectDeserializationError(stream);
|
||||
}, objectsCount - 1);
|
||||
ScenesLock.Lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1330,6 +1303,7 @@ bool Level::LoadScene(const Guid& id)
|
||||
}
|
||||
|
||||
// Load scene
|
||||
ScopeLock lock(ScenesLock);
|
||||
if (loadScene(sceneAsset))
|
||||
{
|
||||
LOG(Error, "Failed to deserialize scene {0}", id);
|
||||
|
||||
@@ -541,8 +541,8 @@ private:
|
||||
};
|
||||
|
||||
static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b);
|
||||
static bool loadScene(const Guid& sceneId);
|
||||
static bool loadScene(const String& scenePath);
|
||||
|
||||
// All loadScene assume that ScenesLock has been taken by the calling thread
|
||||
static bool loadScene(JsonAsset* sceneAsset);
|
||||
static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr);
|
||||
static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr);
|
||||
|
||||
@@ -29,7 +29,7 @@ class PrefabManagerService : public EngineService
|
||||
{
|
||||
public:
|
||||
PrefabManagerService()
|
||||
: EngineService(TEXT("Prefab Manager"), 110)
|
||||
: EngineService(TEXT("Prefab Manager"))
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -39,7 +39,7 @@ PrefabManagerService PrefabManagerServiceInstance;
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab)
|
||||
{
|
||||
Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr;
|
||||
return SpawnPrefab(prefab, Transform::Identity, parent, nullptr);
|
||||
return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr);
|
||||
}
|
||||
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position)
|
||||
@@ -73,12 +73,12 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, const Transform
|
||||
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent)
|
||||
{
|
||||
return SpawnPrefab(prefab, Transform::Identity, parent, nullptr);
|
||||
return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr);
|
||||
}
|
||||
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, const void*>* objectsCache, bool withSynchronization)
|
||||
{
|
||||
return SpawnPrefab(prefab, Transform::Identity, parent, objectsCache, withSynchronization);
|
||||
return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, objectsCache, withSynchronization);
|
||||
}
|
||||
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, const void*>* objectsCache, bool withSynchronization)
|
||||
@@ -191,7 +191,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
|
||||
parent->Children.Add(root);
|
||||
|
||||
// Move root to the right location
|
||||
if (transform != Transform::Identity)
|
||||
if (transform.Translation != Vector3::Minimum)
|
||||
root->SetTransform(transform);
|
||||
|
||||
// Link actors hierarchy
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Threading/ThreadLocal.h"
|
||||
#if !BUILD_RELEASE || USE_EDITOR
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#endif
|
||||
|
||||
SceneObjectsFactory::Context::Context(ISerializeModifier* modifier)
|
||||
: Modifier(modifier)
|
||||
@@ -254,6 +258,10 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria
|
||||
|
||||
void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value)
|
||||
{
|
||||
#if !BUILD_RELEASE || USE_EDITOR
|
||||
// Prevent race-conditions when logging missing objects (especially when adding dummy MissingScript)
|
||||
ScopeLock lock(Level::ScenesLock);
|
||||
|
||||
// Print invalid object data contents
|
||||
rapidjson_flax::StringBuffer buffer;
|
||||
PrettyJsonWriter writer(buffer);
|
||||
@@ -272,14 +280,15 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
|
||||
#if USE_EDITOR
|
||||
// Add dummy script
|
||||
auto* dummyScript = parent->AddScript<MissingScript>();
|
||||
const auto parentIdMember = value.FindMember("TypeName");
|
||||
if (parentIdMember != value.MemberEnd() && parentIdMember->value.IsString())
|
||||
dummyScript->MissingTypeName = parentIdMember->value.GetString();
|
||||
const auto typeNameMember = value.FindMember("TypeName");
|
||||
if (typeNameMember != value.MemberEnd() && typeNameMember->value.IsString())
|
||||
dummyScript->MissingTypeName = typeNameMember->value.GetString();
|
||||
dummyScript->Data = MoveTemp(bufferStr);
|
||||
#endif
|
||||
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id)
|
||||
@@ -361,14 +370,14 @@ SceneObjectsFactory::PrefabSyncData::PrefabSyncData(Array<SceneObject*>& sceneOb
|
||||
{
|
||||
}
|
||||
|
||||
void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData& data)
|
||||
void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyncData& data)
|
||||
{
|
||||
PROFILE_CPU_NAMED("SetupPrefabInstances");
|
||||
const int32 count = data.Data.Size();
|
||||
ASSERT(count <= data.SceneObjects.Count());
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
SceneObject* obj = data.SceneObjects[i];
|
||||
const SceneObject* obj = data.SceneObjects[i];
|
||||
if (!obj)
|
||||
continue;
|
||||
const auto& stream = data.Data[i];
|
||||
@@ -409,6 +418,21 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData&
|
||||
// Add to the prefab instance IDs mapping
|
||||
auto& prefabInstance = context.Instances[index];
|
||||
prefabInstance.IdsMapping[prefabObjectId] = id;
|
||||
|
||||
// Walk over nested prefabs to link any subobjects into this object (eg. if nested prefab uses cross-object references to link them correctly)
|
||||
NESTED_PREFAB_WALK:
|
||||
const ISerializable::DeserializeStream* prefabData;
|
||||
if (prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData) && JsonTools::GetGuidIfValid(prefabObjectId, *prefabData, "PrefabObjectID"))
|
||||
{
|
||||
prefabId = JsonTools::GetGuid(stream, "PrefabID");
|
||||
prefab = Content::LoadAsync<Prefab>(prefabId);
|
||||
if (prefab && !prefab->WaitForLoaded())
|
||||
{
|
||||
// Map prefab object ID to the deserialized instance ID
|
||||
prefabInstance.IdsMapping[prefabObjectId] = id;
|
||||
goto NESTED_PREFAB_WALK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ public:
|
||||
/// </remarks>
|
||||
/// <param name="context">The serialization context.</param>
|
||||
/// <param name="data">The sync data.</param>
|
||||
static void SetupPrefabInstances(Context& context, PrefabSyncData& data);
|
||||
static void SetupPrefabInstances(Context& context, const PrefabSyncData& data);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes the new prefab instances by spawning missing objects that were added to prefab but were not saved with scene objects collection.
|
||||
|
||||
@@ -30,6 +30,20 @@ typedef struct
|
||||
} MonoCultureInfo;
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
const CultureInfoEntry* FindEntry(const StringAnsiView& name)
|
||||
{
|
||||
for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++)
|
||||
{
|
||||
const CultureInfoEntry& e = culture_entries[i];
|
||||
if (name == idx2string(e.name))
|
||||
return &e;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
CultureInfo::CultureInfo(int32 lcid)
|
||||
{
|
||||
_lcid = lcid;
|
||||
@@ -80,23 +94,24 @@ CultureInfo::CultureInfo(const StringAnsiView& name)
|
||||
_englishName = TEXT("Invariant Culture");
|
||||
return;
|
||||
}
|
||||
for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++)
|
||||
const CultureInfoEntry* e = FindEntry(name);
|
||||
if (!e && name.Find('-') != -1)
|
||||
{
|
||||
auto& e = culture_entries[i];
|
||||
if (name == idx2string(e.name))
|
||||
{
|
||||
_data = (void*)&e;
|
||||
_lcid = (int32)e.lcid;
|
||||
_lcidParent = (int32)e.parent_lcid;
|
||||
_name.SetUTF8(name.Get(), name.Length());
|
||||
const char* nativename = idx2string(e.nativename);
|
||||
_nativeName.SetUTF8(nativename, StringUtils::Length(nativename));
|
||||
const char* englishname = idx2string(e.englishname);
|
||||
_englishName.SetUTF8(englishname, StringUtils::Length(englishname));
|
||||
break;
|
||||
}
|
||||
e = FindEntry(name.Substring(0, name.Find('-')));
|
||||
}
|
||||
if (!_data)
|
||||
if (e)
|
||||
{
|
||||
_data = (void*)e;
|
||||
_lcid = (int32)e->lcid;
|
||||
_lcidParent = (int32)e->parent_lcid;
|
||||
const char* ename = idx2string(e->name);
|
||||
_name.SetUTF8(ename, StringUtils::Length(ename));
|
||||
const char* nativename = idx2string(e->nativename);
|
||||
_nativeName.SetUTF8(nativename, StringUtils::Length(nativename));
|
||||
const char* englishname = idx2string(e->englishname);
|
||||
_englishName.SetUTF8(englishname, StringUtils::Length(englishname));
|
||||
}
|
||||
else
|
||||
{
|
||||
_lcid = 127;
|
||||
_lcidParent = 0;
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
#include "NavCrowd.h"
|
||||
#include "NavMesh.h"
|
||||
#include "NavMeshRuntime.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include <ThirdParty/recastnavigation/DetourCrowd.h>
|
||||
|
||||
NavCrowd::NavCrowd(const SpawnParams& params)
|
||||
@@ -26,6 +30,15 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMesh* navMesh)
|
||||
bool NavCrowd::Init(const NavAgentProperties& agentProperties, int32 maxAgents)
|
||||
{
|
||||
NavMeshRuntime* navMeshRuntime = NavMeshRuntime::Get(agentProperties);
|
||||
#if !BUILD_RELEASE
|
||||
if (!navMeshRuntime)
|
||||
{
|
||||
if (NavMeshRuntime::Get())
|
||||
LOG(Error, "Cannot create crowd. Failed to find a navmesh that matches a given agent properties.");
|
||||
else
|
||||
LOG(Error, "Cannot create crowd. No navmesh is loaded.");
|
||||
}
|
||||
#endif
|
||||
return Init(agentProperties.Radius * 3.0f, maxAgents, navMeshRuntime);
|
||||
}
|
||||
|
||||
@@ -33,6 +46,41 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe
|
||||
{
|
||||
if (!_crowd || !navMesh)
|
||||
return true;
|
||||
|
||||
// This can happen on game start when no navmesh is loaded yet (eg. navmesh tiles data is during streaming) so wait for navmesh
|
||||
if (navMesh->GetNavMesh() == nullptr)
|
||||
{
|
||||
PROFILE_CPU_NAMED("WaitForNavMesh");
|
||||
if (IsInMainThread())
|
||||
{
|
||||
// Wait for any navmesh data load
|
||||
for (const Scene* scene : Level::Scenes)
|
||||
{
|
||||
for (const NavMesh* actor : scene->Navigation.Meshes)
|
||||
{
|
||||
if (actor->DataAsset)
|
||||
{
|
||||
actor->DataAsset->WaitForLoaded();
|
||||
if (navMesh->GetNavMesh())
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (navMesh->GetNavMesh())
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (navMesh->GetNavMesh() == nullptr)
|
||||
Platform::Sleep(1);
|
||||
}
|
||||
if (navMesh->GetNavMesh() == nullptr)
|
||||
{
|
||||
LOG(Error, "Cannot create crowd. Navmesh is not yet laoded.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return !_crowd->init(maxAgents, maxAgentRadius, navMesh->GetNavMesh());
|
||||
}
|
||||
|
||||
@@ -48,7 +96,7 @@ Vector3 NavCrowd::GetAgentPosition(int32 id) const
|
||||
{
|
||||
Vector3 result = Vector3::Zero;
|
||||
const dtCrowdAgent* agent = _crowd->getAgent(id);
|
||||
if (agent && agent->state != DT_CROWDAGENT_STATE_INVALID)
|
||||
if (agent)
|
||||
{
|
||||
result = Float3(agent->npos);
|
||||
}
|
||||
@@ -59,7 +107,7 @@ Vector3 NavCrowd::GetAgentVelocity(int32 id) const
|
||||
{
|
||||
Vector3 result = Vector3::Zero;
|
||||
const dtCrowdAgent* agent = _crowd->getAgent(id);
|
||||
if (agent && agent->state != DT_CROWDAGENT_STATE_INVALID)
|
||||
if (agent)
|
||||
{
|
||||
result = Float3(agent->vel);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "Engine/Content/JsonAsset.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#if USE_EDITOR
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Managed/ManagedEditor.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#endif
|
||||
@@ -220,6 +222,17 @@ void NavigationSettings::Apply()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
if (!Editor::IsPlayMode && Editor::Managed && Editor::Managed->CanAutoBuildNavMesh())
|
||||
{
|
||||
// Rebuild all navmeshs after apply changes on navigation
|
||||
for (auto scene : Level::Scenes)
|
||||
{
|
||||
Navigation::BuildNavMesh(scene);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
|
||||
@@ -301,11 +301,8 @@ void NetworkTransform::Deserialize(NetworkStream* stream)
|
||||
_buffer.Clear();
|
||||
_bufferHasDeltas = true;
|
||||
}
|
||||
// TODO: items are added in order to do batch removal
|
||||
for (int32 i = 0; i < _buffer.Count() && _buffer[i].SequenceIndex < sequenceIndex; i++)
|
||||
{
|
||||
_buffer.RemoveAtKeepOrder(i);
|
||||
}
|
||||
while (_buffer.Count() != 0 && _buffer[0].SequenceIndex < sequenceIndex)
|
||||
_buffer.RemoveAtKeepOrder(0);
|
||||
|
||||
// Use received authoritative actor transformation but re-apply all deltas not yet processed by the server due to lag (reconciliation)
|
||||
for (auto& e : _buffer)
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
#include "IOnlinePlatform.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#endif
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
|
||||
class OnlineService : public EngineService
|
||||
{
|
||||
public:
|
||||
OnlineService()
|
||||
: EngineService(TEXT("Online"), 100)
|
||||
: EngineService(TEXT("Online"), 500)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -25,6 +28,16 @@ IOnlinePlatform* Online::Platform = nullptr;
|
||||
Action Online::PlatformChanged;
|
||||
OnlineService OnlineServiceInstance;
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void OnOnlineScriptsReloading()
|
||||
{
|
||||
// Dispose any active platform
|
||||
Online::Initialize(nullptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool Online::Initialize(IOnlinePlatform* platform)
|
||||
{
|
||||
if (Platform == platform)
|
||||
@@ -34,6 +47,9 @@ bool Online::Initialize(IOnlinePlatform* platform)
|
||||
|
||||
if (Platform)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
Scripting::ScriptsReloading.Unbind(OnOnlineScriptsReloading);
|
||||
#endif
|
||||
Platform->Deinitialize();
|
||||
}
|
||||
Platform = platform;
|
||||
@@ -45,6 +61,9 @@ bool Online::Initialize(IOnlinePlatform* platform)
|
||||
LOG(Error, "Failed to initialize online platform.");
|
||||
return true;
|
||||
}
|
||||
#if USE_EDITOR
|
||||
Scripting::ScriptsReloading.Bind(OnOnlineScriptsReloading);
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
|
||||
#define GET_VIEW() auto mainViewTask = MainRenderTask::Instance && MainRenderTask::Instance->LastUsedFrame != 0 ? MainRenderTask::Instance : nullptr
|
||||
#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[node->Attributes[index]].Offset)
|
||||
#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[context.AttributesRemappingTable[node->Attributes[index]]].Offset)
|
||||
#define GET_PARTICLE_ATTRIBUTE(index, type) *(type*)ACCESS_PARTICLE_ATTRIBUTE(index)
|
||||
|
||||
void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value)
|
||||
@@ -436,9 +436,19 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node
|
||||
auto* functionOutputNode = &graph->Nodes[function->Outputs[outputIndex]];
|
||||
Box* functionOutputBox = functionOutputNode->TryGetBox(0);
|
||||
|
||||
// Setup particle attributes remapping (so particle data access nodes inside the function will read data at proper offset, see macro ACCESS_PARTICLE_ATTRIBUTE)
|
||||
byte attributesRemappingTable[PARTICLE_ATTRIBUTES_MAX_COUNT];
|
||||
Platform::MemoryCopy(attributesRemappingTable, context.AttributesRemappingTable, sizeof(attributesRemappingTable));
|
||||
for (int32 i = 0; i < graph->Layout.Attributes.Count(); i++)
|
||||
{
|
||||
const ParticleAttribute& e = graph->Layout.Attributes[i];
|
||||
context.AttributesRemappingTable[i] = context.Data->Buffer->Layout->FindAttribute(e.Name, e.ValueType);
|
||||
}
|
||||
|
||||
// Evaluate the function output
|
||||
context.GraphStack.Push(graph);
|
||||
value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(nodeBase, functionOutputBox->FirstConnection()) : Value::Zero;
|
||||
Platform::MemoryCopy(context.AttributesRemappingTable, attributesRemappingTable, sizeof(attributesRemappingTable));
|
||||
context.GraphStack.Pop();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,8 @@ void ParticleEmitterGraphCPUExecutor::Init(ParticleEmitter* emitter, ParticleEff
|
||||
context.ViewTask = effect->GetRenderTask();
|
||||
context.CallStackSize = 0;
|
||||
context.Functions.Clear();
|
||||
for (int32 i = 0; i < PARTICLE_ATTRIBUTES_MAX_COUNT; i++)
|
||||
context.AttributesRemappingTable[i] = i;
|
||||
}
|
||||
|
||||
bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, BoundingBox& result)
|
||||
|
||||
@@ -119,6 +119,7 @@ struct ParticleEmitterGraphCPUContext
|
||||
class SceneRenderTask* ViewTask;
|
||||
Array<ParticleEmitterGraphCPU*, FixedAllocation<32>> GraphStack;
|
||||
Dictionary<VisjectExecutor::Node*, ParticleEmitterGraphCPU*> Functions;
|
||||
byte AttributesRemappingTable[PARTICLE_ATTRIBUTES_MAX_COUNT]; // Maps node attribute indices to the current particle layout (used to support accessing particle data from function graph which has different layout).
|
||||
int32 CallStackSize = 0;
|
||||
VisjectExecutor::Node* CallStack[PARTICLE_EMITTER_MAX_CALL_STACK];
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Particles/ParticleEmitter.h"
|
||||
#include "Engine/Particles/ParticleEffect.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/GPUBuffer.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
@@ -168,7 +169,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic
|
||||
if (viewTask)
|
||||
{
|
||||
bindMeta.Buffers = viewTask->Buffers;
|
||||
bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = true;
|
||||
bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = bindMeta.Buffers && bindMeta.Buffers->GetWidth() != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -179,7 +180,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic
|
||||
for (int32 i = 0; i < data.Parameters.Count(); i++)
|
||||
{
|
||||
// Copy instance parameters values
|
||||
_params[i].SetValue(data.Parameters[i]);
|
||||
_params[i].SetValue(data.Parameters.Get()[i]);
|
||||
}
|
||||
MaterialParamsLink link;
|
||||
link.This = &_params;
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace
|
||||
ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, const StringView& name, ParticleAttribute::ValueTypes valueType, AccessMode mode)
|
||||
{
|
||||
// Find this attribute
|
||||
return AccessParticleAttribute(caller, ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute(name, valueType), mode);
|
||||
return AccessParticleAttribute(caller, GetRootGraph()->Layout.FindAttribute(name, valueType), mode);
|
||||
}
|
||||
|
||||
ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, int32 index, AccessMode mode)
|
||||
@@ -55,7 +55,7 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt
|
||||
if (value.Variable.Type != VariantType::Null)
|
||||
return value.Variable;
|
||||
|
||||
auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[index];
|
||||
auto& attribute = GetRootGraph()->Layout.Attributes[index];
|
||||
const VariantType::Types type = GetValueType(attribute.ValueType);
|
||||
|
||||
// Generate local variable name that matches the attribute name for easier shader source debugging
|
||||
@@ -77,7 +77,7 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt
|
||||
else if (_contextType == ParticleContextType::Initialize)
|
||||
{
|
||||
// Initialize with default value
|
||||
const Value defaultValue(((ParticleEmitterGraphGPU*)_graphStack.Peek())->AttributesDefaults[index]);
|
||||
const Value defaultValue(GetRootGraph()->AttributesDefaults[index]);
|
||||
value.Variable = writeLocal(type, defaultValue.Value, caller, localName);
|
||||
}
|
||||
else
|
||||
@@ -251,10 +251,10 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
|
||||
{
|
||||
const Char* format;
|
||||
auto valueType = static_cast<ParticleAttribute::ValueTypes>(node->Values[1].AsInt);
|
||||
const int32 attributeIndex = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute((StringView)node->Values[0], valueType);
|
||||
const int32 attributeIndex = GetRootGraph()->Layout.FindAttribute((StringView)node->Values[0], valueType);
|
||||
if (attributeIndex == -1)
|
||||
return;
|
||||
auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[attributeIndex];
|
||||
auto& attribute = GetRootGraph()->Layout.Attributes[attributeIndex];
|
||||
const auto particleIndex = Value::Cast(tryGetValue(node->GetBox(1), Value(VariantType::Uint, TEXT("context.ParticleIndex"))), VariantType::Uint);
|
||||
switch (valueType)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user