Skip to content

Commit 99a5bc5

Browse files
authored
[GEN][ZH] Fix AIGroup memory management and leaks (#1214)
1 parent 1ebce07 commit 99a5bc5

File tree

25 files changed

+826
-184
lines changed

25 files changed

+826
-184
lines changed

Core/GameEngine/Include/Common/GameDefines.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@
2929
#define RETAIL_COMPATIBLE_XFER_SAVE (1) // Game is expected to be Xfer Save compatible with retail Generals 1.08, Zero Hour 1.04
3030
#endif
3131

32+
// This is essentially synonymous for RETAIL_COMPATIBLE_CRC. There is a lot wrong with AIGroup, such as use-after-free, double-free, leaks,
33+
// but we cannot touch it much without breaking retail compatibility. Do not shy away from using massive hacks when fixing issues with AIGroup,
34+
// but put them behind this macro.
35+
36+
#ifndef RETAIL_COMPATIBLE_AIGROUP
37+
#define RETAIL_COMPATIBLE_AIGROUP (1) // AIGroup logic is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04
38+
#endif
39+
3240
#ifndef ENABLE_GAMETEXT_SUBSTITUTES
3341
#define ENABLE_GAMETEXT_SUBSTITUTES (1) // The code can provide substitute texts when labels and strings are missing in the STR or CSF translation file
3442
#endif

Generals/Code/GameEngine/Include/Common/Player.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ class Player : public Snapshot
608608
// add to the player's current selection this hotkey team.
609609
void processAddTeamGameMessage(Int hotkeyNum, GameMessage *msg);
610610

611-
// returns an AIGroup object that is the currently selected group.
611+
// fills an AIGroup object that is the currently selected group.
612612
void getCurrentSelectionAsAIGroup(AIGroup *group);
613613

614614
// sets the currently selected group to be the given AIGroup

Generals/Code/GameEngine/Include/GameLogic/AI.h

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
#include "Common/GameType.h"
3838
#include "GameLogic/Damage.h"
3939
#include "Common/STLTypedefs.h"
40+
#include "refcount.h"
41+
#include "ref_ptr.h"
4042

4143
class AIGroup;
4244
class AttackPriorityInfo;
@@ -57,6 +59,12 @@ enum WeaponSetType CPP_11(: Int);
5759
enum WeaponLockType CPP_11(: Int);
5860
enum SpecialPowerType CPP_11(: Int);
5961

62+
#if RETAIL_COMPATIBLE_AIGROUP
63+
typedef AIGroup* AIGroupPtr;
64+
#else
65+
typedef RefCountPtr<AIGroup> AIGroupPtr;
66+
#endif
67+
6068
typedef std::vector<ObjectID> VecObjectID;
6169
typedef VecObjectID::iterator VecObjectIDIt;
6270

@@ -265,7 +273,7 @@ class AI : public SubsystemInterface, public Snapshot
265273
void loadPostProcess( void );
266274

267275
// AI Groups -----------------------------------------------------------------------------------------------
268-
AIGroup *createGroup( void ); ///< instantiate a new AI Group
276+
AIGroupPtr createGroup( void ); ///< instantiate a new AI Group
269277
void destroyGroup( AIGroup *group ); ///< destroy the given AI Group
270278
AIGroup *findGroup( UnsignedInt id ); ///< return the AI Group with the given ID
271279

@@ -851,6 +859,12 @@ class AIGroup : public MemoryPoolObject, public Snapshot
851859
void xfer( Xfer *xfer );
852860
void loadPostProcess( void );
853861

862+
#if !RETAIL_COMPATIBLE_AIGROUP
863+
void Add_Ref() const { m_refCount.Add_Ref(); }
864+
void Release_Ref() const { m_refCount.Release_Ref(destroy, this); }
865+
UnsignedShort Num_Refs() const { return m_refCount.Num_Refs(); }
866+
#endif
867+
854868
void groupMoveToPosition( const Coord3D *pos, Bool addWaypoint, CommandSourceType cmdSource );
855869
void groupMoveToAndEvacuate( const Coord3D *pos, CommandSourceType cmdSource ); ///< move to given position(s)
856870
void groupMoveToAndEvacuateAndExit( const Coord3D *pos, CommandSourceType cmdSource ); ///< move to given position & unload transport.
@@ -938,13 +952,15 @@ class AIGroup : public MemoryPoolObject, public Snapshot
938952

939953
void add( Object *obj ); ///< add object to group
940954

941-
// returns true if remove destroyed the group for us.
955+
// Returns true if the group was emptied.
942956
Bool remove( Object *obj);
957+
958+
void removeAll( void );
943959

944960
// If the group contains any objects not owned by ownerPlayer, return TRUE.
945961
Bool containsAnyObjectsNotOwnedByPlayer( const Player *ownerPlayer );
946962

947-
// Remove any objects that aren't owned by the player, and return true if the group was destroyed due to emptiness
963+
// Removes any objects that aren't owned by the player, and returns true if the group was emptied.
948964
Bool removeAnyObjectsNotOwnedByPlayer( const Player *ownerPlayer );
949965

950966
UnsignedInt getID( void );
@@ -974,6 +990,13 @@ class AIGroup : public MemoryPoolObject, public Snapshot
974990
friend class AI;
975991
AIGroup( void );
976992

993+
#if !RETAIL_COMPATIBLE_AIGROUP
994+
static void destroy(AIGroup* self)
995+
{
996+
TheAI->destroyGroup(self);
997+
}
998+
#endif
999+
9771000
void recompute( void ); ///< recompute various group info, such as speed, leader, etc
9781001

9791002
ListObjectPtr m_memberList; ///< the list of member Objects
@@ -982,6 +1005,10 @@ class AIGroup : public MemoryPoolObject, public Snapshot
9821005
Real m_speed; ///< maximum speed of group (slowest member)
9831006
Bool m_dirty; ///< "dirty bit" - if true then group speed, leader, needs recompute
9841007

1008+
#if !RETAIL_COMPATIBLE_AIGROUP
1009+
RefCountValue<UnsignedShort> m_refCount; ///< the reference counter
1010+
#endif
1011+
9851012
UnsignedInt m_id; ///< the unique ID of this group
9861013
Path *m_groundPath; ///< Group ground path.
9871014

Generals/Code/GameEngine/Include/GameLogic/Object.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#define _OBJECT_H_
3232

3333
#include "Lib/BaseType.h"
34+
#include "ref_ptr.h"
3435

3536
#include "Common/Geometry.h"
3637
#include "Common/Snapshot.h"
@@ -663,7 +664,11 @@ class Object : public Thing, public Snapshot
663664

664665
GeometryInfo m_geometryInfo;
665666

667+
#if RETAIL_COMPATIBLE_AIGROUP
666668
AIGroup* m_group; ///< if non-NULL, we are part of this group of agents
669+
#else
670+
RefCountPtr<AIGroup> m_group; ///< if non-NULL, we are part of this group of agents
671+
#endif
667672

668673
// These will last for my lifetime. I will reuse them and reset them. The truly dynamic ones are in PartitionManager
669674
SightingInfo *m_partitionLastLook; ///< Where and for whom I last looked, so I can undo its effects when I stop

Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ void AI::reset( void )
330330
m_aiData = m_aiData->m_next;
331331
delete cur;
332332
}
333+
334+
#if RETAIL_COMPATIBLE_AIGROUP
333335
while (m_groupList.size())
334336
{
335337
AIGroup *groupToRemove = m_groupList.front();
@@ -342,6 +344,12 @@ void AI::reset( void )
342344
m_groupList.pop_front(); // NULL group, just kill from list. Shouldn't really happen, but just in case.
343345
}
344346
}
347+
#else
348+
DEBUG_ASSERTCRASH(m_groupList.empty(), ("AI::m_groupList is expected empty already\n"));
349+
350+
m_groupList.clear(); // Clear just in case...
351+
#endif
352+
345353
m_nextGroupID = 0;
346354
m_nextFormationID = NO_FORMATION_ID;
347355
getNextFormationID(); // increment once past NO_FORMATION_ID. jba.
@@ -440,14 +448,22 @@ void AI::parseAiDataDefinition( INI* ini )
440448
/**
441449
* Create a new AI Group
442450
*/
443-
AIGroup *AI::createGroup( void )
451+
AIGroupPtr AI::createGroup( void )
444452
{
445453
// create a new instance
454+
#if RETAIL_COMPATIBLE_AIGROUP
446455
AIGroup *group = newInstance(AIGroup);
456+
#else
457+
AIGroupPtr group = AIGroupPtr::Create_NoAddRef(newInstance(AIGroup));
458+
#endif
447459

448460
// add it to the list
449461
// DEBUG_LOG(("***AIGROUP %x is being added to m_groupList.\n", group ));
462+
#if RETAIL_COMPATIBLE_AIGROUP
450463
m_groupList.push_back( group );
464+
#else
465+
m_groupList.push_back( group.Peek() );
466+
#endif
451467

452468
return group;
453469
}

