Files
GTASource/game/script/script_itemsets.cpp

440 lines
13 KiB
C++
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
//
// script/script_itemsets.cpp
//
// Copyright (C) 1999-2011 Rockstar Games. All Rights Reserved.
//
#include "script_itemsets.h"
#include "script.h" // CTheScripts
#include "script_channel.h" // scriptVerifyf
static const int kDefaultMaxItemSets = 9;
FW_INSTANTIATE_CLASS_POOL(CItemSet, kDefaultMaxItemSets, atHashString("ItemSet",0x6db663ab));
//-----------------------------------------------------------------------------
CItemSetManager* CItemSetManager::sm_Instance = NULL;
void CItemSetManager::InitClass(int bufferSizeRefs)
{
CItemSet::InitPool(MEMBUCKET_GAMEPLAY); // Not sure if this is the most appropriate bucket (or location of the call, for that matter). /FF
Assert(!sm_Instance);
sm_Instance = rage_new CItemSetManager(bufferSizeRefs);
}
void CItemSetManager::ShutdownClass()
{
Assert(sm_Instance); // Supposed to be matched with InitClass().
Assert(CItemSet::GetPool()->GetNoOfUsedSpaces() == 0);
delete sm_Instance;
sm_Instance = NULL;
CItemSet::ShutdownPool();
}
void CItemSetManager::DefragAll(bool removeUnusedElements)
{
// Keep a pointer to the first part of the buffer that hasn't
// been assigned yet.
CItemSetRef* freeBuffPtr = m_Buffer;
// Loop over the sets that are using the buffer, which should be in
// rising order of allocated pointers.
const int numSets = m_ItemSetsSortedByAlloc.GetCount();
for(int i = 0; i < numSets; i++)
{
CItemSet& set = *m_ItemSetsSortedByAlloc[i];
// Check to see that it's not already starting right where
// the previous set ends, in which case we don't have to move it.
if(set.m_EntityArray != freeBuffPtr)
{
// Copy the array elements to the new location. Note that
// overlap here shouldn't be a problem, as we know that we
// are always copying to an earlier address in memory, and
// we do it from the beginning of the array towards the end.
const int setSize = set.m_NumEntities;
Assert(freeBuffPtr < set.m_EntityArray);
for(int j = 0; j < setSize; j++)
{
freeBuffPtr[j] = set.m_EntityArray[j];
// Make sure we don't leave unnecessary object references,
// as tracking these also uses memory.
set.m_EntityArray[j].Reset(NULL);
}
// Store the pointer to the new address.
set.m_EntityArray = freeBuffPtr;
}
if(removeUnusedElements)
{
// In this case, we shrink each set's current allocation down to what's
// currently used, meaning that it will have to reallocate if something
// needs to be added.
freeBuffPtr += set.m_NumEntities;
set.m_MaxEntities = set.m_NumEntities;
}
else
{
// In this case, we leave any unused space for each set intact,
// just eliminating space between the set allocations.
freeBuffPtr += set.m_MaxEntities;
}
}
}
bool CItemSetManager::Allocate(CItemSet& set, int numRefs, bool allowDefrag)
{
// We don't currently support reallocating a set that already has something in it,
// user must free first.
Assert(!set.m_EntityArray);
Assert(!set.m_NumEntities);
Assert(!set.m_MaxEntities);
// Keep track of the start of a range of unallocated buffer space.
CItemSetRef* freeRangeStartPtr = m_Buffer;
// Also keep track of how much total unused space we have, in case
// we need to defragment.
int freeRangeSum = 0;
// Loop over the sets, and then one step further to include space after
// the last set.
const int numSets = m_ItemSetsSortedByAlloc.GetCount();
int foundSetIndex = -1;
for(int i = 0; i < numSets + 1; i++)
{
CItemSetRef* nextFreeRangeStartPtr = NULL;
int unusedSpace = 0;
CItemSetRef* freeRangeEndPtr;
if(i < numSets)
{
// Random verification check: the sets should be ordered based on the pointer into the buffer.
Assert(i == numSets - 1 || m_ItemSetsSortedByAlloc[i + 1]->m_EntityArray >= m_ItemSetsSortedByAlloc[i]->m_EntityArray);
// The current range of free addresses ends where this set begins.
CItemSet* rangeSet = m_ItemSetsSortedByAlloc[i];
freeRangeEndPtr = rangeSet->m_EntityArray;
// The user set shouldn't be in here, since we just verified that
// it has been freed.
Assert(rangeSet != &set);
// The next free range starts where this set ends.
nextFreeRangeStartPtr = freeRangeEndPtr + rangeSet->m_MaxEntities;
// Get the unused space within the set.
unusedSpace = rangeSet->m_MaxEntities - rangeSet->m_NumEntities;
}
else
{
// For this last iteration, the current range of free addresses ends where the buffer ends.
freeRangeEndPtr = m_Buffer + m_BufferSizeRefs;
}
// Compute the free space, and see if it's large enough.
const int freeRangeSize = ptrdiff_t_to_int(freeRangeEndPtr - freeRangeStartPtr);
if(freeRangeSize >= numRefs)
{
// Break out of the loop, found an acceptable range.
// Note: we could be less greedy here and use some heuristic to pick
// the range that is the best fit, somehow,
set.m_EntityArray = freeRangeStartPtr;
set.m_MaxEntities = numRefs;
foundSetIndex = i;
break;
}
// Advance to the next range of free space.
freeRangeStartPtr = nextFreeRangeStartPtr;
// Sum up how much space we've got, for defragmentation purposes.
// We include the unused space within each set here, because we use the
// removeUnusedElements parameter for the DefragAll() call.
freeRangeSum += freeRangeSize;
freeRangeSum += unusedSpace;
}
if(foundSetIndex >= 0)
{
// Add an element to the array, to make space for the new one. Note that we should
// have sized this array based on the pool of CItemSets, so if it fills up, it may
// be worth investigating how that can be possible.
m_ItemSetsSortedByAlloc.Append();
// Now, move all the other sets and insert the new one to preserve the order.
for(int i = numSets; i > foundSetIndex; i--)
{
m_ItemSetsSortedByAlloc[i] = m_ItemSetsSortedByAlloc[i - 1];
}
m_ItemSetsSortedByAlloc[foundSetIndex] = &set;
// Success!
return true;
}
// If allowed to defrag, check to see if we would have enough space after defragmentation.
if(allowDefrag && freeRangeSum >= numRefs)
{
// Note: we use the removeUnusedElements option here, because if we are full, it
// may be a good thing to reclaim unused space in some sets that may be hanging around
// for a long time without changing, even though it means that we may have to reallocate
// others soon again.
DefragAll(true);
// Perform the allocation again (disallowing defragmentation this time). This is expected
// to succeed, because we already determined that there should be enough space.
if(Verifyf(Allocate(set, numRefs, false), "Unexpected defragmentation failure."))
{
// Success!
return true;
}
}
// Not enough space.
return false;
}
void CItemSetManager::Free(CItemSet& set)
{
// Freeing memory for a set involves finding it in the
// set array, resetting the references to set members,
// clearing out the pointers/counts, and removing it
// from the set array.
// Note: we could use binary search here, based on the
// pointer into the buffer, but it's not clear that it would
// be faster because we would have to access memory within each set.
const int numSets = m_ItemSetsSortedByAlloc.GetCount();
for(int i = 0; i < numSets; i++)
{
if(m_ItemSetsSortedByAlloc[i] == &set)
{
const int numUsed = set.m_NumEntities;
CItemSetRef* ptr = set.m_EntityArray;
for(int j = 0; j < numUsed; j++)
{
ptr[j].Reset(NULL);
}
set.m_EntityArray = NULL;
set.m_MaxEntities = 0;
set.m_NumEntities = 0;
// Note: can't be DeleteFast() since we want to maintain the order.
m_ItemSetsSortedByAlloc.Delete(i);
return;
}
}
}
CItemSetManager::CItemSetManager(int bufferSizeRefs)
{
// Allocate the buffer.
m_Buffer = rage_new CItemSetRef[bufferSizeRefs];
m_BufferSizeRefs = bufferSizeRefs;
// Resize the array of set pointers to match the pool.
// Shouldn't need any more than that, unless there is some other source of objects.
const int maxPossibleItemSets = CItemSet::GetPool()->GetSize();
m_ItemSetsSortedByAlloc.Reserve(maxPossibleItemSets);
}
CItemSetManager::~CItemSetManager()
{
// Any sets that use space in the manager need to be destroyed
// before we destroy the manager, or bad things will happen.
Assert(!m_ItemSetsSortedByAlloc.GetCount());
delete []m_Buffer;
}
//-----------------------------------------------------------------------------
INSTANTIATE_RTTI_CLASS(CItemSet,0x129A1749);
CItemSet::CItemSet(bool autoClean)
: m_EntityArray(NULL)
, m_MaxEntities(0)
, m_NumEntities(0)
, m_AutoClean(autoClean)
{
}
CItemSet::~CItemSet()
{
if(m_EntityArray)
{
CItemSetManager::GetInstance().Free(*this);
}
}
bool CItemSet::AddEntityToSet(fwExtensibleBase& rNewEntity)
{
// TODO: Implement type restriction?
#if 0
if ( (m_iTypeRestriction != -1)
&& (rNewEntity->GetEntityClassID() != m_iTypeRestriction)
)
{
sagCoreScript::SCErrorf("Cannot add object of type %s to set restricted to type %s", GOHGetEntityTypeName(rNewEntity->GetEntityClassID()), GOHGetEntityTypeName(m_iTypeRestriction));
return false;
}
#endif
if(!scriptVerifyf(!IsEntityInSet(rNewEntity), "%s:Entity already in set - cannot add multiple times", CTheScripts::GetCurrentScriptNameAndProgramCounter()))
{
return false;
}
if(!scriptVerifyf(m_NumEntities < kMaxRefsPerSet, "Limit of number of references in a CItemSet reached, increase kMaxRefsPerSet (%d).", kMaxRefsPerSet))
{
return false;
}
// Check to see if we need to allocate more memory because we've reached the
// limit of what we allocated.
const int numBefore = m_NumEntities;
if(numBefore >= m_MaxEntities)
{
Assert(m_NumEntities == m_MaxEntities);
// TODO: Think more about how to grow here, maybe. Perhaps should be relative to
// the current size instead?
const int kGrowSize = 16;
int newMaxEntities = Min(m_MaxEntities + kGrowSize, kMaxRefsPerSet);
// We could try to resize the current allocated range, which would
// add some complexity and risk, or allocate a new range before we free
// the old one, which would require space for it to be in memory twice.
// But to keep things simple for now, we make a temporary copy of the
// array contents first, then free the array, allocate a new one, and
// restore from the copy.
// Create the copy.
fwExtensibleBase* tempPtrs[kMaxRefsPerSet];
Assert(numBefore <= kMaxRefsPerSet);
for(int i = 0; i < numBefore; i++)
{
tempPtrs[i] = m_EntityArray[i];
}
// Free old space.
CItemSetManager& mgr = CItemSetManager::GetInstance();
mgr.Free(*this);
// Allocate new space.
if(!Verifyf(mgr.Allocate(*this, newMaxEntities),
"Failed to allocate set space for %d objects. Total size is %d, use PoolSize \"ItemSetBuffer\" in 'gameconfig.xml' to override.",
newMaxEntities, mgr.GetBufferSize()))
{
// Note that if we get here, we've actually lost the current contents of the array,
// may want to do something about that.
return false;
}
// Restore the copy.
Assert(m_MaxEntities > numBefore);
for(int i = 0; i < numBefore; i++)
{
m_EntityArray[i] = tempPtrs[i];
}
m_NumEntities = numBefore;
}
// If we get here, there should be space in the array.
Assert(m_NumEntities < m_MaxEntities);
m_EntityArray[m_NumEntities++] = &rNewEntity;
// Success!
return true;
}
bool CItemSet::RemoveEntityFromSet(fwExtensibleBase& rEntity)
{
// Find out if and where in the set this object is.
int index = FindInSet(rEntity);
if(index >= 0)
{
// Perform an order-changing delete by moving the
// last element to where the removed object was.
const int cnt = m_NumEntities;
const int lastInArray = cnt - 1;
if(index != lastInArray)
{
m_EntityArray[index] = m_EntityArray[lastInArray];
}
m_EntityArray[lastInArray].Reset(NULL);
m_NumEntities = lastInArray;
return true;
}
return false;
}
void CItemSet::Clean()
{
// Here, we loop from the end of the array and clean out
// any references to deleted objects. Note that the order
// in the array may change here.
int index = m_NumEntities - 1;
int lastInArray = index;
while(index >= 0)
{
if(!m_EntityArray[index])
{
if(index != lastInArray)
{
m_EntityArray[index] = m_EntityArray[lastInArray];
}
m_EntityArray[lastInArray].Reset(NULL);
lastInArray--;
}
index--;
}
// Set the new size (same as it was if nothing got cleaned out).
m_NumEntities = lastInArray + 1;
Assert(m_NumEntities <= m_MaxEntities);
}
int CItemSet::FindInSet(const fwExtensibleBase& obj) const
{
// Note: we could try to keep the set ordered and perform a binary search.
// This may be a performance improvement if the set is large and doesn't change
// often.
const int cnt = m_NumEntities;
for(int i = 0; i < cnt; i++)
{
if(m_EntityArray[i] == &obj)
{
return i;
}
}
return -1;
}
//-----------------------------------------------------------------------------
// End of file script/script_itemsets.cpp