From ac4cd6b979df78a8139e7ded64fec82a5c465af9 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 3 Apr 2026 20:42:41 -0700 Subject: [PATCH] Fix crash when performer's child ROIs are left dangling in ScenePlayer NotifyROIDestroyed now walks the parent chain to also invalidate child ROIs of the destroyed performer (head, limbs, etc.) that were placed into the roiMap by BuildROIMap. The ancestor walk happens once; all other fields are cleaned with simple pointer equality. --- .../src/multiplayer/animation/sceneplayer.cpp | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/extensions/src/multiplayer/animation/sceneplayer.cpp b/extensions/src/multiplayer/animation/sceneplayer.cpp index 952f469f..c22e200b 100644 --- a/extensions/src/multiplayer/animation/sceneplayer.cpp +++ b/extensions/src/multiplayer/animation/sceneplayer.cpp @@ -594,38 +594,66 @@ void ScenePlayer::NotifyROIDestroyed(LegoROI* p_roi) return; } - for (auto& p : m_participants) { - if (p.roi == p_roi) { - p.roi = nullptr; - } - if (p.vehicleROI == p_roi) { - p.vehicleROI = nullptr; - } - } + // Walk the m_roiMap once to find p_roi and all its descendants (child ROIs + // are destroyed together with their parent). Collect them so every other + // field can be cleaned with simple pointer equality — the ancestor walk + // happens in exactly one place. + std::vector destroyed; + destroyed.push_back(p_roi); if (m_roiMap) { for (MxU32 i = 0; i < m_roiMapSize; i++) { - if (m_roiMap[i] == p_roi) { - m_roiMap[i] = nullptr; + if (!m_roiMap[i]) { + continue; + } + + for (OrientableROI* cur = m_roiMap[i]; cur != nullptr; cur = cur->GetParentROI()) { + if (cur == p_roi) { + if (m_roiMap[i] != p_roi) { + destroyed.push_back(m_roiMap[i]); + } + m_roiMap[i] = nullptr; + break; + } } } } + auto isDestroyed = [&destroyed](LegoROI* roi) { + for (LegoROI* d : destroyed) { + if (roi == d) { + return true; + } + } + return false; + }; + + for (auto& p : m_participants) { + if (p.roi && isDestroyed(p.roi)) { + p.roi = nullptr; + } + if (p.vehicleROI && isDestroyed(p.vehicleROI)) { + p.vehicleROI = nullptr; + } + } + for (auto& roi : m_ptAtCamROIs) { - if (roi == p_roi) { + if (roi && isDestroyed(roi)) { roi = nullptr; } } - if (m_animRootROI == p_roi) { + if (m_animRootROI && isDestroyed(m_animRootROI)) { m_animRootROI = nullptr; } - if (m_vehicleROI == p_roi) { + if (m_vehicleROI && isDestroyed(m_vehicleROI)) { m_vehicleROI = nullptr; } - m_phonemePlayer.NotifyROIDestroyed(p_roi); + for (LegoROI* d : destroyed) { + m_phonemePlayer.NotifyROIDestroyed(d); + } } void ScenePlayer::CleanupProps()