Integrating up through commit 90f050496

This commit is contained in:
alexpete
2021-04-07 14:03:29 -07:00
parent 8f2ed080a9
commit c2cbd430fe
2694 changed files with 285622 additions and 176874 deletions
File diff suppressed because it is too large Load Diff
@@ -16,6 +16,8 @@
#include "ILevelSystem.h"
#include <AzFramework/Archive/IArchive.h>
// [LYN-2376] Remove the entire file once legacy slice support is removed
namespace LegacyLevelSystem
{
@@ -24,75 +26,38 @@ class CLevelInfo
{
friend class CLevelSystem;
public:
CLevelInfo()
: m_heightmapSize(0)
, m_bMetaDataRead(false)
, m_isModLevel(false)
, m_scanTag(ILevelSystem::TAG_UNKNOWN)
, m_levelTag(ILevelSystem::TAG_UNKNOWN)
{
SwapEndian(m_scanTag, eBigEndian);
SwapEndian(m_levelTag, eBigEndian);
};
CLevelInfo() = default;
// ILevelInfo
virtual const char* GetName() const { return m_levelName.c_str(); };
virtual const bool IsOfType(const char* sType) const;
virtual const char* GetPath() const { return m_levelPath.c_str(); };
virtual const char* GetPaks() const { return m_levelPaks.c_str(); };
virtual bool GetIsModLevel() const { return m_isModLevel; }
virtual const uint32 GetScanTag() const { return m_scanTag; }
virtual const uint32 GetLevelTag() const { return m_levelTag; }
virtual const char* GetDisplayName() const;
virtual const char* GetPreviewImagePath() const { return m_previewImagePath.c_str(); }
virtual const char* GetBackgroundImagePath() const { return m_backgroundImagePath.c_str(); }
virtual const char* GetMinimapImagePath() const {return m_minimapImagePath.c_str(); }
//virtual const ILevelInfo::TStringVec& GetMusicLibs() const { return m_musicLibs; }; // Gets reintroduced when level specific music data loading is implemented.
virtual const bool MetadataLoaded() const { return m_bMetaDataRead; }
virtual int GetGameTypeCount() const { return m_gameTypes.size(); };
virtual const ILevelInfo::TGameTypeInfo* GetGameType(int gameType) const { return &m_gameTypes[gameType]; };
virtual bool SupportsGameType(const char* gameTypeName) const;
virtual const ILevelInfo::TGameTypeInfo* GetDefaultGameType() const;
virtual bool HasGameRules() const{ return !m_gamerules.empty(); }
virtual const ILevelInfo::SMinimapInfo& GetMinimapInfo() const { return m_minimapInfo; }
virtual const char* GetDefaultGameRules() const{ return m_gamerules.empty() ? NULL : m_gamerules[0].c_str(); }
virtual ILevelInfo::TStringVec GetGameRules() const{ return m_gamerules; }
virtual const char* GetName() const { return m_levelName.c_str(); }
virtual const char* GetPath() const { return m_levelPath.c_str(); }
virtual const char* GetAssetName() const { return m_levelAssetName.c_str(); }
// ~ILevelInfo
void GetMemoryUsage(ICrySizer*) const;
private:
void ReadMetaData();
bool ReadInfo();
bool OpenLevelPak();
void CloseLevelPak();
string m_levelName;
string m_levelPath;
string m_levelPaks;
string m_levelDisplayName;
string m_previewImagePath;
string m_backgroundImagePath;
string m_minimapImagePath;
AZStd::string m_defaultGameTypeName;
AZStd::string m_levelName;
AZStd::string m_levelPath;
AZStd::string m_levelAssetName;
string m_levelPakFullPath;
AZStd::string m_levelPakFullPath;
TStringVec m_gamerules;
int m_heightmapSize;
uint32 m_scanTag;
uint32 m_levelTag;
bool m_bMetaDataRead;
std::vector<ILevelInfo::TGameTypeInfo> m_gameTypes;
bool m_isModLevel;
SMinimapInfo m_minimapInfo;
bool m_isPak = false;
};
DynArray<string> m_levelTypeList;
bool m_isPak = false;
struct ILevel
{
virtual ~ILevel() = default;
virtual void Release() = 0;
virtual ILevelInfo* GetLevelInfo() = 0;
};
class CLevel
@@ -121,10 +86,7 @@ public:
void Release() { delete this; };
// ILevelSystem
virtual DynArray<string>* GetLevelTypeList();
virtual void Rescan(const char* levelsFolder, const uint32 tag);
void ScanFolder(const char* subfolder, bool modFolder, const uint32 tag) override;
void PopulateLevels(string searchPattern, string& folder, AZ::IO::IArchive* pPak, bool& modFolder, const uint32& tag, bool fromFileSystemOnly) override;
virtual void Rescan(const char* levelsFolder);
virtual int GetLevelCount();
virtual ILevelInfo* GetLevelInfo(int level);
virtual ILevelInfo* GetLevelInfo(const char* levelName);
@@ -132,74 +94,81 @@ public:
virtual void AddListener(ILevelSystemListener* pListener);
virtual void RemoveListener(ILevelSystemListener* pListener);
virtual ILevel* GetCurrentLevel() const { return m_pCurrentLevel; }
virtual ILevel* LoadLevel(const char* levelName);
virtual void UnLoadLevel();
virtual ILevel* SetEditorLoadedLevel(const char* levelName, bool bReadLevelInfoMetaData = false);
virtual void PrepareNextLevel(const char* levelName);
virtual float GetLastLevelLoadTime() { return m_fLastLevelLoadTime; };
virtual bool LoadLevel(const char* levelName);
virtual void UnloadLevel();
virtual bool IsLevelLoaded() { return m_bLevelLoaded; }
const char* GetCurrentLevelName() const override
{
if (m_pCurrentLevel && m_pCurrentLevel->GetLevelInfo())
{
return m_pCurrentLevel->GetLevelInfo()->GetName();
}
else
{
return "";
}
}
// If the level load failed then we need to have a different shutdown procedure vs when a level is naturally unloaded
virtual void SetLevelLoadFailed(bool loadFailed) { m_levelLoadFailed = loadFailed; }
virtual bool GetLevelLoadFailed() { return m_levelLoadFailed; }
// Unsupported by legacy level system.
virtual AZ::Data::AssetType GetLevelAssetType() const { return {}; }
// ~ILevelSystem
void GetMemoryUsage(ICrySizer* s) const;
void SaveOpenedFilesList();
private:
float GetLastLevelLoadTime() { return m_fLastLevelLoadTime; }
void ScanFolder(const char* subfolder, bool modFolder);
void PopulateLevels(
AZStd::string searchPattern, AZStd::string& folder, AZ::IO::IArchive* pPak, bool& modFolder, bool fromFileSystemOnly);
void PrepareNextLevel(const char* levelName);
ILevel* LoadLevelInternal(const char* _levelName);
// ILevelSystemListener events notification
// Methods to notify ILevelSystemListener
void OnLevelNotFound(const char* levelName);
void OnLoadingStart(ILevelInfo* pLevel);
void OnLoadingComplete(ILevel* pLevel);
void OnLoadingError(ILevelInfo* pLevel, const char* error);
void OnLoadingProgress(ILevelInfo* pLevel, int progressAmount);
void OnUnloadComplete(ILevel* pLevel);
void OnLoadingStart(const char* levelName);
void OnLoadingComplete(const char* levelName);
void OnLoadingError(const char* levelName, const char* error);
void OnLoadingProgress(const char* levelName, int progressAmount);
void OnUnloadComplete(const char* levelName);
// lowercase string and replace backslashes with forward slashes
// TODO: move this to a more general place in CryEngine
string& UnifyName(string& name);
void LogLoadingTime();
bool LoadLevelInfo(CLevelInfo& levelInfo);
// internal get functions for the level infos ... they preserve the type and don't
// directly cast to the interface
CLevelInfo* GetLevelInfoInternal(int level);
CLevelInfo* GetLevelInfoInternal(const char* levelName);
CLevelInfo* GetLevelInfoInternal(const AZStd::string& levelName);
ISystem* m_pSystem;
std::vector<CLevelInfo> m_levelInfos;
string m_levelsFolder;
AZStd::vector<CLevelInfo> m_levelInfos;
AZStd::string m_levelsFolder;
ILevel* m_pCurrentLevel;
ILevelInfo* m_pLoadingLevelInfo;
string m_lastLevelName;
AZStd::string m_lastLevelName;
float m_fLastLevelLoadTime;
float m_fFilteredProgress;
float m_fLastTime;
bool m_bLevelLoaded;
bool m_bRecordingFileOpens;
bool m_levelLoadFailed = false;
int m_nLoadedLevelsCount;
CTimeValue m_levelLoadStartTime;
static int s_loadCount;
std::vector<ILevelSystemListener*> m_listeners;
DynArray<string> m_levelTypeList;
AZStd::vector<ILevelSystemListener*> m_listeners;
AZ::IO::IArchive::LevelPackOpenEvent::Handler m_levelPackOpenHandler;
AZ::IO::IArchive::LevelPackCloseEvent::Handler m_levelPackCloseHandler;
static constexpr const char* LevelPakName = "level.pak";
};
} // namespace LegacyLevelSystem
@@ -0,0 +1,709 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "CrySystem_precompiled.h"
#include "SpawnableLevelSystem.h"
#include <IAudioSystem.h>
#include "IMovieSystem.h"
#include <IResourceManager.h>
#include "IDeferredCollisionEvent.h"
#include <LoadScreenBus.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/IO/FileOperations.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Input/Buses/Requests/InputChannelRequestBus.h>
#include "MainThreadRenderRequestBus.h"
#include <LyShine/ILyShine.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Script/ScriptSystemBus.h>
namespace LegacyLevelSystem
{
//------------------------------------------------------------------------
static void LoadLevel(const AZ::ConsoleCommandContainer& arguments)
{
AZ_Error("SpawnableLevelSystem", arguments.empty(), "LoadLevel requires a level file name to be provided.");
AZ_Error("SpawnableLevelSystem", arguments.size() > 1, "LoadLevel requires a single level file name to be provided.");
if (!arguments.empty() && gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor())
{
gEnv->pSystem->GetILevelSystem()->LoadLevel(arguments[0].data());
}
}
//------------------------------------------------------------------------
static void UnloadLevel([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
AZ_Warning("SpawnableLevelSystem", !arguments.empty(), "UnloadLevel doesn't use any arguments.");
if (gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor())
{
gEnv->pSystem->GetILevelSystem()->UnloadLevel();
if (gEnv->p3DEngine)
{
gEnv->p3DEngine->LoadEmptyLevel();
}
}
}
AZ_CONSOLEFREEFUNC(LoadLevel, AZ::ConsoleFunctorFlags::Null, "Unloads the current level and loads a new one with the given asset name");
AZ_CONSOLEFREEFUNC(UnloadLevel, AZ::ConsoleFunctorFlags::Null, "Unloads the current level");
//------------------------------------------------------------------------
SpawnableLevelSystem::SpawnableLevelSystem(ISystem* pSystem)
: m_pSystem(pSystem)
{
LOADING_TIME_PROFILE_SECTION;
CRY_ASSERT(pSystem);
m_fLastLevelLoadTime = 0;
m_fLastTime = 0;
m_bLevelLoaded = false;
m_levelLoadStartTime.SetValue(0);
m_nLoadedLevelsCount = 0;
AZ_Assert(gEnv && gEnv->pCryPak, "gEnv and CryPak must be initialized for loading levels.");
if (!gEnv || !gEnv->pCryPak)
{
return;
}
auto pPak = gEnv->pCryPak;
AzFramework::RootSpawnableNotificationBus::Handler::BusConnect();
}
//------------------------------------------------------------------------
SpawnableLevelSystem::~SpawnableLevelSystem()
{
AzFramework::RootSpawnableNotificationBus::Handler::BusDisconnect();
}
void SpawnableLevelSystem::Release()
{
delete this;
}
bool SpawnableLevelSystem::IsLevelLoaded()
{
return m_bLevelLoaded;
}
const char* SpawnableLevelSystem::GetCurrentLevelName() const
{
return m_bLevelLoaded ? m_lastLevelName.c_str() : "";
}
// If the level load failed then we need to have a different shutdown procedure vs when a level is naturally unloaded
void SpawnableLevelSystem::SetLevelLoadFailed(bool loadFailed)
{
m_levelLoadFailed = loadFailed;
}
bool SpawnableLevelSystem::GetLevelLoadFailed()
{
return m_levelLoadFailed;
}
AZ::Data::AssetType SpawnableLevelSystem::GetLevelAssetType() const
{
return azrtti_typeid<AzFramework::Spawnable>();
}
// The following methods are deprecated from ILevelSystem and will be removed once slice support is removed.
// [LYN-2376] Remove once legacy slice support is removed
void SpawnableLevelSystem::Rescan([[maybe_unused]] const char* levelsFolder)
{
AZ_Assert(false, "Rescan - No longer supported.");
}
// [LYN-2376] Remove once legacy slice support is removed
int SpawnableLevelSystem::GetLevelCount()
{
AZ_Assert(false, "GetLevelCount - No longer supported.");
return 0;
}
// [LYN-2376] Remove once legacy slice support is removed
ILevelInfo* SpawnableLevelSystem::GetLevelInfo([[maybe_unused]] int level)
{
AZ_Assert(false, "GetLevelInfo - No longer supported.");
return nullptr;
}
// [LYN-2376] Remove once legacy slice support is removed
ILevelInfo* SpawnableLevelSystem::GetLevelInfo([[maybe_unused]] const char* levelName)
{
AZ_Assert(false, "GetLevelInfo - No longer supported.");
return nullptr;
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::AddListener(ILevelSystemListener* pListener)
{
AZStd::vector<ILevelSystemListener*>::iterator it = AZStd::find(m_listeners.begin(), m_listeners.end(), pListener);
if (it == m_listeners.end())
{
m_listeners.push_back(pListener);
}
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::RemoveListener(ILevelSystemListener* pListener)
{
AZStd::vector<ILevelSystemListener*>::iterator it = AZStd::find(m_listeners.begin(), m_listeners.end(), pListener);
if (it != m_listeners.end())
{
m_listeners.erase(it);
}
}
//------------------------------------------------------------------------
bool SpawnableLevelSystem::LoadLevel(const char* levelName)
{
if (gEnv->IsEditor())
{
AZ_TracePrintf("CrySystem::CLevelSystem", "LoadLevel for %s was called in the editor - not actually loading.\n", levelName);
return false;
}
// If a level is currently loaded, unload it before loading the next one.
if (IsLevelLoaded())
{
UnloadLevel();
}
gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_PREPARE, 0, 0);
PrepareNextLevel(levelName);
bool result = LoadLevelInternal(levelName);
if (result)
{
OnLoadingComplete(levelName);
}
return result;
}
//------------------------------------------------------------------------
bool SpawnableLevelSystem::LoadLevelInternal(const char* levelName)
{
gEnv->pSystem->SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_START);
AZ_ASSET_NAMED_SCOPE("Level: %s", levelName);
INDENT_LOG_DURING_SCOPE();
AZ::Data::AssetId rootSpawnableAssetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
rootSpawnableAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, levelName, nullptr, false);
if (!rootSpawnableAssetId.IsValid())
{
OnLoadingError(levelName, "AssetCatalog has no entry for the requested level.");
return false;
}
// This scope is specifically used for marking a loading time profile section
{
LOADING_TIME_PROFILE_SECTION;
m_bLevelLoaded = false;
m_lastLevelName = levelName;
gEnv->pConsole->SetScrollMax(600);
ICVar* con_showonload = gEnv->pConsole->GetCVar("con_showonload");
if (con_showonload && con_showonload->GetIVal() != 0)
{
gEnv->pConsole->ShowConsole(true);
ICVar* g_enableloadingscreen = gEnv->pConsole->GetCVar("g_enableloadingscreen");
if (g_enableloadingscreen)
{
g_enableloadingscreen->Set(0);
}
}
// Reset the camera to (1,1,1) (not (0,0,0) which is the invalid/uninitialised state,
// to avoid the hack in the renderer to not show anything if the camera is at the origin).
CCamera defaultCam;
defaultCam.SetPosition(Vec3(1.0f));
m_pSystem->SetViewCamera(defaultCam);
OnLoadingStart(levelName);
auto pPak = gEnv->pCryPak;
m_pSystem->SetThreadState(ESubsys_Physics, false);
ICVar* pSpamDelay = gEnv->pConsole->GetCVar("log_SpamDelay");
float spamDelay = 0.0f;
if (pSpamDelay)
{
spamDelay = pSpamDelay->GetFVal();
pSpamDelay->Set(0.0f);
}
if (gEnv->p3DEngine)
{
AZ::IO::PathView levelPath(levelName);
AZStd::string parentPath(levelPath.ParentPath().Native());
static constexpr const char* defaultGameTypeName = "Mission0";
bool is3DEngineLoaded = gEnv->IsEditor() ? gEnv->p3DEngine->InitLevelForEditor(parentPath.c_str(), defaultGameTypeName)
: gEnv->p3DEngine->LoadLevel(parentPath.c_str(), defaultGameTypeName);
if (!is3DEngineLoaded)
{
OnLoadingError(levelName, "3DEngine failed to handle loading the level");
return 0;
}
}
// Parse level specific config data.
AZStd::string const sLevelNameOnly(PathUtil::GetFileName(levelName));
if (!sLevelNameOnly.empty())
{
const char* controlsPath = nullptr;
Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath);
if (controlsPath)
{
AZStd::string sAudioLevelPath(controlsPath);
sAudioLevelPath.append("levels/");
sAudioLevelPath += sLevelNameOnly;
Audio::SAudioManagerRequestData<Audio::eAMRT_PARSE_CONTROLS_DATA> oAMData(
sAudioLevelPath.c_str(), Audio::eADS_LEVEL_SPECIFIC);
Audio::SAudioRequest oAudioRequestData;
oAudioRequestData.nFlags =
(Audio::eARF_PRIORITY_HIGH |
Audio::eARF_EXECUTE_BLOCKING); // Needs to be blocking so data is available for next preloading request!
oAudioRequestData.pData = &oAMData;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData);
Audio::SAudioManagerRequestData<Audio::eAMRT_PARSE_PRELOADS_DATA> oAMData2(
sAudioLevelPath.c_str(), Audio::eADS_LEVEL_SPECIFIC);
oAudioRequestData.pData = &oAMData2;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData);
Audio::TAudioPreloadRequestID nPreloadRequestID = INVALID_AUDIO_PRELOAD_REQUEST_ID;
Audio::AudioSystemRequestBus::BroadcastResult(
nPreloadRequestID, &Audio::AudioSystemRequestBus::Events::GetAudioPreloadRequestID, sLevelNameOnly.c_str());
if (nPreloadRequestID != INVALID_AUDIO_PRELOAD_REQUEST_ID)
{
Audio::SAudioManagerRequestData<Audio::eAMRT_PRELOAD_SINGLE_REQUEST> requestData(nPreloadRequestID, true);
oAudioRequestData.pData = &requestData;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData);
}
}
}
AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable(
rootSpawnableAssetId, azrtti_typeid<AzFramework::Spawnable>(), levelName);
m_rootSpawnableId = rootSpawnableAssetId;
m_rootSpawnableGeneration = AzFramework::RootSpawnableInterface::Get()->AssignRootSpawnable(rootSpawnable);
//////////////////////////////////////////////////////////////////////////
// Movie system must be reset after entities.
//////////////////////////////////////////////////////////////////////////
IMovieSystem* movieSys = gEnv->pMovieSystem;
if (movieSys != NULL)
{
// bSeekAllToStart needs to be false here as it's only of interest in the editor
movieSys->Reset(true, false);
}
gEnv->pSystem->SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_START_PRECACHE);
//////////////////////////////////////////////////////////////////////////
// Notify 3D engine that loading finished
//////////////////////////////////////////////////////////////////////////
if (gEnv->p3DEngine)
{
gEnv->p3DEngine->PostLoadLevel();
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
gEnv->pConsole->SetScrollMax(600 / 2);
pPak->GetResourceList(AZ::IO::IArchive::RFOM_NextLevel)->Clear();
if (pSpamDelay)
{
pSpamDelay->Set(spamDelay);
}
m_bLevelLoaded = true;
gEnv->pSystem->SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_END);
}
GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_END, 0, 0);
if (auto cvar = gEnv->pConsole->GetCVar("sv_map"); cvar)
{
cvar->Set(levelName);
}
gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_PRECACHE_START, 0, 0);
m_pSystem->SetThreadState(ESubsys_Physics, true);
return true;
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::PrepareNextLevel(const char* levelName)
{
AZ::Data::AssetId rootSpawnableAssetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
rootSpawnableAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, levelName, nullptr, false);
if (!rootSpawnableAssetId.IsValid())
{
// alert the listener
OnLevelNotFound(levelName);
return;
}
// This work not required in-editor.
if (!gEnv || !gEnv->IsEditor())
{
m_levelLoadStartTime = gEnv->pTimer->GetAsyncTime();
// switched to level heap, so now imm start the loading screen (renderer will be reinitialized in the levelheap)
gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_START_LOADINGSCREEN, 0, 0);
gEnv->pSystem->SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_START_PREPARE);
}
OnPrepareNextLevel(levelName);
}
void SpawnableLevelSystem::OnPrepareNextLevel(const char* levelName)
{
AZ_TracePrintf("LevelSystem", "Level system is preparing to load '%s'\n", levelName);
for (auto& listener : m_listeners)
{
listener->OnPrepareNextLevel(levelName);
}
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::OnLevelNotFound(const char* levelName)
{
AZ_Error("LevelSystem", false, "Requested level not found: '%s'\n", levelName);
for (auto& listener : m_listeners)
{
listener->OnLevelNotFound(levelName);
}
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::OnLoadingStart(const char* levelName)
{
AZ_TracePrintf("LevelSystem", "Level system is loading '%s'\n", levelName);
if (gEnv->pCryPak->GetRecordFileOpenList() == AZ::IO::IArchive::RFOM_EngineStartup)
{
gEnv->pCryPak->RecordFileOpen(AZ::IO::IArchive::RFOM_Level);
}
m_fLastTime = gEnv->pTimer->GetAsyncCurTime();
GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_START, 0, 0);
LOADING_TIME_PROFILE_SECTION(gEnv->pSystem);
for (auto& listener : m_listeners)
{
listener->OnLoadingStart(levelName);
}
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::OnLoadingError(const char* levelName, const char* error)
{
AZ_Error("LevelSystem", false, "Error loading level '%s': %s\n", levelName, error);
if (gEnv->pRenderer)
{
gEnv->pRenderer->SetTexturePrecaching(false);
}
for (auto& listener : m_listeners)
{
listener->OnLoadingError(levelName, error);
}
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::OnLoadingComplete(const char* levelName)
{
CTimeValue t = gEnv->pTimer->GetAsyncTime();
m_fLastLevelLoadTime = (t - m_levelLoadStartTime).GetSeconds();
LogLoadingTime();
m_nLoadedLevelsCount++;
// Hide console after loading.
gEnv->pConsole->ShowConsole(false);
for (auto& listener : m_listeners)
{
listener->OnLoadingComplete(levelName);
}
#if AZ_LOADSCREENCOMPONENT_ENABLED
EBUS_EVENT(LoadScreenBus, Stop);
#endif // if AZ_LOADSCREENCOMPONENT_ENABLED
AZ_TracePrintf("LevelSystem", "Level load complete: '%s'\n", levelName);
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::OnLoadingProgress(const char* levelName, int progressAmount)
{
for (auto& listener : m_listeners)
{
listener->OnLoadingProgress(levelName, progressAmount);
}
}
//------------------------------------------------------------------------
void SpawnableLevelSystem::OnUnloadComplete(const char* levelName)
{
for (auto& listener : m_listeners)
{
listener->OnUnloadComplete(levelName);
}
AZ_TracePrintf("LevelSystem", "Level unload complete: '%s'\n", levelName);
}
//////////////////////////////////////////////////////////////////////////
void SpawnableLevelSystem::LogLoadingTime()
{
if (gEnv->IsEditor())
{
return;
}
if (!GetISystem()->IsDevMode())
{
return;
}
char vers[128];
GetISystem()->GetFileVersion().ToString(vers, sizeof(vers));
const char* sChain = "";
if (m_nLoadedLevelsCount > 0)
{
sChain = " (Chained)";
}
AZStd::string text;
text.format(
"Game Level Load Time: [%s] Level %s loaded in %.2f seconds%s", vers, m_lastLevelName.c_str(), m_fLastLevelLoadTime, sChain);
gEnv->pLog->Log(text.c_str());
}
//////////////////////////////////////////////////////////////////////////
void SpawnableLevelSystem::UnloadLevel()
{
if (gEnv->IsEditor())
{
return;
}
if (m_lastLevelName.empty())
{
return;
}
AZ_TracePrintf("LevelSystem", "UnloadLevel Start\n");
INDENT_LOG_DURING_SCOPE();
// Flush core buses. We're about to unload Cry modules and need to ensure we don't have module-owned functions left behind.
AZ::Data::AssetBus::ExecuteQueuedEvents();
AZ::TickBus::ExecuteQueuedEvents();
AZ::MainThreadRenderRequestBus::ExecuteQueuedEvents();
if (gEnv && gEnv->pSystem)
{
// clear all error messages to prevent stalling due to runtime file access check during chainloading
gEnv->pSystem->ClearErrorMessages();
}
if (gEnv && gEnv->pCryPak)
{
gEnv->pCryPak->DisableRuntimeFileAccess(false);
}
CTimeValue tBegin = gEnv->pTimer->GetAsyncTime();
I3DEngine* p3DEngine = gEnv->p3DEngine;
if (p3DEngine)
{
IDeferredPhysicsEventManager* pPhysEventManager = p3DEngine->GetDeferredPhysicsEventManager();
if (pPhysEventManager)
{
// clear deferred physics queues before renderer, since we could have jobs running
// which access a rendermesh
pPhysEventManager->ClearDeferredEvents();
}
}
// AM: Flush render thread (Flush is not exposed - using EndFrame())
// We are about to delete resources that could be in use
if (gEnv->pRenderer)
{
gEnv->pRenderer->EndFrame();
bool isLoadScreenPlaying = false;
#if AZ_LOADSCREENCOMPONENT_ENABLED
LoadScreenBus::BroadcastResult(isLoadScreenPlaying, &LoadScreenBus::Events::IsPlaying);
#endif // if AZ_LOADSCREENCOMPONENT_ENABLED
// force a black screen as last render command.
// if load screen is playing do not call this draw as it may lead to a crash due to UI loading code getting
// pumped while loading the shaders for this draw.
if (!isLoadScreenPlaying)
{
gEnv->pRenderer->BeginFrame();
gEnv->pRenderer->SetState(GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA | GS_NODEPTHTEST);
gEnv->pRenderer->Draw2dImage(0, 0, 800, 600, -1, 0.0f, 0.0f, 1.0f, 1.0f, 0.f, 0.0f, 0.0f, 0.0f, 1.0, 0.f);
gEnv->pRenderer->EndFrame();
}
// flush any outstanding texture requests
gEnv->pRenderer->FlushPendingTextureTasks();
}
// Clear level entities and prefab instances.
EBUS_EVENT(AzFramework::GameEntityContextRequestBus, ResetGameContext);
if (gEnv->pMovieSystem)
{
gEnv->pMovieSystem->Reset(false, false);
gEnv->pMovieSystem->RemoveAllSequences();
}
// Unload level specific audio binary data.
Audio::SAudioManagerRequestData<Audio::eAMRT_UNLOAD_AFCM_DATA_BY_SCOPE> oAMData(Audio::eADS_LEVEL_SPECIFIC);
Audio::SAudioRequest oAudioRequestData;
oAudioRequestData.nFlags = (Audio::eARF_PRIORITY_HIGH | Audio::eARF_EXECUTE_BLOCKING);
oAudioRequestData.pData = &oAMData;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData);
// Now unload level specific audio config data.
Audio::SAudioManagerRequestData<Audio::eAMRT_CLEAR_CONTROLS_DATA> oAMData2(Audio::eADS_LEVEL_SPECIFIC);
oAudioRequestData.pData = &oAMData2;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData);
Audio::SAudioManagerRequestData<Audio::eAMRT_CLEAR_PRELOADS_DATA> oAMData3(Audio::eADS_LEVEL_SPECIFIC);
oAudioRequestData.pData = &oAMData3;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData);
// Reset the camera to (0,0,0) which is the invalid/uninitialised state
CCamera defaultCam;
m_pSystem->SetViewCamera(defaultCam);
OnUnloadComplete(m_lastLevelName.c_str());
AzFramework::RootSpawnableInterface::Get()->ReleaseRootSpawnable();
m_lastLevelName.clear();
GetISystem()->GetIResourceManager()->UnloadLevel();
/*
Force Lua garbage collection before p3DEngine->UnloadLevel() and pRenderer->FreeResources(flags) are called.
p3DEngine->UnloadLevel() will destroy particle emitters even if they're still referenced by Lua objects that are yet to be
collected. (as per comment in 3dEngineLoad.cpp (line 501) - "Force to clean all particles that are left, even if still referenced.").
Then, during the next GC cycle, Lua finally cleans up, the particle emitter smart pointers will be pointing to invalid memory).
Normally the GC step is triggered at the end of this method (by the ESYSTEM_EVENT_LEVEL_POST_UNLOAD event), which is too late
(after the render resources have been purged).
This extra GC step takes a few ms more level unload time, which is a small price for fixing nasty crashes.
If, however, we wanted to claim it back, we could potentially get rid of the GC step that is triggered by
ESYSTEM_EVENT_LEVEL_POST_UNLOAD to break even.
*/
EBUS_EVENT(AZ::ScriptSystemRequestBus, GarbageCollect);
// Delete engine resources
if (p3DEngine)
{
p3DEngine->UnloadLevel();
}
// Force to clean render resources left after deleting all objects and materials.
IRenderer* pRenderer = gEnv->pRenderer;
if (pRenderer)
{
pRenderer->FlushRTCommands(true, true, true);
CryComment("Deleting Render meshes, render resources and flush texture streaming");
// This may also release some of the materials.
int flags = FRR_DELETED_MESHES | FRR_FLUSH_TEXTURESTREAMING | FRR_OBJECTS | FRR_RENDERELEMENTS | FRR_RP_BUFFERS | FRR_POST_EFFECTS;
// Always keep the system resources around in the editor.
// If a level load fails for any reason, then do not unload the system resources, otherwise we will not have any system resources to
// continue rendering the console and debug output text.
if (!gEnv->IsEditor() && !GetLevelLoadFailed())
{
flags |= FRR_SYSTEM_RESOURCES;
}
pRenderer->FreeResources(flags);
CryComment("done");
}
// Perform level unload procedures for the LyShine UI system
if (gEnv && gEnv->pLyShine)
{
gEnv->pLyShine->OnLevelUnload();
}
m_bLevelLoaded = false;
CTimeValue tUnloadTime = gEnv->pTimer->GetAsyncTime() - tBegin;
AZ_TracePrintf("LevelSystem", "UnloadLevel End: %.1f sec\n", tUnloadTime.GetSeconds());
// Must be sent last.
// Cleanup all containers
GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_POST_UNLOAD, 0, 0);
AzFramework::InputChannelRequestBus::Broadcast(&AzFramework::InputChannelRequests::ResetState);
}
void SpawnableLevelSystem::OnRootSpawnableAssigned(
[[maybe_unused]] AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
{
}
void SpawnableLevelSystem::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
{
}
} // namespace LegacyLevelSystem
@@ -0,0 +1,93 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "ILevelSystem.h"
#include <AzCore/Console/IConsole.h>
#include <AzFramework/Archive/IArchive.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
namespace LegacyLevelSystem
{
class SpawnableLevelSystem
: public ILevelSystem
, public AzFramework::RootSpawnableNotificationBus::Handler
{
public:
explicit SpawnableLevelSystem(ISystem* pSystem);
~SpawnableLevelSystem() override;
// ILevelSystem
void Release() override;
void AddListener(ILevelSystemListener* pListener) override;
void RemoveListener(ILevelSystemListener* pListener) override;
bool LoadLevel(const char* levelName) override;
void UnloadLevel() override;
bool IsLevelLoaded() override;
const char* GetCurrentLevelName() const override;
// If the level load failed then we need to have a different shutdown procedure vs when a level is naturally unloaded
void SetLevelLoadFailed(bool loadFailed) override;
bool GetLevelLoadFailed() override;
AZ::Data::AssetType GetLevelAssetType() const override;
// The following methods are deprecated from ILevelSystem and will be removed once slice support is removed.
// [LYN-2376] Remove once legacy slice support is removed
void Rescan([[maybe_unused]] const char* levelsFolder) override;
int GetLevelCount() override;
ILevelInfo* GetLevelInfo([[maybe_unused]] int level) override;
ILevelInfo* GetLevelInfo([[maybe_unused]] const char* levelName) override;
private:
void OnRootSpawnableAssigned(AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, uint32_t generation) override;
void OnRootSpawnableReleased(uint32_t generation) override;
void PrepareNextLevel(const char* levelName);
bool LoadLevelInternal(const char* levelName);
// Methods to notify ILevelSystemListener
void OnPrepareNextLevel(const char* levelName);
void OnLevelNotFound(const char* levelName);
void OnLoadingStart(const char* levelName);
void OnLoadingComplete(const char* levelName);
void OnLoadingError(const char* levelName, const char* error);
void OnLoadingProgress(const char* levelName, int progressAmount);
void OnUnloadComplete(const char* levelName);
void LogLoadingTime();
ISystem* m_pSystem{nullptr};
AZStd::string m_lastLevelName;
float m_fLastLevelLoadTime{0.0f};
float m_fLastTime{0.0f};
bool m_bLevelLoaded{false};
bool m_levelLoadFailed{false};
int m_nLoadedLevelsCount{0};
CTimeValue m_levelLoadStartTime;
AZStd::vector<ILevelSystemListener*> m_listeners;
// Information about the currently-loaded root spawnable, used for tracking loads and unloads.
uint64_t m_rootSpawnableGeneration{0};
AZ::Data::AssetId m_rootSpawnableId{};
};
} // namespace LegacyLevelSystem