diff --git a/CHANGES.md b/CHANGES.md index 51d33e96..2776b413 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ##### Fixes :wrench: +- Fixed a bug that could cause a crash on AppDomain reloads. - Fixed a bug that could cause a crash or incorrect textures when multiple `Cesium3DTileset` tiles referenced the same image by URL. ## v1.14.0 - 2024-12-02 diff --git a/Reinterop~/CSharpObjectHandleUtility.cs b/Reinterop~/CSharpObjectHandleUtility.cs index d11d95e9..da0671e0 100644 --- a/Reinterop~/CSharpObjectHandleUtility.cs +++ b/Reinterop~/CSharpObjectHandleUtility.cs @@ -37,8 +37,15 @@ public static IntPtr CopyHandle(IntPtr handle) return handle; // Allocate a new GCHandle pointing to the same object. - GCHandle gcHandle = GCHandle.FromIntPtr(handle); - return GCHandle.ToIntPtr(GCHandle.Alloc(gcHandle.Target)); + try + { + GCHandle gcHandle = GCHandle.FromIntPtr(handle); + return GCHandle.ToIntPtr(GCHandle.Alloc(gcHandle.Target)); + } + catch (Exception) + { + return IntPtr.Zero; + } } public static void FreeHandle(IntPtr handle) @@ -50,14 +57,13 @@ public static void FreeHandle(IntPtr handle) { GCHandle.FromIntPtr(handle).Free(); } - catch (ArgumentException e) + catch (Exception) { // The "GCHandle value belongs to a different domain" exception tends // to happen on AppDomain reload, which is common in Unity. // Catch the exception to prevent it propagating through our native // code and blowing things up. // See: https://github.com/CesiumGS/cesium-unity/issues/18 - System.Console.WriteLine(e.ToString()); } } @@ -66,7 +72,14 @@ public static object GetObjectFromHandle(IntPtr handle) if (handle == IntPtr.Zero) return null; - return GCHandle.FromIntPtr(handle).Target; + try + { + return GCHandle.FromIntPtr(handle).Target; + } + catch (Exception) + { + return null; + } } public static object GetObjectAndFreeHandle(IntPtr handle) @@ -74,10 +87,17 @@ public static object GetObjectAndFreeHandle(IntPtr handle) if (handle == IntPtr.Zero) return null; - GCHandle gcHandle = GCHandle.FromIntPtr(handle); - object result = gcHandle.Target; - gcHandle.Free(); - return result; + try + { + GCHandle gcHandle = GCHandle.FromIntPtr(handle); + object result = gcHandle.Target; + gcHandle.Free(); + return result; + } + catch (Exception) + { + return null; + } } public static void ResetHandleObject(IntPtr handle, object newValue) diff --git a/native~/Editor/src/CesiumIonSessionImpl.cpp b/native~/Editor/src/CesiumIonSessionImpl.cpp index 8865b4f3..dad75514 100644 --- a/native~/Editor/src/CesiumIonSessionImpl.cpp +++ b/native~/Editor/src/CesiumIonSessionImpl.cpp @@ -5,6 +5,7 @@ #include "UnityTaskProcessor.h" #include +#include #include #include @@ -156,6 +157,8 @@ void CesiumIonSessionImpl::Connect( std::string ionServerUrl = server.serverUrl().ToStlString(); std::string ionApiUrl = server.apiUrl().ToStlString(); + CesiumUtility::IntrusivePointer pThis = this; + CesiumAsync::Future> futureApiUrl = !ionApiUrl.empty() ? this->_asyncSystem.createResolvedFuture>( @@ -169,7 +172,7 @@ void CesiumIonSessionImpl::Connect( .thenInMainThread([ionServerUrl, server, session, - this, + pThis, asyncSystem = this->_asyncSystem]( std::optional&& ionApiUrl) { CesiumAsync::Promise promise = asyncSystem.createPromise(); @@ -198,24 +201,24 @@ void CesiumIonSessionImpl::Connect( } // Make request to /appData to learn the server's authentication mode - return this->ensureAppDataLoaded(session); + return pThis->ensureAppDataLoaded(session); }) - .thenInMainThread([ionServerUrl, server, session, this]( + .thenInMainThread([ionServerUrl, server, session, pThis]( bool loadedAppData) { - if (!loadedAppData || !this->_appData.has_value()) { + if (!loadedAppData || !pThis->_appData.has_value()) { CesiumAsync::Promise promise = - this->_asyncSystem.createPromise(); + pThis->_asyncSystem.createPromise(); promise.reject(std::runtime_error( "Failed to load _appData, can't create connection")); return promise.getFuture(); } - if (this->_appData->needsOauthAuthentication()) { + if (pThis->_appData->needsOauthAuthentication()) { int64_t clientID = server.oauth2ApplicationID(); return CesiumIonClient::Connection::authorize( - this->_asyncSystem, - this->_pAssetAccessor, + pThis->_asyncSystem, + pThis->_pAssetAccessor, "Cesium for Unity", clientID, "/cesium-for-unity/oauth2/callback", @@ -225,44 +228,44 @@ void CesiumIonSessionImpl::Connect( "tokens:read", "tokens:write", "geocode"}, - [this](const std::string& url) { - this->_authorizeUrl = url; - this->_redirectUrl = + [pThis](const std::string& url) { + pThis->_authorizeUrl = url; + pThis->_redirectUrl = CesiumUtility::Uri::getQueryValue(url, "redirect_uri"); UnityEngine::Application::OpenURL(url); }, - this->_appData.value(), + pThis->_appData.value(), server.apiUrl().ToStlString(), CesiumUtility::Uri::resolve(ionServerUrl, "oauth")); } - return this->_asyncSystem + return pThis->_asyncSystem .createResolvedFuture( CesiumIonClient::Connection( - this->_asyncSystem, - this->_pAssetAccessor, + pThis->_asyncSystem, + pThis->_pAssetAccessor, "", - this->_appData.value(), + pThis->_appData.value(), server.apiUrl().ToStlString())); }) - .thenInMainThread([this, + .thenInMainThread([pThis, session](CesiumIonClient::Connection&& connection) { - this->_isConnecting = false; - this->_connection = std::move(connection); + pThis->_isConnecting = false; + pThis->_connection = std::move(connection); CesiumForUnity::CesiumIonServer server = session.server(); CesiumForUnity::CesiumIonServerManager::instance().SetUserAccessToken( server, - this->_connection.value().getAccessToken()); - this->_quickAddItems = nullptr; - this->broadcastConnectionUpdate(); + pThis->_connection.value().getAccessToken()); + pThis->_quickAddItems = nullptr; + pThis->broadcastConnectionUpdate(); }) - .catchInMainThread([this](std::exception&& e) { + .catchInMainThread([pThis](std::exception&& e) { DotNet::UnityEngine::Debug::Log(System::String(e.what())); - this->_isConnecting = false; - this->_connection = std::nullopt; - this->_quickAddItems = nullptr; - this->broadcastConnectionUpdate(); + pThis->_isConnecting = false; + pThis->_connection = std::nullopt; + pThis->_quickAddItems = nullptr; + pThis->broadcastConnectionUpdate(); }); } @@ -280,9 +283,11 @@ void CesiumIonSessionImpl::Resume( this->_isResuming = true; + CesiumUtility::IntrusivePointer pThis = this; + // Verify that the connection actually works. this->ensureAppDataLoaded(session) - .thenInMainThread([this, + .thenInMainThread([pThis, session, userAccessToken, server, @@ -290,31 +295,31 @@ void CesiumIonSessionImpl::Resume( CesiumAsync::Promise promise = asyncSystem.createPromise(); if (session == nullptr || !loadedAppData || - !this->_appData.has_value()) { + !pThis->_appData.has_value()) { promise.reject(std::runtime_error( "Failed to obtain _appData, can't resume connection")); return promise.getFuture(); } - if (this->_appData->needsOauthAuthentication() && + if (pThis->_appData->needsOauthAuthentication() && System::String::IsNullOrEmpty(userAccessToken)) { // No user access token was stored, so there's no existing session // to resume. promise.resolve(); - this->_isResuming = false; + pThis->_isResuming = false; return promise.getFuture(); } std::shared_ptr pConnection = std::make_shared( - this->_asyncSystem, - this->_pAssetAccessor, + pThis->_asyncSystem, + pThis->_pAssetAccessor, userAccessToken.ToStlString(), - this->_appData.value(), + pThis->_appData.value(), server.apiUrl().ToStlString()); return pConnection->me().thenInMainThread( - [this, session, pConnection]( + [pThis, session, pConnection]( CesiumIonClient::Response&& response) { if (session == nullptr) @@ -322,21 +327,21 @@ void CesiumIonSessionImpl::Resume( logResponseErrors(response); if (response.value.has_value()) { - this->_connection = std::move(*pConnection); + pThis->_connection = std::move(*pConnection); } - this->_isResuming = false; - this->_quickAddItems = nullptr; - this->broadcastConnectionUpdate(); + pThis->_isResuming = false; + pThis->_quickAddItems = nullptr; + pThis->broadcastConnectionUpdate(); - this->startQueuedLoads(session); + pThis->startQueuedLoads(session); }); }) - .catchInMainThread([this, session](std::exception&& e) { + .catchInMainThread([pThis, session](std::exception&& e) { if (session == nullptr) return; logResponseErrors(e); - this->_isResuming = false; + pThis->_isResuming = false; }); } @@ -433,28 +438,30 @@ void CesiumIonSessionImpl::refreshProfile( this->_isLoadingProfile = true; this->_loadProfileQueued = false; + CesiumUtility::IntrusivePointer pThis = this; + this->_connection->me() .thenInMainThread( - [this, session]( + [pThis, session]( CesiumIonClient::Response&& profile) { if (session == nullptr) return; - this->_isLoadingProfile = false; - this->_profile = std::move(profile.value); - this->broadcastProfileUpdate(); - if (this->_loadProfileQueued) - this->refreshProfile(session); + pThis->_isLoadingProfile = false; + pThis->_profile = std::move(profile.value); + pThis->broadcastProfileUpdate(); + if (pThis->_loadProfileQueued) + pThis->refreshProfile(session); }) - .catchInMainThread([this, session](std::exception&& e) { + .catchInMainThread([pThis, session](std::exception&& e) { if (session == nullptr) return; - this->_isLoadingProfile = false; - this->_profile = std::nullopt; - this->broadcastProfileUpdate(); - if (this->_loadProfileQueued) - this->refreshProfile(session); + pThis->_isLoadingProfile = false; + pThis->_profile = std::nullopt; + pThis->broadcastProfileUpdate(); + if (pThis->_loadProfileQueued) + pThis->refreshProfile(session); }); } @@ -473,28 +480,30 @@ void CesiumIonSessionImpl::refreshAssets( this->_isLoadingAssets = true; this->_loadAssetsQueued = false; + CesiumUtility::IntrusivePointer pThis = this; + this->_connection->assets() .thenInMainThread( - [this, session]( + [pThis, session]( CesiumIonClient::Response&& assets) { if (session == nullptr) return; - this->_isLoadingAssets = false; - this->_assets = std::move(assets.value); - this->broadcastAssetsUpdate(); - if (this->_loadAssetsQueued) - this->refreshAssets(session); + pThis->_isLoadingAssets = false; + pThis->_assets = std::move(assets.value); + pThis->broadcastAssetsUpdate(); + if (pThis->_loadAssetsQueued) + pThis->refreshAssets(session); }) - .catchInMainThread([this, session](std::exception&& e) { + .catchInMainThread([pThis, session](std::exception&& e) { if (session == nullptr) return; - this->_isLoadingAssets = false; - this->_assets = std::nullopt; - this->broadcastAssetsUpdate(); - if (this->_loadAssetsQueued) - this->refreshAssets(session); + pThis->_isLoadingAssets = false; + pThis->_assets = std::nullopt; + pThis->broadcastAssetsUpdate(); + if (pThis->_loadAssetsQueued) + pThis->refreshAssets(session); }); } @@ -518,31 +527,33 @@ void CesiumIonSessionImpl::refreshTokens( this->_isLoadingTokens = true; this->_loadTokensQueued = false; + CesiumUtility::IntrusivePointer pThis = this; + this->_connection->tokens() .thenInMainThread( - [this, session]( + [pThis, session]( CesiumIonClient::Response&& tokens) { if (session == nullptr) return; - this->_isLoadingTokens = false; - this->_tokens = + pThis->_isLoadingTokens = false; + pThis->_tokens = tokens.value ? std::make_optional(std::move(tokens.value->items)) : std::nullopt; - this->broadcastTokensUpdate(); - if (this->_loadTokensQueued) - this->refreshTokens(session); + pThis->broadcastTokensUpdate(); + if (pThis->_loadTokensQueued) + pThis->refreshTokens(session); }) - .catchInMainThread([this, session](std::exception&& e) { + .catchInMainThread([pThis, session](std::exception&& e) { if (session == nullptr) return; - this->_isLoadingTokens = false; - this->_tokens = std::nullopt; - this->broadcastTokensUpdate(); - if (this->_loadTokensQueued) - this->refreshTokens(session); + pThis->_isLoadingTokens = false; + pThis->_tokens = std::nullopt; + pThis->broadcastTokensUpdate(); + if (pThis->_loadTokensQueued) + pThis->refreshTokens(session); }); } @@ -556,32 +567,34 @@ void CesiumIonSessionImpl::refreshDefaults( this->_isLoadingDefaults = true; this->_loadDefaultsQueued = false; + CesiumUtility::IntrusivePointer pThis = this; + this->_connection->defaults() .thenInMainThread( - [this, session]( + [pThis, session]( CesiumIonClient::Response&& defaults) { if (session == nullptr) return; logResponseErrors(defaults); - this->_isLoadingDefaults = false; - this->_defaults = std::move(defaults.value); - this->_quickAddItems = nullptr; - this->broadcastDefaultsUpdate(); - if (this->_loadDefaultsQueued) - this->refreshDefaults(session); + pThis->_isLoadingDefaults = false; + pThis->_defaults = std::move(defaults.value); + pThis->_quickAddItems = nullptr; + pThis->broadcastDefaultsUpdate(); + if (pThis->_loadDefaultsQueued) + pThis->refreshDefaults(session); }) - .catchInMainThread([this, session](std::exception&& e) { + .catchInMainThread([pThis, session](std::exception&& e) { if (session == nullptr) return; logResponseErrors(e); - this->_isLoadingDefaults = false; - this->_defaults = std::nullopt; - this->_quickAddItems = nullptr; - this->broadcastDefaultsUpdate(); - if (this->_loadDefaultsQueued) - this->refreshDefaults(session); + pThis->_isLoadingDefaults = false; + pThis->_defaults = std::nullopt; + pThis->_quickAddItems = nullptr; + pThis->broadcastDefaultsUpdate(); + if (pThis->_loadDefaultsQueued) + pThis->refreshDefaults(session); }); } @@ -805,12 +818,14 @@ CesiumAsync::Future CesiumIonSessionImpl::ensureAppDataLoaded( const DotNet::CesiumForUnity::CesiumIonSession& session) { CesiumForUnity::CesiumIonServer server = session.server(); + CesiumUtility::IntrusivePointer pThis = this; + return CesiumIonClient::Connection::appData( this->_asyncSystem, this->_pAssetAccessor, server.apiUrl().ToStlString()) .thenInMainThread( - [this, session, asyncSystem = this->_asyncSystem]( + [pThis, session, asyncSystem = this->_asyncSystem]( CesiumIonClient::Response&& appData) { CesiumAsync::Promise promise = @@ -823,7 +838,7 @@ CesiumAsync::Future CesiumIonSessionImpl::ensureAppDataLoaded( return promise.getFuture(); } - this->_appData = appData.value; + pThis->_appData = appData.value; if (!appData.value.has_value()) { UnityEngine::Debug::LogError(System::String(fmt::format( "Failed to obtain ion server application data: {}", @@ -835,7 +850,7 @@ CesiumAsync::Future CesiumIonSessionImpl::ensureAppDataLoaded( return promise.getFuture(); }) - .catchInMainThread([this, session, asyncSystem = this->_asyncSystem]( + .catchInMainThread([pThis, session, asyncSystem = this->_asyncSystem]( std::exception&& e) { logResponseErrors(e); return asyncSystem.createResolvedFuture(false); diff --git a/native~/Shared/src/CesiumImpl.h b/native~/Shared/src/CesiumImpl.h index dc5d579b..59717ceb 100644 --- a/native~/Shared/src/CesiumImpl.h +++ b/native~/Shared/src/CesiumImpl.h @@ -5,7 +5,7 @@ namespace CesiumForUnityNative { template -class CesiumImpl : public CesiumUtility::ReferenceCounted { +class CesiumImpl : public CesiumUtility::ReferenceCountedThreadSafe { public: CesiumImpl() = default; diff --git a/native~/extern/cesium-native b/native~/extern/cesium-native index 1ece56de..4c837c54 160000 --- a/native~/extern/cesium-native +++ b/native~/extern/cesium-native @@ -1 +1 @@ -Subproject commit 1ece56deca0cb068e720ae67ae716fe80ec231ac +Subproject commit 4c837c543eaffa0ee78a72fedefc15adabdc436d