Generals/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ AIGroup::~AIGroup()
9292
{
9393
// DEBUG_LOG(("***AIGROUP %x is being destructed.\n", this));
9494
// disassociate each member from the group
95+
96+
#if RETAIL_COMPATIBLE_AIGROUP
97+
9598
std::list<Object *>::iterator i;
9699
for( i = m_memberList.begin(); i != m_memberList.end(); /* empty */ )
97100
{
@@ -106,6 +109,13 @@ AIGroup::~AIGroup()
106109
i = m_memberList.erase(i);
107110
}
108111
}
112+
113+
#else
114+
115+
removeAll();
116+
117+
#endif
118+
109119
if (m_groundPath) {
110120
deleteInstance(m_groundPath);
111121
m_groundPath = NULL;
@@ -205,6 +215,11 @@ void AIGroup::add( Object *obj )
205215
*/
206216
Bool AIGroup::remove( Object *obj )
207217
{
218+
#if !RETAIL_COMPATIBLE_AIGROUP
219+
// Defer deletion until the end of this function.
220+
AIGroupPtr refThis = AIGroupPtr::Create_AddRef(this);
221+
#endif
222+
208223
// DEBUG_LOG(("***AIGROUP %x is removing Object %x (%s).\n", this, obj, obj->getTemplate()->getName().str()));
209224
std::list<Object *>::iterator i = std::find( m_memberList.begin(), m_memberList.end(), obj );
210225

@@ -223,15 +238,42 @@ Bool AIGroup::remove( Object *obj )
223238
// list has changed, properties need recomputation
224239
m_dirty = true;
225240

226-
// if the group is empty, no-one is using it any longer, so destroy it
227241
if (isEmpty()) {
242+
#if RETAIL_COMPATIBLE_AIGROUP
243+
// if the group is empty, no-one is using it any longer, so destroy it
228244
TheAI->destroyGroup( this );
245+
#endif
229246
return TRUE;
230247
}
231248

232249
return FALSE;
233250
}
234251

252+
/**
253+
* Remove all objects from group
254+
*/
255+
void AIGroup::removeAll( void )
256+
{
257+
#if !RETAIL_COMPATIBLE_AIGROUP
258+
// Defer deletion until the end of this function.
259+
AIGroupPtr refThis = AIGroupPtr::Create_AddRef(this);
260+
#endif
261+
262+
std::list<Object *> memberList;
263+
memberList.swap(m_memberList);
264+
m_memberListSize = 0;
265+
266+
std::list<Object *>::iterator i;
267+
for ( i = memberList.begin(); i != memberList.end(); ++i )
268+
{
269+
Object *member = *i;
270+
if (member)
271+
member->leaveGroup();
272+
}
273+
274+
m_dirty = true;
275+
}
276+
235277
/**
236278
* If the group contains any objects not owned by ownerPlayer, return TRUE.
237279
*/
@@ -2722,6 +2764,11 @@ void AIGroup::groupCheer( CommandSourceType cmdSource )
27222764
*/
27232765
void AIGroup::groupSell( CommandSourceType cmdSource )
27242766
{
2767+
#if !RETAIL_COMPATIBLE_AIGROUP
2768+
// Defer deletion until the end of this function.
2769+
AIGroupPtr refThis = AIGroupPtr::Create_AddRef(this);
2770+
#endif
2771+
27252772
std::list<Object *>::iterator i;
27262773
std::vector<Object *> groupObjectsCopy;
27272774
groupObjectsCopy.reserve(m_memberListSize);

Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -893,11 +893,15 @@ void AIPlayer::guardSupplyCenter( Team *team, Int minSupplies )
893893
}
894894
if (warehouse) {
895895

896-
AIGroup* theGroup = TheAI->createGroup();
896+
AIGroupPtr theGroup = TheAI->createGroup();
897897
if (!theGroup) {
898898
return;
899899
}
900+
#if RETAIL_COMPATIBLE_AIGROUP
900901
team->getTeamAsAIGroup(theGroup);
902+
#else
903+
team->getTeamAsAIGroup(theGroup.Peek());
904+
#endif
901905
Coord3D location = *warehouse->getPosition();
902906
// It's probably a defensive move - position towards the enemy.
903907
Region2D bounds;
@@ -2467,9 +2471,9 @@ void AIPlayer::checkReadyTeams( void )
24672471
/*
24682472
if (team->m_team->getPrototype()->getTemplateInfo()->m_hasHomeLocation &&
24692473
!team->m_reinforcement) {
2470-
AIGroup* theGroup = TheAI->createGroup();
2474+
AIGroupPtr theGroup = TheAI->createGroup();
24712475
if (theGroup) {
2472-
team->m_team->getTeamAsAIGroup(theGroup);
2476+
team->m_team->getTeamAsAIGroup(theGroup.Peek());
24732477
Coord3D destination = team->m_team->getPrototype()->getTemplateInfo()->m_homeLocation;
24742478
theGroup->groupTightenToPosition( &destination, false, CMD_FROM_AI );
24752479
team->m_frameStarted = TheGameLogic->getFrame();

Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4053,8 +4053,12 @@ StateReturnType AIFollowWaypointPathState::update()
40534053
if (m_moveAsGroup) {
40544054
if (obj->getControllingPlayer()->isSkirmishAIPlayer()) {
40554055
Team *team = obj->getTeam();
4056-
AIGroup *group = TheAI->createGroup();
4056+
AIGroupPtr group = TheAI->createGroup();
4057+
#if RETAIL_COMPATIBLE_AIGROUP
40574058
team->getTeamAsAIGroup(group);
4059+
#else
4060+
team->getTeamAsAIGroup(group.Peek());
4061+
#endif
40584062

40594063
Coord3D pos;
40604064
group->getCenter(&pos);

Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -563,8 +563,7 @@ Object::~Object()
563563
ThePartitionManager->unRegisterObject( this );
564564

565565
// if we are in a group, remove us
566-
if (m_group)
567-
m_group->remove( this );
566+
leaveGroup();
568567

569568
// note, do NOT free these, there are just a shadow copy!
570569
m_ai = NULL;
@@ -3841,7 +3840,7 @@ void Object::xfer( Xfer *xfer )
38413840
}
38423841

38433842
// Doesn't need to be saved. These are created as needed. jba.
3844-
//AIGroup* m_group; ///< if non-NULL, we are part of this group of agents
3843+
//m_group;
38453844

38463845
// don't need to save m_partitionData.
38473846
DEBUG_ASSERTCRASH(!(xfer->getXferMode() == XFER_LOAD && m_partitionData == NULL), ("should not be in partitionmgr yet"));
@@ -5446,7 +5445,11 @@ RadarPriorityType Object::getRadarPriority( void ) const
54465445
// ------------------------------------------------------------------------------------------------
54475446
AIGroup *Object::getGroup(void)
54485447
{
5448+
#if RETAIL_COMPATIBLE_AIGROUP
54495449
return m_group;
5450+
#else
5451+
return m_group.Peek();
5452+
#endif
54505453
}
54515454

54525455
//-------------------------------------------------------------------------------------------------
@@ -5456,7 +5459,11 @@ void Object::enterGroup( AIGroup *group )
54565459
// if we are in another group, remove ourselves from it first
54575460
leaveGroup();
54585461

5462+
#if RETAIL_COMPATIBLE_AIGROUP
54595463
m_group = group;
5464+
#else
5465+
m_group = AIGroupPtr::Create_AddRef(group);
5466+
#endif
54605467
}
54615468

54625469
//-------------------------------------------------------------------------------------------------
@@ -5467,7 +5474,7 @@ void Object::leaveGroup( void )
54675474
if (m_group)
54685475
{
54695476
// to avoid recursion, set m_group to NULL before removing
5470-
AIGroup *group = m_group;
5477+
AIGroupPtr group = m_group;
54715478
m_group = NULL;
54725479
group->remove( this );
54735480
}

0 commit comments

Comments
 (0)