Skip to content

Conversation

@xezon
Copy link

@xezon xezon commented Jun 9, 2025

When a Hero unit is killed, a heap-use-after-free is hit in W3DRadar::drawHeroIcon().

This happens because W3DRadar::m_cachedHeroPosList contains pointers to object positions, which are never cleaned up when the objects are destroyed.

To solve this problem, the cache Hero list has been reworked and tied to the established addObject and removeObject functions.

This fix also revealed a problem with the subsystems reset where cross dependencies can cause issues on GameEngine::reset. This is fixed as well.

I expect that this change fixes the user facing bug #1034. Not tested.

Asan Report

=================================================================
==18020==ERROR: AddressSanitizer: heap-use-after-free on address 0x50db3338 at pc 0x00d2c932 bp 0x018ff258 sp 0x018ff24c
READ of size 4 at 0x50db3338 thread T0
==18020==WARNING: Failed to use and restart external symbolizer!
    #0 0x00d2c931 in W3DRadar::drawHeroIcon D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\W3DDevice\Common\System\W3DRadar.cpp:262
    #1 0x00d2c363 in W3DRadar::draw D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\W3DDevice\Common\System\W3DRadar.cpp:1425
    #2 0x00e12275 in W3DLeftHUDDraw D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\W3DDevice\GameClient\GUI\GUICallbacks\W3DControlBar.cpp:89
    #3 0x007f5eb6 in GameWindowManager::drawWindow D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameClient\GUI\GameWindowManager.cpp:1298
    #4 0x007f5fb7 in GameWindowManager::drawWindow D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameClient\GUI\GameWindowManager.cpp:1313
    #5 0x007fd31d in GameWindowManager::winRepaint D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameClient\GUI\GameWindowManager.cpp:1353
    #6 0x00de6ea9 in W3DInGameUI::draw D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\W3DDevice\GameClient\W3DInGameUI.cpp:439
    #7 0x00ddb5aa in W3DDisplay::draw D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\W3DDevice\GameClient\W3DDisplay.cpp:1902
    #8 0x007e11eb in GameClient::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameClient\GameClient.cpp:766
    #9 0x006897df in GameEngine::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameEngine.cpp:746
    #10 0x00cb253d in Win32GameEngine::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\Win32Device\Common\Win32GameEngine.cpp:90
    #11 0x00686b5a in GameEngine::execute D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameEngine.cpp:822
    #12 0x00676e55 in GameMain D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameMain.cpp:44
    #13 0x00673759 in WinMain D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\Main\WinMain.cpp:986
    #14 0x010665c6 in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #15 0x75e3fcc8 in BaseThreadInitThunk+0x18 (C:\WINDOWS\System32\KERNEL32.DLL+0x6b81fcc8)
    #16 0x76f582ad in RtlGetAppContainerNamedObjectPath+0x11d (C:\WINDOWS\SYSTEM32\ntdll.dll+0x4b2e82ad)
    #17 0x76f5827d in RtlGetAppContainerNamedObjectPath+0xed (C:\WINDOWS\SYSTEM32\ntdll.dll+0x4b2e827d)

0x50db3338 is located 56 bytes inside of 656-byte region [0x50db3300,0x50db3590)
freed by thread T0 here:
    #0 0x53aae6ed in free D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win.cpp:125
    #1 0x53ac081c in __asan::__RuntimeFunctions<__asan::Ucrtbase>::Free D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_win_runtime_functions.cpp:273
    #2 0x01065ca3 in operator delete D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_win_delete_scalar_size_thunk.cpp:44
    #3 0x0088cccb in Object::`scalar deleting destructor'+0x1b (C:\Generals\English\Command & Conquer Generals Zero Hour\generalszh.exe+0x63cccb)
    #4 0x0077c8a2 in GameLogic::processDestroyList D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameLogic\System\GameLogic.cpp:2575
    #5 0x00782533 in GameLogic::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameLogic\System\GameLogic.cpp:3831
    #6 0x006898e8 in GameEngine::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameEngine.cpp:761
    #7 0x00cb253d in Win32GameEngine::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\Win32Device\Common\Win32GameEngine.cpp:90
    #8 0x00686b5a in GameEngine::execute D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameEngine.cpp:822
    #9 0x00676e55 in GameMain D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameMain.cpp:44
    #10 0x00673759 in WinMain D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\Main\WinMain.cpp:986
    #11 0x010665c6 in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #12 0x75e3fcc8 in BaseThreadInitThunk+0x18 (C:\WINDOWS\System32\KERNEL32.DLL+0x6b81fcc8)
    #13 0x76f582ad in RtlGetAppContainerNamedObjectPath+0x11d (C:\WINDOWS\SYSTEM32\ntdll.dll+0x4b2e82ad)
    #14 0x76f5827d in RtlGetAppContainerNamedObjectPath+0xed (C:\WINDOWS\SYSTEM32\ntdll.dll+0x4b2e827d)

