diff --git a/Source/Engine/Content/Storage/FlaxChunk.h b/Source/Engine/Content/Storage/FlaxChunk.h
index 6e9887574..1d3bdce1a 100644
--- a/Source/Engine/Content/Storage/FlaxChunk.h
+++ b/Source/Engine/Content/Storage/FlaxChunk.h
@@ -87,6 +87,10 @@ public:
///
double LastAccessTime = 0.0;
+ ///
+ /// Flag set to indicate that chunk is during loading (atomic access to sync multiple reading threads).
+ ///
+ int64 IsLoading = 0;
///
/// The chunk data.
///
@@ -146,7 +150,7 @@ public:
///
FORCE_INLINE bool IsLoaded() const
{
- return Data.IsValid();
+ return Data.IsValid() && Platform::AtomicRead(&IsLoading) == 0;
}
///
@@ -154,7 +158,7 @@ public:
///
FORCE_INLINE bool IsMissing() const
{
- return Data.IsInvalid();
+ return !IsLoaded();
}
///
diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp
index ed8b623ae..1c6be4971 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.cpp
+++ b/Source/Engine/Content/Storage/FlaxStorage.cpp
@@ -5,6 +5,7 @@
#include "FlaxPackage.h"
#include "ContentStorageManager.h"
#include "Engine/Core/Log.h"
+#include "Engine/Core/ScopeExit.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Platform/File.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -246,6 +247,7 @@ FlaxStorage::~FlaxStorage()
ASSERT(IsDisposed());
CHECK(_chunksLock == 0);
CHECK(_refCount == 0);
+ CHECK(_isUnloadingData == 0);
ASSERT(_chunks.IsEmpty());
#if USE_EDITOR
@@ -261,6 +263,22 @@ FlaxStorage::~FlaxStorage()
#endif
}
+void FlaxStorage::LockChunks()
+{
+RETRY:
+ Platform::InterlockedIncrement(&_chunksLock);
+ if (Platform::AtomicRead(&_isUnloadingData) != 0)
+ {
+ // Someone else is closing file handles or freeing chunks so wait for it to finish and retry
+ Platform::InterlockedDecrement(&_chunksLock);
+ do
+ {
+ Platform::Sleep(1);
+ } while (Platform::AtomicRead(&_isUnloadingData) != 0);
+ goto RETRY;
+ }
+}
+
FlaxStorage::LockData FlaxStorage::LockSafe()
{
auto lock = LockData(this);
@@ -689,7 +707,6 @@ bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data)
return true;
}
- // Load header
return LoadAssetHeader(e, data);
}
@@ -699,7 +716,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
ASSERT(IsLoaded());
ASSERT(chunk != nullptr && _chunks.Contains(chunk));
- // Check if already loaded
+ // Protect against loading the same chunk from multiple threads at once
+ while (Platform::InterlockedCompareExchange(&chunk->IsLoading, 1, 0) != 0)
+ Platform::Sleep(1);
+ SCOPE_EXIT{ Platform::AtomicStore(&chunk->IsLoading, 0); };
if (chunk->IsLoaded())
return false;
@@ -776,12 +796,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
// Raw data
chunk->Data.Read(stream, size);
}
- ASSERT(chunk->IsLoaded());
chunk->RegisterUsage();
}
UnlockChunks();
-
return failed;
}
@@ -1420,10 +1438,12 @@ FileReadStream* FlaxStorage::OpenFile()
bool FlaxStorage::CloseFileHandles()
{
+ // Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
+ Platform::InterlockedIncrement(&_isUnloadingData);
+ SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
+
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
- {
- return false;
- }
+ return false; // Early out when no files are opened
PROFILE_CPU();
PROFILE_MEM(ContentFiles);
@@ -1496,9 +1516,21 @@ void FlaxStorage::Tick(double time)
{
auto chunk = _chunks.Get()[i];
const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime;
- if (!wasUsed && chunk->IsLoaded() && EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory))
+ if (!wasUsed &&
+ chunk->IsLoaded() &&
+ EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory) &&
+ Platform::AtomicRead(&chunk->IsLoading) == 0)
{
+ // Guard the unloading so if other thread wants to lock the chunks will need to wait for this to end
+ Platform::InterlockedIncrement(&_isUnloadingData);
+ if (Platform::AtomicRead(&_chunksLock) != 0 || Platform::AtomicRead(&chunk->IsLoading) != 0)
+ {
+ // Someone started loading so skip ticking
+ Platform::InterlockedDecrement(&_isUnloadingData);
+ return;
+ }
chunk->Unload();
+ Platform::InterlockedDecrement(&_isUnloadingData);
}
wasAnyUsed |= wasUsed;
}
diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h
index 450de9808..6de462214 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.h
+++ b/Source/Engine/Content/Storage/FlaxStorage.h
@@ -90,6 +90,7 @@ protected:
int64 _refCount = 0;
int64 _chunksLock = 0;
int64 _files = 0;
+ int64 _isUnloadingData = 0;
double _lastRefLostTime;
CriticalSection _loadLocker;
@@ -129,10 +130,7 @@ public:
///
/// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked.
///
- FORCE_INLINE void LockChunks()
- {
- Platform::InterlockedIncrement(&_chunksLock);
- }
+ void LockChunks();
///
/// Unlocks the storage chunks data.