- refactored P_CollectConnectedGroups to avoid frequent heap allocations for the common cases

* the temporary checking arrays are now static
 * the array that gets the returned values only starts allocating memory when the third touched sector group is found. The most common cases (no touched portal and one touched portal) can be handled without accessing the heap.

- did some streamlining of AActor::LinkToSector:

 * there's only now version of this function that can handle everything
 * moved the FIXMAPTHINGPOS stuff into a separate function.
 * removed LinkToWorldForMapThing and put all special handling this function did into P_PointInSectorBuggy.
This commit is contained in:
Christoph Oelckers 2016-02-16 12:51:10 +01:00
parent b9037ef3ee
commit 22e8678903
7 changed files with 294 additions and 230 deletions

View File

@ -44,6 +44,7 @@
struct subsector_t;
class PClassAmmo;
struct FBlockNode;
struct FPortalGroupArray;
//
// NOTES: AActor
@ -1131,11 +1132,10 @@ private:
friend class FActorIterator;
friend bool P_IsTIDUsed(int tid);
sector_t *LinkToWorldForMapThing ();
bool FixMapthingPos();
public:
void LinkToWorld (bool buggy=false);
void LinkToWorld (sector_t *sector);
void LinkToWorld (bool spawningmapthing=false, FPortalGroupArray *groups = NULL, sector_t *sector = NULL);
void UnlinkFromWorld ();
void AdjustFloorClip ();
void SetOrigin (fixed_t x, fixed_t y, fixed_t z, bool moving = false);

View File

