diff --git a/PythonClient/tests/smoke_annotation_lifecycle.py b/PythonClient/tests/smoke_annotation_lifecycle.py new file mode 100644 index 000000000..48237fe14 --- /dev/null +++ b/PythonClient/tests/smoke_annotation_lifecycle.py @@ -0,0 +1,36 @@ +"""Regression for dynamic annotation cleanup. Use a disposable simulator.""" +import argparse +import time +import uuid +import cosysairsim as airsim + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--port', type=int, required=True) + args = parser.parse_args() + client = airsim.VehicleClient(port=args.port, timeout_value=15) + assert client.ping() + assert 'Cube' in client.simListAssets(), 'Test level must include a Cube asset' + prefix = 'AnnotationAudit_' + uuid.uuid4().hex[:8] + for cycle in range(2): + name = client.simSpawnObject(prefix, 'Cube', airsim.Pose(airsim.Vector3r(10, 0, -10)), + airsim.Vector3r(1, 1, 1), False, False) + try: + names = [key for key in client.simListInstanceSegmentationObjects() if prefix in key] + assert len(names) == 1, names + key = names[0] + # The render proxy is created after the initial spawn registration. + time.sleep(1) + assert client.simSetSegmentationObjectID(key, 1000 + cycle) + assert client.simGetSegmentationObjectID(key) == 1000 + cycle + finally: + assert client.simDestroyObject(name), name + time.sleep(0.5) + assert not any(prefix in key for key in client.simListInstanceSegmentationObjects()) + assert client.simGetSegmentationObjectID(key) == -1 + print('PASS: two spawn/render/update/destroy cycles, no stale annotation names or IDs') + + +if __name__ == '__main__': + main() diff --git a/Unreal/Plugins/AirSim/Source/Annotation/ObjectAnnotator.cpp b/Unreal/Plugins/AirSim/Source/Annotation/ObjectAnnotator.cpp index d513e5524..2e150bb0a 100644 --- a/Unreal/Plugins/AirSim/Source/Annotation/ObjectAnnotator.cpp +++ b/Unreal/Plugins/AirSim/Source/Annotation/ObjectAnnotator.cpp @@ -3,8 +3,6 @@ // Licensed under the MIT License. #include "ObjectAnnotator.h" #include "Runtime/Engine/Public/EngineUtils.h" -#include "SceneInterface.h" -#include "../Private/ScenePrivate.h" #include "Runtime/Launch/Resources/Version.h" #include "AnnotationComponent.h" #include "AirBlueprintLib.h" @@ -74,16 +72,8 @@ void FObjectAnnotator::getPaintableComponentMeshes(AActor* actor, TMapGetUniqueID(); - if (const UPrimitiveComponent* PrimitiveComp = Cast(component)) - { - if (const FPrimitiveSceneProxy* SceneProxy = PrimitiveComp->SceneProxy) - { - int32 PersistentPrimitiveIndexTemp = SceneProxy->GetPrimitiveSceneInfo()->GetPersistentIndex().Index; - if (PersistentPrimitiveIndexTemp != -1) - PersistentPrimitiveIndex = PersistentPrimitiveIndexTemp; - } - } + // UObject identity remains stable when the render state is recreated. + const uint32 PersistentPrimitiveIndex = component->GetUniqueID(); if (paintable_components.Num() == 1) { if (UStaticMeshComponent* staticmesh_component = Cast(component)) { if (actor->GetParentActor()) { @@ -174,16 +164,8 @@ void FObjectAnnotator::getPaintableComponentMeshesAndTags(AActor* actor, TMapGetUniqueID(); - if (const UPrimitiveComponent* PrimitiveComp = Cast(component)) - { - if (const FPrimitiveSceneProxy* SceneProxy = PrimitiveComp->SceneProxy) - { - int32 PersistentPrimitiveIndexTemp = SceneProxy->GetPrimitiveSceneInfo()->GetPersistentIndex().Index; - if (PersistentPrimitiveIndexTemp != -1) - PersistentPrimitiveIndex = PersistentPrimitiveIndexTemp; - } - } + // UObject identity remains stable when the render state is recreated. + const uint32 PersistentPrimitiveIndex = component->GetUniqueID(); if (paintable_components.Num() == 1) { if (UStaticMeshComponent* staticmesh_component = Cast(component)) { if (actor->GetParentActor()) { @@ -712,28 +694,23 @@ bool FObjectAnnotator::AnnotateNewActorTexture(AActor* actor) { bool FObjectAnnotator::DeleteActor(AActor* actor) { - if (actor && IsPaintable(actor)) { - TMap paintable_components_meshes; - getPaintableComponentMeshes(actor, &paintable_components_meshes); - for (auto it = paintable_components_meshes.CreateConstIterator(); it; ++it) - { - if (name_to_component_map_.Contains(it.Key())) { - component_to_name_map_.Remove(it.Value()); - check(DeleteComponent(it.Value())); - name_to_component_map_.Remove(it.Key()); - //UE_LOG(LogTemp, Log, TEXT("AirSim Annotation [%s]: Deleted object %s."), *name_, *it.Key()); - - } - else { - UE_LOG(LogTemp, Log, TEXT("AirSim Annotation [%s]: could not delete object %s."), *name_, *it.Key()); - return false; - } - } - return true; - } - else { - return false; - } + if (!IsValid(actor)) return false; + TArray components; + actor->GetComponents(components); + for (UMeshComponent* component : components) { + // Resolve the original registration, not a freshly generated render index. + const FString* registered_name = component_to_name_map_.Find(component); + if (registered_name == nullptr) continue; + const FString component_name = *registered_name; + verify(DeleteComponent(component)); + component_to_name_map_.Remove(component); + name_to_component_map_.Remove(component_name); + name_to_color_index_map_.Remove(component_name); + name_to_gammacorrected_color_map_.Remove(component_name); + name_to_value_map_.Remove(component_name); + name_to_texture_path_map_.Remove(component_name); + } + return true; } uint32 FObjectAnnotator::GetComponentIndex(FString component_id) @@ -1216,7 +1193,7 @@ TArray> FObjectAnnotator::GetAnnotationComp std::vector FObjectAnnotator::GetAllComponentNames() { std::vector retval; - TMap nameToColorIndexMapTemp = name_to_color_index_map_; + TMap nameToColorIndexMapTemp = name_to_component_map_; for (auto const& element : nameToColorIndexMapTemp) { retval.emplace_back(std::string(TCHAR_TO_UTF8(*element.Key))); } diff --git a/Unreal/Plugins/AirSim/Source/WorldSimApi.cpp b/Unreal/Plugins/AirSim/Source/WorldSimApi.cpp index 685c75446..578f5970d 100644 --- a/Unreal/Plugins/AirSim/Source/WorldSimApi.cpp +++ b/Unreal/Plugins/AirSim/Source/WorldSimApi.cpp @@ -66,6 +66,7 @@ bool WorldSimApi::destroyObject(const std::string& object_name) UAirBlueprintLib::RunCommandOnGameThread([this, &object_name, &result]() { AActor* actor = UAirBlueprintLib::FindActor(simmode_, FString(object_name.c_str())); if (actor) { + simmode_->DeleteActorFromInstanceSegmentation(actor, true); actor->Destroy(); result = !IsValid(actor); }