previously allocated by thread T0 here:
    #0 0x53aae7ed in malloc D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win.cpp:134
    #1 0x0067434a in operator new D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\System\GameMemoryNull.cpp:158
    #2 0x007785cc in GameLogic::friend_createObject D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameLogic\System\GameLogic.cpp:3998
    #3 0x007d3704 in ThingFactory::newObject D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\Thing\ThingFactory.cpp:329
    #4 0x00780a9d in GameLogic::startNewGame D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameLogic\System\GameLogic.cpp:1919
    #5 0x00782085 in GameLogic::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameLogic\System\GameLogic.cpp:3641
    #6 0x006898e8 in GameEngine::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameEngine.cpp:761
    #7 0x00cb253d in Win32GameEngine::update D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\Win32Device\Common\Win32GameEngine.cpp:90
    #8 0x00686b5a in GameEngine::execute D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameEngine.cpp:822
    #9 0x00676e55 in GameMain D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\Common\GameMain.cpp:44
    #10 0x00673759 in WinMain D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\Main\WinMain.cpp:986
    #11 0x010665c6 in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #12 0x75e3fcc8 in BaseThreadInitThunk+0x18 (C:\WINDOWS\System32\KERNEL32.DLL+0x6b81fcc8)
    #13 0x76f582ad in RtlGetAppContainerNamedObjectPath+0x11d (C:\WINDOWS\SYSTEM32\ntdll.dll+0x4b2e82ad)
    #14 0x76f5827d in RtlGetAppContainerNamedObjectPath+0xed (C:\WINDOWS\SYSTEM32\ntdll.dll+0x4b2e827d)

SUMMARY: AddressSanitizer: heap-use-after-free D:\Projects\TheSuperHackers\GeneralsGameCode\GeneralsMD\Code\GameEngineDevice\Source\W3DDevice\Common\System\W3DRadar.cpp:262 in W3DRadar::drawHeroIcon
Shadow bytes around the buggy address:
  0x50db3080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50db3100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50db3180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50db3200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50db3280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x50db3300: fd fd fd fd fd fd fd[fd]fd fd fd fd fd fd fd fd
  0x50db3380: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x50db3400: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x50db3480: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x50db3500: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x50db3580: fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
Address Sanitizer Error: Use of deallocated memory

TODO

  • Replicate in Generals

@xezon xezon added this to the Major bug fixes milestone Jun 9, 2025
@xezon xezon added Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Memory Is memory related labels Jun 9, 2025
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, this resetAll crashed on map unload in

