2242 lines
68 KiB
C++
2242 lines
68 KiB
C++
//
|
|
// streaming/streamingcleanup.cpp
|
|
//
|
|
// Copyright (C) 1999-2014 Rockstar Games. All Rights Reserved.
|
|
//
|
|
|
|
#include "streaming/streaming_channel.h"
|
|
|
|
STREAMING_OPTIMISATIONS()
|
|
|
|
#include "fwanimation/pointcloud.h"
|
|
#include "camera/caminterface.h"
|
|
#include "camera/helpers/Frame.h"
|
|
#include "camera/viewports/viewportmanager.h"
|
|
#include "cutscene/CutSceneManagerNew.h"
|
|
#include "fwdebug/vectormap.h"
|
|
#include "fwscene/stores/mapdatastore.h"
|
|
#include "fwscene/stores/staticboundsstore.h"
|
|
#include "fwpheffects/ropemanager.h"
|
|
#include "game/config.h"
|
|
#include "grcore/resourcecache.h"
|
|
#include "peds/ped.h"
|
|
#include "peds/rendering/PedVariationPack.h"
|
|
#include "renderer/renderer.h"
|
|
#include "scene/ContinuityMgr.h"
|
|
#include "scene/FocusEntity.h"
|
|
#include "scene/lod/LodMgr.h"
|
|
#include "scene/streamer/SceneStreamerList.h"
|
|
#include "scene/streamer/SceneStreamerMgr.h"
|
|
#include "scene/texLod.h"
|
|
#include "scene/debug/SceneStreamerDebug.h"
|
|
#include "scene/world/gameworld.h"
|
|
#include "scene/EntityIterator.h"
|
|
#include "script/streamedscripts.h"
|
|
#include "streaming/packfilemanager.h"
|
|
#include "streaming/populationstreaming.h"
|
|
#include "streaming/streaming.h"
|
|
#include "streaming/streamingcleanup.h"
|
|
#include "streaming/streamingengine.h"
|
|
#include "streaming/streaminginfo.h"
|
|
#include "streaming/streamingmodule.h"
|
|
#include "streaming/streamingvisualize.h"
|
|
#include "streaming/zonedassetmanager.h"
|
|
#include "script/commands_player.h"
|
|
#include "script/commands_ped.h"
|
|
#include "system/buddyallocator.h"
|
|
#include "system/controlMgr.h"
|
|
#include "system/param.h"
|
|
#include "pathserver/ExportCollision.h"
|
|
|
|
// cleanup related
|
|
#include "animation/debug/AnimPlacementTool.h"
|
|
#include "animation/debug/AnimViewer.h"
|
|
#include "animation/debug/ClipEditor.h"
|
|
#include "debug/TextureViewer/TextureViewer.h"
|
|
#include "fwsys/metadatastore.h"
|
|
#include "Game/Dispatch/DispatchManager.h"
|
|
#include "pickups/PickupManager.h"
|
|
#include "peds/playerinfo.h"
|
|
#include "peds/PedFactory.h"
|
|
#include "scene/world/gameworld.h"
|
|
#include "vehicles/vehicle.h"
|
|
#include "vehicles/vehiclepopulation.h"
|
|
#include "peds/pedpopulation.h"
|
|
#include "vehicles/train.h"
|
|
#include "fwtl/pool.h"
|
|
#include "peds/ped.h"
|
|
#include "objects/object.h"
|
|
#include "objects/objectpopulation.h"
|
|
#include "renderer/gtadrawable.h"
|
|
#include "Peds/PedFactory.h"
|
|
#include "fragment/cachemanager.h"
|
|
#include "core/game.h"
|
|
#include "system/controlMgr.h"
|
|
#include "vfx/clouds/CloudHat.h"
|
|
|
|
#include "data/aes_init.h"
|
|
AES_INIT_B;
|
|
|
|
#if __STATS
|
|
PF_PAGE(StreamCleanupPage, "Streaming Cleanup");
|
|
PF_GROUP(StreamCleanup);
|
|
PF_LINK(StreamCleanupPage, StreamCleanup);
|
|
|
|
PF_TIMER(MakeSpaceForMap, StreamCleanup);
|
|
PF_TIMER(FindLowestScoring, StreamCleanup);
|
|
PF_TIMER(AbsorbPVS, StreamCleanup);
|
|
PF_TIMER(SortBuckets, StreamCleanup);
|
|
PF_TIMER(WaitForSPUSort, StreamCleanup);
|
|
PF_TIMER(IssueRequests, StreamCleanup);
|
|
|
|
#if STREAM_STATS
|
|
PF_VALUE_INT(AddedEntities, StreamCleanup);
|
|
PF_VALUE_INT(RemovedEntities, StreamCleanup);
|
|
PF_VALUE_INT(Entities, StreamCleanup);
|
|
PF_VALUE_INT(DeletedEntities, StreamCleanup);
|
|
PF_VALUE_INT(NewRemoved, StreamCleanup);
|
|
PF_VALUE_INT(NewDeleted, StreamCleanup);
|
|
PF_VALUE_INT(CleanupCalls, StreamCleanup);
|
|
PF_VALUE_INT(ValidationAttempts, StreamCleanup);
|
|
PF_VALUE_INT(ActiveEntities, StreamCleanup);
|
|
PF_VALUE_INT(NeededEntities, StreamCleanup);
|
|
PF_VALUE_INT(PriorityRequestsODD, StreamCleanup);
|
|
PF_VALUE_INT(SecondaryRequestsODD, StreamCleanup);
|
|
PF_VALUE_INT(PriorityRequestsHDD, StreamCleanup);
|
|
PF_VALUE_INT(SecondaryRequestsHDD, StreamCleanup);
|
|
|
|
static int s_numRemoved;
|
|
static int s_numDeleted;
|
|
|
|
#endif // STREAM_STATS
|
|
#endif // __STATS
|
|
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
PARAM( streamingcleanupsim, "Use the simulation step in the new streaming system" );
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
|
|
#if RSG_PC
|
|
namespace rage
|
|
{
|
|
XPARAM( disableExtraMem );
|
|
}
|
|
#endif
|
|
|
|
extern void InterruptInitialize();
|
|
extern void InterruptEnd();
|
|
extern bool g_show_streaming_on_vmap;
|
|
|
|
#define OBJ_INSTANCES_LIMIT (CSceneStreamerList::MAX_OBJ_INSTANCES - 300)
|
|
|
|
static int s_CurrentThreshold = OBJ_INSTANCES_LIMIT - 3000;
|
|
float s_AutoCleanupCutoff = 0.95f;
|
|
static int s_MaxObjsToAutoCleanup = 70;
|
|
static bool s_ForceAutoCleanup;
|
|
|
|
// Percentage threshold that dictates when garbage collection should kick in - if either
|
|
// resource heap is full by more than this number (in percent), we'll toss out some entities.
|
|
int s_MemoryGarbageCollectionThresholdVirt = 98;
|
|
int s_MemoryGarbageCollectionThresholdPhys = 90;
|
|
float s_GarbageCollectionCutoff = 0.999999f;
|
|
bool s_GarbageCollectEntitiesOnly = false;
|
|
|
|
|
|
struct StreamingEntry
|
|
{
|
|
int m_virtualSize;
|
|
int m_physicalSize;
|
|
short m_refCount;
|
|
short m_RealRefCount;
|
|
short m_RealDepCount;
|
|
bool m_IsWorthDeleting;
|
|
char m_ReferenceCount;
|
|
StreamingEntry **m_References;
|
|
StreamingEntry *m_Parent;
|
|
strIndex m_Index;
|
|
int m_Status;
|
|
|
|
// If true, we determined that this resource can be deleted.
|
|
bool ShouldDelete() const { return m_refCount == m_RealRefCount; }
|
|
bool IsWorthDeleting() const { return m_IsWorthDeleting; }
|
|
|
|
};
|
|
|
|
struct DeletionMap
|
|
{
|
|
enum
|
|
{
|
|
// Total number of possible references for all elements
|
|
REF_BUFFER_SIZE = 8192,
|
|
|
|
// Total number of streamables being tracked
|
|
MAX_TRACKED_STREAMABLES = 4096,
|
|
};
|
|
|
|
DeletionMap();
|
|
|
|
void Reset();
|
|
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
void ExecuteDeletion();
|
|
|
|
atFixedArray<BucketEntry *, MAX_TRACKED_STREAMABLES> m_EntitiesToDelete;
|
|
atFixedArray<strIndex, MAX_TRACKED_STREAMABLES> m_ObjectsToRemove;
|
|
atSimplePooledMapType<strIndex, StreamingEntry> m_StreamableMap;
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
|
|
u32 m_Timestamp; // Timestamp of the buckets in the scene streamer manager
|
|
int m_ActiveIndex;
|
|
int m_NeededIndex;
|
|
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
StreamingEntry *m_ReferenceBuffer[REF_BUFFER_SIZE];
|
|
int m_ReferenceBufferCursor;
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
};
|
|
|
|
static DeletionMap s_DeletionMap;
|
|
|
|
|
|
|
|
#define DISTANCE_WE_DONT_DELETE_OBJECTS_WITH_LIGHTS 30.0f
|
|
|
|
|
|
// --- DeletionMap -------------------------------------------------------------------------------------
|
|
|
|
DeletionMap::DeletionMap()
|
|
{
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
m_StreamableMap.Init(DeletionMap::MAX_TRACKED_STREAMABLES);
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
|
|
Reset();
|
|
m_Timestamp = ~0U;
|
|
}
|
|
|
|
void DeletionMap::Reset()
|
|
{
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
m_ReferenceBufferCursor = 0;
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
m_ActiveIndex = 0;
|
|
m_NeededIndex = 0;
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
m_StreamableMap.Reset();
|
|
m_ObjectsToRemove.Resize(0);
|
|
m_EntitiesToDelete.Resize(0);
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
}
|
|
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
void DeletionMap::ExecuteDeletion()
|
|
{
|
|
int objCount = m_ObjectsToRemove.GetCount();
|
|
|
|
// Short-circuit: If we didn't delete anything, there definitely won't be any
|
|
// entities to get rid of.
|
|
if (!objCount)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// Go through the entities and see which one actually contributed to the freeing up of
|
|
// memory.
|
|
strStreamingModule *streamingModule = fwArchetypeManager::GetStreamingModule();
|
|
int entityCount = m_EntitiesToDelete.GetCount();
|
|
|
|
// We need to put objects we didn't remove back into the list.
|
|
int newIndex = 0;
|
|
strIndex *leftOverObjects = Alloca(strIndex, objCount);
|
|
|
|
for (int x=0; x<entityCount; x++)
|
|
{
|
|
CEntity *entity = m_EntitiesToDelete[x]->GetEntity();
|
|
streamAssert(entity); // Why did we put an empty entity on this list?
|
|
|
|
u32 objIndex = entity->GetModelIndex();
|
|
|
|
strIndex archIndex = streamingModule->GetStreamingIndex(objIndex);
|
|
|
|
StreamingEntry* entry = m_StreamableMap.Access(archIndex);
|
|
|
|
// Could be NULL if the archetype had flags that match the ignoreFlags.
|
|
if (entry)
|
|
{
|
|
bool worthDeleting = entry->IsWorthDeleting();
|
|
|
|
if (!worthDeleting)
|
|
{
|
|
|
|
int refs = entry->m_ReferenceCount;
|
|
for (int y=0; y<refs; y++)
|
|
{
|
|
if (entry->m_References[y]->ShouldDelete())
|
|
{
|
|
worthDeleting = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// If deleting this entity will save us memory, do it.
|
|
if (worthDeleting)
|
|
{
|
|
// streamDisplayf("ACTUAL DELETE: Entity %p: %s", entity, entity->GetModelName());
|
|
#if STREAMING_DETAILED_STATS
|
|
// TODO: Get the score!
|
|
CStreaming::GetCleanup().MarkDrawHandlerDeleted(entity, -4.0f);
|
|
#endif // STREAMING_DETAILED_STATS
|
|
entity->DeleteDrawable();
|
|
#if __BANK
|
|
g_SceneStreamerMgr.GetStreamingLists().GetActiveEntityList().GetBucketSet().TrackRemoval();
|
|
#endif // __BANK
|
|
#if STREAM_STATS
|
|
s_numRemoved++;
|
|
#endif // STREAM_STATS
|
|
|
|
// Don't process this entity again.
|
|
m_EntitiesToDelete.DeleteFast(x);
|
|
x--;
|
|
entityCount--;
|
|
}
|
|
else
|
|
{
|
|
//streamDisplayf("Not worth deleting: Entity %p: %s. Removing references.", entity, entity->GetModelName());
|
|
// Didn't save anything, so we may as well not delete it.
|
|
for (int x=0; x<objCount; x++)
|
|
{
|
|
if (m_ObjectsToRemove[x] == archIndex)
|
|
{
|
|
leftOverObjects[newIndex++] = archIndex;
|
|
// TODO: Null instead of Delete()?
|
|
m_ObjectsToRemove.Delete(x);
|
|
objCount--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now delete the actual streamables. This must be in the proper order so
|
|
// we handle dependencies correctly.
|
|
#if STREAM_STATS
|
|
s_numDeleted += objCount;
|
|
#endif // STREAM_STATS
|
|
|
|
for (int x=0; x<objCount; x++)
|
|
{
|
|
#if __ASSERT
|
|
strIndex index = m_ObjectsToRemove[x];
|
|
strStreamingModule* module = strStreamingEngine::GetInfo().GetModule( index );
|
|
s32 objIndex = module->GetObjectIndex( index );
|
|
|
|
// streamDisplayf("ACTUAL DELETE: Object %d: %s, refs=%d, deps=%d", m_ObjectsToRemove[x].Get(), strStreamingEngine::GetObjectName(m_ObjectsToRemove[x]), module->GetNumRefs(objIndex),
|
|
// strStreamingEngine::GetInfo().GetStreamingInfoRef(index).GetDependentCount());
|
|
|
|
|
|
if (streamVerifyf(module->GetNumRefs(objIndex) == 0, "Object %d %s (%s) still has a reference count of %d even though we thought it's safe to delete.",
|
|
index.Get(), strStreamingEngine::GetObjectName(index), module->GetModuleName(), module->GetNumRefs(objIndex)))
|
|
#endif // __ASSERT
|
|
{
|
|
strStreamingEngine::GetInfo().RemoveObject(m_ObjectsToRemove[x]);
|
|
}
|
|
}
|
|
|
|
// Put all those back we didn't delete this time - we may delete them next time.
|
|
if (newIndex)
|
|
{
|
|
sysMemCpy(m_ObjectsToRemove.GetElements(), leftOverObjects, sizeof(strIndex) * newIndex);
|
|
}
|
|
|
|
m_ObjectsToRemove.Resize(newIndex);
|
|
}
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
|
|
// --- CStreamingCleanup -------------------------------------------------------------------------------
|
|
|
|
//
|
|
// name: CStreamingCleanup::Init
|
|
// description: Initialise streaming cleanup class
|
|
void CStreamingCleanup::Init()
|
|
{
|
|
m_LastAllocationWasAborted = false;
|
|
m_LastAllocationWasFragmented = false;
|
|
m_flushRequested = false;
|
|
m_flushProcessed = false;
|
|
|
|
m_FlushClips = true;
|
|
m_FlushCollisions = true;
|
|
m_FlushMapStore = true;
|
|
m_FlushPeds = true;
|
|
m_FlushVehicles = true;
|
|
m_FlushObjects = true;
|
|
m_FlushCloth = true;
|
|
m_FlushClouds = true;
|
|
|
|
#if !__64BIT && __BANK
|
|
NOTFINAL_ONLY(InterruptInitialize());
|
|
#endif
|
|
|
|
#if __BANK && SUPPORT_STREAMING_SIM_MODE
|
|
m_enableStreamingSystemNoSim = !PARAM_streamingcleanupsim.Get();
|
|
#endif //
|
|
|
|
#if __BANK
|
|
m_DisableChurnProtection = false;
|
|
m_LastChurnTimestamp = 0;
|
|
m_LastChurnPoint = 0.0f;
|
|
#endif // __BANK
|
|
|
|
#if STREAM_STATS
|
|
s_numRemoved = 0;
|
|
s_numDeleted = 0;
|
|
m_numCleanupCalls = 0;
|
|
m_numValidationAttempts = 0;
|
|
#endif // STREAM_STATS
|
|
|
|
#if STREAMING_DETAILED_STATS
|
|
m_LastDeletedHandlerIndex = 0;
|
|
|
|
for (int x=0; x<MAX_DELETED_CHURN_FRAMES; x++)
|
|
{
|
|
m_LastDeletedHandlers[x].Init(MAX_DELETED_COUNT);
|
|
}
|
|
|
|
sysMemSet(m_ChurnCounts, 0, sizeof(m_ChurnCounts));
|
|
#endif // STREAMING_DETAILED_STATS
|
|
}
|
|
|
|
//
|
|
// name: CStreamingCleanup::Shutdown
|
|
// description: Shutdown streaming cleanup class
|
|
void CStreamingCleanup::Shutdown()
|
|
{
|
|
#if !__64BIT
|
|
NOTFINAL_ONLY(InterruptEnd());
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// name: CStreamingCleanup::Update
|
|
// description: Do a continual cleanup
|
|
void CStreamingCleanup::Update()
|
|
{
|
|
// m_drawableList.DeleteTheLastUsedDrawable();
|
|
|
|
if (m_flushRequested)
|
|
{
|
|
m_flushRequested = false;
|
|
Flush();
|
|
}
|
|
#if STREAM_STATS
|
|
PF_SET(NewRemoved, s_numRemoved);
|
|
PF_SET(NewDeleted, s_numDeleted);
|
|
PF_SET(CleanupCalls, m_numCleanupCalls);
|
|
PF_SET(ValidationAttempts, m_numValidationAttempts);
|
|
|
|
s_numRemoved = 0;
|
|
s_numDeleted = 0;
|
|
m_numCleanupCalls = 0;
|
|
m_numValidationAttempts = 0;
|
|
#endif
|
|
|
|
#if STREAMING_DETAILED_STATS
|
|
m_LastDeletedHandlerIndex++;
|
|
m_LastDeletedHandlerIndex %= MAX_DELETED_CHURN_FRAMES;
|
|
m_LastDeletedHandlers[m_LastDeletedHandlerIndex].Reset();
|
|
#endif // STREAMING_DETAILED_STATS
|
|
|
|
#if !__FINAL && !__64BIT
|
|
if (CControlMgr::GetKeyboard().GetKeyDown(KEY_F, KEYBOARD_MODE_DEBUG_CNTRL_ALT, "Interrupt"))
|
|
{
|
|
InterruptEnd();
|
|
}
|
|
#endif // !__FINAL
|
|
}
|
|
|
|
//
|
|
// name: CStreamingCleanup::CDrawableList::DeleteAllDrawableReferences
|
|
// description: Delete all entity references to modelinfo drawables
|
|
void CStreamingCleanup::DeleteAllDrawableReferences()
|
|
{
|
|
streamAssert(sysThreadType::IsUpdateThread()); // Don't even think about it.
|
|
|
|
const fwLinkList<ActiveEntity>& activeList = g_SceneStreamerMgr.GetStreamingLists().GetActiveList();
|
|
|
|
fwLink<ActiveEntity>* pLastLink = activeList.GetLast()->GetPrevious();
|
|
|
|
while(pLastLink != activeList.GetFirst())
|
|
{
|
|
CEntity* pEntity = static_cast<CEntity*>(pLastLink->item.GetEntity());
|
|
pLastLink = pLastLink->GetPrevious();
|
|
|
|
#if STREAMING_DETAILED_STATS
|
|
CStreaming::GetCleanup().MarkDrawHandlerDeleted(pEntity, -2.0f);
|
|
#endif // STREAMING_DETAILED_STATS
|
|
|
|
pEntity->DeleteDrawable();
|
|
}
|
|
}
|
|
|
|
// Remove all references to a drawable.
|
|
bool CStreamingCleanup::PurgeDrawable(rmcDrawable* pDrawable)
|
|
{
|
|
streamAssert(sysThreadType::IsUpdateThread()); // Don't even think about it.
|
|
|
|
const fwLinkList<ActiveEntity>& activeList = g_SceneStreamerMgr.GetStreamingLists().GetActiveList();
|
|
fwLink<ActiveEntity>* pLastLink = activeList.GetLast()->GetPrevious();
|
|
|
|
while(pLastLink != activeList.GetFirst())
|
|
{
|
|
CEntity* pEntity = static_cast<CEntity*>(pLastLink->item.GetEntity());
|
|
pLastLink = pLastLink->GetPrevious();
|
|
|
|
if (pEntity->GetDrawable() == pDrawable)
|
|
{
|
|
#if STREAMING_DETAILED_STATS
|
|
CStreaming::GetCleanup().MarkDrawHandlerDeleted(pEntity, -3.0f);
|
|
#endif // STREAMING_DETAILED_STATS
|
|
pEntity->DeleteDrawable();
|
|
streamAssertf(!pEntity->GetIsTypeLight(), "The streaming cleanup deleted a light entity (%s) via purge.",
|
|
pEntity->GetModelName());
|
|
|
|
// remove object if
|
|
if(pEntity->GetBaseModelInfo()->GetNumRefs() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// name: CStreamingCleanup::DeleteVehiclesAndPeds
|
|
// description: Delete vehicles and peds to make space
|
|
bool CStreamingCleanup::DeleteVehiclesAndPeds(bool bCutscene)
|
|
{
|
|
static bool bDeleteVehicle=false;
|
|
s32 minVehicles = MIN_NUMBER_STREAMED_CARS;
|
|
s32 minPeds = MIN_NUMBER_STREAMED_PEDS;
|
|
bool bForce = false;
|
|
|
|
if(bCutscene)
|
|
{
|
|
minVehicles = 2;
|
|
minPeds = 2;
|
|
bForce = true;
|
|
}
|
|
|
|
// if last time I deleted a vehicle lets delete a ped this time
|
|
bDeleteVehicle = !bDeleteVehicle;
|
|
if(bDeleteVehicle)
|
|
{
|
|
// if there is a vehicle to delete
|
|
if(gPopStreaming.GetAppropriateLoadedCars().CountMembers() > minVehicles)
|
|
{
|
|
// remove vehicle
|
|
if(gPopStreaming.StreamOneCarOut(bForce))
|
|
return true;
|
|
}
|
|
// let function fall out to deleting peds if no vehicle was found
|
|
}
|
|
// if there is a ped to delete
|
|
if(gPopStreaming.GetAppropriateLoadedPeds().CountMembers() > minPeds)
|
|
{
|
|
// remove ped
|
|
if(gPopStreaming.StreamOnePedOut(bForce))
|
|
return true;
|
|
}
|
|
// if there is a vehicle to delete
|
|
if(gPopStreaming.GetAppropriateLoadedCars().CountMembers() > minVehicles)
|
|
{
|
|
// remove vehicle
|
|
if(gPopStreaming.StreamOneCarOut(bForce))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
bool CStreamingCleanup::SimulateRemovalNew(DeletionMap &deletionMap, StreamingEntry *entry, int &virtualMemoryToFree, int &physicalMemoryToFree)
|
|
{
|
|
/* if (strStreamingEngine::GetInfo().IsObjectRequired(entry->m_Index))
|
|
{
|
|
// Can't.
|
|
return;
|
|
}*/
|
|
|
|
if (entry->m_Parent)
|
|
{
|
|
entry->m_RealDepCount--;
|
|
}
|
|
|
|
if (entry->m_Status == STRINFO_LOADED)
|
|
{
|
|
entry->m_refCount++;
|
|
|
|
// strStreamingInfo &info = strStreamingEngine::GetInfo().GetStreamingInfoRef(entry->m_Index);
|
|
// streamDisplayf("SIMULATE REMOVAL - index %d %s, %d/%d, status=%s", entry->m_Index.Get(), strStreamingEngine::GetObjectName(entry->m_Index),
|
|
// entry->m_refCount, entry->m_RealRefCount, strStreamingInfo::GetFriendlyStatusName(info.GetStatus()));
|
|
}
|
|
else
|
|
{
|
|
//streamDisplayf("Not Loaded - %s", strStreamingEngine::GetObjectName(entry->m_Index));
|
|
//streamAssertf(entry->m_RealRefCount == 0, "%s has a ref count of %d even though status is %s",
|
|
// strStreamingEngine::GetObjectName(entry->m_Index), entry->m_RealRefCount, strStreamingInfo::GetFriendlyStatusName(entry->m_Status));
|
|
}
|
|
|
|
|
|
// There are more references to this object than its actual reference count?
|
|
streamAssertf(entry->m_RealRefCount >= entry->m_refCount, "Mismatch during removal simulation - %s (status is %s) has a ref count of %d, but %d removal simulations have been done already.",
|
|
strStreamingEngine::GetObjectName(entry->m_Index), strStreamingInfo::GetFriendlyStatusName(entry->m_Status),
|
|
entry->m_RealRefCount, entry->m_refCount);
|
|
|
|
// Would we be able to delete this object by now?
|
|
if (entry->m_RealRefCount == entry->m_refCount)
|
|
{
|
|
streamAssert(entry->m_RealDepCount >= 0);
|
|
if (entry->m_RealDepCount == 0)
|
|
{
|
|
// Yes.
|
|
if (entry->m_Status == STRINFO_LOADED)
|
|
{
|
|
// Write this down as an object we can delete.
|
|
// Put it on the deletion map even if it's not loaded yet - we have to make sure it'll be
|
|
// unrequested in case it's on the request list.
|
|
deletionMap.m_ObjectsToRemove.Append() = entry->m_Index;
|
|
|
|
virtualMemoryToFree -= entry->m_virtualSize;
|
|
physicalMemoryToFree -= entry->m_physicalSize;
|
|
|
|
// If we get to delete this entry, we can delete its children as well.
|
|
int count = entry->m_ReferenceCount;
|
|
|
|
for (int x=0; x<count; x++)
|
|
{
|
|
entry->m_IsWorthDeleting |= SimulateRemovalNew(deletionMap, entry->m_References[x], virtualMemoryToFree, physicalMemoryToFree);
|
|
}
|
|
|
|
// if (virtualMemoryToFree || physicalMemoryToFree)
|
|
// {
|
|
// entry->m_IsWorthDeleting = true;
|
|
// }
|
|
|
|
/* if (entry->m_IsWorthDeleting)
|
|
{
|
|
streamDisplayf("%s found worth deleting", strStreamingEngine::GetObjectName(entry->m_Index));
|
|
}*/
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
|
|
bool CStreamingCleanup::PerformRemoval(strIndex index, u32 ignoreFlags, int &virtualMemoryToFree, int &physicalMemoryToFree, strIndex indexNeedingMemory, bool *mustAbort)
|
|
{
|
|
strStreamingModule* streamingModule = strStreamingEngine::GetInfo().GetModule(index);
|
|
strLocalIndex localIndex = streamingModule->GetObjectIndex(index);
|
|
|
|
if (index == indexNeedingMemory)
|
|
{
|
|
*mustAbort = true;
|
|
return false;
|
|
}
|
|
|
|
if (strStreamingEngine::GetInfo().IsObjectReadyToDelete(index, ignoreFlags))
|
|
{
|
|
strStreamingInfo &info = strStreamingEngine::GetInfo().GetStreamingInfoRef(index);
|
|
|
|
//if ((info.GetFlags() & ignoreFlags) == 0)
|
|
{
|
|
if (info.GetStatus() == STRINFO_LOADING || info.GetStatus() == STRINFO_LOADED)
|
|
{
|
|
virtualMemoryToFree += info.ComputeOccupiedVirtualSize(index, false);
|
|
physicalMemoryToFree += info.ComputePhysicalSize(index, false);
|
|
}
|
|
|
|
STR_TELEMETRY_MESSAGE_LOG("(streaming/memory/cleanup)Freeing up object %s (savings: %dKB virt, %dKB phys)", strStreamingEngine::GetObjectName(index),
|
|
virtualMemoryToFree / 1024, physicalMemoryToFree / 1024);
|
|
|
|
strStreamingEngine::GetInfo().RemoveObject(index);
|
|
|
|
// We can start deleting the children.
|
|
strIndex deps[ STREAMING_MAX_DEPENDENCIES ];
|
|
int depCount = streamingModule->GetDependencies( localIndex, deps, STREAMING_MAX_DEPENDENCIES);
|
|
|
|
for (int j = 0; j < depCount; ++j)
|
|
{
|
|
PerformRemoval(deps[j], ignoreFlags, virtualMemoryToFree, physicalMemoryToFree, indexNeedingMemory, mustAbort);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
|
|
StreamingEntry* CStreamingCleanup::AddCandidateNew(DeletionMap &deletionMap, BucketEntry *bucketEntry, strIndex index, u32 ignoreFlags, StreamingEntry *parent, int &virtualMemoryToFree, int &physicalMemoryToFree, strIndex indexNeedingMemory, bool *mustAbort)
|
|
{
|
|
streamAssert(index.IsValid());
|
|
|
|
if (index == indexNeedingMemory)
|
|
{
|
|
// We're trying to free up memory for something that isn't that important. Abort.
|
|
*mustAbort = true;
|
|
strStreamingEngine::GetInfo().RemoveObject(indexNeedingMemory);
|
|
return NULL;
|
|
}
|
|
|
|
// Did we already create an entry for this streamable?
|
|
StreamingEntry* entry = deletionMap.m_StreamableMap.Access(index);
|
|
|
|
if (!entry)
|
|
{
|
|
// No - let's create a new one.
|
|
strStreamingInfo* streamingInfo = strStreamingEngine::GetInfo().GetStreamingInfo( index );
|
|
|
|
// Unless it has one of the ignore flags.
|
|
if ( streamingInfo->IsFlagSet( ignoreFlags ) )
|
|
return NULL;
|
|
|
|
strIndex deps[ STREAMING_MAX_DEPENDENCIES ];
|
|
|
|
entry = &deletionMap.m_StreamableMap.Insert(index).data;
|
|
entry->m_Status = streamingInfo->GetStatus();
|
|
|
|
if (entry->m_Status == STRINFO_LOADED && (!(streamingInfo->GetFlags() & STRFLAG_INTERNAL_DUMMY)))
|
|
{
|
|
entry->m_virtualSize = streamingInfo->ComputeOccupiedVirtualSize(index);
|
|
entry->m_physicalSize = streamingInfo->ComputePhysicalSize(index);
|
|
}
|
|
else
|
|
{
|
|
entry->m_virtualSize = 0;
|
|
entry->m_physicalSize = 0;
|
|
}
|
|
|
|
entry->m_refCount = 0;
|
|
entry->m_Parent = parent;
|
|
entry->m_Index = index;
|
|
|
|
#if ONE_STREAMING_HEAP
|
|
entry->m_physicalSize += entry->m_virtualSize;
|
|
entry->m_virtualSize = 0;
|
|
#endif // ONE_STREAMING_HEAP
|
|
|
|
strStreamingModule* streamingModule = strStreamingEngine::GetInfo().GetModule(index);
|
|
int localIndex = streamingModule->GetObjectIndex(index);
|
|
|
|
entry->m_RealRefCount = (short) streamingModule->GetNumRefs(localIndex);
|
|
entry->m_RealDepCount = (short) streamingInfo->GetDependentCount();
|
|
entry->m_IsWorthDeleting = false;
|
|
|
|
int depCount = streamingModule->GetDependencies( localIndex, deps, STREAMING_MAX_DEPENDENCIES);
|
|
|
|
if (streamVerifyf(deletionMap.m_ReferenceBufferCursor + depCount <= DeletionMap::REF_BUFFER_SIZE, "Too many references! Increase REF_BUFFER_SIZE."))
|
|
{
|
|
StreamingEntry **references = &deletionMap.m_ReferenceBuffer[deletionMap.m_ReferenceBufferCursor];
|
|
deletionMap.m_ReferenceBufferCursor += depCount;
|
|
entry->m_References = references;
|
|
entry->m_ReferenceCount = (char) depCount;
|
|
|
|
for (int j = 0; j < depCount; ++j)
|
|
{
|
|
StreamingEntry *childEntry = AddCandidateNew(deletionMap, NULL, deps[j], ignoreFlags /* TODO: Or 0? */, entry, virtualMemoryToFree, physicalMemoryToFree, indexNeedingMemory, mustAbort);
|
|
|
|
if (childEntry)
|
|
{
|
|
*(references++) = childEntry;
|
|
}
|
|
else
|
|
{
|
|
entry->m_ReferenceCount--;
|
|
}
|
|
}
|
|
|
|
// Short-circuit if we determine that this allocation is not worth it.
|
|
/*if (*mustAbort)
|
|
{
|
|
return NULL;
|
|
}*/
|
|
}
|
|
}
|
|
|
|
// Short-circuit if we determine that this allocation is not worth it.
|
|
if (!(*mustAbort))
|
|
{
|
|
if (bucketEntry)
|
|
{
|
|
deletionMap.m_EntitiesToDelete.Append() = bucketEntry;
|
|
}
|
|
|
|
// If this is an archetype, see what would happen if we were to delete it.
|
|
if (parent == NULL)
|
|
{
|
|
///// streamDisplayf("Simulate removal on %s", strStreamingEngine::GetObjectName(entry->m_Index));
|
|
SimulateRemovalNew(deletionMap, entry, virtualMemoryToFree, physicalMemoryToFree);
|
|
}
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
|
|
|
|
bool CStreamingCleanup::MakeSpaceForResourceFile(const char* filename, const char* ext, s32 version)
|
|
{
|
|
char file[RAGE_MAX_PATH];
|
|
pgRscBuilder::ConstructName(file, RAGE_MAX_PATH, filename, ext);
|
|
const fiDevice* pDevice = fiDevice::GetDevice(file, true);
|
|
|
|
streamAssertf(pDevice, "Could not get device for %s", file);
|
|
|
|
datResourceInfo info;
|
|
if (pDevice)
|
|
{
|
|
const int resVersion = pDevice->GetResourceInfo(file, info);
|
|
if (resVersion == version)
|
|
{
|
|
datResourceMap map;
|
|
pgRscBuilder::GenerateMap(info, map);
|
|
|
|
MakeSpaceForMap(map, strIndex(), false);
|
|
}
|
|
|
|
streamAssertf(resVersion || resVersion == version, "Could not get resource info for file '%s' ", file);
|
|
streamAssertf(!resVersion || resVersion == version, "File '%s' is version %d, expecting %d", file, resVersion, version);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CStreamingCleanup::RecurseResult CStreamingCleanup::RecurseDeleteDependencies(strIndex index, strIndex indexNeedingMemory, u32 ignoreFlags)
|
|
{
|
|
if (index == indexNeedingMemory)
|
|
{
|
|
streamErrorf("Trying to free up memory for %s - not important enough to warrant that.", strStreamingEngine::GetInfo().GetObjectName(index));
|
|
return MUST_ABORT;
|
|
}
|
|
|
|
strStreamingInfo &info = strStreamingEngine::GetInfo().GetStreamingInfoRef(index);
|
|
|
|
if (info.GetFlags() & ignoreFlags)
|
|
{
|
|
return NOTHING_DELETED;
|
|
}
|
|
|
|
if (/*1 ||*/ info.GetStatus() != STRINFO_NOTLOADED && info.GetDependentCount() == 0)
|
|
{
|
|
strStreamingModule* streamingModule = strStreamingEngine::GetInfo().GetModule(index);
|
|
strLocalIndex localIndex = streamingModule->GetObjectIndex(index);
|
|
|
|
if (streamingModule->GetNumRefs(localIndex) == 0)
|
|
{
|
|
////////// streamDisplayf("Removing %s, status is %s", strStreamingEngine::GetInfo().GetObjectName(index),
|
|
/////////// strStreamingInfo::GetFriendlyStatusName(strStreamingEngine::GetInfo().GetStreamingInfoRef(index).GetStatus()));
|
|
|
|
u32 memory = 0;
|
|
if (info.GetStatus() == STRINFO_LOADED)
|
|
{
|
|
memory = info.ComputeOccupiedVirtualSize(index, false) + info.ComputePhysicalSize(index, false);
|
|
}
|
|
|
|
RecurseResult result = (memory > 0) ? FREED_MEMORY : DELETED_DUMMIES;
|
|
|
|
STR_TELEMETRY_MESSAGE_LOG("(streaming/memory/cleanup)Freeing up object %s", strStreamingEngine::GetObjectName(index));
|
|
|
|
strStreamingEngine::GetInfo().RemoveObject(index);
|
|
|
|
strIndex deps[ STREAMING_MAX_DEPENDENCIES ];
|
|
int depCount = streamingModule->GetDependencies(localIndex, deps, STREAMING_MAX_DEPENDENCIES);
|
|
|
|
for (int j = 0; j < depCount; ++j)
|
|
{
|
|
// Is this now available for deletion?
|
|
RecurseResult subResult = RecurseDeleteDependencies(deps[j], indexNeedingMemory, ignoreFlags);
|
|
if (result == MUST_ABORT)
|
|
{
|
|
return subResult;
|
|
}
|
|
|
|
if (subResult == FREED_MEMORY)
|
|
{
|
|
result = FREED_MEMORY;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
///////// streamDisplayf("Ref count %d, dep count %d on %s", streamingModule->GetNumRefs(localIndex),
|
|
//////// info.GetDependentCount(), strStreamingEngine::GetInfo().GetObjectName(index));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//////// streamDisplayf("Cannot delete %s - status=%s, dependents (%d)", strStreamingEngine::GetInfo().GetObjectName(index),
|
|
//////// strStreamingInfo::GetFriendlyStatusName(info.GetStatus()), info.GetDependentCount());
|
|
}
|
|
|
|
return NOTHING_DELETED;
|
|
}
|
|
|
|
|
|
CStreamingCleanup::RecurseResult CStreamingCleanup::DeleteEntityAndDependencies(DeletionMap & /*deletionMap*/, BucketEntry *bucketEntry, CEntity *entity, strIndex indexNeedingMemory, u32 ignoreFlags, CStreamingBucketSet &BANK_ONLY(bucketSet))
|
|
{
|
|
if (!IsSceneStreamingNoSim() && !entity->GetDrawHandlerPtr())
|
|
{
|
|
return NOTHING_DELETED;
|
|
}
|
|
|
|
strStreamingModule* streamingModule = fwArchetypeManager::GetStreamingModule();
|
|
Assertf((entity->GetOwnedBy() != ENTITY_OWNEDBY_STATICBOUNDS), "attempting to delete dummy entity created by static bounds! Crash.");
|
|
|
|
u32 objIndex = entity->GetModelIndex();
|
|
|
|
// Sanity check - we've had problems with invalid objIndices.
|
|
/*
|
|
if (objIndex >= streamingModule->GetCount())
|
|
{
|
|
streamErrorf("Bad object index detected - %d", objIndex);
|
|
streamErrorf("Number of registered archetypes: %d", streamingModule->GetCount());
|
|
streamErrorf("Entity in question: %p, entity index: %d", entity, modelIndex);
|
|
streamErrorf("Model name: %s", entity->GetModelName());
|
|
streamErrorf("Archetype/BaseModelInfo ptr from entity: %p", entity->GetBaseModelInfo());
|
|
streamErrorf("Archetype stream slot: %d", entity->GetBaseModelInfo()->GetStreamSlot());
|
|
streamErrorf("Base model pointer from model ID: %p", CModelInfo::GetBaseModelInfo(modelId));
|
|
streamErrorf("Names of both base models: %s (from entity ptr), %s (from model ID)",
|
|
entity->GetBaseModelInfo()->GetModelName(), CModelInfo::GetBaseModelInfo(modelId)->GetModelName());
|
|
|
|
streamAssertf(false, "Bad model index (%d) - something is broken with the entity and will likely cause more problems. See TTY for more information.", objIndex);
|
|
return NOTHING_DELETED;
|
|
}
|
|
#if __DEV || __BANK
|
|
if (streamingModule == fwArchetypeManager::GetStreamingModule())
|
|
{
|
|
fwArchetype* pModelInfo = fwArchetypeManager::GetArchetype(objIndex);
|
|
|
|
if (pModelInfo == NULL)
|
|
{
|
|
Errorf("Invalid archetype being deleted (%d) - this will crash!", objIndex);
|
|
Vec3V pos = entity->GetTransform().GetPosition();
|
|
Errorf("Type: %d", entity->GetType());
|
|
Errorf("Position: %.2f %.2f %.2f", pos.GetXf(), pos.GetYf(), pos.GetZf());
|
|
Errorf("Entity: %p; dmap: %p; bentry: %p", entity, &deletionMap, bucketEntry);
|
|
Assertf(false, "DELETING INVALID ENTITY - see TTY for details.");
|
|
}
|
|
}
|
|
#endif // __DEV || __BANK
|
|
*/
|
|
strIndex archIndex = streamingModule->GetStreamingIndex(strLocalIndex(objIndex));
|
|
|
|
if (strStreamingEngine::GetInfo().GetStreamingInfoRef(archIndex).GetFlags() & ignoreFlags)
|
|
{
|
|
//////// streamDebugf1("Ignore flags hit (%x/%x) on %s, entity %p, objIndex %d, strIndex %d", strStreamingEngine::GetInfo().GetStreamingInfoRef(archIndex).GetFlags(), ignoreFlags, entity->GetModelName(), entity, objIndex, archIndex.Get());
|
|
return NOTHING_DELETED;
|
|
}
|
|
|
|
// Let's see if we can delete this guy.
|
|
s32 virtMemoryFree = 0, physMemoryFree = 0;
|
|
|
|
bool mustAbort = false;
|
|
|
|
if (IsSceneStreamingNoSim())
|
|
{
|
|
STRVIS_SET_SCORE(bucketEntry->m_Score);
|
|
streamDebugf3("Deleting entity %s, score %f", entity->GetModelName(), bucketEntry->m_Score);
|
|
#if STREAMING_DETAILED_STATS
|
|
MarkDrawHandlerDeleted(entity, bucketEntry->m_Score);
|
|
#endif // STREAMING_DETAILED_STATS
|
|
// Let's be a MAN and delete this thing right away.
|
|
entity->DeleteDrawable();
|
|
|
|
streamAssertf(!entity->GetIsTypeLight(), "The streaming cleanup deleted a light entity (%s). That shouldn't have been in the cleanup bucket in the first place",
|
|
entity->GetModelName());
|
|
|
|
#if __BANK
|
|
bucketSet.TrackDeletion((int) bucketEntry->m_Score);
|
|
if (CSceneStreamerDebug::DrawRequestsOnVmap()) { fwVectorMap::Draw3DBoundingBoxOnVMap(entity->GetBoundBox(), Color_yellow); }
|
|
#endif // __BANK
|
|
|
|
#if STREAMING_TELEMETRY_INTERFACE
|
|
if (strStreamingEngine::IsTelemetryLogEnabled()) {
|
|
Vec3V entityCenter;
|
|
float entityRadius;
|
|
entityRadius = entity->GetBoundCentreAndRadius(RC_VECTOR3(entityCenter));
|
|
|
|
Vec3V camPos = RCC_VEC3V(g_SceneToGBufferPhaseNew->GetCameraPositionForFade());
|
|
|
|
float dist = Dist(entityCenter, camPos).Getf();
|
|
|
|
TELEMETRY_MESSAGE_LOG("(streaming/memory/cleanup)Freeing up object %s, score=%f, dist=%f, radius=%f", strStreamingEngine::GetObjectName(archIndex), bucketEntry->m_Score, dist, entityRadius);
|
|
}
|
|
#endif // STREAMING_TELEMETRY_INTERFACE
|
|
|
|
PerformRemoval(archIndex, ignoreFlags, virtMemoryFree, physMemoryFree, indexNeedingMemory, &mustAbort);
|
|
STRVIS_RESET_SCORE();
|
|
}
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
else
|
|
{
|
|
AddCandidateNew(deletionMap, bucketEntry, archIndex, ignoreFlags, NULL, virtMemoryFree, physMemoryFree, indexNeedingMemory, &mustAbort);
|
|
}
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
|
|
if (mustAbort)
|
|
{
|
|
streamDebugf2("Giving up allocating memory for %s - deemed not important enough. (When trying to free up %s)", strStreamingEngine::GetObjectName(indexNeedingMemory), entity->GetModelName());
|
|
|
|
LogChurnProtectionEvent(*bucketEntry);
|
|
|
|
return MUST_ABORT;
|
|
}
|
|
|
|
return (virtMemoryFree != 0 || physMemoryFree != 0) ? FREED_MEMORY : NOTHING_DELETED;
|
|
}
|
|
|
|
void CStreamingCleanup::LogChurnProtectionEvent(BucketEntry &bucketEntry)
|
|
{
|
|
CEntity *entity = bucketEntry.GetEntity();
|
|
|
|
STR_TELEMETRY_MESSAGE_WARNING("(streaming/memory/cleanup)Giving up on freeing memory - score is %f", bucketEntry.m_Score);
|
|
|
|
streamDebugf1("Aborting allocation for %s (score=%f)", entity->GetModelName(), bucketEntry.m_Score);
|
|
|
|
#if STREAMING_DETAILED_STATS
|
|
MarkDrawHandlerDeleted(entity, bucketEntry.m_Score);
|
|
#endif // STREAMING_DETAILED_STATS
|
|
entity->DeleteDrawable();
|
|
|
|
streamAssertf(!entity->GetIsTypeLight(), "The streaming cleanup deleted a light entity (%s). That shouldn't have been in the cleanup bucket in the first place",
|
|
entity->GetModelName());
|
|
|
|
g_SceneStreamerMgr.GetStreamingLists().MarkCutoffEvent(bucketEntry.m_Score);
|
|
|
|
#if __BANK
|
|
g_SceneStreamerMgr.GetStreamingLists().TrackCutoff(bucketEntry.m_Score);
|
|
u32 timestamp = TIME.GetFrameCount();
|
|
if (m_LastChurnTimestamp == timestamp)
|
|
{
|
|
// Multiple churns in one frame - pick the worst one.
|
|
m_LastChurnPoint = Max(m_LastChurnPoint, bucketEntry.m_Score);
|
|
}
|
|
else
|
|
{
|
|
m_LastChurnTimestamp = timestamp;
|
|
m_LastChurnPoint = bucketEntry.m_Score;
|
|
}
|
|
#endif // __BANK
|
|
}
|
|
|
|
CStreamingCleanup::CleanupResult CStreamingCleanup::FindLowestScoringEntityToDelete(strIndex indexNeedingMemory, u32 ignoreFlags)
|
|
{
|
|
PF_FUNC(FindLowestScoring);
|
|
|
|
// We're not allowed to free up memory for another file on auxiliary threads.
|
|
streamAssert(indexNeedingMemory.IsInvalid() || sysThreadType::IsUpdateThread());
|
|
|
|
CStreamingLists &lists = g_SceneStreamerMgr.GetStreamingLists();
|
|
CStreamingBucketSet &activeList = lists.GetActiveEntityList().GetBucketSet();
|
|
CStreamingBucketSet &neededList = lists.GetNeededEntityList().GetBucketSet();
|
|
|
|
DeletionMap &deletionMap = s_DeletionMap;
|
|
|
|
// If we're starting a new frame, begin from scratch - don't continue where we left off last time.
|
|
if (deletionMap.m_Timestamp != lists.GetTimestamp())
|
|
{
|
|
// The buckets have changed. Reset the map.
|
|
deletionMap.Reset();
|
|
deletionMap.m_Timestamp = lists.GetTimestamp();
|
|
}
|
|
|
|
STR_TELEMETRY_MESSAGE_LOG("(streaming/memory/cleanup)Trying to remove entities to make room for %d (%s), ignoreFlags=%x", indexNeedingMemory.Get(), (indexNeedingMemory.IsValid()) ? strStreamingEngine::GetObjectName(indexNeedingMemory) : "", ignoreFlags);
|
|
|
|
// If this is a required entity, don't use the churn protection - we HAVE to stream it in.
|
|
// Note that IsObjectRequired doesn't check for LOADSCENE, hence the manual check.
|
|
// if (indexNeedingMemory.IsValid() && strStreamingEngine::GetInfo().IsObjectRequired(indexNeedingMemory))
|
|
if (indexNeedingMemory.IsValid() && (strStreamingEngine::GetInfo().GetStreamingInfoRef(indexNeedingMemory).GetFlags() & (ignoreFlags | STRFLAG_LOADSCENE | STRFLAG_DONTDELETE)))
|
|
{
|
|
// We HAVE to get this one through.
|
|
indexNeedingMemory = strIndex();
|
|
}
|
|
|
|
#if __BANK
|
|
// Don't do churn protection if it's disabled.
|
|
if (m_DisableChurnProtection)
|
|
{
|
|
indexNeedingMemory = strIndex();
|
|
}
|
|
#endif // __BANK
|
|
|
|
// Let's get the top contenders from the active and the needed list.
|
|
int actIndex = s_DeletionMap.m_ActiveIndex;
|
|
int needIndex = s_DeletionMap.m_NeededIndex;
|
|
|
|
int actCount = activeList.GetEntryCount();
|
|
int needCount = neededList.GetEntryCount();
|
|
|
|
BucketEntry *actPtr = activeList.GetEntries();
|
|
BucketEntry *needPtr = neededList.GetEntries();
|
|
|
|
// Make sure the SPU is done sorting the lists.
|
|
activeList.WaitForSorting();
|
|
neededList.WaitForSorting();
|
|
|
|
CleanupResult resultState = CLEANUP_FAILED;
|
|
|
|
// Let's keep going until we've exhausted both lists.
|
|
while (actIndex < actCount || needIndex < needCount)
|
|
{
|
|
BucketEntry *activeBucketEntry = NULL;
|
|
BucketEntry *neededBucketEntry = NULL;
|
|
|
|
CEntity *topActiveContender = NULL;
|
|
|
|
// Find the next valid contender in the active list.
|
|
while (actIndex < actCount)
|
|
{
|
|
activeBucketEntry = &actPtr[actIndex];
|
|
topActiveContender = activeBucketEntry->GetValidatedEntity();
|
|
|
|
// Skip empty entries.
|
|
if (topActiveContender)
|
|
{
|
|
break;
|
|
}
|
|
|
|
actIndex++;
|
|
}
|
|
|
|
// Get the next valid contender in the needed list.
|
|
CEntity *topNeededContender = NULL;
|
|
|
|
while (needIndex < needCount)
|
|
{
|
|
neededBucketEntry = &needPtr[needIndex];
|
|
topNeededContender = neededBucketEntry->GetValidatedEntity();
|
|
|
|
// Skip empty entries.
|
|
if (topNeededContender)
|
|
{
|
|
break;
|
|
}
|
|
|
|
needIndex++;
|
|
}
|
|
|
|
// This could happen if there were nothing but invalid entries in both
|
|
// lists. In that case, we don't have anything else to clean up.
|
|
if(!topActiveContender && !topNeededContender)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Let's figure out who's got the lower score. Lower scores gets deleted.
|
|
bool activeTrumpsNeeded;
|
|
|
|
// Nothing in the active list means we'll take the needed contender.
|
|
if (!topActiveContender)
|
|
{
|
|
activeTrumpsNeeded = false;
|
|
}
|
|
else
|
|
{
|
|
// And nothing in the needed list means we'll take the active one.
|
|
if (!topNeededContender)
|
|
{
|
|
activeTrumpsNeeded = true;
|
|
}
|
|
else
|
|
{
|
|
// We have two contenders - let them duke it out.
|
|
activeTrumpsNeeded = (activeBucketEntry->m_Score < neededBucketEntry->m_Score);
|
|
}
|
|
}
|
|
|
|
if (activeTrumpsNeeded)
|
|
{
|
|
actIndex++;
|
|
|
|
// An active element we no longer need. Kill.
|
|
RecurseResult result = DeleteEntityAndDependencies(deletionMap, activeBucketEntry, topActiveContender, indexNeedingMemory, ignoreFlags, activeList);
|
|
|
|
if (result == MUST_ABORT)
|
|
{
|
|
// We have to stop now.
|
|
resultState = CLEANUP_ABORTED;
|
|
break;
|
|
}
|
|
|
|
if (result == FREED_MEMORY)
|
|
{
|
|
// We freed up memory. Let's try allocating the map again.
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
deletionMap.ExecuteDeletion();
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
resultState = CLEANUP_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
needIndex++;
|
|
|
|
// A needed element is not really that needed after all. Remove the request for it.
|
|
strStreamingModule* streamingModule = fwArchetypeManager::GetStreamingModule();
|
|
u32 objIndex = topNeededContender->GetModelIndex();
|
|
|
|
strIndex archIndex = streamingModule->GetStreamingIndex(strLocalIndex(objIndex));
|
|
|
|
if (indexNeedingMemory == archIndex)
|
|
{
|
|
streamWarningf("Request for %s deemed not important enough - removing.", strStreamingEngine::GetInfo().GetObjectName(indexNeedingMemory));
|
|
LogChurnProtectionEvent(*neededBucketEntry);
|
|
strStreamingEngine::GetInfo().RemoveObject(archIndex);
|
|
resultState = CLEANUP_ABORTED;
|
|
break;
|
|
}
|
|
|
|
RecurseResult result = DeleteEntityAndDependencies(deletionMap, neededBucketEntry, topNeededContender, indexNeedingMemory, ignoreFlags, neededList);
|
|
|
|
if (result == MUST_ABORT)
|
|
{
|
|
// We have to stop now.
|
|
resultState = CLEANUP_ABORTED;
|
|
break;
|
|
}
|
|
|
|
if (result == FREED_MEMORY)
|
|
{
|
|
// We freed up memory. Let's try allocating the map again.
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
deletionMap.ExecuteDeletion();
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
resultState = CLEANUP_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
deletionMap.m_NeededIndex = needIndex;
|
|
deletionMap.m_ActiveIndex = actIndex;
|
|
|
|
m_LastAllocationWasAborted = (resultState == CLEANUP_ABORTED);
|
|
|
|
return resultState;
|
|
}
|
|
|
|
int CStreamingCleanup::DeleteUnusedEntities(int maxObjsToDelete, bool ASSERT_ONLY(mustDelete), float scoreCutoff)
|
|
{
|
|
STRVIS_AUTO_CONTEXT(strStreamingVisualize::GARBAGECOLLECT);
|
|
|
|
ASSERT_ONLY(int deletedEntities = 0);
|
|
|
|
// We're not allowed to free up memory for another file on auxiliary threads.
|
|
streamAssert(sysThreadType::IsUpdateThread());
|
|
|
|
CStreamingLists &lists = g_SceneStreamerMgr.GetStreamingLists();
|
|
CStreamingBucketSet &activeList = lists.GetActiveEntityList().GetBucketSet();
|
|
|
|
DeletionMap &deletionMap = s_DeletionMap;
|
|
u32 ignoreFlags = STR_DONTDELETEARCHETYPE_MASK | STRFLAG_LOADSCENE;
|
|
|
|
if (deletionMap.m_Timestamp != lists.GetTimestamp())
|
|
{
|
|
// The buckets have changed. Reset the map.
|
|
deletionMap.Reset();
|
|
deletionMap.m_Timestamp = lists.GetTimestamp();
|
|
}
|
|
|
|
int actIndex = s_DeletionMap.m_ActiveIndex;
|
|
int startIndex = actIndex;
|
|
int actCount = Min(activeList.GetEntryCount(), actIndex + maxObjsToDelete);
|
|
|
|
BucketEntry *actPtr = activeList.GetEntries();
|
|
|
|
activeList.WaitForSorting();
|
|
|
|
OUTPUT_ONLY(float lastScore = -1.0f;)
|
|
|
|
// Note that the list is considered unsorted at this point, so we'll have to go through it in its entirety.
|
|
while (actIndex < actCount)
|
|
{
|
|
BucketEntry *activeBucketEntry = NULL;
|
|
activeBucketEntry = &actPtr[actIndex];
|
|
|
|
OUTPUT_ONLY(lastScore = activeBucketEntry->m_Score;)
|
|
|
|
if (activeBucketEntry->m_Score > scoreCutoff)
|
|
{
|
|
streamAssertf(deletedEntities || !mustDelete, "Unable to delete any entities - the lowest score I found was %f.", activeBucketEntry->m_Score);
|
|
break;
|
|
}
|
|
|
|
if (activeBucketEntry->IsStillValid())
|
|
{
|
|
DeleteEntityAndDependencies(deletionMap, activeBucketEntry, activeBucketEntry->GetEntity(), strIndex(), ignoreFlags, activeList);
|
|
}
|
|
|
|
actIndex++;
|
|
|
|
ASSERT_ONLY(deletedEntities++);
|
|
}
|
|
|
|
if (actIndex != startIndex)
|
|
{
|
|
streamDebugf2("Clean-up done - got to %d out of %d, last score is %f.", actIndex, actCount, lastScore);
|
|
|
|
#if SUPPORT_STREAMING_SIM_MODE
|
|
deletionMap.ExecuteDeletion();
|
|
#endif // SUPPORT_STREAMING_SIM_MODE
|
|
deletionMap.m_ActiveIndex = actIndex;
|
|
}
|
|
|
|
return actIndex - startIndex;
|
|
}
|
|
|
|
bool g_HACK_GlobalSRLSkipGC;
|
|
bool g_HACK_GlobalSRLForceGC;
|
|
|
|
void CStreamingCleanup::RemoveExcessEntities()
|
|
{
|
|
CStreamingLists &lists = g_SceneStreamerMgr.GetStreamingLists();
|
|
CStreamingBucketSet &activeBucketSet = lists.GetActiveEntityList().GetBucketSet();
|
|
|
|
#if RSG_DURANGO || RSG_ORBIS || __WIN32PC
|
|
const bool skipGC = true;
|
|
#else
|
|
const bool skipGC = g_HACK_GlobalSRLSkipGC || (CFocusEntityMgr::GetMgr().IsPlayerPed() && g_ContinuityMgr.GetPlayerVelocity()>20.0f);
|
|
#endif
|
|
int oldCount = activeBucketSet.GetEntryCount();
|
|
bool performCleanup = s_ForceAutoCleanup || (!skipGC && (oldCount >= s_CurrentThreshold));
|
|
bool emergency = false;
|
|
float cutoff = s_AutoCleanupCutoff;
|
|
|
|
// We should perform garbage collection if memory is low.
|
|
size_t virtUsed = strStreamingEngine::GetAllocator().GetVirtualMemoryAvailable()>=100? strStreamingEngine::GetAllocator().GetVirtualMemoryUsed() / (strStreamingEngine::GetAllocator().GetVirtualMemoryAvailable()/100) : 0;
|
|
size_t physUsed = strStreamingEngine::GetAllocator().GetPhysicalMemoryAvailable()>=100? strStreamingEngine::GetAllocator().GetPhysicalMemoryUsed() / (strStreamingEngine::GetAllocator().GetPhysicalMemoryAvailable()/100) : 0;
|
|
(void)virtUsed;(void)physUsed; // keep Durango compiler happy
|
|
|
|
int maxToCleanUp = s_MaxObjsToAutoCleanup;
|
|
|
|
if ( !skipGC && (g_HACK_GlobalSRLForceGC || (strStreamingAllocator::GetSloshState() && (virtUsed >= s_MemoryGarbageCollectionThresholdVirt || physUsed >= s_MemoryGarbageCollectionThresholdPhys))))
|
|
{
|
|
streamDebugf2("Memory threshold hit (%" SIZETFMT "d%% virt, %" SIZETFMT "d%% phys) - triggering garbage collection", virtUsed, physUsed);
|
|
if (!s_GarbageCollectEntitiesOnly)
|
|
{
|
|
strStreamingAllocator::SetSloshState(strStreamingEngine::GetInfo().DeleteNextLeastUsedObject(STRFLAG_LOADSCENE, NULL));
|
|
strPackfileManager::DeleteLRUPackfile();
|
|
}
|
|
else
|
|
{
|
|
strStreamingAllocator::SetSloshState(false);
|
|
}
|
|
|
|
performCleanup = true;
|
|
cutoff = s_GarbageCollectionCutoff;
|
|
maxToCleanUp = 2;
|
|
g_HACK_GlobalSRLForceGC = false;
|
|
}
|
|
|
|
int maxLoadedInfo = fwConfigManager::GetInstance().GetSizeOfPool(ATSTRINGHASH("MaxLoadedInfo",0xF2E37BE0), CONFIGURED_FROM_FILE);
|
|
if (strStreamingEngine::GetInfo().GetLoadedInfoPool().GetUsedCount() > maxLoadedInfo - 100)
|
|
{
|
|
Displayf("Emergency loaded object cleanup");
|
|
performCleanup = true;
|
|
emergency = true;
|
|
cutoff = 6.0f;
|
|
}
|
|
|
|
if (oldCount >= OBJ_INSTANCES_LIMIT)
|
|
{
|
|
CStreaming::GetCleanup().AssertAndDumpEntities();
|
|
performCleanup = true;
|
|
emergency = true;
|
|
cutoff = 6.0f;
|
|
}
|
|
|
|
if (performCleanup)
|
|
{
|
|
sysTimer timer;
|
|
int cleanedObjs = CStreaming::GetCleanup().DeleteUnusedEntities((emergency) ? (OBJ_INSTANCES_LIMIT - s_CurrentThreshold) : maxToCleanUp, emergency, cutoff);
|
|
|
|
if (cleanedObjs)
|
|
{
|
|
streamDebugf2("Performed entity auto-cleanup because current count is %d, cleaned up %d entities, only took %.2fms", oldCount, cleanedObjs, timer.GetMsTime());
|
|
}
|
|
|
|
// Do we have more left that we can delete? If so, do it again next frame.
|
|
s_ForceAutoCleanup = (cleanedObjs == s_MaxObjsToAutoCleanup);
|
|
}
|
|
}
|
|
|
|
|
|
void CStreamingCleanup::AssertAndDumpEntities()
|
|
{
|
|
#if !__NO_OUTPUT
|
|
static bool alreadyDumped;
|
|
|
|
if (!alreadyDumped)
|
|
{
|
|
// Let's not spew the user to death.
|
|
alreadyDumped = true;
|
|
streamDisplayf("List of all active entities:");
|
|
streamDisplayf("Entity\tModel\tPos X\tPos Y\tPos Z\tType\tUnrendered frame count");
|
|
|
|
CStreamingLists &lists = g_SceneStreamerMgr.GetStreamingLists();
|
|
|
|
const fwLinkList<ActiveEntity>& activeLinkedList = lists.GetActiveEntityList().GetDrawableLinkList();
|
|
fwLink<ActiveEntity>* pLink = activeLinkedList.GetFirst()->GetNext();
|
|
|
|
while (pLink != activeLinkedList.GetLast())
|
|
{
|
|
CEntity* entity = static_cast<CEntity*>(pLink->item.GetEntity());
|
|
u32 timeSinceLastVisible = pLink->item.GetTimeSinceLastVisible();
|
|
pLink = pLink->GetNext();
|
|
streamAssert(pLink != NULL);
|
|
PrefetchDC(pLink);
|
|
|
|
Vec3V pos = entity->GetTransform().GetPosition();
|
|
|
|
streamDisplayf("%p\t%s\t%.2f\t%.2f\t%.2f\t%d",
|
|
entity, entity->GetModelName(), pos.GetXf(), pos.GetYf(), pos.GetZf(),
|
|
timeSinceLastVisible);
|
|
}
|
|
|
|
g_SceneStreamerMgr.DumpStreamingInterests();
|
|
}
|
|
#endif // !__NO_OUTPUT
|
|
|
|
streamWarningf("There are too many active entities. A complete list has been dumped to the TTY (it's LONG). Please open a bug and be sure to include the TTY.");
|
|
}
|
|
|
|
|
|
bool CStreamingCleanup::WasLastAllocationAborted() const
|
|
{
|
|
// This function is only intended to be used on the update thread, where allocations can fail
|
|
// due to an object being not important enough to warrant freeing up memory.
|
|
streamAssert(sysThreadType::IsUpdateThread());
|
|
|
|
return m_LastAllocationWasAborted;
|
|
}
|
|
|
|
bool CStreamingCleanup::WasLastAllocationFragmented() const
|
|
{
|
|
// This function is only intended to be used on the update thread, where we allocate
|
|
// resources.
|
|
streamAssert(sysThreadType::IsUpdateThread());
|
|
|
|
return m_LastAllocationWasFragmented;
|
|
}
|
|
|
|
#if RSG_PC
|
|
size_t CStreamingCleanup::GetPhysicalSize(datResourceMap &map)
|
|
{
|
|
size_t physicalSize = 0; //virtualSize + physicalSize;
|
|
|
|
// Vertex/Index buffers are now in virtual memory so counting both as physical.
|
|
// Better to be safe with an over estimation
|
|
for (size_t i=0 /*map.VirtualCount*/; i<(size_t)(map.VirtualCount+map.PhysicalCount); ++i)
|
|
physicalSize += map.Chunks[i].Size;
|
|
|
|
return physicalSize;
|
|
}
|
|
|
|
size_t CStreamingCleanup::GetVirtualSize(datResourceMap &map)
|
|
{
|
|
size_t physicalSize = 0; //virtualSize + physicalSize;
|
|
|
|
// Vertex/Index buffers are now in virtual memory so counting both as physical.
|
|
// Better to be safe with an over estimation
|
|
for (size_t i=0 /*map.VirtualCount*/; i<(size_t)(map.VirtualCount); ++i)
|
|
physicalSize += map.Chunks[i].Size;
|
|
|
|
return physicalSize;
|
|
}
|
|
|
|
bool CStreamingCleanup::HandleOutOfMemory(size_t virtualSize, size_t physicalSize)
|
|
{
|
|
#if USE_RESOURCE_CACHE
|
|
//size_t streamSize = virtualSize + physicalSize;
|
|
s64 iAvailable = grcResourceCache::GetInstance().GetStreamingMemory();
|
|
streamDebugf1("Available Virt %" I64FMT "d Phys %" I64FMT "d VidMem %" I64FMT "d but Requires Virt %" SIZETFMT "u Phys %" SIZETFMT "d",
|
|
strStreamingEngine::GetAllocator().GetVirtualMemoryAvailable(),
|
|
strStreamingEngine::GetAllocator().GetPhysicalMemoryAvailable(),
|
|
iAvailable,
|
|
virtualSize,
|
|
physicalSize);
|
|
|
|
if (virtualSize > strStreamingEngine::GetAllocator().GetVirtualMemoryAvailable())
|
|
{
|
|
// Out of Virtual Memory to stream in resources
|
|
}
|
|
else if (physicalSize > strStreamingEngine::GetAllocator().GetPhysicalMemoryAvailable())
|
|
{
|
|
// Out of Physical Memory to stream in temp resources
|
|
Assertf(0, "Physical memory fragmented - Should only be used for temporary allocation");
|
|
Warningf("Available Virt %" I64FMT "d Phys %" I64FMT "d VidMem %" I64FMT "d but Requires Virt %" SIZETFMT "u Phys %" SIZETFMT "d",
|
|
strStreamingEngine::GetAllocator().GetVirtualMemoryAvailable(),
|
|
strStreamingEngine::GetAllocator().GetPhysicalMemoryAvailable(),
|
|
iAvailable,
|
|
virtualSize,
|
|
physicalSize);
|
|
}
|
|
else if (iAvailable > (s64) physicalSize)
|
|
{
|
|
return true;
|
|
}
|
|
else if (!PARAM_disableExtraMem.Get()) // (iAvailable < (s64)physicalSize)
|
|
{
|
|
s64 iIncreaseAmount = (s64)(Min((u32)physicalSize, (u32)(16*1024*1024)) * 2.0f / grcResourceCache::GetInstance().GetPercentageForStreaming());
|
|
streamDebugf1("Increasing Video Memory %" I64FMT "d Requested %" SIZETFMT "u", iIncreaseAmount, physicalSize);
|
|
iIncreaseAmount += grcResourceCache::GetInstance().GetExtraMemory(MT_Video);
|
|
grcResourceCache::GetInstance().SetExtraMemory(MT_Video, iIncreaseAmount);
|
|
return true;
|
|
}
|
|
#else
|
|
virtualSize;
|
|
physicalSize;
|
|
#endif // USE_RESOURCE_CACHE
|
|
return false;
|
|
}
|
|
#endif // RSG_PC
|
|
|
|
bool CStreamingCleanup::HasSpaceFor(strStreamingAllocator&
|
|
#if __CONSOLE || !USE_RESOURCE_CACHE
|
|
allocator
|
|
#endif // __CONSOLE || !USE_RESOURCE_CACHE
|
|
, size_t virtualSize
|
|
, size_t physicalSize)
|
|
{
|
|
#if USE_RESOURCE_CACHE && RESOURCE_CACHE_MANAGING
|
|
if (grcResourceCache::ManageResources())
|
|
{
|
|
s64 iAvailable = grcResourceCache::GetInstance().GetStreamingMemory();
|
|
if (iAvailable < (s64)(/*virtualSize +*/ physicalSize))
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if __CONSOLE || !USE_RESOURCE_CACHE
|
|
#if ONE_STREAMING_HEAP
|
|
bool enoughMemory = allocator.GetPhysicalMemoryUsed() <= allocator.GetPhysicalMemoryAvailable() - virtualSize - physicalSize;
|
|
#else // ONE_STREAMING_HEAP
|
|
bool enoughMemory = ((!virtualSize) || (allocator.GetVirtualMemoryUsed() <= allocator.GetVirtualMemoryAvailable() - virtualSize)) &&
|
|
((!physicalSize) || (allocator.GetPhysicalMemoryUsed() <= allocator.GetPhysicalMemoryAvailable() - physicalSize));
|
|
#endif // ONE_STREAMING_HEAP
|
|
return enoughMemory;
|
|
#else // !__CONSOLE
|
|
#if ONE_STREAMING_HEAP
|
|
physicalSize += virtualSize;
|
|
#endif // #if ONE_STREAMING_HEAP
|
|
|
|
sysMemAllocator* pAllocator = NULL;
|
|
pAllocator = sysMemAllocator::GetMaster().GetAllocator(MEMTYPE_RESOURCE_VIRTUAL);
|
|
if (pAllocator->GetMemoryAvailable() < virtualSize)
|
|
return false;
|
|
|
|
/* No need has proper safety
|
|
if (pAllocator->GetLargestAvailableBlock() < virtualSize)
|
|
return false;
|
|
*/
|
|
|
|
pAllocator = sysMemAllocator::GetMaster().GetAllocator(MEMTYPE_RESOURCE_PHYSICAL);
|
|
if (pAllocator->GetMemoryAvailable() < physicalSize)
|
|
return false;
|
|
|
|
/* No need has proper safety
|
|
if (pAllocator->GetLargestAvailableBlock() < physicalSize)
|
|
return false;
|
|
*/
|
|
return true;
|
|
#endif // __CONSOLE
|
|
|
|
}
|
|
|
|
ASSERT_ONLY(dev_bool klaasAssertCheck = false);
|
|
bool CStreamingCleanup::MakeSpaceForMap(datResourceMap& map, strIndex index, bool keepMemory)
|
|
{
|
|
PF_FUNC(MakeSpaceForMap);
|
|
|
|
USE_MEMBUCKET(MEMBUCKET_DEFAULT);
|
|
STRVIS_SET_STREAMABLE_NEEDING_MEMORY(index);
|
|
|
|
if (!index.IsInvalid())
|
|
{
|
|
// Reset the flag indicating that an allocation failed.
|
|
// Note that we're not allowed to free up memory for another file on auxiliary threads.
|
|
// streamAssert if somebody tries to.
|
|
streamAssert(sysThreadType::IsUpdateThread());
|
|
m_LastAllocationWasAborted = false;
|
|
}
|
|
|
|
if (sysThreadType::IsUpdateThread())
|
|
{
|
|
m_LastAllocationWasFragmented = false;
|
|
}
|
|
|
|
size_t virtualSize = 0;
|
|
size_t physicalSize = 0;
|
|
|
|
for (int i=0; i<map.VirtualCount; ++i)
|
|
{
|
|
virtualSize += pgRscBuilder::ComputeLeafSize(map.Chunks[i].Size, false);
|
|
}
|
|
|
|
for (int i=map.VirtualCount; i<map.VirtualCount+map.PhysicalCount; ++i)
|
|
{
|
|
physicalSize += pgRscBuilder::ComputeLeafSize(map.Chunks[i].Size, true);
|
|
}
|
|
|
|
#if ONE_STREAMING_HEAP
|
|
physicalSize += virtualSize;
|
|
virtualSize = 0;
|
|
#endif // ONE_STREAMING_HEAP
|
|
|
|
bool deleteFromList = true;
|
|
|
|
#if STREAM_STATS
|
|
m_numCleanupCalls++;
|
|
m_numValidationAttempts++;
|
|
#endif // STREAM_STATS
|
|
|
|
|
|
strStreamingAllocator& allocator = strStreamingEngine::GetAllocator();
|
|
|
|
while (true)
|
|
{
|
|
bool fragmented = false;
|
|
|
|
#if __DEV
|
|
OUTPUT_ONLY(size_t physMemoryUsed = allocator.GetPhysicalMemoryUsed();)
|
|
#if !ONE_STREAMING_HEAP
|
|
OUTPUT_ONLY(size_t virtMemoryUsed = allocator.GetVirtualMemoryUsed();)
|
|
#endif // ONE_STREAMING_HEAP
|
|
#endif // __DEV
|
|
|
|
bool enoughMemory = HasSpaceFor(allocator, virtualSize, physicalSize);
|
|
|
|
if (enoughMemory)
|
|
{
|
|
MEM_USE_USERDATA(~0U);
|
|
fragmented = !pgRscBuilder::AllocateMap(map);
|
|
}
|
|
|
|
if (enoughMemory && !fragmented)
|
|
{
|
|
// We have enough memory, and we are able to allocate the map. Mission accomplished.
|
|
break;
|
|
}
|
|
|
|
streamDebugf2("Need to make room for %" SIZETFMT "dKB/%" SIZETFMT "dKB for strIndex %d", virtualSize / 1024, physicalSize / 1024, index.Get());
|
|
|
|
STR_TELEMETRY_MESSAGE_WARNING("(streaming/memory)Unable to allocate v=%dK,p=%dK memory - fragmented=%d, for %d (%s)", virtualSize / 1024, physicalSize / 1024, fragmented, index.Get(), index.IsValid() ? strStreamingEngine::GetObjectName(index) : "");
|
|
|
|
#if STREAM_STATS
|
|
m_numValidationAttempts++;
|
|
#endif // STREAM_STATS
|
|
|
|
#if __ASSERT
|
|
for (int i = 0; i < map.VirtualCount + map.PhysicalCount; ++i)
|
|
{
|
|
streamAssertf(map.Chunks[i].DestAddr == NULL, "Failed to delete all the memory during cleanup");
|
|
}
|
|
#endif
|
|
|
|
deleteFromList = deleteFromList && strStreamingEngine::GetInfo().DeleteNextLeastUsedObject(STRFLAG_LOADSCENE, NULL);
|
|
if (deleteFromList)
|
|
{
|
|
STR_TELEMETRY_MESSAGE_LOG("(streaming/memory/cleanup)Freed up unused objects, trying again");
|
|
continue;
|
|
}
|
|
|
|
// Start deleting entities until we free up some memory.
|
|
CleanupResult cleanupResult;
|
|
|
|
{
|
|
STRVIS_AUTO_CONTEXT(strStreamingVisualize::MEMORYFREE);
|
|
cleanupResult = FindLowestScoringEntityToDelete(index, STR_DONTDELETE_MASK | STRFLAG_LOADSCENE);
|
|
}
|
|
|
|
// If we freed up memory, or if we decided that this object ain't worth it, abort.
|
|
if (cleanupResult != CLEANUP_FAILED)
|
|
{
|
|
if (cleanupResult == CLEANUP_SUCCESS)
|
|
{
|
|
STR_TELEMETRY_MESSAGE_LOG("(streaming/memory/cleanup)Clean-up successful for %d (%s) - trying again", index.Get(), index.IsValid() ? strStreamingEngine::GetObjectName(index) : "");
|
|
// Don't assume that everything worked out - let's just try again and make sure
|
|
// the AllocateMap() call succeeds. Particularly on 360 with its two heaps,
|
|
// we may still not be able to allocate our map.
|
|
continue;
|
|
}
|
|
|
|
#if USE_RESOURCE_CACHE
|
|
// If we couldn't free up memory in any other way, we may just need
|
|
// to increase our available heap.
|
|
if (HandleOutOfMemory(GetVirtualSize(map), GetPhysicalSize(map)))
|
|
{
|
|
// If that was an option, let's try one more time to allocate memory.
|
|
if (keepMemory && pgRscBuilder::AllocateMap(map))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
#endif // USE_RESOURCE_CACHE
|
|
|
|
// We decided it ain't worth it.
|
|
STR_TELEMETRY_MESSAGE_WARNING("(streaming/memory)Giving up on allocating memory");
|
|
STRVIS_SET_STREAMABLE_NEEDING_MEMORY(strIndex());
|
|
return false;
|
|
}
|
|
|
|
streamDebugf2("Unable to make room by deleting entities, now trying packfiles");
|
|
|
|
// Let's try packfiles again, unless we don't need RscVirt.
|
|
if (!__PPU || virtualSize)
|
|
{
|
|
if (strPackfileManager::DeleteLRUPackfile())
|
|
{
|
|
streamDebugf3("Deleted packfile - let's try again");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Let's delete objects - including archetypes, and things that were added this frame.
|
|
strStreamingEngine::GetInfo().ResetInfoIteratorForDeletingNextLeastUsed();
|
|
strStreamingEngine::GetInfo().ClearLastLoaded();
|
|
if (strStreamingEngine::GetInfo().DeleteNextLeastUsedObject(STRFLAG_LOADSCENE, NULL))
|
|
{
|
|
// Got something - let's try again.
|
|
continue;
|
|
}
|
|
|
|
streamDebugf2("Now trying vehicles and peds");
|
|
|
|
// Try a vehicle or ped then.
|
|
STR_TELEMETRY_MESSAGE_LOG("(streaming/memory/cleanup)Deleting vehicles and peds");
|
|
if (!DeleteVehiclesAndPeds(false))
|
|
{
|
|
STR_TELEMETRY_MESSAGE_LOG("(streaming/memory/cleanup)Deleting vehicles and peds, more severely");
|
|
if (!DeleteVehiclesAndPeds(true))
|
|
{
|
|
STR_TELEMETRY_MESSAGE_WARNING("(streaming/memory/cleanup)Unable to free up any memory");
|
|
streamDebugf2("Unable to free up enough memory (%" SIZETFMT "uk/%" SIZETFMT "uk) for %s", virtualSize / 1024, physicalSize / 1024,
|
|
(index.IsValid()) ? strStreamingEngine::GetInfo().GetObjectName(index) : "[anonymous]");
|
|
|
|
// We're fucked, but let's try next frame to free up HD vehicles.
|
|
CVehicle::SetDisableHDVehicles();
|
|
|
|
|
|
#if __DEV
|
|
size_t physMemUsedNow = allocator.GetPhysicalMemoryUsed();
|
|
if (physMemoryUsed != physMemUsedNow)
|
|
{
|
|
streamWarningf("Physical memory used changed from %" SIZETFMT "uKB to %" SIZETFMT "uKB - either another thread changed it, or memory was freed even though we thought it wasn't.",
|
|
physMemoryUsed / 1024, physMemUsedNow / 1024);
|
|
}
|
|
|
|
#if !ONE_STREAMING_HEAP
|
|
size_t virtMemUsedNow = allocator.GetVirtualMemoryUsed();
|
|
if (virtMemoryUsed != virtMemUsedNow)
|
|
{
|
|
streamWarningf("Virtual memory used changed from %dKB to %dKB - either another thread changed it, or memory was freed even though we thought it wasn't.",
|
|
virtMemoryUsed / 1024, virtMemUsedNow / 1024);
|
|
}
|
|
#endif // !ONE_STREAMING_HEAP
|
|
#endif // __DEV
|
|
|
|
#if USE_RESOURCE_CACHE
|
|
// If we couldn't free up memory in any other way, we may just need
|
|
// to increase our available heap.
|
|
if (HandleOutOfMemory(GetVirtualSize(map), GetPhysicalSize(map)))
|
|
{
|
|
// If that was an option, let's try one more time to allocate memory.
|
|
if (keepMemory && pgRscBuilder::AllocateMap(map))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
#endif // USE_RESOURCE_CACHE
|
|
|
|
#if !__NO_OUTPUT && __DEV
|
|
if (fragmented)
|
|
{
|
|
#if ONE_STREAMING_HEAP
|
|
streamWarningf("Unable to allocate resource due to fragmentation - %dKB free, %" SIZETFMT "uKB needed for %s", int(allocator.GetPhysicalMemoryAvailable() - physMemoryUsed) / 1024, physicalSize / 1024, strStreamingEngine::GetObjectName(index));
|
|
#else // ONE_STREAMING_HEAP
|
|
streamWarningf("Unable to allocate resource due to fragmentation - Virtual %dKB available %dKB free %dKB total, Physical %dKB available %dKB free %dKB total, for %s",
|
|
virtualSize / 1024, allocator.GetVirtualMemoryAvailable() / 1024, virtualSize / 1024,
|
|
physicalSize / 1024, allocator.GetPhysicalMemoryAvailable() / 1024, physicalSize / 1024,
|
|
strStreamingEngine::GetObjectName(index));
|
|
#endif // ONE_STREAMING_HEAP
|
|
}
|
|
#endif // !__NO_OUTPUT && __DEV
|
|
|
|
if (sysThreadType::IsUpdateThread())
|
|
{
|
|
m_LastAllocationWasFragmented = fragmented;
|
|
}
|
|
|
|
#if !__FINAL
|
|
MarkFailedAllocation();
|
|
#endif // !__FINAL
|
|
STRVIS_SET_STREAMABLE_NEEDING_MEMORY(strIndex());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Although we weren't able to free up the entire amount we wanted,
|
|
// it may still be enough to allocate the memory our map needs. Let's
|
|
// try again.
|
|
continue;
|
|
}
|
|
|
|
if (!keepMemory)
|
|
{
|
|
MEM_USE_USERDATA(~0U);
|
|
pgRscBuilder::FreeMemory(map);
|
|
}
|
|
STRVIS_SET_STREAMABLE_NEEDING_MEMORY(strIndex());
|
|
|
|
return true;
|
|
}
|
|
|
|
#if !__FINAL
|
|
void CStreamingCleanup::MarkFailedAllocation()
|
|
{
|
|
u32 timeDiff = TIME.GetFrameCount() - m_LastFailedAlloc;
|
|
|
|
// Don't spew this more than once every minute or so.
|
|
if (timeDiff > 30 * 60)
|
|
{
|
|
streamWarningf("Failed allocation - current memory usage as follows:");
|
|
CStreamingDebug::DumpModuleMemoryComparedToAverage();
|
|
|
|
m_LastFailedAlloc = TIME.GetFrameCount();
|
|
}
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
void CStreamingCleanup::RequestFlush(bool loadScene)
|
|
{
|
|
m_flushRequested = true;
|
|
m_loadSceneAfterFlush = loadScene;
|
|
m_flushProcessed = false;
|
|
}
|
|
|
|
void CStreamingCleanup::RequestFlushNonMission(bool scriptsOnly)
|
|
{
|
|
strStreamingEngine::GetInfo().PurgeRequestList(STR_DONTDELETE_MASK|STRFLAG_FORCE_LOAD|STRFLAG_LOADSCENE);
|
|
strStreamingEngine::GetLoader().Flush();
|
|
|
|
strStreamingInfoManager &im = strStreamingEngine::GetInfo();
|
|
|
|
#if SANITY_CHECK_REQUESTS
|
|
im.SanityCheckRequests();
|
|
#endif
|
|
|
|
bool removedObjectThisCycle;
|
|
strStreamingModule *scriptModule = &g_StreamedScripts;
|
|
|
|
do
|
|
{
|
|
removedObjectThisCycle = false;
|
|
LoadedInfoList::Iterator it(im.GetLoadedInfoList()->GetFirst());
|
|
|
|
while (it.IsValid())
|
|
{
|
|
strIndex index = it.Item()->m_Index;
|
|
it.Next();
|
|
|
|
strStreamingInfo& info = im.GetStreamingInfoRef(index);
|
|
|
|
strStreamingModule* pModule = im.GetModule(index);
|
|
|
|
if (!scriptsOnly || pModule == scriptModule)
|
|
{
|
|
strLocalIndex objIndex = pModule->GetObjectIndex(index);
|
|
int refCount = pModule->GetNumRefs(objIndex);
|
|
int depCount = info.GetDependentCount();
|
|
|
|
if (refCount == 0 && depCount == 0)
|
|
{
|
|
if (!(info.GetFlags() & STRFLAG_MISSION_REQUIRED))
|
|
{
|
|
if (im.RemoveObject(index))
|
|
{
|
|
streamDebugf2("Deleting %s", strStreamingEngine::GetObjectName(index));
|
|
removedObjectThisCycle = true;
|
|
|
|
// TODO: Don't break.
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
streamDebugf1("Can't delete %s", strStreamingEngine::GetObjectName(index));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
streamDebugf2("Not Deleting %s", strStreamingEngine::GetObjectName(index));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
streamDebugf2("Not deleting %s, ref count=%d, dep count=%d", strStreamingEngine::GetObjectName(index), refCount, depCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (removedObjectThisCycle);
|
|
|
|
streamDisplayf("GC Flush complete");
|
|
#if LIVE_STREAMING
|
|
strStreamingEngine::GetLive().UpdateAllDirtyFiles();
|
|
#endif // LIVE_STREAMING
|
|
}
|
|
|
|
void CStreamingCleanup::Flush()
|
|
{
|
|
m_flushProcessed = true;
|
|
|
|
#if NAVMESH_EXPORT
|
|
const bool bCalledFromNavmeshExporter = CNavMeshDataExporter::IsExportingCollision();
|
|
#else
|
|
const bool bCalledFromNavmeshExporter = false;
|
|
#endif
|
|
|
|
#if __BANK
|
|
if(!bCalledFromNavmeshExporter)
|
|
{
|
|
CDebugTextureViewerInterface::Close(true);
|
|
CAnimPlacementEditor::DeactivateBank();
|
|
if (CAnimViewer::m_pBank->IsActive())
|
|
{
|
|
CAnimViewer::m_pBank->ToggleActive();
|
|
}
|
|
CAnimClipEditor::ms_previewClipOnFocusEntity = false;
|
|
CAnimClipEditor::StartPreviewClip();
|
|
}
|
|
|
|
clothManager::SetSceneIsFlushing(true); //for bugstar 2253343, will be removed in the future!
|
|
#endif // __BANK
|
|
|
|
CStreaming::LoadAllRequestedObjects();
|
|
CTexLod::UnbindAllBoundTxds();
|
|
CZonedAssetManager::Reset();
|
|
|
|
gRenderThreadInterface.Flush();
|
|
|
|
if (m_FlushMapStore)
|
|
{
|
|
INSTANCE_STORE.SafeRemoveAll(false);
|
|
}
|
|
|
|
CPlayerInfo* pPlayerInfo = CGameWorld::GetMainPlayerInfo();
|
|
Vector3 playerPos = pPlayerInfo ? pPlayerInfo->GetPos() : CPlayerInfo::ms_cachedMainPlayerPos;
|
|
CGameWorld::ClearExcitingStuffFromArea(playerPos, 4000.0f, true, true, false, false, NULL, true, true);
|
|
|
|
// Vehicles
|
|
if (m_FlushVehicles)
|
|
{
|
|
CTrain::RemoveAllTrains();
|
|
CVehiclePopulation::RemoveAllVehsHard();
|
|
}
|
|
|
|
CDispatchManager::GetInstance().Flush();
|
|
|
|
if (m_FlushPeds)
|
|
{
|
|
// Peds
|
|
{
|
|
CPed::Pool* pool = CPed::GetPool();
|
|
s32 i = pool->GetSize();
|
|
while(i--)
|
|
{
|
|
CPed* pPed = pool->GetSlot(i);
|
|
if(pPed && !pPed->IsPlayer())
|
|
{
|
|
CPedFactory::GetFactory()->DestroyPed(pPed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure we don't try create any scheduled peds after the flush
|
|
CPedPopulation::FlushPedQueues();
|
|
|
|
if (m_FlushObjects)
|
|
{
|
|
// Objects
|
|
{
|
|
CObject::Pool *pool = CObject::GetPool();
|
|
CObject* pObject;
|
|
s32 i = (s32) pool->GetSize();
|
|
|
|
while(i--)
|
|
{
|
|
pObject = pool->GetSlot(i);
|
|
if (pObject)
|
|
{
|
|
if (pObject->GetOwnedBy() != ENTITY_OWNEDBY_SCRIPT)
|
|
{
|
|
CObjectPopulation::ConvertToDummyObject(pObject, true);
|
|
}
|
|
#if !__FINAL
|
|
else
|
|
{
|
|
streamDisplayf("CStreamingCleanup::Flush - ConvertToDummyObject() not called for script object %s", pObject->GetModelName());
|
|
}
|
|
#endif // !__FINAL
|
|
}
|
|
}
|
|
}
|
|
|
|
CPickupManager::RemoveAllPickups(true, true);
|
|
}
|
|
|
|
if (m_FlushCollisions)
|
|
{
|
|
// Flush all collisions.
|
|
g_StaticBoundsStore.RemoveAll();
|
|
}
|
|
|
|
//Vector3 playerPos = pPlayerInfo->GetPos();
|
|
|
|
if (m_FlushPeds)
|
|
{
|
|
CPed * pPlayerPed = FindPlayerPed();
|
|
streamAssert(pPlayerPed);
|
|
if(pPlayerPed)
|
|
{
|
|
CPedFactory::GetFactory()->DestroyPlayerPed( pPlayerPed );
|
|
|
|
gPopStreaming.Shutdown(SHUTDOWN_SESSION);
|
|
CPedVariationPack::FlushSlods();
|
|
}
|
|
}
|
|
|
|
if (m_FlushClouds)
|
|
{
|
|
CLOUDHATMGR->Flush(true);
|
|
}
|
|
|
|
CEntityIterator entityIterator(IterateAllTypes);
|
|
CEntity* pEntity = entityIterator.GetNext();
|
|
while (pEntity) {
|
|
pEntity->DeleteDrawable();
|
|
pEntity = entityIterator.GetNext();
|
|
}
|
|
|
|
if (m_FlushClips)
|
|
{
|
|
#if __BANK
|
|
if(!bCalledFromNavmeshExporter)
|
|
{
|
|
if(CAnimClipEditor::m_pClip)
|
|
{
|
|
if (CAnimClipEditor::m_pClip)
|
|
{
|
|
CAnimClipEditor::m_pClip->Release();
|
|
CAnimClipEditor::m_pClip = NULL;
|
|
}
|
|
}
|
|
}
|
|
#endif // __BANK
|
|
|
|
fwClipSetManager::Shutdown(SHUTDOWN_WITH_MAP_LOADED);
|
|
|
|
//reload the move networks
|
|
fwAnimDirector::ShutdownStatic(SHUTDOWN_WITH_MAP_LOADED);
|
|
}
|
|
|
|
DRAWLISTMGR->ClothCleanup();
|
|
DRAWLISTMGR->CharClothCleanup();
|
|
|
|
if (m_FlushCloth)
|
|
{
|
|
g_ClothStore.ShutdownLevel();
|
|
}
|
|
|
|
CPhysics::GetRopeManager()->Deactivate();
|
|
|
|
g_fwMetaDataStore.RemoveAll();
|
|
|
|
strStreamingEngine::GetInfo().ResetInfoIteratorForDeletingNextLeastUsed();
|
|
while (strStreamingEngine::GetInfo().DeleteNextLeastUsedObject(STR_DONTDELETE_MASK, NULL, false)) {}
|
|
|
|
strStreamingEngine::GetInfo().PurgeRequestList(STR_DONTDELETE_MASK|STRFLAG_FORCE_LOAD|STRFLAG_LOADSCENE);
|
|
|
|
#if __BANK
|
|
strPackfileManager::EvictUnusedImages();
|
|
clothManager::SetSceneIsFlushing(false); //for bugstar 2253343, will be removed in the future!
|
|
#endif // __BANK
|
|
|
|
strStreamingEngine::GetLoader().Flush();
|
|
|
|
strStreamingEngine::GetInfo().FlushLoadedList();
|
|
#if LIVE_STREAMING
|
|
strStreamingEngine::GetLive().UpdateAllDirtyFiles();
|
|
#endif // LIVE_STREAMING
|
|
|
|
if (m_FlushPeds)
|
|
{
|
|
gPopStreaming.Init(INIT_SESSION);
|
|
CPedVariationPack::LoadSlods();
|
|
}
|
|
|
|
if (m_FlushClips)
|
|
{
|
|
fwAnimDirector::InitStatic(INIT_AFTER_MAP_LOADED);
|
|
fwClipSetManager::Init(INIT_AFTER_MAP_LOADED);
|
|
#if __BANK
|
|
if(!bCalledFromNavmeshExporter)
|
|
{
|
|
if(CAnimClipEditor::m_pClip)
|
|
{
|
|
CAnimClipEditor::m_pClip = CAnimClipEditor::m_clipEditAnimSelector.GetSelectedClip();
|
|
if (CAnimClipEditor::m_pClip)
|
|
{
|
|
CAnimClipEditor::m_pClip->AddRef();
|
|
}
|
|
}
|
|
}
|
|
#endif // __BANK
|
|
}
|
|
|
|
if (m_loadSceneAfterFlush)
|
|
g_SceneStreamerMgr.LoadScene(playerPos);
|
|
|
|
if (m_FlushPeds)
|
|
{
|
|
if(!bCalledFromNavmeshExporter)
|
|
{
|
|
const CModelInfoConfig& cfgModel = CGameConfig::Get().GetConfigModelInfo();
|
|
CGameWorld::CreatePlayer(playerPos, atStringHash(cfgModel.m_defaultPlayerName), 200.0f);
|
|
}
|
|
}
|
|
|
|
if (m_FlushClouds)
|
|
{
|
|
CLOUDHATMGR->Flush(false);
|
|
}
|
|
}
|
|
|
|
#if STREAMING_DETAILED_STATS
|
|
void CStreamingCleanup::MarkDrawHandlerDeleted(CEntity *entity, float score)
|
|
{
|
|
// Overrun check.
|
|
if (m_LastDeletedHandlers[m_LastDeletedHandlerIndex].IsFull())
|
|
{
|
|
streamAssertf(false, "Array overflow - increase MAX_DELETED_COUNT. Don't open a bug, just mail the Krehan.");
|
|
return;
|
|
}
|
|
|
|
DeletedDrawableHandler handlerInfo;
|
|
handlerInfo.m_BucketScore = score;
|
|
m_LastDeletedHandlers[m_LastDeletedHandlerIndex].Insert(entity, handlerInfo);
|
|
}
|
|
|
|
void CStreamingCleanup::MarkDrawHandlerCreated(CEntity *entity, float score)
|
|
{
|
|
int mapIndex = m_LastDeletedHandlerIndex;
|
|
int frameCount = 0;
|
|
|
|
for (int x=0; x<MAX_DELETED_CHURN_FRAMES; x++)
|
|
{
|
|
--mapIndex;
|
|
if (mapIndex < 0)
|
|
{
|
|
mapIndex = MAX_DELETED_CHURN_FRAMES - 1;
|
|
}
|
|
|
|
DeletedDrawableHandler *handlerInfo = m_LastDeletedHandlers[mapIndex].Access(entity);
|
|
|
|
if (handlerInfo)
|
|
{
|
|
m_ChurnCounts[frameCount]++;
|
|
|
|
// CHURN! This had just been removed!
|
|
streamDisplayf("CHURN (frame count: %d). Entity %s (%p) was deleted with score %f, recreated with score %f.",
|
|
frameCount, entity->GetModelName(), entity, handlerInfo->m_BucketScore, score);
|
|
break;
|
|
}
|
|
|
|
frameCount++;
|
|
}
|
|
}
|
|
|
|
void CStreamingCleanup::DisplayChurnStats()
|
|
{
|
|
char churnString[192];
|
|
churnString[0] = 0;
|
|
|
|
for (int x=0; x<MAX_DELETED_CHURN_FRAMES; x++)
|
|
{
|
|
char substring[128];
|
|
|
|
formatf(substring, "%d: %d ", x, m_ChurnCounts[x]);
|
|
safecat(churnString, substring);
|
|
}
|
|
|
|
grcDebugDraw::AddDebugOutputEx(false, "Entity draw handler churn: %s", churnString);
|
|
}
|
|
|
|
#endif // STREAMING_DETAILED_STATS
|
|
|
|
#if __BANK
|
|
void CStreamingCleanup::DisplayChurnPoint()
|
|
{
|
|
grcDebugDraw::AddDebugOutputEx(false, "Most recent churn point: %f (age: %d)", m_LastChurnPoint, TIME.GetFrameCount() - m_LastChurnTimestamp);
|
|
}
|
|
|
|
void CStreamingCleanup::AddWidgets(bkGroup &group)
|
|
{
|
|
bkGroup *flushGroup = group.AddGroup("Flush Control");
|
|
|
|
flushGroup->AddToggle("Flush Map Store", &m_FlushMapStore, datCallback(MFA(CStreamingCleanup::UpdateNonClipsFlushControls), this));
|
|
flushGroup->AddToggle("Flush Vehicles", &m_FlushVehicles, datCallback(MFA(CStreamingCleanup::UpdateNonClipsFlushControls), this));
|
|
//flushGroup->AddToggle("Flush Peds", &m_FlushPeds, datCallback(MFA(CStreamingCleanup::UpdateNonClipsFlushControls), this));
|
|
flushGroup->AddToggle("Flush Collisions", &m_FlushCollisions, datCallback(MFA(CStreamingCleanup::UpdateNonClipsFlushControls), this));
|
|
flushGroup->AddToggle("Flush Clips", &m_FlushClips, datCallback(MFA(CStreamingCleanup::UpdateClipsFlushControls), this));
|
|
flushGroup->AddToggle("Flush Objects", &m_FlushObjects, datCallback(MFA(CStreamingCleanup::UpdateNonClipsFlushControls), this));
|
|
flushGroup->AddToggle("Flush Cloth", &m_FlushCloth, datCallback(MFA(CStreamingCleanup::UpdateNonClipsFlushControls), this));
|
|
flushGroup->AddToggle("Flush Clouds", &m_FlushClouds, datCallback(MFA(CStreamingCleanup::UpdateNonClipsFlushControls), this));
|
|
flushGroup->AddButton("Flush Now", datCallback(MFA1(CStreamingCleanup::RequestFlush), this, NULL));
|
|
flushGroup->AddButton("Flush Non-Mission Required Scripts Only", datCallback(MFA1(CStreamingCleanup::RequestFlushNonMission), this, (CallbackData) true));
|
|
flushGroup->AddButton("Flush - Garbage Collect Everything Non-Mission Required", datCallback(MFA1(CStreamingCleanup::RequestFlushNonMission), this, NULL));
|
|
}
|
|
|
|
void CStreamingCleanup::UpdateClipsFlushControls()
|
|
{
|
|
// Flush animations requires flushing all peds.
|
|
if (m_FlushClips)
|
|
{
|
|
m_FlushVehicles = true;
|
|
m_FlushObjects = true;
|
|
m_FlushCloth = true;
|
|
//m_FlushPeds = true;
|
|
}
|
|
}
|
|
|
|
void CStreamingCleanup::UpdateNonClipsFlushControls()
|
|
{
|
|
if (!m_FlushVehicles || !m_FlushCloth || !m_FlushObjects)
|
|
{
|
|
m_FlushClips = false;
|
|
}
|
|
}
|
|
|
|
#endif // __BANK
|