PS_Win_BLE: fix crash on disconnect - use TWeakObjectPtr in all async dispatches

All Dispatch* functions now capture UPS_BLE_Device via TWeakObjectPtr instead of
a raw pointer, preventing an access violation when the UObject is garbage collected
before the GameThread lambda executes (EXCEPTION_ACCESS_VIOLATION @ 0x60).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
j.foucher 2026-02-19 09:25:16 +01:00
parent 10ca1e19c7
commit aa8df59af2

View File

@ -664,10 +664,12 @@ void UPS_BLE_Module::DispatchDeviceConnected(UPS_BLE_Device* Dev)
}
else
{
AsyncTask(ENamedThreads::GameThread, [Dev]()
TWeakObjectPtr<UPS_BLE_Device> WeakDev(Dev);
AsyncTask(ENamedThreads::GameThread, [WeakDev]()
{
Dev->RefToManager->JustConnectedDevice(Dev);
Dev->OnConnect.Broadcast(Dev, Dev->ActiveServices);
if (!WeakDev.IsValid()) return;
WeakDev->RefToManager->JustConnectedDevice(WeakDev.Get());
WeakDev->OnConnect.Broadcast(WeakDev.Get(), WeakDev->ActiveServices);
});
}
}
@ -682,10 +684,12 @@ void UPS_BLE_Module::DispatchDeviceDisconnected(UPS_BLE_Device* Dev)
}
else
{
AsyncTask(ENamedThreads::GameThread, [Dev]()
TWeakObjectPtr<UPS_BLE_Device> WeakDev(Dev);
AsyncTask(ENamedThreads::GameThread, [WeakDev]()
{
Dev->RefToManager->JustDisconnectedDevice(Dev);
Dev->OnDisconnect.Broadcast(Dev);
if (!WeakDev.IsValid()) return;
WeakDev->RefToManager->JustDisconnectedDevice(WeakDev.Get());
WeakDev->OnDisconnect.Broadcast(WeakDev.Get());
});
}
}
@ -700,10 +704,12 @@ void UPS_BLE_Module::DispatchServicesDiscovered(UPS_BLE_Device* Dev)
}
else
{
AsyncTask(ENamedThreads::GameThread, [Dev]()
TWeakObjectPtr<UPS_BLE_Device> WeakDev(Dev);
AsyncTask(ENamedThreads::GameThread, [WeakDev]()
{
Dev->RefToManager->JustDiscoveredServices(Dev);
Dev->OnServicesDiscovered.Broadcast(Dev, Dev->ActiveServices);
if (!WeakDev.IsValid()) return;
WeakDev->RefToManager->JustDiscoveredServices(WeakDev.Get());
WeakDev->OnServicesDiscovered.Broadcast(WeakDev.Get(), WeakDev->ActiveServices);
});
}
}
@ -719,11 +725,13 @@ void UPS_BLE_Module::DispatchRead(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS_G
}
else
{
AsyncTask(ENamedThreads::GameThread, [Dev, SI, CI, Status, Data]()
TWeakObjectPtr<UPS_BLE_Device> WeakDev(Dev);
AsyncTask(ENamedThreads::GameThread, [WeakDev, SI, CI, Status, Data]()
{
if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num())
Dev->OnRead.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID,
Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data);
if (!WeakDev.IsValid()) return;
if (SI < WeakDev->ActiveServices.Num() && CI < WeakDev->ActiveServices[SI].Characteristics.Num())
WeakDev->OnRead.Broadcast(Status, WeakDev.Get(), WeakDev->ActiveServices[SI].ServiceUUID,
WeakDev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data);
});
}
}
@ -739,11 +747,13 @@ void UPS_BLE_Module::DispatchNotify(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS
}
else
{
AsyncTask(ENamedThreads::GameThread, [Dev, SI, CI, Status, Data]()
TWeakObjectPtr<UPS_BLE_Device> WeakDev(Dev);
AsyncTask(ENamedThreads::GameThread, [WeakDev, SI, CI, Status, Data]()
{
if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num())
Dev->OnNotify.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID,
Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data);
if (!WeakDev.IsValid()) return;
if (SI < WeakDev->ActiveServices.Num() && CI < WeakDev->ActiveServices[SI].Characteristics.Num())
WeakDev->OnNotify.Broadcast(Status, WeakDev.Get(), WeakDev->ActiveServices[SI].ServiceUUID,
WeakDev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data);
});
}
}
@ -751,11 +761,13 @@ void UPS_BLE_Module::DispatchNotify(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS
void UPS_BLE_Module::DispatchWrite(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS_GATTStatus Status)
{
if (!Dev) return;
auto Fire = [Dev, SI, CI, Status]()
TWeakObjectPtr<UPS_BLE_Device> WeakDev(Dev);
auto Fire = [WeakDev, SI, CI, Status]()
{
if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num())
Dev->OnWrite.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID,
Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID);
if (!WeakDev.IsValid()) return;
if (SI < WeakDev->ActiveServices.Num() && CI < WeakDev->ActiveServices[SI].Characteristics.Num())
WeakDev->OnWrite.Broadcast(Status, WeakDev.Get(), WeakDev->ActiveServices[SI].ServiceUUID,
WeakDev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID);
};
if (IsInGameThread()) Fire();
else AsyncTask(ENamedThreads::GameThread, Fire);
@ -764,13 +776,15 @@ void UPS_BLE_Module::DispatchWrite(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS_
void UPS_BLE_Module::DispatchSubscribe(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS_GATTStatus Status)
{
if (!Dev) return;
auto Fire = [Dev, SI, CI, Status]()
TWeakObjectPtr<UPS_BLE_Device> WeakDev(Dev);
auto Fire = [WeakDev, SI, CI, Status]()
{
if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num())
if (!WeakDev.IsValid()) return;
if (SI < WeakDev->ActiveServices.Num() && CI < WeakDev->ActiveServices[SI].Characteristics.Num())
{
Dev->ActiveServices[SI].Characteristics[CI].subscribed = true;
Dev->OnSubscribe.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID,
Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID);
WeakDev->ActiveServices[SI].Characteristics[CI].subscribed = true;
WeakDev->OnSubscribe.Broadcast(Status, WeakDev.Get(), WeakDev->ActiveServices[SI].ServiceUUID,
WeakDev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID);
}
};
if (IsInGameThread()) Fire();
@ -780,13 +794,15 @@ void UPS_BLE_Module::DispatchSubscribe(UPS_BLE_Device* Dev, uint8 SI, uint8 CI,
void UPS_BLE_Module::DispatchUnsubscribe(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS_GATTStatus Status)
{
if (!Dev) return;
auto Fire = [Dev, SI, CI, Status]()
TWeakObjectPtr<UPS_BLE_Device> WeakDev(Dev);
auto Fire = [WeakDev, SI, CI, Status]()
{
if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num())
if (!WeakDev.IsValid()) return;
if (SI < WeakDev->ActiveServices.Num() && CI < WeakDev->ActiveServices[SI].Characteristics.Num())
{
Dev->ActiveServices[SI].Characteristics[CI].subscribed = false;
Dev->OnUnsubscribe.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID,
Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID);
WeakDev->ActiveServices[SI].Characteristics[CI].subscribed = false;
WeakDev->OnUnsubscribe.Broadcast(Status, WeakDev.Get(), WeakDev->ActiveServices[SI].ServiceUUID,
WeakDev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID);
}
};
if (IsInGameThread()) Fire();