bool URuntimeAssetCacheBPHooks::PollAsynchronousCompletion(int32 Handle)
{
	return GetRuntimeAssetCache().PollAsynchronousCompletion(Handle);
}
FVoidPtrParam URuntimeAssetCacheBPHooks::GetSynchronous(TScriptInterface<IRuntimeAssetCacheBuilder> CacheBuilder)
{
	return GetRuntimeAssetCache().GetSynchronous(static_cast<IRuntimeAssetCacheBuilder*>(CacheBuilder.GetInterface()));
}
void URuntimeAssetCacheBPHooks::WaitAsynchronousCompletion(int32 Handle)
{
	GetRuntimeAssetCache().WaitAsynchronousCompletion(Handle);
}
FVoidPtrParam URuntimeAssetCacheBPHooks::GetAsynchronousResults(int32 Handle)
{
	return GetRuntimeAssetCache().GetAsynchronousResults(Handle);
}
bool URuntimeAssetCacheBPHooks::ClearCache(FName Bucket)
{
	return GetRuntimeAssetCache().ClearCache(Bucket);
}
int32 URuntimeAssetCacheBPHooks::GetCacheSize(FName Bucket)
{
	return GetRuntimeAssetCache().GetCacheSize(Bucket);
}
int32 URuntimeAssetCacheBPHooks::GetAsynchronous(TScriptInterface<IRuntimeAssetCacheBuilder> CacheBuilder, const FOnRuntimeAssetCacheAsyncComplete& CompletionDelegate)
{
	return GetRuntimeAssetCache().GetAsynchronous(static_cast<IRuntimeAssetCacheBuilder*>(CacheBuilder.GetInterface()), CompletionDelegate);
}
void FRuntimeAssetCacheAsyncWorker::DoWork()
{
	const TCHAR* Bucket = CacheBuilder->GetTypeName();
	FName BucketName = FName(Bucket);
	FString CacheKey = BuildCacheKey(CacheBuilder);
	FName CacheKeyName = FName(*CacheKey);
	FRuntimeAssetCacheBucket* CurrentBucket = (*Buckets)[BucketName];
	INC_DWORD_STAT(STAT_RAC_NumGets);

	FCacheEntryMetadata* Metadata = nullptr;
	{
		DECLARE_SCOPE_CYCLE_COUNTER(TEXT("RAC async get time"), STAT_RAC_AsyncGetTime, STATGROUP_RAC);
		Metadata = CurrentBucket->GetMetadata(CacheKey);
	}

	if (Metadata == nullptr)
	{
		FRuntimeAssetCacheBucketScopeLock Guard(*CurrentBucket);
		CurrentBucket->AddMetadataEntry(CacheKey, new FCacheEntryMetadata(FDateTime::MaxValue(), 0, 0, CacheKeyName), false);
	}
	else
	{
		while (Metadata->IsBuilding())
		{
			FPlatformProcess::SleepNoStats(0.0f);
		}

		Metadata = FRuntimeAssetCacheBackend::Get().GetCachedData(BucketName, *CacheKey, Data);
	}

	ON_SCOPE_EXIT
	{
		/** Make sure completed work counter works properly regardless of where function is exited. */
		GetRuntimeAssetCache().AddToAsyncCompletionCounter(-1);
	};

	/* Entry found. */
	if (Metadata
		/* But was saved with older builder version. */
		&& Metadata->GetCachedAssetVersion() < CacheBuilder->GetAssetVersion())
	{
		/* Pretend entry wasn't found, so it gets rebuilt. */
		FRuntimeAssetCacheBucketScopeLock Guard(*CurrentBucket);
		FRuntimeAssetCacheBackend::Get().RemoveCacheEntry(BucketName, *CacheKey);
		CurrentBucket->AddToCurrentSize(-Metadata->GetCachedAssetSize());
		CurrentBucket->RemoveMetadataEntry(CacheKey);
		delete Metadata;
		Metadata = nullptr;
	}

	if (Metadata)
	{
		check(Data.Num());
		INC_DWORD_STAT(STAT_RAC_NumCacheHits);
		FRuntimeAssetCacheBucketScopeLock Guard(*CurrentBucket);
		Metadata->SetCachedAssetSize(Data.Num());
		Metadata->SetLastAccessTime(FDateTime::Now());
		bEntryRetrieved = true;
		return;
	}

	if (!CacheBuilder)
	{
		// Failed, cleanup data and return false.
		INC_DWORD_STAT(STAT_RAC_NumFails);
		Data.Empty();
		bEntryRetrieved = false;
		CurrentBucket->RemoveMetadataEntry(CacheKey);
		return;
	}

	bool bSuccess;
	{
		INC_DWORD_STAT(STAT_RAC_NumBuilds);
		DECLARE_SCOPE_CYCLE_COUNTER(TEXT("RAC async build time"), STAT_RAC_AsyncBuildTime, STATGROUP_RAC)
		bSuccess = CacheBuilder->Build(Data);
	}

	if (!bSuccess)
	{
		// Failed, cleanup data and return false.
		INC_DWORD_STAT(STAT_RAC_NumFails);
		Data.Empty();
		CurrentBucket->RemoveMetadataEntry(CacheKey);
		return;
	}

	checkf(Data.Num(), TEXT("Size of asset to cache cannot be null. Asset cache key: %s"), *CacheKey);
	checkf(Data.Num() < CurrentBucket->GetSize(), TEXT("Cached asset is bigger than cache size. Increase cache size or reduce asset size. Asset cache key: %s"), *CacheKey);

	FRuntimeAssetCacheBucketScopeLock Lock(*CurrentBucket);

	// Do we need to make some space in cache?
	int32 SizeOfSpaceToFree = CurrentBucket->GetCurrentSize() + Data.Num() - CurrentBucket->GetSize();
	if (SizeOfSpaceToFree > 0)
	{
		// Remove oldest entries from cache until we can fit upcoming entry.
		FreeCacheSpace(BucketName, SizeOfSpaceToFree);
	}

	{
		DECLARE_SCOPE_CYCLE_COUNTER(TEXT("RAC async put time"), STAT_RAC_PutTime, STATGROUP_RAC)
		FDateTime Now = FDateTime::Now();
		FCacheEntryMetadata* Metadata = CurrentBucket->GetMetadata(*CacheKey);
		if (Metadata)
		{
			Metadata->SetLastAccessTime(Now);
			if (Metadata->GetCachedAssetSize() == 0)
			{
				CurrentBucket->AddToCurrentSize(Data.Num());
			}
			Metadata->SetCachedAssetSize(Data.Num());
			Metadata->SetCachedAssetVersion(CacheBuilder->GetAssetVersion());
			CurrentBucket->AddMetadataEntry(CacheKey, Metadata, false);
		}
		else
		{
			Metadata = new FCacheEntryMetadata(Now, Data.Num(), CacheBuilder->GetAssetVersion(), CacheKeyName);
			CurrentBucket->AddMetadataEntry(CacheKey, Metadata, true);
		}
		FRuntimeAssetCacheBackend::Get().PutCachedData(BucketName, *CacheKey, Data, Metadata);

		// Mark that building is finished AFTER putting data into
		// cache to avoid duplicate builds of the same entry.
		Metadata->FinishBuilding();
	}

	bEntryRetrieved = true;
}
void URuntimeAssetCacheBuilder_ObjectBase::GetFromCacheAsync(const FOnAssetCacheComplete& OnComplete)
{
    OnAssetCacheComplete = OnComplete;
    GetFromCacheAsyncCompleteDelegate.BindDynamic(this, &URuntimeAssetCacheBuilder_ObjectBase::GetFromCacheAsyncComplete);
    GetRuntimeAssetCache().GetAsynchronous(this, GetFromCacheAsyncCompleteDelegate);
}