>	generalszh.exe!TunnelContain::iterateContained(void(*)(Object *, void *) func, void * userData, bool reverse) Line 191	C++
 	generalszh.exe!Object::isHero() Line 2044	C++
 	generalszh.exe!W3DRadar::addObject(Object * obj) Line 1003	C++
 	generalszh.exe!Player::becomingLocalPlayer(bool yes) Line 1138	C++
 	[Inline Frame] generalszh.exe!PlayerList::setLocalPlayer(Player *) Line 324	C++
 	generalszh.exe!PlayerList::init() Line 246	C++
 	generalszh.exe!PlayerList::reset() Line 127	C++
 	generalszh.exe!SubsystemInterfaceList::resetAll() Line 188	C++
 	[Inline Frame] generalszh.exe!GameLogic::isInMultiplayerGame() Line 418	C++
 	generalszh.exe!GameEngine::reset() Line 703	C++
 	generalszh.exe!GameLogic::clearGameData(bool showScoreScreen) Line 281	C++
 	generalszh.exe!GameLogic::logicMessageDispatcher(GameMessage * msg, void * userData) Line 464	C++
 	generalszh.exe!GameLogic::processCommandList(CommandList * list) Line 2597	C++
 	generalszh.exe!GameLogic::update() Line 3767	C++
 	[Inline Frame] generalszh.exe!SubsystemInterface::UPDATE() Line 134	C++
 	generalszh.exe!GameEngine::update() Line 766	C++
 	generalszh.exe!Win32GameEngine::update() Line 93	C++
 	generalszh.exe!GameEngine::execute() Line 823	C++
 	generalszh.exe!GameMain(int argc, char * * argv) Line 47	C++
 	generalszh.exe!WinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, char * lpCmdLine, int nCmdShow) Line 988	C++
 	[Inline Frame] generalszh.exe!invoke_main() Line 102	C++
 	generalszh.exe!__scrt_common_main_seh() Line 288	C++
 	kernel32.dll!75e3fcc9()	Unknown
 	[Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll]	
 	ntdll.dll!76f582ae()	Unknown
 	ntdll.dll!76f5827e()	Unknown
Exception thrown: read access violation.
**owningPlayer** was 0x150.

The problem is that the Object's TunnelContain module has a dependency on the player, but the player was already purged from the PlayerList. And so this would crash.

Resetting GameLogic first avoids this (and potentially similar issues in the future). Alternatively we could add more NULL checks in TunnelContain, but I think that is not necessary if Subsystem just have a proper reset sequence.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the container type here because list of coordinate pointers is not particularly great.

Copy link

@Caball009 Caball009 Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to change to std::vector<Coord2D>? As far as I can tell, the z coordinate is irrelevant for the radar and the hero coordinates are updated each frame, so using a pointer is not an advantage when the size of 2D coordinates is 2 * sizeof(float).

Changes:
void W3DRadar::drawHeroIcon( Int pixelX, Int pixelY, Int width, Int height, const Coord3D *pos )
->
void W3DRadar::drawHeroIcon( Int pixelX, Int pixelY, Int width, Int height, Coord2D pos )

m_cachedHeroPosList.push_back(obj->getPosition());
->
m_cachedHeroPosList.push_back(Coord2D(obj->getPosition()->x, obj->getPosition()->y));

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The position changes when the Hero moves. Putting a fixed position into the vector would paint the radar icon at a static position.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it would. I just tried it and the position is not fixed on the radar. It wouldn't be if the position is updated each frame; i.e. if W3DRadar::renderObjectList is called for each frame.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect you test with calcHero code? The calcHero rebuilds the vector every now and then. This change does not.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sorry.

@xezon xezon modified the milestones: Major bug fixes, Stability fixes Jun 9, 2025
@xezon xezon added Stability Concerns stability of the runtime and removed Memory Is memory related labels Jun 9, 2025
@Caball009
Copy link

The stl namespace helper functions are a nice addition. I find myself wishing for C++98 support for lambdas because that would make it very easy to write two more helper functions that erase all elements, based on any sort of predicate.

@xezon xezon requested a review from a team June 13, 2025 10:19
@xezon xezon requested a review from Caball009 June 25, 2025 21:32
@Mauller
Copy link

Mauller commented Jul 3, 2025

I think this entire change can be made far simpler and have it touch fewer places by just changing the original design to cache the object ID of the hero, then retrieve the object by its object ID in the loop and check if it is null or not before continuing or removing the object ID from the cache.

This is how quite a few other systems already work.

@xezon
Copy link
Author

xezon commented Jul 3, 2025

I think this entire change can be made far simpler and have it touch fewer places by just changing the original design to cache the object ID of the hero, then retrieve the object by its object ID in the loop and check if it is null or not before continuing or removing the object ID from the cache.

This is how quite a few other systems already work.

The disadvantage of this approach is that every iteration needs to test if the object is valid, and then remove it if it no longer is used. Or it needs a prior cleanup update to detect which objects have been deleted.

With the proposed implementation, we have a guarantee that all hero objects are valid and no headache.

Also note that class Radar also maintains a list of all other Radar objects, and it also holds pointer to Object. So this is consistent with how it is done in class Radar.

