440 lines
13 KiB
C++
440 lines
13 KiB
C++
//
|
|
// 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
|