diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 5dc00712da..191211cd17 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()) 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..2f5350ea7d 100644 --- a/Client/mods/deathmatch/logic/CClientModel.cpp +++ b/Client/mods/deathmatch/logic/CClientModel.cpp @@ -84,6 +84,11 @@ 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..e7a34cafb9 --- /dev/null +++ b/Client/mods/deathmatch/logic/CResourceModelStreamer.cpp @@ -0,0 +1,115 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * 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(uint16_t modelId, bool addRef, bool blocking) +{ + CModelInfo* model = g_pGame->GetModelInfo(modelId); + + if (!model) + return false; + + if (addRef) + { + 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 + { + model->Request(blocking ? EModelRequestType::BLOCKING : EModelRequestType::NON_BLOCKING, "CResourceModelStreamer::RequestModel With out reference"); + return true; + } +} + +// Retrun true if model was unloaded +bool CResourceModelStreamer::ReleaseModel(uint16_t modelId, bool removeRef) +{ + if (removeRef) + { + auto refs = m_requestedModels.find(modelId); + if (refs == m_requestedModels.end()) + return false; + + 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(uint16_t modelId) +{ + 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..d6c02d6974 --- /dev/null +++ b/Client/mods/deathmatch/logic/CResourceModelStreamer.h @@ -0,0 +1,30 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * 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(uint16_t modelId, bool addRef = false, bool blocking = false); + bool ReleaseModel(uint16_t modelId, bool removeRef = false); + + void ReleaseAll(); + void FullyReleaseModel(uint16_t modelId); + +private: + std::unordered_map m_requestedModels; +}; 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 415227890f..17ac077d9f 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,8 @@ void CLuaEngineDefs::LoadFunctions() {"engineStreamingSetBufferSize", ArgumentParser}, {"engineStreamingGetBufferSize", ArgumentParser}, {"engineStreamingRestoreBufferSize", ArgumentParser}, + {"engineStreamingRequestModel", ArgumentParser}, + {"engineStreamingReleaseModel", ArgumentParser}, {"engineRequestTXD", ArgumentParser}, {"engineFreeTXD", ArgumentParser}, {"engineGetPoolCapacity", ArgumentParser}, @@ -2505,3 +2508,35 @@ bool CLuaEngineDefs::EngineSetPoolCapacity(lua_State* luaVM, ePools pool, size_t } return true; } + +bool CLuaEngineDefs::EngineStreamingRequestModel(lua_State* const luaVM, 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 >= 25000 || !pModelInfo) + throw std::invalid_argument("Expected a valid model ID in range [0-24999] 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, 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 >= 25000 || !pModelInfo) + throw std::invalid_argument("Expected a valid model ID in range [0-24999] at argument 1"); + + // Get the resource we belong to + CResource* pResource = pLuaMain->GetResource(); + + return pResource->GetResourceModelStreamer()->ReleaseModel(modelId, removeReference.value_or(false)); +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h index 5426c31015..39889f26ea 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h @@ -88,6 +88,9 @@ 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, uint16_t modelId, std::optional addReference, std::optional blocking); + static bool EngineStreamingReleaseModel(lua_State* const luaVM, uint16_t modelId, std::optional removeReference); + 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;