To reproduce this, you can easily create an actor as described, and then just start assigning actors to it in editor. My guess, though, is that in a test level, this works fine all day long. The level I’m working on is quite complex with lots of level instances and content plugins and all sorts of stuff. It’s also using world partition. I’m not sure if that is a contributing factor as well. I’ve attached a call-stack, if that’s helpful. This always seems to be happening in the post-edit-change-property callback.
Steps to Reproduce
If I use the “Place Actors” panel to pull a custom C++ actor into a level, I can then look at the properties of the actor. If one of those properties is an actor pointer, then I have little trouble assigning an actor to it using the details panel when the actor is selected. However! If one of the properties of the said actor is an array of structures, each structure owning an actor pointer, then the details panel handles this structure just fine up until the point where I try to assign an actor, and then it crashes, but not always. In a test level, it works fine without crashing. In a more complicated level, I get random crashing. It may depend on the actor I choose. In my 100% crash case, I’m choosing an actor in the same level as the actor whose properties I’m trying to change, so I don’t think it’s a cross-level or cross-map issue. In any case, the single actor pointer property doesn’t exhibit this same unstable behavior. It’s just a problem with an array of structs. Is it just a bad idea to try to make a property that is an array of structs, each struct having its own actor pointer properties?
I’ve attached a callback of the crash. I’ve attached a screen-shot of the details panel. In that screenshot, I just assign an actor to “Actor A” and then the editor blows up.
Am I just doing something bad in the way I’m designing my actor? Am I missing some preprocessor property tags? I’m using “EditAnywhere”, but that’s about it.
Thanks for any ideas you may have.
--Spencer
I think the problem was that we had some code that intercepts all property changes, and when it does, it calls the function…
Event.Property->ExportText_InContainer(0, ValueAsString, Object, Object, nullptr, 0);
…which may be correct in some cases, but not all. For example, if you have a AActor* property or a TArray<Actor*> property, then it doesn’t crash. (And who knows if it’s not stomping memory in those cases anyway.) But in the case of TArray<MyStruct> with MyStruct = struct { AActor* };, it crashes.
So my solution was to delete the offending code since it wasn’t being used anyway.
Hi Spencer,
Nothing immediately comes to mind as to why this would work as expected in a TArray but break in a TArray of UStructs. I don’t think I can see the callstack you mention, would you mind trying to upload that again?
I have also put together a test case, both using a World Partition level and a non-WP level, and like your test level I too cannot reproduce the issue. This does make me at least think that this isn’t due to unloaded World Partition Actors or some strange behaviour to do with circular Actor pointers, at least on this small scale.
Would you be able to also share a snippet of the relevant properties inside of your Actor, the UStruct, and the UMeta specifiers on the Actor itself? In particular I’m quite interested in if you’re using regular AActor* or a TObjectPtr, and the specifiers on the UStruct, Actor, and Properties.
My assumption is that this Actor array would default empty, and you would manually add Actors to the instance once placed in the world. In this situation you shouldn’t need the Instanced UMeta specifier, but if my assumption is wrong about the default state of the array and your particular use case then you may want this. This would be the only other specifier that I can think would be relevant at the moment.
Lastly for now, if you are able to get a somewhat stable repro of the issue in a small project you can share, that would be very valuable to look through.
Thanks,
Hayden
Hi, Hayden.
Sorry it took so long for me to get back to this. Attached is the call stack and some code snippets.
It probably doesn’t matter if it’s just an actor pointer property, array of actor pointers, array of structs of actor pointers, but I need to verify that. There’s just something fishy going on where I can’t pull an actor into the level and assign actor properties to it. It probably just exposes a massive hole in my knowledge of Unreal, I don’t know, but in any case, the editor just…in my opinion…it just shouldn’t ever crash when you’re trying to assign a property on an actor. It’s not as if it’s running any code that I wrote, in which case, my code could then be called into question as a possible cause of the crash, no matter where in the call stack the crash occurs. But this just seems to me like a bug that has to be in the editor itself.
Anyhow, I’m going to play around with this some more today and see if I can learn anything new. Even after compiling a full debug build of the editor and then stepping through the bowels of the editor/engine, it is very hard to understand what in the world is going on.
Thanks,
--Spencer
Okay, I just tested it. The struct does matter! I can use an actor pointer and it works fine. I can use an array of actor pointers and it works fine. BUT! If I use an array of structs of actor pointers, then it crashes! I attached what may be a more accurate call stack. (Unless I’m going crazy.)
Anyhow, this has everything to do with the way the data is structured. Unreal just doesn’t like it.
I lied. I think we do have some custom code that is handling the property change event, and the crash is happening inside that custom code. See “UnrealEditor-CatalystEditor.dll!UActorPropertyChangeListenerSubSystem::HandlePropertyChanged”. That’s our code, and it’s doing something dumb, probably.
Hi Spencer,
I’m glad to hear you were able to track down the problematic code and arrive at a solution.
Admittedly, I’m not too familiar with ExportText_InContainer(), but (with some assumptions on the rest of your code) my guess as to what is going wrong would be as follows:
- First of all, ExportText_InContainer() doesn’t extract data recursively through containers, it just tries to get a value pointer for the data at the index you provide. This would still be a FMyStruct at this point, not your Actor pointer. Containers in this context would be an array, set, or map. If the assumption was to be dealing with an Actor pointer only at this point, this wouldn’t be the case.
- It’s more than likely just failing to pass around your struct as a void* as it makes its way through ContainerPtrToValuePtr() on its way to produce a uint8* value pointer.
I would expect some additional type checking plus safety around the kind of property that changed would help here, if you don’t have it already, i.e.
if (PropertyChangedEvent.Property->IsA<FArrayProperty>() || PropertyChangedEvent.Property->IsA<FSetProperty>() || PropertyChangedEvent.Property->IsA<FMapProperty>()) ...
If you’re happy with the solution thus far, let me know and I will go ahead and close this case.
Thanks,
Hayden
Feel free to close the case, and thank you for your help, Hayden. I would also say that this “ExportText_InContainer()” feels to me like a lower-level function, and maybe not meant to be part of the official UE API, if there even is such a thing.