Also if id lookups can be avoided, then we should do that, because id lookups likely have additional cost, some more than others.

@Mauller
Copy link

Mauller commented Jul 3, 2025

I think this entire change can be made far simpler and have it touch fewer places by just changing the original design to cache the object ID of the hero, then retrieve the object by its object ID in the loop and check if it is null or not before continuing or removing the object ID from the cache.
This is how quite a few other systems already work.

The disadvantage of this approach is that every iteration needs to test if the object is valid, and then remove it if it no longer is used. Or it needs a prior cleanup update to detect which objects have been deleted.

With the proposed implementation, we have a guarantee that all hero objects are valid and no headache.

Also note that class Radar also maintains a list of all other Radar objects, and it also holds pointer to Object. So this is consistent with how it is done in class Radar.

Also if id lookups can be avoided, then we should do that, because id lookups likely have additional cost, some more than others.

yes but it also keeps objects and the radar decoupled.

I also did not realise that the underlying radar class keeps a list of objects to be seen on the radar. Objects themselves also have to know about the radar too, which is quite nasty, as they removed themselves from it on destruction etc.

But i can see where you were going with this overall.

@xezon
Copy link
Author

xezon commented Jul 3, 2025

yes but it also keeps objects and the radar decoupled.

If we want to decouple then it would be better to use a listener/event approach.

Copy link

@Mauller Mauller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this is okay overall with it keeping to what the radar base class already does.

@xezon xezon force-pushed the xezon/fix-w3dradar-hero-bug branch from e6437b9 to 114ce85 Compare July 5, 2025 16:48
@xezon
Copy link
Author

xezon commented Jul 5, 2025

Rebased on latest main. Cmake changes removed. Replicated in Generals with 1 small conflict

$ git diff c14b5ae50d27ac8e3fd166db52c60db9cb5b1f40..d9d40c1c40b73c56b4d338d86cf6ef159bf17cb7 > changes.patch

$ git apply -p2 --directory=Generals --reject --whitespace=fix changes.patch
changes.patch:98: space before tab in indent.
        Bool tryEvent( RadarEventType event, const Coord3D *pos );      ///< try to make a "stealth" event
changes.patch:239: trailing whitespace.
        void interpolateColorForHeight( RGBColor *color,
changes.patch:240: trailing whitespace.
                                                                                                                          Real height,
changes.patch:241: trailing whitespace.
                                                                                                                          Real hiZ,
changes.patch:243: trailing whitespace.
        Real m_viewZoom;                                                                                                  ///< camera zoom used for the view box we have
Checking patch Generals/GameEngine/CMakeLists.txt...
error: Generals/GameEngine/CMakeLists.txt: No such file or directory
Checking patch Generals/GameEngine/Include/Common/STLUtils.h...
Checking patch Generals/Code/GameEngine/Include/Common/GameEngine.h...
Hunk #1 succeeded at 85 (offset 1 line).
Checking patch Generals/Code/GameEngine/Include/Common/Radar.h...
Checking patch Generals/Code/GameEngine/Include/Common/STLTypedefs.h...
Checking patch Generals/Code/GameEngine/Source/Common/GameEngine.cpp...
error: while searching for:
        initDisabledMasks();
        initDamageTypeFlags();

        TheSubsystemList->resetAll();
        HideControlBar();
}  // end init


