Merge branch 'master' into visject_grid_snap

Merged master.
This commit is contained in:
Menotdan
2023-11-10 10:44:59 -05:00
358 changed files with 9598 additions and 4356 deletions
@@ -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)
{
+1
View File
@@ -225,6 +225,7 @@ bool AudioClip::ExtractDataRaw(Array<byte>& resultData, AudioDataInfo& resultDat
void AudioClip::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}
+29 -2
View File
@@ -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"))
+35 -11
View File
@@ -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);
+3 -6
View File
@@ -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
+1
View File
@@ -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);
+14 -35
View File
@@ -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;
}
+1 -3
View File
@@ -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);
+17 -7
View File
@@ -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>
+47 -24
View File
@@ -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);
}
};
+18 -5
View File
@@ -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)
+95 -51
View File
@@ -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;
}
};
+130 -34
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -2
View File
@@ -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;
+109
View File
@@ -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);
+6
View File
@@ -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>
+6
View File
@@ -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>
+6
View File
@@ -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>
+10 -7
View File
@@ -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"
+20
View File
@@ -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>
+24
View File
@@ -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>
+57
View File
@@ -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;
+3
View File
@@ -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);
+3 -3
View File
@@ -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;
}
+6 -2
View File
@@ -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
+3
View File
@@ -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);
}
+2 -9
View File
@@ -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;
}
+2 -1
View File
@@ -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)
+1 -1
View File
@@ -22,7 +22,7 @@ class ScreenService : public EngineService
{
public:
ScreenService()
: EngineService(TEXT("Screen"), 120)
: EngineService(TEXT("Screen"), 500)
{
}
+1 -4
View File
@@ -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();
}
+43
View File
@@ -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;
+59 -83
View File
@@ -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,
};
+1 -1
View File
@@ -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
+15
View File
@@ -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.
+6 -5
View File
@@ -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.
+10 -1
View File
@@ -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();
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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)
+2 -3
View File
@@ -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
+1 -1
View File
@@ -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);
+6 -6
View File
@@ -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>
+13 -19
View File
@@ -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:
+2 -1
View File
@@ -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:
+1 -1
View File
@@ -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.
+6 -32
View File
@@ -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);
+2 -2
View File
@@ -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
+29 -5
View File
@@ -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;
}
}
}
}
+1 -1
View File
@@ -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 -15
View File
@@ -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;
+50 -2
View File
@@ -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);
}
+13
View File
@@ -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)
+20 -1
View File
@@ -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