I have a TMap being filled during the editor mode, and I want to use it during PIE. But whenever I try and make an iterator or access it, the game crashes with “Access violation reading location 0x0000000000000000.”
I’m using this TMap in a A* navigation system. It stores a struct representing an edge (key of map), and the cost of the edge (element of map).
In beginPlay() during PIE I’m printing to the log the size of edgeMap.Num(), and it says 0. I can retrigger the method to fill it, which then says that edgeMap.Num() is the expected number. But I still get the violation crash.
I read that TMaps still cant be made a UPROPERTY(), but doing that re-filling^ still causes a crash.
I can share more code if it helps. Thanks for looking.
It’s hard to tell what the issue is without more context (e.g. the stack trace of the crash and the code around it), but one possible issue is that you’re using a USTRUCT with naked pointers. Unreal treats USTRUCTs differently than UCLASSs, and your AVASFVVolumes and TMap may be reclaimed by the garbage collector before you are done with them.
Check if changing the USTRUCT to UCLASS fixes the issue.
Today I changed my struct to it’s own UObject, gotta admit it looks a lot cleaner ha. Because of that, I’m also allowed to make the TMap edgeMap; a UPROPERTY(), without the compiler complaining.
Now I can set up my TMap in the editor, and start PIE, and edgeMap.Num() stays the correct size.
I get to a point in my code though that the TMap is iterated through to find all the neighbouring volumes (the volumeBs). In my A* navigation, a volume might be connected to many other volumes.
What I’m finding though is that when I’m iterating through:
for (auto It = edgeMap.CreateConstIterator(); It; ++It)
if (It.Key()->volumeA() == currentVolume) //test both pointers point to the same thing
then It.Key()->volumeA() == currentVolume is never true. Despite the UObject definitely being instantiated and added to the TMap. When I put some debug logging into that loop to print out volumeA’s and currentVolume’s GetHumanReadableName, they are both the same. But the objects GetUniqueID are different!
The currentVolume is getting assigned during PIE, and is a pointer to a AVASFVVolume. As is volumeA, as you can see in my original struct (now a UObject).
Hmm… I can’t see where AVASFVVolume’s are created, so I can’t confirm these are actually the same object.
I think the gotcha here is that, when you Play-In-Editor, actors are duplicated from the original editor objects (as per UObject::PostDuplicate(bool bDuplicateForPIE)), so somehow your code is checking equality between a pointer to the original actor and a pointer to the duplicated actor.
Is there some other property you could use for checking equality? Could you use some other property (e.g. dimensions or name) for checking equality of AVASFVVolume *?
I was able to use the GetHumanReadableName to check equality fine, thanks!
But now I just have the original crashing problem that when I try to use .Add or .Find on the TMap I get Exception thrown: read access violation.
Each AVASFVVolume has a UPROPERTY(EditAnywhere) TArray<AVASFVVolume*> connections; so that you can create the volume-volume connections in the editor (and save the level). Then when PIE begins, all volumes are iterated through and a UNavEdge is created and added to TMap UPROPERTY() TMap<UNavEdge*, float> edgeMap;, but this crashes when the add happens.
Are the AVASFVVolume pointers created in the editor not transformed when PIE begins to be pointers to the duplicated actors? If not, then how could I possibly accomplish a TMap of these connections? If I create the TMap during the editor mode, then it will also be full of editor pointers right?
I’m really clueless, but here’s my best guess:
When calling NewObject(UNavEdge) you are not specifying an Outer. I don’t know the consequences of this, but I do know that ActorComponents are always outered to their actor and that StaticDuplicateObject also copies objects inside the target object. Try providing AVolumeNavigator as the Outer and see if that fixes it.
Another thought, which might be relevant: TMap is a hashing collection. This means that objects are stored in buckets based on their computed hash value. BUT your hash function UNavEdge::GetTypeHash is based on the pointers inside NavEdge. So anything that moves a AVASFVVolume * in memory will invalidate your TMap. Something could be copying your map and its contents without rehashing (which will cause things to be in the wrong bucket) or something is causing your NavEdges to be moved around in memory. It’s possible that UObjects are not supported in a UPROPERTY hashing collection — I can’t find any examples of doing this in the engine code.