Engine: implement pak preloading

Some specific paks are loaded through code (common.rpak, ui.rpak. etc). We have implemented a system that also loads 2 common rpaks that are of our own (common_sdk.rpak and ui_sdk.rpak).

There is al additional syustem we had implemented earlier that allows you to mount rpaks during level load, which will get loaded before the actual level rpak, this is useful as when we port a map that has evolved slightly from its original one, only needing 20 new models, it makes more sense to load the original rpak + a new one that contains the 20 new models to save disk space.

This is also useful for mounting paks specific to mods for a certain map, however since we have some core mods (flowstate) we have to add it to the settings for for each level. These paks also get dropped and reloaded each time on level changes causing longer load times and more memory usage during level loads.

This system mounts paks after all common paks are loaded (from both the engine and sdk), and keeps them active just like the common paks throughout level changes until they are either manually unloaded, or the game has been closed.
This commit is contained in:
Kawe Mazidjatari 2024-11-14 14:54:27 +01:00
parent b3796c32cc
commit da6ffe9005
2 changed files with 144 additions and 30 deletions

View File

@ -15,6 +15,7 @@
#include "engine/host_cmd.h"
#include "engine/cmodel_bsp.h"
#include "rtech/rson.h"
#include "rtech/pak/pakstate.h"
#include "rtech/pak/pakparse.h"
#include "rtech/pak/paktools.h"
@ -40,13 +41,13 @@ PakHandle_t CustomPakData_t::LoadAndAddPak(const char* const pakFile)
{
if (numHandles >= MAX_CUSTOM_PAKS)
{
Error(eDLL_T::ENGINE, NO_ERROR, "Tried to load pak '%s', but already reached the SDK's limit of %d!\n", pakFile, MAX_CUSTOM_PAKS);
Error(eDLL_T::ENGINE, NO_ERROR, "Tried to load pak '%s', but already reached the limit of %d!\n", pakFile, MAX_CUSTOM_PAKS);
return PAK_INVALID_HANDLE;
}
const PakHandle_t pakId = g_pakLoadApi->LoadAsync(pakFile, AlignedMemAlloc(), 4, 0);
// failure, don't add and return the invalid handle.
// failure, don't add; return the invalid handle.
if (pakId == PAK_INVALID_HANDLE)
return pakId;
@ -55,23 +56,68 @@ PakHandle_t CustomPakData_t::LoadAndAddPak(const char* const pakFile)
}
//-----------------------------------------------------------------------------
// Purpose: unloads all active custom pak handles
// Purpose: unload a custom pak
// NOTE : the array must be kept contiguous; this means that the last pak in
// the array should always be unloaded fist!
//-----------------------------------------------------------------------------
void CustomPakData_t::UnloadAndRemoveAll()
void CustomPakData_t::UnloadAndRemovePak(const int index)
{
// Base SDK paks should not be unloaded here, but only right before base
const PakHandle_t pakId = handles[index];
assert(pakId != PAK_INVALID_HANDLE); // invalid handles should not be inserted
g_pakLoadApi->UnloadAsync(pakId);
handles[index] = PAK_INVALID_HANDLE;
numHandles--;
}
//-----------------------------------------------------------------------------
// Purpose: preload a custom pak; this keeps it available throughout the
// duration of the process, unless manually removed by user.
//-----------------------------------------------------------------------------
PakHandle_t CustomPakData_t::PreloadAndAddPak(const char* const pakFile)
{
// this must never be called after a non-preloaded pak has been added!
// preloaded paks must always appear before custom user requested paks
// due to the unload order: user-requested -> preloaded -> sdk -> core.
assert(handles[CustomPakData_t::PAK_TYPE_COUNT+numPreload] == PAK_INVALID_HANDLE);
const PakHandle_t pakId = LoadAndAddPak(pakFile);
if (pakId != PAK_INVALID_HANDLE)
numPreload++;
return pakId;
}
//-----------------------------------------------------------------------------
// Purpose: unloads all non-preloaded custom pak handles
//-----------------------------------------------------------------------------
void CustomPakData_t::UnloadAndRemoveNonPreloaded()
{
// Preloaded paks should not be unloaded here, but only right before sdk /
// engine paks are unloaded. Only unload user requested and level settings
// paks from here. Also, ideally this loop runs in reverse, but the engine
// does not support that as it would crash when paks are unloaded that way.
for (size_t i = CustomPakData_t::PAK_TYPE_COUNT, n = numHandles; i < n; i++)
// paks from here. Also, the load and unload order here is FIFO, this is
// needed because when you load a pak, and then load another pak which
// happens to have an overlapping asset, the asset will be updated with
// that of the newer pak. If we remove the newer pak, the engine will try
// and revert the asset to its original state. However we asynchronously
// unload everything on a single FIFO lock so this is undefined behavior.
for (int i = CustomPakData_t::PAK_TYPE_COUNT+numPreload, n = numHandles; i < n; i++)
{
const PakHandle_t pakId = handles[i];
assert(pakId != PAK_INVALID_HANDLE); // invalid handles should not be inserted
UnloadAndRemovePak(i);
}
}
g_pakLoadApi->UnloadAsync(pakId);
handles[i] = PAK_INVALID_HANDLE;
numHandles--;
//-----------------------------------------------------------------------------
// Purpose: unloads all preloaded custom pak handles
//-----------------------------------------------------------------------------
void CustomPakData_t::UnloadAndRemovePreloaded()
{
for (int i = 0, n = numPreload; i < n; i++)
{
UnloadAndRemovePak(CustomPakData_t::PAK_TYPE_COUNT + i);
numPreload--;
}
}
@ -274,10 +320,6 @@ void Mod_QueuedPakCacheFrame()
s_customPakData.UnloadBasePak(CustomPakData_t::PAK_TYPE_COMMON_SDK);
break;
case CommonPakData_t::PAK_TYPE_LOBBY:
s_customPakData.basePaksLoaded = false;
break;
default:
break;
}
@ -286,7 +328,7 @@ void Mod_QueuedPakCacheFrame()
if (numLeftToProcess == CommonPakData_t::PAK_TYPE_LEVEL)
{
Mod_UnloadPakFile(); // Unload mod pak files.
Mod_UnloadLevelPaks(); // Unload mod pak files.
if (s_pLevelSetKV)
{
@ -295,6 +337,11 @@ void Mod_QueuedPakCacheFrame()
s_pLevelSetKV = nullptr;
}
}
else if (numLeftToProcess == CommonPakData_t::PAK_TYPE_LOBBY)
{
Mod_UnloadPreloadedPaks();
s_customPakData.basePaksLoaded = false;
}
}
if (status && (unsigned int)(status - 13) > 1)
@ -392,11 +439,14 @@ void Mod_QueuedPakCacheFrame()
}
if (it == CommonPakData_t::PAK_TYPE_LOBBY)
{
Mod_PreloadPaks();
s_customPakData.basePaksLoaded = true;
}
if (s_customPakData.basePaksLoaded && !s_customPakData.levelResourcesLoaded)
{
Mod_PreloadLevelPaks(s_CurrentLevelName.String());
Mod_LoadLevelPaks(s_CurrentLevelName.String());
s_customPakData.levelResourcesLoaded = true;
}
@ -425,6 +475,57 @@ CHECK_FOR_FAILURE:
goto LOOP_AGAIN_OR_FINISH;
}
//-----------------------------------------------------------------------------
// Purpose: preload paks in list and keeps them active throughout level changes
//-----------------------------------------------------------------------------
void Mod_PreloadPaks()
{
static const char* const preloadFile = "paks/preload.rson";
bool parseFailure = false;
RSON::Node_t* const rson = RSON::LoadFromFile(preloadFile, "GAME", &parseFailure);
if (!rson)
{
if (parseFailure)
Error(eDLL_T::ENGINE, EXIT_FAILURE, "%s: failure parsing file '%s'\n", __FUNCTION__, preloadFile);
else
{
Warning(eDLL_T::ENGINE, "%s: could not load file '%s'\n", __FUNCTION__, preloadFile);
return; // No preload file, thus no error. Warn and return out.
}
}
static const char* const arrayName = "Paks";
const RSON::Field_t* const key = rson->FindKey(arrayName);
if (!key)
Error(eDLL_T::ENGINE, EXIT_FAILURE, "%s: missing array key \"%s\" in file '%s'\n", __FUNCTION__, arrayName, preloadFile);
if ((key->m_Node.m_Type != (RSON::eFieldType::RSON_ARRAY | RSON::eFieldType::RSON_STRING)) &&
(key->m_Node.m_Type != (RSON::eFieldType::RSON_ARRAY | RSON::eFieldType::RSON_VALUE)))
{
Error(eDLL_T::ENGINE, EXIT_FAILURE, "%s: expected an array of strings in file '%s'\n", __FUNCTION__, preloadFile);
}
for (int i = 0; i < key->m_Node.m_nValueCount; i++)
{
const RSON::Value_t* const value = key->m_Node.GetArrayValue(i);
s_customPakData.PreloadAndAddPak(value->pszString);
}
RSON_Free(rson, AlignedMemAlloc());
AlignedMemAlloc()->Free(rson);
}
//-----------------------------------------------------------------------------
// Purpose: unloads all preloaded paks
//-----------------------------------------------------------------------------
void Mod_UnloadPreloadedPaks()
{
s_customPakData.UnloadAndRemovePreloaded();
}
//-----------------------------------------------------------------------------
// Purpose: load assets for level with fifolock.
// Input : *szLevelName -
@ -467,10 +568,10 @@ KeyValues* Mod_GetLevelSettings(const char* const pszLevelName)
}
//-----------------------------------------------------------------------------
// Purpose: loads required pakfile assets for specified BSP level
// Input : &svSetFile -
// Purpose: loads paks specified inside the level settings file
// Input : *pszLevelName -
//-----------------------------------------------------------------------------
void Mod_PreloadLevelPaks(const char* const pszLevelName)
void Mod_LoadLevelPaks(const char* const pszLevelName)
{
KeyValues* const pSettingsKV = Mod_GetLevelSettings(pszLevelName);
@ -498,11 +599,11 @@ void Mod_PreloadLevelPaks(const char* const pszLevelName)
}
//-----------------------------------------------------------------------------
// Purpose: unloads all pakfiles loaded by the SDK
// Purpose: unloads all paks loaded by the level settings file
//-----------------------------------------------------------------------------
void Mod_UnloadPakFile(void)
void Mod_UnloadLevelPaks()
{
s_customPakData.UnloadAndRemoveAll();
s_customPakData.UnloadAndRemoveNonPreloaded();
g_StudioMdlFallbackHandler.ClearBadModelHandleCache();
g_StudioMdlFallbackHandler.ClearSuppresionList();

View File

@ -95,23 +95,33 @@ struct CustomPakData_t
// the first # handles are reserved for base SDK paks
numHandles = PAK_TYPE_COUNT;
numPreload = 0;
levelResourcesLoaded = false;
basePaksLoaded = false;
}
PakHandle_t LoadAndAddPak(const char* const pakFile);
void UnloadAndRemoveAll();
PakHandle_t PreloadAndAddPak(const char* const pakFile);
void UnloadAndRemoveNonPreloaded();
void UnloadAndRemovePreloaded();
PakHandle_t LoadBasePak(const char* const pakFile, const EPakType type);
void UnloadBasePak(const EPakType type);
private:
void UnloadAndRemovePak(const int index);
public:
// Pak handles that have been loaded with the level
// from within the level settings KV (located in
// scripts/levels/settings/*.kv). On level unload,
// each pak listed in this vector gets unloaded.
PakHandle_t handles[MAX_CUSTOM_PAKS];
size_t numHandles;
int numHandles;
int numPreload;
bool levelResourcesLoaded;
bool basePaksLoaded;
@ -131,11 +141,14 @@ inline void(*Mod_UnloadPendingAndPrecacheRequestedPaks)(void);
extern CUtlVector<CUtlString> g_InstalledMaps;
extern CThreadMutex g_InstalledMapsMutex;
void Mod_PreloadPaks();
void Mod_UnloadPreloadedPaks();
bool Mod_LevelHasChanged(const char* pszLevelName);
void Mod_GetAllInstalledMaps();
KeyValues* Mod_GetLevelSettings(const char* pszLevelName);
void Mod_PreloadLevelPaks(const char* pszLevelName);
void Mod_UnloadPakFile(void);
void Mod_LoadLevelPaks(const char* pszLevelName);
void Mod_UnloadLevelPaks(void);
///////////////////////////////////////////////////////////////////////////////