Effect memory pool cleanup

I played a bit with implementing a better memory pool for effects and this
 is what came out.

Major changes:
Allocates in chunks of 10k effects, uses free/used lists to speed up search
 for free and updates of active effects.

Benefits:
 * Unlimited number of effects possible.
 * Faster
   Not (yet) benchmarked, but logic dictates that iterating over MAX_EFFECTS
   is slower than iterating over just the number of active effects.

(Patch as posted to mailinglist.)

git-svn-id: svn+ssh://svn.gna.org/svn/warzone/trunk@6807 4a71c877-e1ca-e34f-864e-861f7616d084
master
Dennis Schridde 2009-03-08 11:45:08 +00:00
parent 4f27904b35
commit d655d548f4
2 changed files with 241 additions and 243 deletions

View File

@ -71,8 +71,6 @@
#include "game.h"
#define MAX_EFFECTS 2500
#define GRAVITON_GRAVITY ((float)-800)
#define EFFECT_X_FLIP 0x1
#define EFFECT_Y_FLIP 0x2
@ -165,19 +163,44 @@
#define MAX_SHOCKWAVE_SIZE 500
/* Our list of all game world effects */
static EFFECT asEffectsList[MAX_EFFECTS];
static EFFECT_STATUS effectStatus[MAX_EFFECTS];
/*! Number of effects in one chunk */
#define EFFECT_CHUNK_SIZE 10000
/*! A memory chunk of effects */
typedef struct _EffectChunk EffectChunk;
struct _EffectChunk {
EFFECT effects[EFFECT_CHUNK_SIZE]; //!< Chunk of effects
EffectChunk *next; //!< Next element in list
};
/*! List containing all allocated effect chunks */
struct {
EffectChunk *first; //!< First element of list, used for iteration
EffectChunk *last; //!< Last element of list, used for appending
} chunkList = {
NULL, NULL
};
/*! Our lists of all game world effects */
struct {
size_t num; //!< Number of effects in this list, used when saving
EFFECT *first; //!< First element of list, used for iteration / finding free effects
EFFECT *last; //!< Last element of list, used for appending
} activeList = { //!< List of all active effects
0, NULL, NULL
}, inactiveList = { //!< List of unused effects
0, NULL, NULL
};
/* Tick counts for updates on a particular interval */
static UDWORD lastUpdateDroids[EFFECT_DROID_DIVISION];
static UDWORD lastUpdateStructures[EFFECT_STRUCTURE_DIVISION];
static UDWORD freeEffect; /* Current next slot to use - cyclic */
static UDWORD numEffects;
static UDWORD activeEffects;
static UDWORD missCount;
static UDWORD skipped,skippedEffects,letThrough;
static UDWORD auxVar; // dirty filthy hack - don't look for what this does.... //FIXME
static UDWORD auxVarSec; // dirty filthy hack - don't look for what this does.... //FIXME
static UDWORD specifiedSize;
@ -233,6 +256,164 @@ static void effectDroidUpdates(void);
static UDWORD effectGetNumFrames(EFFECT *psEffect);
/*!
* Initialise memory between first and last as singly linked list
* \param first First element in memory chunk
* \param last Last element in memory chunk
*/
static void initEffectPool(EFFECT *first, EFFECT *last)
{
for (EFFECT *it = first; it < last; it++)
{
// We do not need a double-linked-list for inactiveeffects, since we always pick from the front:
it->prev = NULL;
it->next = it+1;
}
last->prev = NULL;
last->next = NULL;
}
/*!
* Allocate a new effect from memory pool
* FIXME: Does not deal with out-of-memory conditions (yet)
* \return New, uninitialised effect
*/
static EFFECT *Effect_malloc(void)
{
/* Take the first item in inactiveList */
EFFECT *instance = inactiveList.first;
/* Remove from inactiveList */
inactiveList.first = instance->next; // Singly linked, so do not update prev
instance->next = NULL; // We are the last in activeList now
/* Append to activeList */
instance->prev = activeList.last;
if (instance->prev == NULL) {
activeList.first = instance; // activeList was empty, so fill it
}
else {
activeList.last->next = instance;
}
activeList.last = instance;
/* Adjust counts */
activeList.num++;
inactiveList.num--;
/* Ensure the next search will have something to feed its hunger with */
if (inactiveList.first == NULL)
{
/* Allocate new effect chunk */
EffectChunk *chunk = malloc(sizeof(EffectChunk));
debug(LOG_MEMORY, "%zd effects in use, allocating %d extra", activeList.num, EFFECT_CHUNK_SIZE);
/* Deal with out-of-memory conditions */
if (chunk == NULL) {
debug(LOG_ERROR, "Out of memory");
return NULL; // The next call relies on inactiveList being non-empty, and would thus segfault, so bail out early instead
}
/* Append to list */
chunk->next = NULL; // Last element
chunkList.last->next = chunk; // chunkList is never empty, thus we can rely on last!=NULL
/* Update inactiveList */
inactiveList.num = EFFECT_CHUNK_SIZE;
inactiveList.first = &chunk->effects[0];
inactiveList.last = &chunk->effects[EFFECT_CHUNK_SIZE-1];
/* Initialise list links between inactive effects */
initEffectPool(inactiveList.first, inactiveList.last);
}
return instance;
}
/*!
* Return an effect into memory pool
* \param self Effect to be freed
*/
static void Effect_free(void *self)
{
EFFECT *instance = self;
/* Remove from activeList and fixup endings necessary */
if (instance->prev != NULL) {
instance->prev->next = instance->next;
}
else {
activeList.first = instance->next; // We were the first in activeList
}
if (instance->next != NULL) {
instance->next->prev = instance->prev;
}
else {
activeList.last = instance->prev; // We were the last in activeList
}
/* Append to inactiveList */
instance->prev = NULL; // Singly linked
instance->next = NULL; // Last element
// inactiveList is never empty (guaranteed in Effect_malloc), thus we can rely on last!=NULL
inactiveList.last->next = instance;
inactiveList.last = instance;
/* Adjust counts */
inactiveList.num++;
activeList.num--;
}
/*!
* Initialise effects system
* Cleans up old effects, allocates a first chunk of effects, initialises (in)active lists
*/
void initEffectsSystem(void)
{
/* Clean up old chunks */
EffectChunk *chunk;
for (chunk = chunkList.first; chunk != NULL;)
{
EffectChunk *chunkNext = chunk->next;
free(chunk);
chunk = chunkNext;
}
/* Allocate new chunk */
chunk = malloc(sizeof(EffectChunk));
/* Deal with out-of-memory conditions */
if (chunk == NULL)
return; // FIXME We have no way to report an error here...
/* Create chunkList */
chunkList.first = chunk;
chunkList.last = chunk;
chunk->next = NULL; // Last element
/* activeList is empty at start */
activeList.num = 0;
activeList.first = NULL;
activeList.last = NULL;
/* inactiveList contains all elements */
inactiveList.num = EFFECT_CHUNK_SIZE;
inactiveList.first = &chunk->effects[0];
inactiveList.last = &chunk->effects[EFFECT_CHUNK_SIZE-1];
/* Initialise free-effects pool */
initEffectPool(inactiveList.first, inactiveList.last);
}
static void positionEffect(const EFFECT *psEffect)
{
int rx, rz;
@ -272,76 +453,9 @@ static void killEffect(EFFECT *e)
psTile->tileInfoBits &= ~BITS_ON_FIRE; // clear fire bit
}
}
effectStatus[e-asEffectsList] = ES_INACTIVE;
e->control = (UBYTE) 0;
}
static BOOL essentialEffect(EFFECT_GROUP group, EFFECT_TYPE type)
{
switch(group)
{
case EFFECT_FIRE:
case EFFECT_WAYPOINT:
case EFFECT_DESTRUCTION:
case EFFECT_SAT_LASER:
case EFFECT_STRUCTURE:
return(true);
break;
case EFFECT_EXPLOSION:
if(type == EXPLOSION_TYPE_LAND_LIGHT)
{
return(true);
}
default:
return(false);
}
}
static BOOL utterlyReject( EFFECT_GROUP group )
{
switch(group)
{
case EFFECT_BLOOD:
case EFFECT_DUST_BALL:
case EFFECT_CONSTRUCTION:
return(true);
default:
return(false);
}
}
/** Simply sets the free pointer to the start - actually this isn't necessary
as it will work just fine anyway. This WOULD be necessary were we to change
the system so that it seeks FREE slots rather than the oldest one. This is because
different effects last for different times and the oldest effect may have
just got going (if it was a long effect).
*/
void initEffectsSystem( void )
{
UDWORD i;
EFFECT *psEffect;
/* Set position to first */
freeEffect = 0;
/* None are active */
numEffects = 0;
activeEffects = 0;
missCount=0;
skipped = letThrough = 0;
for(i=0; i<MAX_EFFECTS; i++)
{
/* Get a pointer - just cos our macro requires it, speeds not an issue here */
psEffect = &asEffectsList[i];
/* Clear all the control bits */
psEffect->control = (UBYTE)0;
/* All effects are initially inactive */
effectStatus[i] = ES_INACTIVE;
}
/* Put effect back into pool */
Effect_free(e);
}
void effectSetLandLightSpec(LAND_LIGHT_SPEC spec)
@ -392,96 +506,27 @@ void addMultiEffect(const Vector3i *basePos, Vector3i *scatter, EFFECT_GROUP gro
}
}
UDWORD getNumActiveEffects( void )
{
return(activeEffects);
}
UDWORD getMissCount( void )
{
return(missCount);
}
UDWORD getNumSkippedEffects(void)
{
return(skippedEffects);
}
UDWORD getNumEvenEffects(void)
{
return(letThrough);
}
void addEffect(const Vector3i *pos, EFFECT_GROUP group, EFFECT_TYPE type, bool specified, iIMDShape *imd, bool lit)
{
static unsigned int aeCalls = 0;
UDWORD essentialCount;
UDWORD i;
EFFECT *psEffect = NULL;
aeCalls++;
EFFECT *psEffect = NULL;
if(gamePaused())
{
return;
}
/* Quick optimsation to reject every second non-essential effect if it's off grid */
if(clipXY(pos->x, pos->z) == false)
{
/* If effect is essentail - then let it through */
if(!essentialEffect(group,type) )
{
bool bSmoke = false;
/* Retrieve a new effect from pool */
psEffect = Effect_malloc();
/* Some we can get rid of right away */
if ( utterlyReject( group ) )
{
skipped++;
return;
}
/* Smoke gets culled more than most off grid effects */
if(group == EFFECT_SMOKE)
{
bSmoke = true;
}
/* Others intermittently (50/50 for most and 25/100 for smoke */
if(bSmoke ? (aeCalls & 0x03) : (aeCalls & 0x01) )
{
/* Do one */
skipped++;
return;
}
letThrough++;
}
/* Deal with out-of-memory conditions */
if (psEffect == NULL) {
debug(LOG_ERROR, "Out of memory");
return; // FIXME We have no way to report an error here...
}
for (i = freeEffect, essentialCount = 0; (asEffectsList[i].control & EFFECT_ESSENTIAL)
&& essentialCount < MAX_EFFECTS; i++)
{
/* Check for wrap around */
if (i >= (MAX_EFFECTS-1))
{
/* Go back to the first one */
i = 0;
}
essentialCount++;
missCount++;
}
/* Check the list isn't just full of essential effects */
if (essentialCount >= MAX_EFFECTS)
{
/* All of the effects are essential!?!? */
return;
}
freeEffect = i;
psEffect = &asEffectsList[freeEffect];
/* Reset control bits */
psEffect->control = 0;
/* Store away it's position - into FRACTS */
psEffect->position.x = pos->x;
@ -499,7 +544,6 @@ void addEffect(const Vector3i *pos, EFFECT_GROUP group, EFFECT_TYPE type, bool s
{
psEffect->frameNumber = lit;
}
else
{
/* Starts off on frame zero */
@ -562,9 +606,6 @@ void addEffect(const Vector3i *pos, EFFECT_GROUP group, EFFECT_TYPE type, bool s
break;
}
/* Make the effect active */
effectStatus[freeEffect] = ES_ACTIVE;
/* As of yet, it hasn't bounced (or whatever)... */
if(type!=EXPLOSION_TYPE_LAND_LIGHT)
{
@ -572,43 +613,28 @@ void addEffect(const Vector3i *pos, EFFECT_GROUP group, EFFECT_TYPE type, bool s
}
ASSERT(psEffect->imd != NULL || group == EFFECT_DESTRUCTION || group == EFFECT_FIRE || group == EFFECT_SAT_LASER, "null effect imd");
/* No more slots available? */
if(freeEffect++ >= (MAX_EFFECTS-1))
{
/* Go back to the first one */
freeEffect = 0;
}
}
/* Calls all the update functions for each different currently active effect */
void processEffects(void)
{
unsigned int i;
/* Establish how long the last game frame took */
missCount = 0;
/* Reset counter */
numEffects = 0;
/* Traverse the list */
for (i = 0; i < MAX_EFFECTS; i++)
for (EFFECT *it = activeList.first; it != NULL;)
{
/* Don't bother unless it's active */
if(effectStatus[i] == ES_ACTIVE)
EFFECT *itNext = it->next; // If updateEffect deletes something, we would be screwed...
/* Run updates, effect may be deleted here */
updateEffect(it);
/* Is it on the grid */
if (clipXY(it->position.x, it->position.z))
{
updateEffect(&asEffectsList[i]);
/* One more is active */
numEffects++;
/* Is it on the grid */
if (clipXY(asEffectsList[i].position.x, asEffectsList[i].position.z))
{
/* Add it to the bucket */
bucketAddTypeToList(RENDER_EFFECT,&asEffectsList[i]);
}
/* Add it to the bucket */
bucketAddTypeToList(RENDER_EFFECT, it);
}
it = itNext;
}
/* Add any droid effects */
@ -616,9 +642,6 @@ void processEffects(void)
/* Add any structure effects */
effectStructureUpdates();
activeEffects = numEffects;
skippedEffects = skipped;
}
@ -2552,8 +2575,6 @@ static const char FXData_file_identifier[] = "FXData";
/** This will save out the effects data */
bool writeFXData(const char* fileName)
{
unsigned int count, i;
if (!tagOpenWrite(FXData_tag_definition, fileName))
{
ASSERT(false, "writeFXData: error while opening file (%s)", fileName);
@ -2562,45 +2583,30 @@ bool writeFXData(const char* fileName)
tagWriteString(0x01, FXData_file_identifier);
// Count all the active EFFECTs
for (i = 0, count = 0; i < MAX_EFFECTS; ++i)
// Enter effects group and dump all active EFFECTs
tagWriteEnter(0x02, activeList.num);
for (EFFECT *it = activeList.first; it != NULL; it = it->next)
{
if(effectStatus[i] == ES_ACTIVE)
{
++count;
}
}
tagWrite(0x01, it->control);
tagWrite(0x02, it->group);
tagWrite(0x03, it->type);
tagWrite(0x04, it->frameNumber);
tagWrite(0x05, it->size);
tagWrite(0x06, it->baseScale);
tagWrite(0x07, it->specific);
// Enter effects group and dump all EFFECTs
tagWriteEnter(0x02, count);
for (i = 0; i < MAX_EFFECTS; ++i)
{
const EFFECT* curEffect = &asEffectsList[i];
tagWritefv (0x08, 3, &it->position.x);
tagWritefv (0x09, 3, &it->velocity.x);
tagWrites32v (0x0A, 3, &it->rotation.x);
tagWrites32v (0x0B, 3, &it->spin.x);
// Don't save inactive effects
if (effectStatus[i] != ES_ACTIVE)
continue;
tagWrite(0x0C, it->birthTime);
tagWrite(0x0D, it->lastFrame);
tagWrite(0x0E, it->frameDelay);
tagWrite(0x0F, it->lifeSpan);
tagWrite(0x10, it->radius);
tagWrite(0x01, curEffect->control);
tagWrite(0x02, curEffect->group);
tagWrite(0x03, curEffect->type);
tagWrite(0x04, curEffect->frameNumber);
tagWrite(0x05, curEffect->size);
tagWrite(0x06, curEffect->baseScale);
tagWrite(0x07, curEffect->specific);
tagWritefv (0x08, 3, &curEffect->position.x);
tagWritefv (0x09, 3, &curEffect->velocity.x);
tagWrites32v (0x0A, 3, &curEffect->rotation.x);
tagWrites32v (0x0B, 3, &curEffect->spin.x);
tagWrite(0x0C, curEffect->birthTime);
tagWrite(0x0D, curEffect->lastFrame);
tagWrite(0x0E, curEffect->frameDelay);
tagWrite(0x0F, curEffect->lifeSpan);
tagWrite(0x10, curEffect->radius);
tagWriteString(0x11, resGetNamefromData("IMD", curEffect->imd));
tagWriteString(0x11, resGetNamefromData("IMD", it->imd));
// Move on to reading the next effect group
tagWriteNext();
@ -2642,10 +2648,14 @@ bool readFXData(const char* fileName)
count = tagReadEnter(0x02);
for(i = 0; i < count; ++i)
{
EFFECT* curEffect = &asEffectsList[i];
char imd_name[PATH_MAX];
EFFECT *curEffect = Effect_malloc();
ASSERT(i < MAX_EFFECTS, "readFXData: more effects in this file than our array can contain");
/* Deal with out-of-memory conditions */
if (curEffect == NULL) {
debug(LOG_ERROR, "Out of memory");
return false;
}
curEffect->control = tagRead(0x01);
curEffect->group = tagRead(0x02);
@ -2676,17 +2686,12 @@ bool readFXData(const char* fileName)
curEffect->imd = NULL;
}
effectStatus[i] = ES_ACTIVE;
// Move on to reading the next effect group
tagReadNext();
}
// Leave the effects group again...
tagReadLeave(0x02);
/* Ensure free effects kept up to date */
freeEffect = i;
// Close the file
tagClose();

View File

@ -113,14 +113,6 @@ typedef enum
} EFFECT_TYPE;
/* Is the slot currently being used and is it active? */
typedef enum
{
ES_INACTIVE,
ES_ACTIVE
} EFFECT_STATUS;
typedef enum
{
LL_MIDDLE,
@ -149,6 +141,7 @@ struct _effect_def
uint16_t lifeSpan; // what is it's life expectancy?
uint16_t radius; // Used for area effects
iIMDShape *imd; // pointer to the imd the effect uses.
EFFECT *prev, *next; // Previous and next element in linked list
};