diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 5dc00712da..483615d3e1 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -440,6 +440,16 @@ void CModelInfoSA::Remove() } } +bool CModelInfoSA::UnloadUnused() +{ + if (m_pInterface->usNumberOfRefs == 0 && !m_pCustomClump && !m_pCustomColModel) + { + pGame->GetStreaming()->RemoveModel(m_dwModelID); + return true; + } + return false; +} + bool CModelInfoSA::IsLoaded() { if (DoIsLoaded()) @@ -1035,7 +1045,7 @@ void CModelInfoSA::StaticFlushPendingRestreamIPL() for (it = removedModels.begin(); it != removedModels.end(); it++) { pGame->GetStreaming()->RemoveModel(*it); - pGame->GetStreaming()->GetStreamingInfo(*it)->loadState = 0; + pGame->GetStreaming()->GetStreamingInfo(*it)->loadState = eModelLoadState::LOADSTATE_NOT_LOADED; } } diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index 329d2e3e50..b261bcc04a 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -362,6 +362,7 @@ class CModelInfoSA : public CModelInfo BYTE GetVehicleType(); void Request(EModelRequestType requestType, const char* szTag); void Remove(); + bool UnloadUnused(); bool IsLoaded(); bool DoIsLoaded(); unsigned short GetFlags(); diff --git a/Client/mods/deathmatch/logic/CClientModel.cpp b/Client/mods/deathmatch/logic/CClientModel.cpp index 795bc08579..2a9eeb0c8d 100644 --- a/Client/mods/deathmatch/logic/CClientModel.cpp +++ b/Client/mods/deathmatch/logic/CClientModel.cpp @@ -84,6 +84,9 @@ bool CClientModel::Deallocate() if (!m_bAllocatedByUs) return false; + if (m_pParentResource) + m_pParentResource->GetResourceModelStreamer()->FullyReleaseModel(m_iModelID); + SetParentResource(nullptr); CModelInfo* pModelInfo = g_pGame->GetModelInfo(m_iModelID, true); diff --git a/Client/mods/deathmatch/logic/CResource.cpp b/Client/mods/deathmatch/logic/CResource.cpp index c1b5fdf9e7..dcf119ce46 100644 --- a/Client/mods/deathmatch/logic/CResource.cpp +++ b/Client/mods/deathmatch/logic/CResource.cpp @@ -94,6 +94,9 @@ CResource::CResource(unsigned short usNetID, const char* szResourceName, CClient CResource::~CResource() { + // Remove refrences from requested models + m_modelStreamer.ReleaseAll(); + // Deallocate all models that this resource allocated earlier g_pClientGame->GetManager()->GetModelManager()->DeallocateModelsAllocatedByResource(this); diff --git a/Client/mods/deathmatch/logic/CResource.h b/Client/mods/deathmatch/logic/CResource.h index 99df87fc3c..8100d3fc79 100644 --- a/Client/mods/deathmatch/logic/CResource.h +++ b/Client/mods/deathmatch/logic/CResource.h @@ -15,6 +15,7 @@ #include "CClientEntity.h" #include "CResourceConfigItem.h" #include "CResourceFile.h" +#include "CResourceModelStreamer.h" #include "CElementGroup.h" #include @@ -79,6 +80,8 @@ class CResource CClientEntity* GetResourceIFPRoot() { return m_pResourceIFPRoot; }; CClientEntity* GetResourceIMGRoot() { return m_pResourceIMGRoot; }; + CResourceModelStreamer* GetResourceModelStreamer() { return &m_modelStreamer; }; + // This is to delete all the elements created in this resource that are created locally in this client void DeleteClientChildren(); @@ -145,4 +148,6 @@ class CResource CFastHashSet m_exportedFunctions; CElementGroup* m_pDefaultElementGroup; // stores elements created by scripts in this resource std::list m_NoClientCacheScriptList; + + CResourceModelStreamer m_modelStreamer{}; }; diff --git a/Client/mods/deathmatch/logic/CResourceModelStreamer.cpp b/Client/mods/deathmatch/logic/CResourceModelStreamer.cpp new file mode 100644 index 0000000000..9c58219d8b --- /dev/null +++ b/Client/mods/deathmatch/logic/CResourceModelStreamer.cpp @@ -0,0 +1,122 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/CResourceModelStreamer.cpp + * PURPOSE: Resource model manager + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +#include "CResourceModelStreamer.h" +#include "CClientGame.h" +#include + +bool CResourceModelStreamer::RequestModel(std::uint16_t modelId, bool addRef, bool blocking) +{ + CModelInfo* model = g_pGame->GetModelInfo(modelId); + + if (!model) + return false; + + if (addRef) + { + std::uint16_t refsCount = ++m_requestedModels[modelId]; + if (refsCount == 1) + { + model->ModelAddRef(blocking ? EModelRequestType::BLOCKING : EModelRequestType::NON_BLOCKING, "CResourceModelStreamer::RequestModel With reference"); + return true; + } + return false; + } + else + { + if (model->IsLoaded()) + { + return false; + } + else + { + model->Request(blocking ? EModelRequestType::BLOCKING : EModelRequestType::NON_BLOCKING, "CResourceModelStreamer::RequestModel With out reference"); + return true; + } + } +} + +// Return true if model was unloaded +bool CResourceModelStreamer::ReleaseModel(std::uint16_t modelId, bool removeRef) +{ + if (removeRef) + { + auto refs = m_requestedModels.find(modelId); + if (refs == m_requestedModels.end()) + return false; + + std::uint16_t& refsCount = (*refs).second; + + if (refsCount == 0) + return false; + + refsCount--; + + if (refsCount != 0) + return false; + + CModelInfo* model = g_pGame->GetModelInfo(modelId); + + if (!model) + return false; + + // Hack + // This check will update models pending references + model->IsLoaded(); + + // This call can unload the model + model->RemoveRef(); + + return !model->IsLoaded(); + } + else + { + CModelInfo* model = g_pGame->GetModelInfo(modelId); + + if (!model) + return false; + + return model->UnloadUnused(); + } +} + +void CResourceModelStreamer::ReleaseAll() +{ + for (const auto &modelRefs : m_requestedModels) + { + if (modelRefs.second > 0) + { + CModelInfo* model = g_pGame->GetModelInfo(modelRefs.first); + model->RemoveRef(); + } + } + + m_requestedModels.clear(); +} + +void CResourceModelStreamer::FullyReleaseModel(std::uint16_t modelId) +{ + std::uint16_t &refsCount = m_requestedModels[modelId]; + + if (refsCount > 0) + { + refsCount = 0; + + CModelInfo* model = g_pGame->GetModelInfo(modelId); + + if (!model) + return; + + model->RemoveRef(); + } +} diff --git a/Client/mods/deathmatch/logic/CResourceModelStreamer.h b/Client/mods/deathmatch/logic/CResourceModelStreamer.h new file mode 100644 index 0000000000..fa72720b37 --- /dev/null +++ b/Client/mods/deathmatch/logic/CResourceModelStreamer.h @@ -0,0 +1,30 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/CResourceModelStreamer.h + * PURPOSE: Resource model manager + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include + +class CResourceModelStreamer +{ +public: + CResourceModelStreamer() = default; + ~CResourceModelStreamer() = default; + + bool RequestModel(std::uint16_t modelId, bool addRef = false, bool blocking = false); + bool ReleaseModel(std::uint16_t modelId, bool removeRef = false); + + void ReleaseAll(); + void FullyReleaseModel(std::uint16_t modelId); + +private: + std::unordered_map m_requestedModels; +}; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index 7090203704..8d2d3a891d 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -896,6 +896,14 @@ ADD_ENUM(WEATHER_SANDSTORM, "Sandstorm") ADD_ENUM(WEATHER_RAINBOW, "Rainbow") IMPLEMENT_ENUM_END("world-property") +IMPLEMENT_ENUM_CLASS_BEGIN(eModelLoadState) +ADD_ENUM(eModelLoadState::LOADSTATE_NOT_LOADED, "unloaded") +ADD_ENUM(eModelLoadState::LOADSTATE_LOADED, "loaded") +ADD_ENUM(eModelLoadState::LOADSTATE_REQUESTED, "requested") +ADD_ENUM(eModelLoadState::LOADSTATE_READING, "reading") +ADD_ENUM(eModelLoadState::LOADSTATE_FINISHING, "finishing") +IMPLEMENT_ENUM_CLASS_END("model-load-state") + // // CResource from userdata // diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index 9b93980d85..1b75369d02 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -13,6 +13,7 @@ #include #include #include +#include #include enum eLuaType @@ -85,6 +86,7 @@ DECLARE_ENUM_CLASS(eRenderStage); DECLARE_ENUM_CLASS(eFxParticleSystems); DECLARE_ENUM(ePools); DECLARE_ENUM(eWorldProperty); +DECLARE_ENUM_CLASS(eModelLoadState); class CRemoteCall; diff --git a/Client/mods/deathmatch/logic/lua/CLuaMain.h b/Client/mods/deathmatch/logic/lua/CLuaMain.h index abe2c96cf1..3839e5995e 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaMain.h +++ b/Client/mods/deathmatch/logic/lua/CLuaMain.h @@ -59,7 +59,7 @@ class CLuaMain //: public CClient void ResetInstructionCount(); - class CResource* GetResource() { return m_pResource; } + class CResource* GetResource() const { return m_pResource; } CXMLFile* CreateXML(const char* szFilename, bool bUseIDs = true, bool bReadOnly = false); CXMLNode* ParseString(const char* strXmlContent); diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index f0ebabb419..530b6e58a3 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "CLuaEngineDefs.h" //! Set the CModelCacheManager limits //! By passing `nil`/no value the original values are restored @@ -135,6 +136,9 @@ void CLuaEngineDefs::LoadFunctions() {"engineStreamingSetBufferSize", ArgumentParser}, {"engineStreamingGetBufferSize", ArgumentParser}, {"engineStreamingRestoreBufferSize", ArgumentParser}, + {"engineStreamingRequestModel", ArgumentParser}, + {"engineStreamingReleaseModel", ArgumentParser}, + {"engineStreamingGetModelLoadState", ArgumentParser}, {"engineRequestTXD", ArgumentParser}, {"engineFreeTXD", ArgumentParser}, {"engineGetPoolCapacity", ArgumentParser}, @@ -2502,3 +2506,44 @@ bool CLuaEngineDefs::EngineSetPoolCapacity(lua_State* luaVM, ePools pool, size_t } return true; } + +bool CLuaEngineDefs::EngineStreamingRequestModel(lua_State* const luaVM, std::uint16_t modelId, std::optional addReference, std::optional blocking) +{ + // Grab the lua main and the resource belonging to this script + CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM); + + CModelInfo* pModelInfo = g_pGame->GetModelInfo(modelId); + + if (modelId >= g_pGame->GetBaseIDforCOL() || !pModelInfo) + throw std::invalid_argument("Expected a valid model ID at argument 1"); + + // Get the resource we belong to + CResource* pResource = pLuaMain->GetResource(); + + return pResource->GetResourceModelStreamer()->RequestModel(modelId, addReference.value_or(false), blocking.value_or(false)); +} + +bool CLuaEngineDefs::EngineStreamingReleaseModel(lua_State* const luaVM, std::uint16_t modelId, std::optional removeReference) +{ + // Grab the lua main and the resource belonging to this script + CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM); + + CModelInfo* pModelInfo = g_pGame->GetModelInfo(modelId); + + if (modelId >= g_pGame->GetBaseIDforCOL() || !pModelInfo) + throw std::invalid_argument("Expected a valid model ID at argument 1"); + + // Get the resource we belong to + CResource* pResource = pLuaMain->GetResource(); + + return pResource->GetResourceModelStreamer()->ReleaseModel(modelId, removeReference.value_or(false)); +} + +eModelLoadState CLuaEngineDefs::EngineStreamingGetModelLoadState(std::uint16_t modelId) +{ + const auto allCount = g_pGame->GetCountOfAllFileIDs(); + if (modelId >= g_pGame->GetCountOfAllFileIDs()) + throw std::invalid_argument("Expected a valid model ID at argument 1"); + + return g_pGame->GetStreaming()->GetStreamingInfo(modelId)->loadState; +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h index 0226cebc3a..8346f6ef0b 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h @@ -88,6 +88,10 @@ class CLuaEngineDefs : public CLuaDefs static uint EngineRequestTXD(lua_State* const luaVM, std::string strTxdName); static bool EngineFreeTXD(uint txdID); + static bool EngineStreamingRequestModel(lua_State* const luaVM, std::uint16_t modelId, std::optional addReference, std::optional blocking); + static bool EngineStreamingReleaseModel(lua_State* const luaVM, std::uint16_t modelId, std::optional removeReference); + static eModelLoadState EngineStreamingGetModelLoadState(std::uint16_t modelId); + private: static void AddEngineColClass(lua_State* luaVM); static void AddEngineTxdClass(lua_State* luaVM); diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 440f079be6..af9fa1bdf3 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -179,6 +179,7 @@ class CModelInfo virtual void RemoveRef(bool bRemoveExtraGTARef = false) = 0; virtual int GetRefCount() = 0; virtual bool ForceUnload() = 0; + virtual bool UnloadUnused() = 0; virtual void DeallocateModel() = 0; virtual float GetDistanceFromCentreOfMassToBaseOfModel() = 0; diff --git a/Client/sdk/game/CStreaming.h b/Client/sdk/game/CStreaming.h index c8ff17c558..6eb47f13a6 100644 --- a/Client/sdk/game/CStreaming.h +++ b/Client/sdk/game/CStreaming.h @@ -16,6 +16,27 @@ #define INVALID_ARCHIVE_ID 0xFF #define INVALID_STREAM_ID 0xFF +enum class eModelLoadState : std::uint32_t +{ + // Model isn't loaded + LOADSTATE_NOT_LOADED = 0, + + // Model is loaded + LOADSTATE_LOADED = 1, + + // Model in request list, but not yet in loading channel (TODO: Verify this) + LOADSTATE_REQUESTED = 2, + + // Model is being read + LOADSTATE_READING = 3, + + // If the model is a `big` one this state is used to indicate + // that the model's first half has been loaded and is yet to be + // finished by loading the second half. + // When it has been loaded the state is set to `LOADED` + LOADSTATE_FINISHING = 4 +}; + struct CStreamingInfo { uint16_t prevId = (uint16_t)-1; @@ -25,7 +46,7 @@ struct CStreamingInfo uint8_t archiveId = 0u; uint32_t offsetInBlocks = 0u; uint32_t sizeInBlocks = 0u; - uint32_t loadState = 0u; + eModelLoadState loadState = eModelLoadState::LOADSTATE_NOT_LOADED; }; static_assert(sizeof(CStreamingInfo) == 0x14, "Invalid size for CStreamingInfo");