Added OLSAssetManager, OLSExperienceManager, and OLSAssetManagerStartupJob
This commit is contained in:
parent
0eb6d52ca8
commit
38ae567d20
48
Source/ols/Private/GameModes/OLSExperienceManager.cpp
Normal file
48
Source/ols/Private/GameModes/OLSExperienceManager.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GameModes/OLSExperienceManager.h"
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
void UOLSExperienceManager::OnPlayInEditorBegun()
|
||||||
|
{
|
||||||
|
ensure(GameFeaturePluginRequestCountMap.IsEmpty());
|
||||||
|
GameFeaturePluginRequestCountMap.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManager::NotifyOfPluginActivation(const FString pluginURL)
|
||||||
|
{
|
||||||
|
if (GIsEditor)
|
||||||
|
{
|
||||||
|
UOLSExperienceManager* experienceManagerSubsystem = GEngine->GetEngineSubsystem<UOLSExperienceManager>();
|
||||||
|
check(experienceManagerSubsystem);
|
||||||
|
|
||||||
|
// Track the number of requesters who activate this plugin. Multiple load/activation requests are always allowed because concurrent requests are handled.
|
||||||
|
int32& count = experienceManagerSubsystem->GameFeaturePluginRequestCountMap.FindOrAdd(pluginURL);
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UOLSExperienceManager::RequestToDeactivatePlugin(const FString pluginURL)
|
||||||
|
{
|
||||||
|
if (GIsEditor)
|
||||||
|
{
|
||||||
|
UOLSExperienceManager* experienceManagerSubsystem = GEngine->GetEngineSubsystem<UOLSExperienceManager>();
|
||||||
|
check(experienceManagerSubsystem);
|
||||||
|
|
||||||
|
// Only let the last requester to get this far deactivate the plugin
|
||||||
|
int32& count = experienceManagerSubsystem->GameFeaturePluginRequestCountMap.FindChecked(pluginURL);
|
||||||
|
--count;
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
experienceManagerSubsystem->GameFeaturePluginRequestCountMap.Remove(pluginURL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
@ -3,3 +3,471 @@
|
|||||||
|
|
||||||
#include "GameModes/OLSExperienceManagerComponent.h"
|
#include "GameModes/OLSExperienceManagerComponent.h"
|
||||||
|
|
||||||
|
#include "GameFeatureAction.h"
|
||||||
|
#include "GameModes/OLSExperienceManager.h"
|
||||||
|
#include "GameFeaturesSubsystem.h"
|
||||||
|
#include "GameFeatureAction.h"
|
||||||
|
#include "GameFeaturesSubsystemSettings.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
|
#include "DataAssets/OLSExperienceDefinitionPrimaryDataAsset.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
#include "Systems/OLSAssetManager.h"
|
||||||
|
|
||||||
|
//@TODO: Async load the experience definition itself
|
||||||
|
//@TODO: Handle failures explicitly (go into a 'completed but failed' state rather than check()-ing)
|
||||||
|
//@TODO: Do the action phases at the appropriate times instead of all at once
|
||||||
|
//@TODO: Support deactivating an experience and do the unloading actions
|
||||||
|
//@TODO: Think about what deactivation/cleanup means for preloaded assets
|
||||||
|
//@TODO: Handle deactivating game features, right now we 'leak' them enabled
|
||||||
|
// (for a client moving from experience to experience we actually want to diff the requirements and only unload some, not unload everything for them to just be immediately reloaded)
|
||||||
|
//@TODO: Handle both built-in and URL-based plugins (search for colon?)
|
||||||
|
|
||||||
|
namespace OLSConsoleVariables
|
||||||
|
{
|
||||||
|
static float ExperienceLoadRandomDelayMin = 0.0f;
|
||||||
|
static FAutoConsoleVariableRef CVarExperienceLoadRandomDelayMin(
|
||||||
|
TEXT("lyra.chaos.ExperienceDelayLoad.MinSecs"),
|
||||||
|
ExperienceLoadRandomDelayMin,
|
||||||
|
TEXT("This value (in seconds) will be added as a delay of load completion of the experience (along with the random value lyra.chaos.ExperienceDelayLoad.RandomSecs)"),
|
||||||
|
ECVF_Default);
|
||||||
|
|
||||||
|
static float ExperienceLoadRandomDelayRange = 0.0f;
|
||||||
|
static FAutoConsoleVariableRef CVarExperienceLoadRandomDelayRange(
|
||||||
|
TEXT("lyra.chaos.ExperienceDelayLoad.RandomSecs"),
|
||||||
|
ExperienceLoadRandomDelayRange,
|
||||||
|
TEXT("A random amount of time between 0 and this value (in seconds) will be added as a delay of load completion of the experience (along with the fixed value lyra.chaos.ExperienceDelayLoad.MinSecs)"),
|
||||||
|
ECVF_Default);
|
||||||
|
|
||||||
|
float GetExperienceLoadDelayDuration()
|
||||||
|
{
|
||||||
|
return FMath::Max(0.0f, ExperienceLoadRandomDelayMin + FMath::FRand() * ExperienceLoadRandomDelayRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
UOLSExperienceManagerComponent::UOLSExperienceManagerComponent(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||||
|
{
|
||||||
|
SetIsReplicatedByDefault(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||||
|
{
|
||||||
|
Super::EndPlay(endPlayReason);
|
||||||
|
|
||||||
|
// deactivate any features this experience loaded
|
||||||
|
//@TODO: This should be handled FILO as well
|
||||||
|
for (const FString& PluginURL : GameFeaturePluginURLs)
|
||||||
|
{
|
||||||
|
if (UOLSExperienceManager::RequestToDeactivatePlugin(PluginURL))
|
||||||
|
{
|
||||||
|
UGameFeaturesSubsystem::Get().DeactivateGameFeaturePlugin(PluginURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//@TODO: Ensure proper handling of a partially-loaded state too
|
||||||
|
if (LoadState == EOLSExperienceLoadState::Loaded)
|
||||||
|
{
|
||||||
|
LoadState = EOLSExperienceLoadState::Deactivating;
|
||||||
|
|
||||||
|
// Make sure we won't complete the transition prematurely if someone registers as a pauser but fires immediately
|
||||||
|
NumExpectedPausers = INDEX_NONE;
|
||||||
|
NumObservedPausers = 0;
|
||||||
|
|
||||||
|
// Deactivate and unload the actions
|
||||||
|
FGameFeatureDeactivatingContext context(TEXT(""), [this](FStringView) { this->OnActionDeactivationCompleted(); });
|
||||||
|
|
||||||
|
const FWorldContext* existingWorldContext = GEngine->GetWorldContextFromWorld(GetWorld());
|
||||||
|
if (existingWorldContext)
|
||||||
|
{
|
||||||
|
context.SetRequiredWorldContextHandle(existingWorldContext->ContextHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DeactivateListOfActions = [&context](const TArray<UGameFeatureAction*>& ActionList)
|
||||||
|
{
|
||||||
|
for (UGameFeatureAction* Action : ActionList)
|
||||||
|
{
|
||||||
|
if (Action)
|
||||||
|
{
|
||||||
|
Action->OnGameFeatureDeactivating(context);
|
||||||
|
Action->OnGameFeatureUnregistering();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeactivateListOfActions(CurrentExperience->Actions);
|
||||||
|
// @Todo implement this.
|
||||||
|
// for (const TObjectPtr<UOLSExperienceActionSet>& ActionSet : CurrentExperience->ActionSets)
|
||||||
|
// {
|
||||||
|
// if (ActionSet != nullptr)
|
||||||
|
// {
|
||||||
|
// DeactivateListOfActions(ActionSet->Actions);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
NumExpectedPausers = context.GetNumPausers();
|
||||||
|
|
||||||
|
if (NumExpectedPausers > 0)
|
||||||
|
{
|
||||||
|
// @Todo replace this with our custom.
|
||||||
|
// UE_LOG(LogLyraExperience, Error, TEXT("Actions that have asynchronous deactivation aren't fully supported yet in Lyra experiences"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NumExpectedPausers == NumObservedPausers)
|
||||||
|
{
|
||||||
|
OnAllActionsDeactivated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
DOREPLIFETIME(ThisClass, CurrentExperience);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UOLSExperienceManagerComponent::ShouldShowLoadingScreen(FString& outReason) const
|
||||||
|
{
|
||||||
|
if (LoadState != EOLSExperienceLoadState::Loaded)
|
||||||
|
{
|
||||||
|
outReason = TEXT("Experience still loading");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UOLSExperienceDefinitionPrimaryDataAsset* UOLSExperienceManagerComponent::GetCurrentExperienceChecked() const
|
||||||
|
{
|
||||||
|
check(LoadState == EOLSExperienceLoadState::Loaded);
|
||||||
|
check(CurrentExperience != nullptr);
|
||||||
|
return CurrentExperience;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::SetCurrentExperience(FPrimaryAssetId experienceId)
|
||||||
|
{
|
||||||
|
UOLSAssetManager& assetManager = UOLSAssetManager::Get();
|
||||||
|
FSoftObjectPath assetPath = assetManager.GetPrimaryAssetPath(experienceId);
|
||||||
|
TSubclassOf<UOLSExperienceDefinitionPrimaryDataAsset> assetClass = Cast<UClass>(assetPath.TryLoad());
|
||||||
|
check(assetClass);
|
||||||
|
const UOLSExperienceDefinitionPrimaryDataAsset* experience = GetDefault<UOLSExperienceDefinitionPrimaryDataAsset>(assetClass);
|
||||||
|
|
||||||
|
check(experience != nullptr);
|
||||||
|
check(CurrentExperience == nullptr);
|
||||||
|
CurrentExperience = experience;
|
||||||
|
StartExperienceLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UOLSExperienceManagerComponent::IsExperienceLoaded() const
|
||||||
|
{
|
||||||
|
return (LoadState == EOLSExperienceLoadState::Loaded) && (CurrentExperience != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_HighPriority(
|
||||||
|
FOnOLSExperienceLoaded::FDelegate&& delegate)
|
||||||
|
{
|
||||||
|
if (IsExperienceLoaded())
|
||||||
|
{
|
||||||
|
delegate.Execute(CurrentExperience);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnExperienceLoaded_HighPriority.Add(MoveTemp(delegate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded(FOnOLSExperienceLoaded::FDelegate&& delegate)
|
||||||
|
{
|
||||||
|
if (IsExperienceLoaded())
|
||||||
|
{
|
||||||
|
delegate.Execute(CurrentExperience);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnExperienceLoaded.Add(MoveTemp(delegate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_LowPriority(
|
||||||
|
FOnOLSExperienceLoaded::FDelegate&& delegate)
|
||||||
|
{
|
||||||
|
if (IsExperienceLoaded())
|
||||||
|
{
|
||||||
|
delegate.Execute(CurrentExperience);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnExperienceLoaded_LowPriority.Add(MoveTemp(delegate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::OnRep_CurrentExperience()
|
||||||
|
{
|
||||||
|
StartExperienceLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::StartExperienceLoad()
|
||||||
|
{
|
||||||
|
check(CurrentExperience != nullptr);
|
||||||
|
check(LoadState == EOLSExperienceLoadState::Unloaded);
|
||||||
|
|
||||||
|
// @Todo replace this by custom log.
|
||||||
|
// UE_LOG(LogLyraExperience, Log, TEXT("EXPERIENCE: StartExperienceLoad(CurrentExperience = %s, %s)"),
|
||||||
|
// *CurrentExperience->GetPrimaryAssetId().ToString(),
|
||||||
|
// *GetClientServerContextString(this));
|
||||||
|
|
||||||
|
LoadState = EOLSExperienceLoadState::Loading;
|
||||||
|
|
||||||
|
UOLSAssetManager& assetManager = UOLSAssetManager::Get();
|
||||||
|
|
||||||
|
TSet<FPrimaryAssetId> bundleAssetList;
|
||||||
|
TSet<FSoftObjectPath> rawAssetList;
|
||||||
|
|
||||||
|
bundleAssetList.Add(CurrentExperience->GetPrimaryAssetId());
|
||||||
|
// @Todo implement this
|
||||||
|
// for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets)
|
||||||
|
// {
|
||||||
|
// if (ActionSet != nullptr)
|
||||||
|
// {
|
||||||
|
// BundleAssetList.Add(ActionSet->GetPrimaryAssetId());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Load assets associated with the experience
|
||||||
|
|
||||||
|
TArray<FName> bundlesToLoad;
|
||||||
|
bundlesToLoad.Add(FOLSBundles::Equipped);
|
||||||
|
|
||||||
|
//@TODO: Centralize this client/server stuff into the OLSAssetManager
|
||||||
|
const ENetMode ownerNetMode = GetOwner()->GetNetMode();
|
||||||
|
const bool shouldLoadClient = GIsEditor || (ownerNetMode != NM_DedicatedServer);
|
||||||
|
const bool shouldLoadServer = GIsEditor || (ownerNetMode != NM_Client);
|
||||||
|
if (shouldLoadClient)
|
||||||
|
{
|
||||||
|
bundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateClient);
|
||||||
|
}
|
||||||
|
if (shouldLoadServer)
|
||||||
|
{
|
||||||
|
bundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FStreamableHandle> bundleLoadHandle = nullptr;
|
||||||
|
if (bundleAssetList.Num() > 0)
|
||||||
|
{
|
||||||
|
bundleLoadHandle = assetManager.ChangeBundleStateForPrimaryAssets(bundleAssetList.Array(), bundlesToLoad, {}, false, FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority);
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FStreamableHandle> rawLoadHandle = nullptr;
|
||||||
|
if (rawAssetList.Num() > 0)
|
||||||
|
{
|
||||||
|
rawLoadHandle = assetManager.LoadAssetList(rawAssetList.Array(), FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority, TEXT("StartExperienceLoad()"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both async loads are running, combine them
|
||||||
|
TSharedPtr<FStreamableHandle> handle = nullptr;
|
||||||
|
if (bundleLoadHandle.IsValid() && rawLoadHandle.IsValid())
|
||||||
|
{
|
||||||
|
handle = assetManager.GetStreamableManager().CreateCombinedHandle({ bundleLoadHandle, rawLoadHandle });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handle = bundleLoadHandle.IsValid() ? bundleLoadHandle : rawLoadHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
FStreamableDelegate onAssetsLoadedDelegate = FStreamableDelegate::CreateUObject(this, &ThisClass::OnExperienceLoadComplete);
|
||||||
|
if (!handle.IsValid() || handle->HasLoadCompleted())
|
||||||
|
{
|
||||||
|
// Assets were already loaded, call the delegate now
|
||||||
|
FStreamableHandle::ExecuteDelegate(onAssetsLoadedDelegate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handle->BindCompleteDelegate(onAssetsLoadedDelegate);
|
||||||
|
|
||||||
|
handle->BindCancelDelegate(FStreamableDelegate::CreateLambda([onAssetsLoadedDelegate]()
|
||||||
|
{
|
||||||
|
onAssetsLoadedDelegate.ExecuteIfBound();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This set of assets gets preloaded, but we don't block the start of the experience based on it
|
||||||
|
TSet<FPrimaryAssetId> preloadAssetList;
|
||||||
|
//@TODO: Determine assets to preload (but not blocking-ly)
|
||||||
|
if (preloadAssetList.Num() > 0)
|
||||||
|
{
|
||||||
|
assetManager.ChangeBundleStateForPrimaryAssets(preloadAssetList.Array(), bundlesToLoad, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::OnExperienceLoadComplete()
|
||||||
|
{
|
||||||
|
check(LoadState == EOLSExperienceLoadState::Loading);
|
||||||
|
check(CurrentExperience != nullptr);
|
||||||
|
|
||||||
|
// @Todo replace this by our custom.
|
||||||
|
// UE_LOG(LogLyraExperience, Log, TEXT("EXPERIENCE: OnExperienceLoadComplete(CurrentExperience = %s, %s)"),
|
||||||
|
// *CurrentExperience->GetPrimaryAssetId().ToString(),
|
||||||
|
// *GetClientServerContextString(this));
|
||||||
|
|
||||||
|
// find the URLs for our GameFeaturePlugins - filtering out dupes and ones that don't have a valid mapping
|
||||||
|
GameFeaturePluginURLs.Reset();
|
||||||
|
|
||||||
|
auto collectGameFeaturePluginURLs = [This=this](const UPrimaryDataAsset* context, const TArray<FString>& featurePluginList)
|
||||||
|
{
|
||||||
|
for (const FString& pluginName : featurePluginList)
|
||||||
|
{
|
||||||
|
FString pluginURL;
|
||||||
|
if (UGameFeaturesSubsystem::Get().GetPluginURLByName(pluginName, /*out*/ pluginURL))
|
||||||
|
{
|
||||||
|
This->GameFeaturePluginURLs.AddUnique(pluginURL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ensureMsgf(false, TEXT("OnExperienceLoadComplete failed to find plugin URL from PluginName %s for experience %s - fix data, ignoring for this run"), *pluginName, *context->GetPrimaryAssetId().ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Add in our extra plugin
|
||||||
|
// if (!CurrentPlaylistData->GameFeaturePluginToActivateUntilDownloadedContentIsPresent.IsEmpty())
|
||||||
|
// {
|
||||||
|
// FString PluginURL;
|
||||||
|
// if (UGameFeaturesSubsystem::Get().GetPluginURLByName(CurrentPlaylistData->GameFeaturePluginToActivateUntilDownloadedContentIsPresent, PluginURL))
|
||||||
|
// {
|
||||||
|
// GameFeaturePluginURLs.AddUnique(PluginURL);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
collectGameFeaturePluginURLs(CurrentExperience, CurrentExperience->GameFeaturesToEnable);
|
||||||
|
// @Todo implement this.
|
||||||
|
// for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets)
|
||||||
|
// {
|
||||||
|
// if (ActionSet != nullptr)
|
||||||
|
// {
|
||||||
|
// CollectGameFeaturePluginURLs(ActionSet, ActionSet->GameFeaturesToEnable);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Load and activate the features
|
||||||
|
NumGameFeaturePluginsLoading = GameFeaturePluginURLs.Num();
|
||||||
|
if (NumGameFeaturePluginsLoading > 0)
|
||||||
|
{
|
||||||
|
LoadState = EOLSExperienceLoadState::LoadingGameFeatures;
|
||||||
|
for (const FString& pluginURL : GameFeaturePluginURLs)
|
||||||
|
{
|
||||||
|
UOLSExperienceManager::NotifyOfPluginActivation(pluginURL);
|
||||||
|
UGameFeaturesSubsystem::Get().LoadAndActivateGameFeaturePlugin(
|
||||||
|
pluginURL,
|
||||||
|
FGameFeaturePluginLoadComplete::CreateUObject(this, &ThisClass::OnGameFeaturePluginLoadComplete));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnExperienceFullLoadCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::OnGameFeaturePluginLoadComplete(const UE::GameFeatures::FResult& result)
|
||||||
|
{
|
||||||
|
// decrement the number of plugins that are loading
|
||||||
|
NumGameFeaturePluginsLoading--;
|
||||||
|
|
||||||
|
if (NumGameFeaturePluginsLoading == 0)
|
||||||
|
{
|
||||||
|
OnExperienceFullLoadCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::OnExperienceFullLoadCompleted()
|
||||||
|
{
|
||||||
|
check(LoadState != EOLSExperienceLoadState::Loaded);
|
||||||
|
|
||||||
|
// Insert a random delay for testing (if configured)
|
||||||
|
if (LoadState != EOLSExperienceLoadState::LoadingChaosTestingDelay)
|
||||||
|
{
|
||||||
|
const float delaySecs = OLSConsoleVariables::GetExperienceLoadDelayDuration();
|
||||||
|
if (delaySecs > 0.0f)
|
||||||
|
{
|
||||||
|
FTimerHandle dummyHandle;
|
||||||
|
|
||||||
|
LoadState = EOLSExperienceLoadState::LoadingChaosTestingDelay;
|
||||||
|
GetWorld()->GetTimerManager().SetTimer(dummyHandle, this, &ThisClass::OnExperienceFullLoadCompleted, delaySecs, /*bLooping=*/ false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadState = EOLSExperienceLoadState::ExecutingActions;
|
||||||
|
|
||||||
|
// Execute the actions
|
||||||
|
FGameFeatureActivatingContext context;
|
||||||
|
|
||||||
|
// Only apply to our specific world context if set
|
||||||
|
const FWorldContext* existingWorldContext = GEngine->GetWorldContextFromWorld(GetWorld());
|
||||||
|
if (existingWorldContext)
|
||||||
|
{
|
||||||
|
context.SetRequiredWorldContextHandle(existingWorldContext->ContextHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto activateListOfActions = [&context](const TArray<UGameFeatureAction*>& actionList)
|
||||||
|
{
|
||||||
|
for (UGameFeatureAction* action : actionList)
|
||||||
|
{
|
||||||
|
if (action != nullptr)
|
||||||
|
{
|
||||||
|
//@TODO: The fact that these don't take a world are potentially problematic in client-server PIE
|
||||||
|
// The current behavior matches systems like gameplay tags where loading and registering apply to the entire process,
|
||||||
|
// but actually applying the results to actors is restricted to a specific world
|
||||||
|
action->OnGameFeatureRegistering();
|
||||||
|
action->OnGameFeatureLoading();
|
||||||
|
action->OnGameFeatureActivating(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
activateListOfActions(CurrentExperience->Actions);
|
||||||
|
// @Todo implement this
|
||||||
|
// for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets)
|
||||||
|
// {
|
||||||
|
// if (ActionSet != nullptr)
|
||||||
|
// {
|
||||||
|
// activateListOfActions(ActionSet->Actions);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
LoadState = EOLSExperienceLoadState::Loaded;
|
||||||
|
|
||||||
|
OnExperienceLoaded_HighPriority.Broadcast(CurrentExperience);
|
||||||
|
OnExperienceLoaded_HighPriority.Clear();
|
||||||
|
|
||||||
|
OnExperienceLoaded.Broadcast(CurrentExperience);
|
||||||
|
OnExperienceLoaded.Clear();
|
||||||
|
|
||||||
|
OnExperienceLoaded_LowPriority.Broadcast(CurrentExperience);
|
||||||
|
OnExperienceLoaded_LowPriority.Clear();
|
||||||
|
|
||||||
|
// Apply any necessary scalability settings
|
||||||
|
#if !UE_SERVER
|
||||||
|
// @Todo implement this
|
||||||
|
// UOLSSettingsLocal::Get()->OnExperienceLoaded();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::OnActionDeactivationCompleted()
|
||||||
|
{
|
||||||
|
check(IsInGameThread());
|
||||||
|
++NumObservedPausers;
|
||||||
|
|
||||||
|
if (NumObservedPausers == NumExpectedPausers)
|
||||||
|
{
|
||||||
|
OnAllActionsDeactivated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSExperienceManagerComponent::OnAllActionsDeactivated()
|
||||||
|
{
|
||||||
|
//@TODO: We actually only deactivated and didn't fully unload...
|
||||||
|
LoadState = EOLSExperienceLoadState::Unloaded;
|
||||||
|
CurrentExperience = nullptr;
|
||||||
|
//@TODO: GEngine->ForceGarbageCollection(true);
|
||||||
|
}
|
||||||
|
@ -4,13 +4,61 @@
|
|||||||
#include "Player/OLSPlayerState.h"
|
#include "Player/OLSPlayerState.h"
|
||||||
|
|
||||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||||
|
#include "Components/GameFrameworkComponentManager.h"
|
||||||
|
#include "DataAssets/OLSAbilitySetPrimaryDataAsset.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
#include "Player/OLSPlayerController.h"
|
#include "Player/OLSPlayerController.h"
|
||||||
|
|
||||||
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSPlayerState)
|
||||||
|
|
||||||
|
const FName AOLSPlayerState::NAME_OLSAbilityReady("OLSAbilitiesReady");
|
||||||
|
|
||||||
AOLSPlayerState::AOLSPlayerState(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
AOLSPlayerState::AOLSPlayerState(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||||
{
|
{
|
||||||
// Create attribute sets here.
|
// Create attribute sets here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
const T* AOLSPlayerState::GetPawnData() const
|
||||||
|
{
|
||||||
|
return Cast<T>(PawnData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AOLSPlayerState::SetPawnData(const UOLSPawnPrimaryDataAsset* pawnData)
|
||||||
|
{
|
||||||
|
check(pawnData);
|
||||||
|
|
||||||
|
if (!HasAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PawnData)
|
||||||
|
{
|
||||||
|
// @Todo: replace this by our custom log.
|
||||||
|
// UE_LOG(LogLyra, Error, TEXT("Trying to set PawnData [%s] on player state [%s] that already has valid PawnData [%s]."), *GetNameSafe(InPawnData), *GetNameSafe(this), *GetNameSafe(PawnData));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, PawnData, this);
|
||||||
|
|
||||||
|
for (const UOLSAbilitySetPrimaryDataAsset* abilityDataAsset : PawnData->AbilitySets)
|
||||||
|
{
|
||||||
|
if (abilityDataAsset)
|
||||||
|
{
|
||||||
|
abilityDataAsset->GiveToAbilitySystem(AbilitySystemComponent, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, NAME_OLSAbilityReady);
|
||||||
|
|
||||||
|
ForceNetUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AOLSPlayerState::OnRep_PawnData()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void AOLSPlayerState::PostInitializeComponents()
|
void AOLSPlayerState::PostInitializeComponents()
|
||||||
{
|
{
|
||||||
Super::PostInitializeComponents();
|
Super::PostInitializeComponents();
|
||||||
@ -29,6 +77,16 @@ void AOLSPlayerState::PostInitializeComponents()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AOLSPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
FDoRepLifetimeParams SharedParams;
|
||||||
|
SharedParams.bIsPushBased = true;
|
||||||
|
|
||||||
|
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, PawnData, SharedParams);
|
||||||
|
}
|
||||||
|
|
||||||
AOLSPlayerController* AOLSPlayerState::GetOLSPlayerController() const
|
AOLSPlayerController* AOLSPlayerState::GetOLSPlayerController() const
|
||||||
{
|
{
|
||||||
return Cast<AOLSPlayerController>(GetOwner());
|
return Cast<AOLSPlayerController>(GetOwner());
|
||||||
@ -37,4 +95,4 @@ AOLSPlayerController* AOLSPlayerState::GetOLSPlayerController() const
|
|||||||
UOLSAbilitySystemComponent* AOLSPlayerState::GetOLSAbilitySystemComponent() const
|
UOLSAbilitySystemComponent* AOLSPlayerState::GetOLSAbilitySystemComponent() const
|
||||||
{
|
{
|
||||||
return AbilitySystemComponent;
|
return AbilitySystemComponent;
|
||||||
}
|
}
|
329
Source/ols/Private/Systems/OLSAssetManager.cpp
Normal file
329
Source/ols/Private/Systems/OLSAssetManager.cpp
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Systems/OLSAssetManager.h"
|
||||||
|
#include "Misc/App.h"
|
||||||
|
#include "Stats/StatsMisc.h"
|
||||||
|
#include "Misc/ScopedSlowTask.h"
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
#include "Systems/OLSAssetManagerStartupJob.h"
|
||||||
|
#include "DataAssets/OLSPawnPrimaryDataAsset.h"
|
||||||
|
|
||||||
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSAssetManager)
|
||||||
|
|
||||||
|
const FName FOLSBundles::Equipped("Equipped");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static FAutoConsoleCommand CVarDumpLoadedAssets(
|
||||||
|
TEXT("OLS.DumpLoadedAssets"),
|
||||||
|
TEXT("Shows all assets that were loaded via the asset manager and are currently in memory."),
|
||||||
|
FConsoleCommandDelegate::CreateStatic(UOLSAssetManager::DumpLoadedAssets)
|
||||||
|
);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#define STARTUP_JOB_WEIGHTED(jobFunc, jobWeight) StartupJobs.Add(FOLSAssetManagerStartupJob(#jobFunc, [this](const FOLSAssetManagerStartupJob& startupJob, TSharedPtr<FStreamableHandle>& loadHandle){jobFunc;}, jobWeight))
|
||||||
|
#define STARTUP_JOB(jobFunc) STARTUP_JOB_WEIGHTED(jobFunc, 1.f)
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
UOLSAssetManager::UOLSAssetManager()
|
||||||
|
{
|
||||||
|
DefaultPawnData = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UOLSAssetManager& UOLSAssetManager::Get()
|
||||||
|
{
|
||||||
|
check(GEngine);
|
||||||
|
|
||||||
|
if (UOLSAssetManager* Singleton = Cast<UOLSAssetManager>(GEngine->AssetManager))
|
||||||
|
{
|
||||||
|
return *Singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Todo replace this our custom log.
|
||||||
|
// UE_LOG(LogLyra, Fatal, TEXT("Invalid AssetManagerClassName in DefaultEngine.ini. It must be set to LyraAssetManager!"));
|
||||||
|
|
||||||
|
// Fatal error above prevents this from being called.
|
||||||
|
return *NewObject<UOLSAssetManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AssetType>
|
||||||
|
AssetType* UOLSAssetManager::GetAsset(const TSoftObjectPtr<AssetType>& assetPointer, bool shouldKeepInMemory)
|
||||||
|
{
|
||||||
|
AssetType* loadedAsset = nullptr;
|
||||||
|
|
||||||
|
const FSoftObjectPath& assetPath = assetPointer.ToSoftObjectPath();
|
||||||
|
|
||||||
|
if (assetPath.IsValid())
|
||||||
|
{
|
||||||
|
loadedAsset = assetPointer.Get();
|
||||||
|
if (!loadedAsset)
|
||||||
|
{
|
||||||
|
loadedAsset = Cast<AssetType>(SynchronousLoadAsset(assetPath));
|
||||||
|
ensureAlwaysMsgf(loadedAsset, TEXT("Failed to load asset [%s]"), *assetPointer.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedAsset && shouldKeepInMemory)
|
||||||
|
{
|
||||||
|
// Added to loaded asset list.
|
||||||
|
Get().AddLoadedAsset(Cast<UObject>(loadedAsset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadedAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AssetType>
|
||||||
|
TSubclassOf<AssetType> UOLSAssetManager::GetSubclass(const TSoftClassPtr<AssetType>& assetPointer, bool shouldKeepInMemory)
|
||||||
|
{
|
||||||
|
TSubclassOf<AssetType> loadedSubclass = nullptr;
|
||||||
|
|
||||||
|
const FSoftObjectPath& assetPath = assetPointer.ToSoftObjectPath();
|
||||||
|
|
||||||
|
if (assetPath.IsValid())
|
||||||
|
{
|
||||||
|
loadedSubclass = assetPointer.Get();
|
||||||
|
if (!loadedSubclass)
|
||||||
|
{
|
||||||
|
loadedSubclass = Cast<UClass>(SynchronousLoadAsset(assetPath));
|
||||||
|
ensureAlwaysMsgf(loadedSubclass, TEXT("Failed to load asset class [%s]"), *assetPointer.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedSubclass && shouldKeepInMemory)
|
||||||
|
{
|
||||||
|
// Added to loaded asset list.
|
||||||
|
Get().AddLoadedAsset(Cast<UObject>(loadedSubclass));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadedSubclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSAssetManager::DumpLoadedAssets()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const UOLSPawnPrimaryDataAsset* UOLSAssetManager::GetDefaultPawnData() const
|
||||||
|
{
|
||||||
|
return GetAsset(DefaultPawnData);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename GameDataClass>
|
||||||
|
const GameDataClass& UOLSAssetManager::GetOrLoadTypedGameData(const TSoftObjectPtr<GameDataClass>& dataPath)
|
||||||
|
{
|
||||||
|
if (const TObjectPtr<UPrimaryDataAsset>* pResult = GameDataMap.Find(GameDataClass::StaticClass()))
|
||||||
|
{
|
||||||
|
return *CastChecked<GameDataClass>(*pResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does a blocking load if needed
|
||||||
|
return *CastChecked<const GameDataClass>(
|
||||||
|
LoadGameDataOfClass(GameDataClass::StaticClass(), dataPath, GameDataClass::StaticClass()->GetFName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UObject* UOLSAssetManager::SynchronousLoadAsset(const FSoftObjectPath& assetPath)
|
||||||
|
{
|
||||||
|
if (assetPath.IsValid())
|
||||||
|
{
|
||||||
|
TUniquePtr<FScopeLogTime> logTimePtr;
|
||||||
|
|
||||||
|
if (ShouldLogAssetLoads())
|
||||||
|
{
|
||||||
|
logTimePtr = MakeUnique<FScopeLogTime>(*FString::Printf(TEXT("Synchronously loaded asset [%s]"), *assetPath.ToString()), nullptr, FScopeLogTime::ScopeLog_Seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UAssetManager::IsInitialized())
|
||||||
|
{
|
||||||
|
return UAssetManager::GetStreamableManager().LoadSynchronous(assetPath, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use LoadObject if asset manager isn't ready yet.
|
||||||
|
return assetPath.TryLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UOLSAssetManager::ShouldLogAssetLoads()
|
||||||
|
{
|
||||||
|
static bool shouldLogAssetLoads = FParse::Param(FCommandLine::Get(), TEXT("LogAssetLoads"));
|
||||||
|
return shouldLogAssetLoads;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSAssetManager::AddLoadedAsset(const UObject* Asset)
|
||||||
|
{
|
||||||
|
if (ensureAlways(Asset))
|
||||||
|
{
|
||||||
|
FScopeLock LoadedAssetsLock(&LoadedAssetsCritical);
|
||||||
|
LoadedAssets.Add(Asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSAssetManager::StartInitialLoading()
|
||||||
|
{
|
||||||
|
SCOPED_BOOT_TIMING("UOLSAssetManager::StartInitialLoading");
|
||||||
|
|
||||||
|
// This does all of the scanning, need to do this now even if loads are deferred
|
||||||
|
Super::StartInitialLoading();
|
||||||
|
|
||||||
|
STARTUP_JOB(InitializeGameplayCueManager());
|
||||||
|
|
||||||
|
{
|
||||||
|
// Load base game data asset
|
||||||
|
// @Todo uncomment this after implementing GetGameData().
|
||||||
|
// STARTUP_JOB_WEIGHTED(GetGameData(), 25.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all the queued up startup jobs
|
||||||
|
DoAllStartupJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
void UOLSAssetManager::PreBeginPIE(bool shouldStartSimulate)
|
||||||
|
{
|
||||||
|
Super::PreBeginPIE(shouldStartSimulate);
|
||||||
|
|
||||||
|
{
|
||||||
|
FScopedSlowTask slowTask(0, NSLOCTEXT("OLSEditor", "BeginLoadingPIEData", "Loading PIE Data"));
|
||||||
|
const bool shouldShowCancelButton = false;
|
||||||
|
const bool shouldAllowInPIE = true;
|
||||||
|
slowTask.MakeDialog(shouldShowCancelButton, shouldAllowInPIE);
|
||||||
|
|
||||||
|
// @Todo unlock this comment.
|
||||||
|
// const ULyraGameData& LocalGameDataCommon = GetGameData();
|
||||||
|
|
||||||
|
// Intentionally after GetGameData to avoid counting GameData time in this timer
|
||||||
|
SCOPE_LOG_TIME_IN_SECONDS(TEXT("PreBeginPIE asset preloading complete"), nullptr);
|
||||||
|
|
||||||
|
// You could add preloading of anything else needed for the experience we'll be using here
|
||||||
|
// (e.g., by grabbing the default experience from the world settings + the experience override in developer settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
UPrimaryDataAsset* UOLSAssetManager::LoadGameDataOfClass(
|
||||||
|
TSubclassOf<UPrimaryDataAsset> dataClass,
|
||||||
|
const TSoftObjectPtr<UPrimaryDataAsset>& dataClassPath,
|
||||||
|
FPrimaryAssetType primaryAssetType)
|
||||||
|
{
|
||||||
|
UPrimaryDataAsset* asset = nullptr;
|
||||||
|
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading GameData Object"), STAT_GameData, STATGROUP_LoadTime);
|
||||||
|
if (!dataClassPath.IsNull())
|
||||||
|
{
|
||||||
|
#if WITH_EDITOR
|
||||||
|
FScopedSlowTask slowTask(0, FText::Format(NSLOCTEXT("OLSEditor", "BeginLoadingGameDataTask", "Loading GameData {0}"), FText::FromName(dataClass->GetFName())));
|
||||||
|
const bool shouldShowCancelButton = false;
|
||||||
|
const bool shouldAllowInPIE = true;
|
||||||
|
slowTask.MakeDialog(shouldShowCancelButton, shouldAllowInPIE);
|
||||||
|
#endif
|
||||||
|
// @Todo replace this log with our custom.
|
||||||
|
// UE_LOG(LogLyra, Log, TEXT("Loading GameData: %s ..."), *dataClassPath.ToString());
|
||||||
|
SCOPE_LOG_TIME_IN_SECONDS(TEXT(" ... GameData loaded!"), nullptr);
|
||||||
|
|
||||||
|
// This can be called recursively in the editor because it is called on demand from PostLoad so force a sync load for primary asset and async load the rest in that case
|
||||||
|
if (GIsEditor)
|
||||||
|
{
|
||||||
|
asset = dataClassPath.LoadSynchronous();
|
||||||
|
LoadPrimaryAssetsWithType(primaryAssetType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TSharedPtr<FStreamableHandle> handle = LoadPrimaryAssetsWithType(primaryAssetType);
|
||||||
|
if (handle.IsValid())
|
||||||
|
{
|
||||||
|
handle->WaitUntilComplete(0.0f, false);
|
||||||
|
|
||||||
|
// This should always work
|
||||||
|
asset = Cast<UPrimaryDataAsset>(handle->GetLoadedAsset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asset)
|
||||||
|
{
|
||||||
|
GameDataMap.Add(dataClass, asset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// It is not acceptable to fail to load any GameData asset. It will result in soft failures that are hard to diagnose.
|
||||||
|
// @Todo replace this log with our custom.
|
||||||
|
// UE_LOG(LogLyra, Fatal, TEXT("Failed to load GameData asset at %s. Type %s. This is not recoverable and likely means you do not have the correct data to run %s."), *dataClassPath.ToString(), *primaryAssetType.ToString(), FApp::GetProjectName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSAssetManager::DoAllStartupJobs()
|
||||||
|
{
|
||||||
|
SCOPED_BOOT_TIMING("ULyraAssetManager::DoAllStartupJobs");
|
||||||
|
const double AllStartupJobsStartTime = FPlatformTime::Seconds();
|
||||||
|
|
||||||
|
// if (IsRunningDedicatedServer())
|
||||||
|
// {
|
||||||
|
// // No need for periodic progress updates, just run the jobs
|
||||||
|
// for (const FLyraAssetManagerStartupJob& StartupJob : StartupJobs)
|
||||||
|
// {
|
||||||
|
// StartupJob.DoJob();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if (StartupJobs.Num() > 0)
|
||||||
|
// {
|
||||||
|
// float TotalJobValue = 0.0f;
|
||||||
|
// for (const FLyraAssetManagerStartupJob& StartupJob : StartupJobs)
|
||||||
|
// {
|
||||||
|
// TotalJobValue += StartupJob.JobWeight;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// float AccumulatedJobValue = 0.0f;
|
||||||
|
// for (FLyraAssetManagerStartupJob& StartupJob : StartupJobs)
|
||||||
|
// {
|
||||||
|
// const float JobValue = StartupJob.JobWeight;
|
||||||
|
// StartupJob.SubstepProgressDelegate.BindLambda([This = this, AccumulatedJobValue, JobValue, TotalJobValue](float NewProgress)
|
||||||
|
// {
|
||||||
|
// const float SubstepAdjustment = FMath::Clamp(NewProgress, 0.0f, 1.0f) * JobValue;
|
||||||
|
// const float OverallPercentWithSubstep = (AccumulatedJobValue + SubstepAdjustment) / TotalJobValue;
|
||||||
|
//
|
||||||
|
// This->UpdateInitialGameContentLoadPercent(OverallPercentWithSubstep);
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// StartupJob.DoJob();
|
||||||
|
//
|
||||||
|
// StartupJob.SubstepProgressDelegate.Unbind();
|
||||||
|
//
|
||||||
|
// AccumulatedJobValue += JobValue;
|
||||||
|
//
|
||||||
|
// UpdateInitialGameContentLoadPercent(AccumulatedJobValue / TotalJobValue);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// UpdateInitialGameContentLoadPercent(1.0f);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// StartupJobs.Empty();
|
||||||
|
|
||||||
|
// @Todo replace this with our custom log.
|
||||||
|
// UE_LOG(LogLyra, Display, TEXT("All startup jobs took %.2f seconds to complete"), FPlatformTime::Seconds() - AllStartupJobsStartTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSAssetManager::InitializeGameplayCueManager()
|
||||||
|
{
|
||||||
|
SCOPED_BOOT_TIMING("UOLSAssetManager::InitializeGameplayCueManager");
|
||||||
|
|
||||||
|
// ULyraGameplayCueManager* GCM = ULyraGameplayCueManager::Get();
|
||||||
|
// check(GCM);
|
||||||
|
// GCM->LoadAlwaysLoadedCues();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UOLSAssetManager::UpdateInitialGameContentLoadPercent(float gameContentPercent)
|
||||||
|
{
|
||||||
|
// Could route this to the early startup loading screen
|
||||||
|
}
|
46
Source/ols/Private/Systems/OLSAssetManagerStartupJob.cpp
Normal file
46
Source/ols/Private/Systems/OLSAssetManagerStartupJob.cpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Systems/OLSAssetManagerStartupJob.h"
|
||||||
|
|
||||||
|
TSharedPtr<FStreamableHandle> FOLSAssetManagerStartupJob::DoJob() const
|
||||||
|
{
|
||||||
|
const double jobStartTime = FPlatformTime::Seconds();
|
||||||
|
|
||||||
|
TSharedPtr<FStreamableHandle> handle;
|
||||||
|
// @Todo replace this with our custom log.
|
||||||
|
// UE_LOG(LogLyra, Display, TEXT("Startup job \"%s\" starting"), *JobName);
|
||||||
|
JobFunc(*this, handle);
|
||||||
|
|
||||||
|
if (handle.IsValid())
|
||||||
|
{
|
||||||
|
handle->BindUpdateDelegate(FStreamableUpdateDelegate::CreateRaw(this, &FOLSAssetManagerStartupJob::UpdateSubstepProgressFromStreamable));
|
||||||
|
handle->WaitUntilComplete(0.0f, false);
|
||||||
|
handle->BindUpdateDelegate(FStreamableUpdateDelegate());
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Todo replace this with our custom log.
|
||||||
|
// UE_LOG(LogLyra, Display, TEXT("Startup job \"%s\" took %.2f seconds to complete"), *JobName, FPlatformTime::Seconds() - jobStartTime);
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FOLSAssetManagerStartupJob::UpdateSubstepProgress(float newProgress) const
|
||||||
|
{
|
||||||
|
SubstepProgressDelegate.ExecuteIfBound(newProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FOLSAssetManagerStartupJob::UpdateSubstepProgressFromStreamable(
|
||||||
|
TSharedRef<FStreamableHandle> streamableHandle) const
|
||||||
|
{
|
||||||
|
if (SubstepProgressDelegate.IsBound())
|
||||||
|
{
|
||||||
|
// StreamableHandle::GetProgress traverses() a large graph and is quite expensive
|
||||||
|
double now = FPlatformTime::Seconds();
|
||||||
|
if (LastUpdate - now > 1.0 / 60)
|
||||||
|
{
|
||||||
|
SubstepProgressDelegate.Execute(streamableHandle->GetProgress());
|
||||||
|
LastUpdate = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
//~ End of UPrimaryDataAsset interface
|
//~ End of UPrimaryDataAsset interface
|
||||||
|
|
||||||
protected:
|
public:
|
||||||
|
|
||||||
// List of Game Feature Plugins this experience wants to have active
|
// List of Game Feature Plugins this experience wants to have active
|
||||||
UPROPERTY(EditDefaultsOnly, Category = "OLSExperienceDefinition")
|
UPROPERTY(EditDefaultsOnly, Category = "OLSExperienceDefinition")
|
||||||
|
@ -26,7 +26,7 @@ public:
|
|||||||
virtual FString GetIdentifierString() const override;
|
virtual FString GetIdentifierString() const override;
|
||||||
//~ End UOLSPrimaryDataAsset interface
|
//~ End UOLSPrimaryDataAsset interface
|
||||||
|
|
||||||
protected:
|
public:
|
||||||
|
|
||||||
// Class to instantiate for this pawn (should usually derive from AOLSPawn or AOLSCharacter).
|
// Class to instantiate for this pawn (should usually derive from AOLSPawn or AOLSCharacter).
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "OLS|Pawn")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "OLS|Pawn")
|
||||||
|
33
Source/ols/Public/GameModes/OLSExperienceManager.h
Normal file
33
Source/ols/Public/GameModes/OLSExperienceManager.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Subsystems/EngineSubsystem.h"
|
||||||
|
#include "OLSExperienceManager.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager for experiences - primarily for arbitration between multiple PIE sessions
|
||||||
|
*/
|
||||||
|
UCLASS(MinimalAPI)
|
||||||
|
class UOLSExperienceManager : public UEngineSubsystem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
#if WITH_EDITOR
|
||||||
|
OLS_API void OnPlayInEditorBegun();
|
||||||
|
|
||||||
|
static void NotifyOfPluginActivation(const FString pluginURL);
|
||||||
|
static bool RequestToDeactivatePlugin(const FString pluginURL);
|
||||||
|
#else
|
||||||
|
static void NotifyOfPluginActivation(const FString pluginURL) {}
|
||||||
|
static bool RequestToDeactivatePlugin(const FString pluginURL) { return true; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// The map of requests to active count for a given game feature plugin
|
||||||
|
// (to allow first in, last out activation management during PIE)
|
||||||
|
TMap<FString, int32> GameFeaturePluginRequestCountMap;
|
||||||
|
};
|
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Components/GameStateComponent.h"
|
#include "Components/GameStateComponent.h"
|
||||||
// #include "LoadingProcessInterface.h"
|
#include "LoadingProcessInterface.h"
|
||||||
#include "OLSExperienceManagerComponent.generated.h"
|
#include "OLSExperienceManagerComponent.generated.h"
|
||||||
|
|
||||||
namespace UE::GameFeatures { struct FResult; }
|
namespace UE::GameFeatures { struct FResult; }
|
||||||
|
|
||||||
// DECLARE_MULTICAST_DELEGATE_OneParam(FOnLyraExperienceLoaded, const ULyraExperienceDefinition* /*Experience*/);
|
DECLARE_MULTICAST_DELEGATE_OneParam(FOnOLSExperienceLoaded, const class UOLSExperienceDefinitionPrimaryDataAsset* /*experience*/);
|
||||||
|
|
||||||
enum class EOLSExperienceLoadState
|
enum class EOLSExperienceLoadState
|
||||||
{
|
{
|
||||||
@ -23,8 +23,87 @@ enum class EOLSExperienceLoadState
|
|||||||
};
|
};
|
||||||
|
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class OLS_API UOLSExperienceManagerComponent : public UGameStateComponent /*, public ILoadingProcessInterface */
|
class OLS_API UOLSExperienceManagerComponent : public UGameStateComponent, public ILoadingProcessInterface
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UOLSExperienceManagerComponent(const FObjectInitializer& objectInitializer);
|
||||||
|
|
||||||
|
//~ Begin UActorComponent interface
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type endPlayReason) override;
|
||||||
|
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||||
|
//~ End UActorComponent interface
|
||||||
|
|
||||||
|
//~ Begin ILoadingProcessInterface interface
|
||||||
|
virtual bool ShouldShowLoadingScreen(FString& outReason) const override;
|
||||||
|
//~ End ILoadingProcessInterface
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// This returns the current experience if it is fully loaded, asserting otherwise
|
||||||
|
// (i.e., if you called it too soon)
|
||||||
|
const UOLSExperienceDefinitionPrimaryDataAsset* GetCurrentExperienceChecked() const;
|
||||||
|
|
||||||
|
// Tries to set the current experience, either a UI or gameplay one
|
||||||
|
void SetCurrentExperience(FPrimaryAssetId experienceId);
|
||||||
|
|
||||||
|
// Returns true if the experience is fully loaded
|
||||||
|
bool IsExperienceLoaded() const;
|
||||||
|
|
||||||
|
// Ensures the delegate is called once the experience has been loaded,
|
||||||
|
// before others are called.
|
||||||
|
// However, if the experience has already loaded, calls the delegate immediately.
|
||||||
|
void CallOrRegister_OnExperienceLoaded_HighPriority(FOnOLSExperienceLoaded::FDelegate&& delegate);
|
||||||
|
|
||||||
|
// Ensures the delegate is called once the experience has been loaded
|
||||||
|
// If the experience has already loaded, calls the delegate immediately
|
||||||
|
void CallOrRegister_OnExperienceLoaded(FOnOLSExperienceLoaded::FDelegate&& delegate);
|
||||||
|
|
||||||
|
// Ensures the delegate is called once the experience has been loaded
|
||||||
|
// If the experience has already loaded, calls the delegate immediately
|
||||||
|
void CallOrRegister_OnExperienceLoaded_LowPriority(FOnOLSExperienceLoaded::FDelegate&& delegate);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRep_CurrentExperience();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void StartExperienceLoad();
|
||||||
|
void OnExperienceLoadComplete();
|
||||||
|
void OnGameFeaturePluginLoadComplete(const UE::GameFeatures::FResult& result);
|
||||||
|
void OnExperienceFullLoadCompleted();
|
||||||
|
|
||||||
|
void OnActionDeactivationCompleted();
|
||||||
|
void OnAllActionsDeactivated();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
UPROPERTY(ReplicatedUsing = OnRep_CurrentExperience)
|
||||||
|
TObjectPtr<const class UOLSExperienceDefinitionPrimaryDataAsset> CurrentExperience = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
EOLSExperienceLoadState LoadState = EOLSExperienceLoadState::Unloaded;
|
||||||
|
|
||||||
|
int32 NumGameFeaturePluginsLoading = 0;
|
||||||
|
TArray<FString> GameFeaturePluginURLs;
|
||||||
|
|
||||||
|
int32 NumObservedPausers = 0;
|
||||||
|
int32 NumExpectedPausers = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate called when the experience has finished loading just before others
|
||||||
|
* (e.g., subsystems that set up for regular gameplay)
|
||||||
|
*/
|
||||||
|
FOnOLSExperienceLoaded OnExperienceLoaded_HighPriority;
|
||||||
|
|
||||||
|
/** Delegate called when the experience has finished loading */
|
||||||
|
FOnOLSExperienceLoaded OnExperienceLoaded;
|
||||||
|
|
||||||
|
/** Delegate called when the experience has finished loading */
|
||||||
|
FOnOLSExperienceLoaded OnExperienceLoaded_LowPriority;
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "ModularGameplayActors/OLSModularPlayerState.h"
|
#include "ModularGameplayActors/OLSModularPlayerState.h"
|
||||||
#include "OLSPlayerState.generated.h"
|
#include "OLSPlayerState.generated.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AOLSPlayerState
|
* AOLSPlayerState
|
||||||
*
|
*
|
||||||
@ -20,9 +21,11 @@ public:
|
|||||||
|
|
||||||
AOLSPlayerState(const FObjectInitializer& objectInitializer);
|
AOLSPlayerState(const FObjectInitializer& objectInitializer);
|
||||||
|
|
||||||
|
static const FName NAME_OLSAbilityReady;
|
||||||
|
|
||||||
//~ Begin AActor interface
|
//~ Begin AActor interface
|
||||||
virtual void PostInitializeComponents() override;
|
virtual void PostInitializeComponents() override;
|
||||||
|
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||||
//~ End AActor interface
|
//~ End AActor interface
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -32,4 +35,19 @@ public:
|
|||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "OLS|PlayerState")
|
UFUNCTION(BlueprintCallable, Category = "OLS|PlayerState")
|
||||||
class UOLSAbilitySystemComponent* GetOLSAbilitySystemComponent() const;
|
class UOLSAbilitySystemComponent* GetOLSAbilitySystemComponent() const;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
const T* GetPawnData() const;
|
||||||
|
|
||||||
|
void SetPawnData(const class UOLSPawnPrimaryDataAsset* pawnData);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRep_PawnData();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
UPROPERTY(ReplicatedUsing = OnRep_PawnData)
|
||||||
|
TObjectPtr<const class UOLSPawnPrimaryDataAsset> PawnData = nullptr;
|
||||||
};
|
};
|
||||||
|
111
Source/ols/Public/Systems/OLSAssetManager.h
Normal file
111
Source/ols/Public/Systems/OLSAssetManager.h
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/AssetManager.h"
|
||||||
|
#include "OLSAssetManager.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct FOLSBundles
|
||||||
|
{
|
||||||
|
static const FName Equipped;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UOLSAssetManager
|
||||||
|
*
|
||||||
|
* Game implementation of the asset manager that overrides functionality and stores game-specific types.
|
||||||
|
* It is expected that most games will want to override AssetManager as it provides a good place for game-specific loading logic.
|
||||||
|
* Thi
|
||||||
|
*/
|
||||||
|
UCLASS(Config = Game)
|
||||||
|
class OLS_API UOLSAssetManager : public UAssetManager
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UOLSAssetManager();
|
||||||
|
|
||||||
|
// Returns the AssetManager singleton object.
|
||||||
|
static UOLSAssetManager& Get();
|
||||||
|
|
||||||
|
// Returns the asset referenced by a TSoftObjectPtr. This will synchronously load the asset if it's not already loaded.
|
||||||
|
template<typename AssetType>
|
||||||
|
static AssetType* GetAsset(const TSoftObjectPtr<AssetType>& assetPointer, bool shouldKeepInMemory = true);
|
||||||
|
|
||||||
|
// Returns the subclass referenced by a TSoftClassPtr. This will synchronously load the asset if it's not already loaded.
|
||||||
|
template<typename AssetType>
|
||||||
|
static TSubclassOf<AssetType> GetSubclass(const TSoftClassPtr<AssetType>& assetPointer, bool shouldKeepInMemory = true);
|
||||||
|
|
||||||
|
// Logs all assets currently loaded and tracked by the asset manager.
|
||||||
|
static void DumpLoadedAssets();
|
||||||
|
|
||||||
|
// @Todo implement this function.
|
||||||
|
// const class UOLSPawnPrimaryDataAsset& GetGameData();
|
||||||
|
const class UOLSPawnPrimaryDataAsset* GetDefaultPawnData() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
template <typename GameDataClass>
|
||||||
|
const GameDataClass& GetOrLoadTypedGameData(const TSoftObjectPtr<GameDataClass>& dataPath);
|
||||||
|
|
||||||
|
static UObject* SynchronousLoadAsset(const FSoftObjectPath& assetPath);
|
||||||
|
static bool ShouldLogAssetLoads();
|
||||||
|
|
||||||
|
// Thread safe way of adding a loaded asset to keep in memory.
|
||||||
|
void AddLoadedAsset(const UObject* Asset);
|
||||||
|
|
||||||
|
//~UAssetManager interface
|
||||||
|
virtual void StartInitialLoading() override;
|
||||||
|
#if WITH_EDITOR
|
||||||
|
virtual void PreBeginPIE(bool shouldStartSimulate) override;
|
||||||
|
#endif
|
||||||
|
//~End of UAssetManager interface
|
||||||
|
|
||||||
|
class UPrimaryDataAsset* LoadGameDataOfClass(
|
||||||
|
TSubclassOf<UPrimaryDataAsset> dataClass,
|
||||||
|
const TSoftObjectPtr<UPrimaryDataAsset>& dataClassPath,
|
||||||
|
FPrimaryAssetType primaryAssetType);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Flushes the StartupJobs array. Processes all startup work.
|
||||||
|
void DoAllStartupJobs();
|
||||||
|
|
||||||
|
// Sets up the ability system
|
||||||
|
void InitializeGameplayCueManager();
|
||||||
|
|
||||||
|
// Called periodically during loads, could be used to feed the status to a loading screen
|
||||||
|
void UpdateInitialGameContentLoadPercent(float gameContentPercent);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// @Todo implement this.
|
||||||
|
// Global game data asset to use.
|
||||||
|
// UPROPERTY(Config)
|
||||||
|
// TSoftObjectPtr<ULyraGameData> LyraGameDataPath;
|
||||||
|
|
||||||
|
// Loaded version of the game data
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TMap<TObjectPtr<UClass>, TObjectPtr<UPrimaryDataAsset>> GameDataMap;
|
||||||
|
|
||||||
|
// Pawn data used when spawning player pawns if there isn't one set on the player state.
|
||||||
|
UPROPERTY(Config)
|
||||||
|
TSoftObjectPtr<class UOLSPawnPrimaryDataAsset> DefaultPawnData;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Assets loaded and tracked by the asset manager.
|
||||||
|
UPROPERTY()
|
||||||
|
TSet<TObjectPtr<const UObject>> LoadedAssets;
|
||||||
|
|
||||||
|
// Used for a scope lock when modifying the list of load assets.
|
||||||
|
FCriticalSection LoadedAssetsCritical;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// The list of tasks to execute on startup. Used to track startup progress.
|
||||||
|
TArray<struct FOLSAssetManagerStartupJob> StartupJobs;
|
||||||
|
};
|
32
Source/ols/Public/Systems/OLSAssetManagerStartupJob.h
Normal file
32
Source/ols/Public/Systems/OLSAssetManagerStartupJob.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/StreamableManager.h"
|
||||||
|
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_OneParam(FOLSAssetManagerStartupJobSubstepProgress, float /*newProgress*/);
|
||||||
|
|
||||||
|
/** Handles reporting progress from streamable handles */
|
||||||
|
struct FOLSAssetManagerStartupJob
|
||||||
|
{
|
||||||
|
FOLSAssetManagerStartupJobSubstepProgress SubstepProgressDelegate;
|
||||||
|
TFunction<void(const FOLSAssetManagerStartupJob&, TSharedPtr<FStreamableHandle>&)> JobFunc;
|
||||||
|
FString JobName;
|
||||||
|
float JobWeight;
|
||||||
|
mutable double LastUpdate = 0;
|
||||||
|
|
||||||
|
/** Simple job that is all synchronous */
|
||||||
|
FOLSAssetManagerStartupJob(const FString& jobName, const TFunction<void(const FOLSAssetManagerStartupJob&, TSharedPtr<FStreamableHandle>&)>& jobFunc, float jobWeight)
|
||||||
|
: JobFunc(jobFunc)
|
||||||
|
, JobName(jobName)
|
||||||
|
, JobWeight(jobWeight)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/** Perform actual loading, will return a handle if it created one */
|
||||||
|
TSharedPtr<FStreamableHandle> DoJob() const;
|
||||||
|
|
||||||
|
void UpdateSubstepProgress(float newProgress) const;
|
||||||
|
|
||||||
|
void UpdateSubstepProgressFromStreamable(TSharedRef<FStreamableHandle> streamableHandle) const;
|
||||||
|
};
|
@ -19,7 +19,7 @@ public class ols : ModuleRules
|
|||||||
"GameFeatures",
|
"GameFeatures",
|
||||||
"ModularGameplay",
|
"ModularGameplay",
|
||||||
"EnhancedInput",
|
"EnhancedInput",
|
||||||
"OLSAnimation", "AIModule",
|
"OLSAnimation", "AIModule", "CommonLoadingScreen",
|
||||||
});
|
});
|
||||||
|
|
||||||
PrivateDependencyModuleNames.AddRange(new[]
|
PrivateDependencyModuleNames.AddRange(new[]
|
||||||
|
Loading…
Reference in New Issue
Block a user