error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:682
Hunk #2 succeeded at 528 (offset -174 lines).
Hunk #3 succeeded at 545 (offset -174 lines).
Checking patch Generals/Code/GameEngine/Source/Common/System/Radar.cpp...
Hunk #1 succeeded at 394 (offset -3 lines).
Hunk #2 succeeded at 544 (offset -3 lines).
Hunk #3 succeeded at 591 (offset -3 lines).
Checking patch Generals/Code/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h...
Checking patch Generals/Code/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp...
Hunk #1 succeeded at 608 (offset 2 lines).
Hunk #2 succeeded at 619 (offset 2 lines).
Hunk #3 succeeded at 637 (offset 2 lines).
Hunk #4 succeeded at 646 (offset 2 lines).
Hunk #5 succeeded at 990 (offset 6 lines).
Hunk #6 succeeded at 1433 (offset 12 lines).
Hunk #7 succeeded at 1492 (offset 12 lines).
Hunk #8 succeeded at 1512 (offset 12 lines).
Hunk #9 succeeded at 1524 (offset 12 lines).
Applied patch Generals/GameEngine/Include/Common/STLUtils.h cleanly.
Applied patch Generals/Code/GameEngine/Include/Common/GameEngine.h cleanly.
Applied patch Generals/Code/GameEngine/Include/Common/Radar.h cleanly.
Applied patch Generals/Code/GameEngine/Include/Common/STLTypedefs.h cleanly.
Applying patch Generals/Code/GameEngine/Source/Common/GameEngine.cpp with 1 reject...
Rejected hunk #1.
Hunk #2 applied cleanly.
Hunk #3 applied cleanly.
Applied patch Generals/Code/GameEngine/Source/Common/System/Radar.cpp cleanly.
Applied patch Generals/Code/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h cleanly.
Applied patch Generals/Code/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp cleanly.

@xezon
Copy link
Author

xezon commented Jul 5, 2025

This change does not touch RTS_INTERNAL or Logging, so should be ok to merge now.

@xezon xezon merged commit d8af372 into TheSuperHackers:main Jul 5, 2025
15 checks passed
@xezon xezon deleted the xezon/fix-w3dradar-hero-bug branch July 5, 2025 17:58
Copy link
Collaborator

@barefootlogician barefootlogician left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not have been merged. It significantly reduces maintainability

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this not added in the utility folder instead and why are two copies of the same file being added? Also why are we not using standard library algorithms instead?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why are we not using standard library algorithms instead?

What do you propose we use instead?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove erase idiom

bool W3DRadar::removeObject(Object* obj)
{
    bool removed = Radar::removeObject(obj);
    if (!removed)
	    return false;
    if (obj->isHero())
	    m_cachedHeroObjectList.erase(std::remove(m_cachedHeroObjectList.begin(), m_cachedHeroObjectList.end(), obj), m_cachedHeroObjectList.end());
    return true;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for find_and_erase_unordered, after we drop vc6 we can create an rts::unstable_remove function and use it in places in the code where it can be demonstrated with profiling that it yields a significant performance improvement.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is to be considered an improvement:
stl::find_and_erase_unordered(m_cachedHeroObjectList, obj);
->
m_cachedHeroObjectList.erase(std::remove(m_cachedHeroObjectList.begin(), m_cachedHeroObjectList.end(), obj), m_cachedHeroObjectList.end());

we can create an rts::unstable_remove function and use it in places in the code where it can be demonstrated with profiling that it yields a significant performance improvement

Frankly, I disagree as not every improvement needs to be measured. In my opinion, improvements should not affect correctness naturally, and should not be disproportionate to the scope of the change. The scope of the change here is trivial.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is O n/2 not a thing :D . It is to me. Going through half the range on average is faster than going through the full range always.

But why not use std::set instead

Because std::set is not space efficient/contiguous. We use std::vector because READ is most important per frame. Insert/erase happens sparingly and is therefore not performance priority.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is O n/2 not a thing :D . It is to me. Going through half the range on average is faster than going through the full range always.

But why not use std::set instead

Because std::set is not space efficient/contiguous. We use std::vector because READ is most important per frame. Insert/erase happens sparingly and is therefore not performance priority.

Because thats how Big O notation works. Its about how the value scales in respect to scaling the input size n. Coefficients are dropped because they would be the same in the scaled value and the previous value and cancel out.

If it wasn't this way then I could write a helper function bar that only searches 10 elements. Obiously the performance of bar is always fixed ie constant time. Now I could write a function foo that iterates in steps of size 10 and calls bar on the next 10 elements. Technically foo would need 10 times less iterations than a function that that iterated with a step size of 1. Now I could claim I've created a search function thats 10 times faster because it has a time complexity of O (n/10) instead of O (n)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will name that C n/2 then.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well foo is C n/20 then

xezon added a commit that referenced this pull request Jul 8, 2025
Enemy and friendly Hero units no longer show on Radar and no longer crash the game on save/load
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Stability Concerns stability of the runtime ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sometimes the Radar shows wrong Hero position markers

5 participants