@ -10,12 +10,13 @@ struct FBlockNode
{
AActor *Me; // actor this node references
int BlockIndex; // index into blocklinks for the block this node is in
int Group; // portal group this link belongs to (can be different than the actor's own group
FBlockNode **PrevActor; // previous actor in this block
FBlockNode *NextActor; // next actor in this block
FBlockNode **PrevBlock; // previous block this actor is in
FBlockNode *NextBlock; // next block this actor is in
static FBlockNode *Create (AActor *who, int x, int y);
static FBlockNode *Create (AActor *who, int x, int y, int group = -1);
void Release ();
static FBlockNode *FreeBlocks;

View File

@ -4,41 +4,58 @@
//============================================================================
//
// Data structure which collects all portals being touched by an actor
// when checking its position.
// This is a dynamic array which holds its first MAX_STATIC entries in normal
// variables to avoid constant allocations which this would otherwise
// require.
//
// When collecting touched portal groups the normal cases are either
// no portals == one group or
// two portals = two groups
//
// Anything with more can happen but far less infrequently, so this
// organization helps avoiding the overhead from heap allocations
// in the vast majority of situations.
//
//============================================================================
struct FPortalGroupTable
struct FPortalGroupArray
{
TArray<DWORD> data;
TArray<int> touchingGroups;
void setSize(int num)
enum
{
data.Resize((num + 31) / 32);
memset(&data[0], 0, data.Size()*sizeof(DWORD));
MAX_STATIC = 2
};
FPortalGroupArray()
{
varused = 0;
}
void clear()
void Clear()
{
data.Clear();
touchingGroups.Clear();
varused = 0;
}
void setBit(int group)
void Add(DWORD num)
{
if (!getBit(group))
{
data[group >> 5] |= (1 << (group & 31));
touchingGroups.Push(group);
}
if (varused < MAX_STATIC) entry[varused++] = num;
else data.Push(num);
}
int getBit(int group)
unsigned Size()
{
return data[group >> 5] & (1 << (group & 31));
return varused + data.Size();
}
DWORD operator[](unsigned index)
{
return index < MAX_STATIC ? entry[index] : data[index - MAX_STATIC];
}
private:
DWORD entry[MAX_STATIC];
unsigned varused;
TArray<DWORD> data;
};
@ -78,7 +95,7 @@ struct FCheckPosition
bool DoRipping;
TMap<AActor*, bool> LastRipped;
FPortalGroupTable Groups;
FPortalGroupArray Groups;
int PushTime;
FCheckPosition(bool rip=false)

View File

@ -46,6 +46,8 @@
#include "po_man.h"
static AActor *RoughBlockCheck (AActor *mo, int index, void *);
static int R_PointOnSideSlow(fixed_t x, fixed_t y, node_t *node);
sector_t *P_PointInSectorBuggy(fixed_t x, fixed_t y);
//==========================================================================
@ -315,39 +317,118 @@ void AActor::UnlinkFromWorld ()
}
//==========================================================================
//
// If the thing is exactly on a line, move it into the sector
// slightly in order to resolve clipping issues in the renderer.
//
//==========================================================================
bool AActor::FixMapthingPos()
{
sector_t *secstart = P_PointInSectorBuggy(X(), Y());
int blockx = GetSafeBlockX(X() - bmaporgx);
int blocky = GetSafeBlockY(Y() - bmaporgy);
bool success = false;
if ((unsigned int)blockx < (unsigned int)bmapwidth &&
(unsigned int)blocky < (unsigned int)bmapheight)
{
int *list;
for (list = blockmaplump + blockmap[blocky*bmapwidth + blockx] + 1; *list != -1; ++list)
{
line_t *ldef = &lines[*list];
if (ldef->frontsector == ldef->backsector)
{ // Skip two-sided lines inside a single sector
continue;
}
if (ldef->backsector != NULL)
{
if (ldef->frontsector->floorplane == ldef->backsector->floorplane &&
ldef->frontsector->ceilingplane == ldef->backsector->ceilingplane)
{ // Skip two-sided lines without any height difference on either side
continue;
}
}
// Not inside the line's bounding box
if (X() + radius <= ldef->bbox[BOXLEFT]
|| X() - radius >= ldef->bbox[BOXRIGHT]
|| Y() + radius <= ldef->bbox[BOXBOTTOM]
|| Y() - radius >= ldef->bbox[BOXTOP])
continue;
// Get the exact distance to the line
divline_t dll, dlv;
fixed_t linelen = (fixed_t)sqrt((double)ldef->dx*ldef->dx + (double)ldef->dy*ldef->dy);
P_MakeDivline(ldef, &dll);
dlv.x = X();
dlv.y = Y();
dlv.dx = FixedDiv(dll.dy, linelen);
dlv.dy = -FixedDiv(dll.dx, linelen);
fixed_t distance = abs(P_InterceptVector(&dlv, &dll));
if (distance < radius)
{
DPrintf("%s at (%d,%d) lies on %s line %td, distance = %f\n",
this->GetClass()->TypeName.GetChars(), X() >> FRACBITS, Y() >> FRACBITS,
ldef->dx == 0 ? "vertical" : ldef->dy == 0 ? "horizontal" : "diagonal",
ldef - lines, FIXED2DBL(distance));
angle_t finean = R_PointToAngle2(0, 0, ldef->dx, ldef->dy);
if (ldef->backsector != NULL && ldef->backsector == secstart)
{
finean += ANGLE_90;
}
else
{
finean -= ANGLE_90;
}
finean >>= ANGLETOFINESHIFT;
// Get the distance we have to move the object away from the wall
distance = radius - distance;
SetXY(X() + FixedMul(distance, finecosine[finean]), Y() + FixedMul(distance, finesine[finean]));
success = true;
}
}
}
return success;
}
//==========================================================================
//
// P_SetThingPosition
// Links a thing into both a block and a subsector based on it's x y.
// Links a thing into both a block and a subsector based on its x y.
// Sets thing->sector properly
//
//==========================================================================
void AActor::LinkToWorld (bool buggy)
void AActor::LinkToWorld (bool spawningmapthing, FPortalGroupArray *groups, sector_t *sector)
{
// link into subsector
sector_t *sec;
if (!buggy || numgamenodes == 0)
if (spawningmapthing && (flags4 & MF4_FIXMAPTHINGPOS) && sector == NULL)
{
sec = P_PointInSector (X(), Y());
}
else
{
sec = LinkToWorldForMapThing ();
if (FixMapthingPos()) spawningmapthing = false;
}
LinkToWorld (sec);
}
void AActor::LinkToWorld (sector_t *sec)
{
if (sec == NULL)
if (sector == NULL)
{
LinkToWorld ();
return;
if (!spawningmapthing || numgamenodes == 0)
{
sector = P_PointInSector(X(), Y());
}
else
{
sector = P_PointInSectorBuggy(X(), Y());
}
}
Sector = sec;
Sector = sector;
subsector = R_PointInSubsector(X(), Y()); // this is from the rendering nodes, not the gameplay nodes!
if ( !(flags & MF_NOSECTOR) )
@ -356,7 +437,7 @@ void AActor::LinkToWorld (sector_t *sec)
// killough 8/11/98: simpler scheme using pointer-to-pointer prev
// pointers, allows head nodes to be treated like everything else
AActor **link = &sec->thinglist;
AActor **link = &sector->thinglist;
AActor *next = *link;
if ((snext = next))
next->sprev = &snext;
@ -405,7 +486,7 @@ void AActor::LinkToWorld (sector_t *sec)
for (int x = x1; x <= x2; ++x)
{
FBlockNode **link = &blocklinks[y*bmapwidth + x];
FBlockNode *node = FBlockNode::Create (this, x, y);
FBlockNode *node = FBlockNode::Create (this, x, y, this->Sector->PortalGroup);
// Link in to block
if ((node->NextActor = *link) != NULL)
@ -426,168 +507,6 @@ void AActor::LinkToWorld (sector_t *sec)
}
}
//==========================================================================
//
// [RH] LinkToWorldForMapThing
//
// Emulate buggy PointOnLineSide and fix actors that lie on
// lines to compensate for some IWAD maps.
//
//==========================================================================
static int R_PointOnSideSlow (fixed_t x, fixed_t y, node_t *node)
{
// [RH] This might have been faster than two multiplies and an
// add on a 386/486, but it certainly isn't on anything newer than that.
fixed_t dx;
fixed_t dy;
double left;
double right;
if (!node->dx)
{
if (x <= node->x)
return node->dy > 0;
return node->dy < 0;
}
if (!node->dy)
{
if (y <= node->y)
return node->dx < 0;
return node->dx > 0;
}
dx = (x - node->x);
dy = (y - node->y);
// Try to quickly decide by looking at sign bits.
if ( (node->dy ^ node->dx ^ dx ^ dy)&0x80000000 )
{
if ( (node->dy ^ dx) & 0x80000000 )
{
// (left is negative)
return 1;
}
return 0;
}
// we must use doubles here because the fixed point code will produce errors due to loss of precision for extremely short linedefs.
left = (double)node->dy * (double)dx;
right = (double)dy * (double)node->dx;
if (right < left)
{
// front side
return 0;
}
// back side
return 1;
}
sector_t *AActor::LinkToWorldForMapThing ()
{
node_t *node = gamenodes + numgamenodes - 1;
do
{
// Use original buggy point-on-side test when spawning
// things at level load so that the map spots in the
// emerald key room of Hexen MAP01 are spawned on the
// window ledge instead of the blocking floor in front
// of it. Why do I consider it buggy? Because a point
// that lies directly on a line should always be
// considered as "in front" of the line. The orientation
// of the line should be irrelevant.
node = (node_t *)node->children[R_PointOnSideSlow (X(), Y(), node)];
}
while (!((size_t)node & 1));
subsector_t *ssec = (subsector_t *)((BYTE *)node - 1);
if (flags4 & MF4_FIXMAPTHINGPOS)
{
// If the thing is exactly on a line, move it into the subsector
// slightly in order to resolve clipping issues in the renderer.
// This check needs to use the blockmap, because an actor on a
// one-sided line might go into a subsector behind the line, so
// the line would not be included as one of its subsector's segs.
int blockx = GetSafeBlockX(X() - bmaporgx);
int blocky = GetSafeBlockY(Y() - bmaporgy);
if ((unsigned int)blockx < (unsigned int)bmapwidth &&
(unsigned int)blocky < (unsigned int)bmapheight)
{
int *list;
for (list = blockmaplump + blockmap[blocky*bmapwidth + blockx] + 1; *list != -1; ++list)
{
line_t *ldef = &lines[*list];
if (ldef->frontsector == ldef->backsector)
{ // Skip two-sided lines inside a single sector
continue;
}
if (ldef->backsector != NULL)
{
if (ldef->frontsector->floorplane == ldef->backsector->floorplane &&
ldef->frontsector->ceilingplane == ldef->backsector->ceilingplane)
{ // Skip two-sided lines without any height difference on either side
continue;
}
}
// Not inside the line's bounding box
if (X() + radius <= ldef->bbox[BOXLEFT]
|| X() - radius >= ldef->bbox[BOXRIGHT]
|| Y() + radius <= ldef->bbox[BOXBOTTOM]
|| Y() - radius >= ldef->bbox[BOXTOP] )
continue;
// Get the exact distance to the line
divline_t dll, dlv;
fixed_t linelen = (fixed_t)sqrt((double)ldef->dx*ldef->dx + (double)ldef->dy*ldef->dy);
P_MakeDivline (ldef, &dll);
dlv.x = X();
dlv.y = Y();
dlv.dx = FixedDiv(dll.dy, linelen);
dlv.dy = -FixedDiv(dll.dx, linelen);
fixed_t distance = abs(P_InterceptVector(&dlv, &dll));
if (distance < radius)
{
DPrintf ("%s at (%d,%d) lies on %s line %td, distance = %f\n",
this->GetClass()->TypeName.GetChars(), X()>>FRACBITS, Y()>>FRACBITS,
ldef->dx == 0? "vertical" : ldef->dy == 0? "horizontal" : "diagonal",
ldef-lines, FIXED2DBL(distance));
angle_t finean = R_PointToAngle2 (0, 0, ldef->dx, ldef->dy);
if (ldef->backsector != NULL && ldef->backsector == ssec->sector)
{
finean += ANGLE_90;
}
else
{
finean -= ANGLE_90;
}
finean >>= ANGLETOFINESHIFT;
// Get the distance we have to move the object away from the wall
distance = radius - distance;
SetXY(X() + FixedMul(distance, finecosine[finean]), Y() + FixedMul(distance, finesine[finean]));
return P_PointInSector (X(), Y());
}
}
}
}
return ssec->sector;
}
void AActor::SetOrigin (fixed_t ix, fixed_t iy, fixed_t iz, bool moving)
{
UnlinkFromWorld ();
@ -601,7 +520,7 @@ void AActor::SetOrigin (fixed_t ix, fixed_t iy, fixed_t iz, bool moving)
FBlockNode *FBlockNode::FreeBlocks = NULL;
FBlockNode *FBlockNode::Create (AActor *who, int x, int y)
FBlockNode *FBlockNode::Create (AActor *who, int x, int y, int group)
{
FBlockNode *block;
@ -1528,6 +1447,67 @@ static AActor *RoughBlockCheck (AActor *mo, int index, void *param)
return NULL;
}
//==========================================================================
//
// [RH] LinkToWorldForMapThing
//
// Emulate buggy PointOnLineSide and fix actors that lie on
// lines to compensate for some IWAD maps.
//
//==========================================================================
static int R_PointOnSideSlow(fixed_t x, fixed_t y, node_t *node)
{
// [RH] This might have been faster than two multiplies and an
// add on a 386/486, but it certainly isn't on anything newer than that.
fixed_t dx;
fixed_t dy;
double left;
double right;
if (!node->dx)
{
if (x <= node->x)
return node->dy > 0;
return node->dy < 0;
}
if (!node->dy)
{
if (y <= node->y)
return node->dx < 0;
return node->dx > 0;
}
dx = (x - node->x);
dy = (y - node->y);
// Try to quickly decide by looking at sign bits.
if ((node->dy ^ node->dx ^ dx ^ dy) & 0x80000000)
{
if ((node->dy ^ dx) & 0x80000000)
{
// (left is negative)
return 1;
}
return 0;
}
// we must use doubles here because the fixed point code will produce errors due to loss of precision for extremely short linedefs.
left = (double)node->dy * (double)dx;
right = (double)dy * (double)node->dx;
if (right < left)
{
// front side
return 0;
}
// back side
return 1;
}
//===========================================================================
//
// P_VanillaPointOnLineSide
@ -1616,3 +1596,24 @@ int P_VanillaPointOnDivlineSide(fixed_t x, fixed_t y, const divline_t* line)
return 1; // back side
}
sector_t *P_PointInSectorBuggy(fixed_t x, fixed_t y)
{
node_t *node = gamenodes + numgamenodes - 1;
do
{
// Use original buggy point-on-side test when spawning
// things at level load so that the map spots in the
// emerald key room of Hexen MAP01 are spawned on the
// window ledge instead of the blocking floor in front
// of it. Why do I consider it buggy? Because a point
// that lies directly on a line should always be
// considered as "in front" of the line. The orientation
// of the line should be irrelevant.
node = (node_t *)node->children[R_PointOnSideSlow(x, y, node)];
} while (!((size_t)node & 1));
subsector_t *ssec = (subsector_t *)((BYTE *)node - 1);
return ssec->sector;
}

View File

@ -456,7 +456,7 @@ void AActor::Serialize (FArchive &arc)
if (arc.IsLoading ())
{
touching_sectorlist = NULL;
LinkToWorld (Sector);
LinkToWorld (false, NULL, Sector);
AddToHash ();
SetShade (fillcolor);
if (player)

View File

@ -62,6 +62,39 @@ FDisplacementTable Displacements;
TArray<FLinePortal> linePortals;
TArray<FLinePortal*> linkedPortals; // only the linked portals, this is used to speed up looking for them in P_CollectConnectedGroups.
//============================================================================
//
// This is used to mark processed portals for some collection functions.
//
//============================================================================
struct FPortalBits
{
TArray<DWORD> data;
void setSize(int num)
{
data.Resize((num + 31) / 32);
clear();
}
void clear()
{
memset(&data[0], 0, data.Size()*sizeof(DWORD));
}
void setBit(int group)
{
data[group >> 5] |= (1 << (group & 31));
}
int getBit(int group)
{
return data[group >> 5] & (1 << (group & 31));
}
};
//============================================================================
//
@ -1021,22 +1054,31 @@ void P_CreateLinkedPortals()
//
//============================================================================
bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortalGroupTable &out)
bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortalGroupArray &out)
{
TArray<FLinePortal*> foundPortals;
// Keep this temporary work stuff static. This function can never be called recursively
// and this would have to be reallocated for each call otherwise.
static FPortalBits processMask;
static TArray<FLinePortal*> foundPortals;
bool retval = false;
if (linePortals.Size() == 0)
if (linkedPortals.Size() == 0)
{
// If there are no portals, all sectors are in group 0.
out.Add(0);
return false;
}
out.setSize(Displacements.size);
out.setBit(actor->Sector->PortalGroup);
//FBoundingBox box(newx, newy, actor->radius);
processMask.setSize(linkedPortals.Size());
processMask.clear();
foundPortals.Clear();
int thisgroup = actor->Sector->PortalGroup;
for (unsigned i = 0; i < linePortals.Size(); i++)
processMask.setBit(thisgroup);
out.Add(thisgroup);
for (unsigned i = 0; i < linkedPortals.Size(); i++)
{
if (linePortals[i].mType != PORTT_LINKED) continue; // not a linked portal
line_t *ld = linePortals[i].mOrigin;
line_t *ld = linkedPortals[i]->mOrigin;
int othergroup = ld->frontsector->PortalGroup;
FDisplacement &disp = Displacements(thisgroup, othergroup);
if (!disp.isSet) continue; // no connection.
@ -1049,8 +1091,8 @@ bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortal
|| box.Bottom() >= ld->bbox[BOXTOP])
continue; // not touched
if (box.BoxOnLineSide(linePortals[i].mOrigin) != -1) continue; // not touched
foundPortals.Push(&linePortals[i]);
if (box.BoxOnLineSide(linkedPortals[i]->mOrigin) != -1) continue; // not touched
foundPortals.Push(linkedPortals[i]);
}
bool foundone = true;
while (foundone)
@ -1058,10 +1100,11 @@ bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortal
foundone = false;
for (int i = foundPortals.Size() - 1; i >= 0; i--)
{
if (out.getBit(foundPortals[i]->mOrigin->frontsector->PortalGroup) &&
!out.getBit(foundPortals[i]->mDestination->frontsector->PortalGroup))
if (processMask.getBit(foundPortals[i]->mOrigin->frontsector->PortalGroup) &&
!processMask.getBit(foundPortals[i]->mDestination->frontsector->PortalGroup))
{
out.setBit(foundPortals[i]->mDestination->frontsector->PortalGroup);
processMask.setBit(foundPortals[i]->mDestination->frontsector->PortalGroup);
out.Add(foundPortals[i]->mDestination->frontsector->PortalGroup);
foundone = true;
retval = true;
foundPortals.Delete(i);
@ -1076,8 +1119,9 @@ bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortal
FDisplacement &disp = Displacements(actor->Sector->PortalGroup, othersec->PortalGroup);
fixed_t dx = newx + disp.x;
fixed_t dy = newx + disp.y;
out.setBit(othersec->PortalGroup);
wsec = P_PointInSector(dx, dy); // get upper sector at the exact spot we want to check and repeat,
processMask.setBit(othersec->PortalGroup);
out.Add(othersec->PortalGroup);
wsec = P_PointInSector(dx, dy); // get upper sector at the exact spot we want to check and repeat
retval = true;
}
wsec = sec;
@ -1087,8 +1131,9 @@ bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortal
FDisplacement &disp = Displacements(actor->Sector->PortalGroup, othersec->PortalGroup);
fixed_t dx = newx + disp.x;
fixed_t dy = newx + disp.y;
out.setBit(othersec->PortalGroup);
wsec = P_PointInSector(dx, dy); // get lower sector at the exact spot we want to check and repeat,
processMask.setBit(othersec->PortalGroup);
out.Add(othersec->PortalGroup);
wsec = P_PointInSector(dx, dy); // get lower sector at the exact spot we want to check and repeat
retval = true;
}
return retval;

View File

@ -9,7 +9,7 @@
#include "m_bbox.h"
#include "a_sharedglobal.h"
struct FPortalGroupTable;
struct FPortalGroupArray;
//============================================================================
//
// This table holds the offsets for the different parts of a map
@ -120,7 +120,7 @@ void P_SpawnLinePortal(line_t* line);
void P_FinalizePortals();
bool P_ChangePortal(line_t *ln, int thisid, int destid);
void P_CreateLinkedPortals();
bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortalGroupTable &out);
bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortalGroupArray &out);
void P_CollectLinkedPortals();
inline int P_NumPortalGroups()
{