Skip to content

[WIN-1972] Thread-safe Unity bridge, mediation APIs, and Editor tooling#70

Merged
stanley-vungle merged 21 commits intomasterfrom
WIN-1972
Apr 15, 2026
Merged

[WIN-1972] Thread-safe Unity bridge, mediation APIs, and Editor tooling#70
stanley-vungle merged 21 commits intomasterfrom
WIN-1972

Conversation

@stanley-vungle
Copy link
Copy Markdown
Collaborator

@stanley-vungle stanley-vungle commented Apr 13, 2026

Summary

  • Thread-safe native bridge: Rewrote LiftoffUnityBridge.cpp with shared_mutex for instance lifetime, atomic generation counters, safe init/shutdown sequencing, and TOCTOU race prevention
  • C# API improvements: Added MonoPInvokeCallback trampolines, LiftoffMainThread dispatch for thread-safe event callbacks, mediation LoadAd/PlayAd with header bidding markup, and GetSuperToken API
  • Editor tooling: Added LiftoffEditor.cs menu window for SDK operations in the Unity Editor
  • Removed IsAdPlayable API: Publishers should use OnAdLoaded/OnAdLoadFailed callbacks instead
  • Added Liftoff_GetSdkInstance() C++ export: Unsupported raw pointer getter at the DLL layer (not exposed in C#), added per publisher request
  • Cleanup: Removed legacy Sample/ directory, CHANGELOG.md, and VungleSDK-6.11.0.1.unitypackage

Test infrastructure and Debug/Release DLL split are on branch WIN-1972-test-infra-and-debug-dlls for a separate release.

Test plan

  • Verify SDK initializes and OnInitialized fires on Windows standalone build
  • Verify LoadAd / PlayAd waterfall flow completes with callbacks
  • Verify mediated LoadAd / PlayAd with header bidding markup
  • Verify GetSuperToken returns a valid token after initialization
  • Verify Shutdown cleanly tears down without crashes or hangs
  • Verify privacy APIs (SetCoppaStatus, SetCcpaStatus, SetGdprConsentStatus) propagate to native SDK
  • Verify Editor tooling menu appears under Liftoff in Unity Editor

🤖 Generated with Claude Code

stanley-vungle and others added 11 commits March 26, 2026 09:51
Add GetSuperToken public API to LiftoffWindows so publishers can
retrieve the mediation super token without direct SDK access.

- Add Liftoff_GetSuperToken to C++ bridge (CoTaskMemAlloc for interop)
- Add LiftoffWindows.GetSuperToken P/Invoke wrapper in both Packages
  and SampleApp copies of LiftoffWindows.cs
- Refactor test app into multi-screen navigation (Launch, Ads, Privacy,
  Super Token) matching the iOS/Android test app pattern
- Add post-build step to vcxproj to auto-copy DLL to Packages and
  SampleApp plugin folders

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Liftoff_IsAdPlayable to C++ bridge wrapping LiftoffAds::IsAdPlayable
- Add LiftoffWindows.IsAdPlayable P/Invoke wrapper (both Packages and
  SampleApp copies)
- Replace Super Token page with comprehensive Bidding page showing the
  full mediation flow: Get Token -> Enter Markup -> Load -> Play
- Add IsAdPlayable button to both Ads and Bidding pages

The existing LoadAd/PlayAd already support bidding via the optional
biddingMarkup parameter. The Bidding page now demonstrates this
complete flow for mediation partners like Microsoft.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…acks

IsAdPlayable is outdated. Publishers should attach listeners to the
OnAdLoaded/OnAdLoadFailed callbacks to know when an ad is ready to play.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… to Unity bridge

Fix concurrency issues in the native bridge: shared_mutex for g_sdkInstance
lifetime, init generation counter to discard stale threads, timed joins with
detach fallback, deadlock-safe diagnostics registration, and cross-thread
DestroyWindow fix. Add input validation for CCPA/GDPR status and cache
WebView2 availability check. Fix infinite loop in DiagnosticLogEvent exception
chain walking.

Introduce INativeBridge interface for testability and add comprehensive test
suites at both C++ (Google Test) and C# (NUnit) layers covering init, ads,
callbacks, diagnostics, privacy, shutdown, and super tokens.

Add Editor scripts for play-mode teardown and Debug/Release native DLL
switching. Reorganize plugin DLLs into Debug/Release folders. Remove legacy
Sample directory, VungleSDK package, and CHANGELOG.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…her access

Exposes the raw LiftoffAds* pointer via a C-ABI getter at the DLL layer
only (not surfaced in C# LiftoffWindows). Emits a debug warning on every
call. No lifetime or thread-safety guarantees for the caller.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move tests, FakeNativeBridge, INativeBridge, AssemblyInfo, C++ test
project, LiftoffNativePluginConfig, and Debug/Release DLL directories
to branch WIN-1972-test-infra-and-debug-dlls for a separate release.

Strip TestBridge injection hooks from LiftoffWindows.cs (both package
and sample app) and revert the .sln to the single-project layout.
Flat DLL structure at Plugins/x86_64/ is preserved (matching master).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stanley-vungle stanley-vungle changed the title Win 1972 [WIN-1972] Thread-safe Unity bridge, mediation APIs, and Editor tooling Apr 13, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Liftoff Windows Unity plugin/bridge to add a Super Token API and improve editor/runtime teardown and main-thread dispatching, while also removing a legacy Unity sample application and related configuration/assets.

Changes:

  • Added Super Token retrieval end-to-end (native bridge export + C# P/Invoke wrapper).
  • Improved lifecycle handling (editor teardown hooks, safer native init/shutdown threading, main-thread dispatch fallback queue).
  • Removed the old “Unity Sample Application” project files/assets and the legacy CHANGELOG.md.

Reviewed changes

Copilot reviewed 38 out of 56 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
WindowsSDK7SampleApp/Assets/Liftoff/Editor/LiftoffEditor.cs.meta Updated Unity GUID metadata for the new editor hook script.
WindowsSDK7SampleApp/Assets/Liftoff/Editor/LiftoffEditor.cs Added editor-only teardown hook (calls LiftoffWindows.Shutdown() on playmode exit / reload).
WindowsSDK7SampleApp/Assets/Liftoff/Editor.meta Added Unity folder metadata for Assets/Liftoff/Editor.
VungleSDK-6.11.0.1.unitypackage Added/updated Unity package artifact in repo.
Sample/Unity Sample Application/ProjectSettings/XRSettings.asset Removed sample project XR settings.
Sample/Unity Sample Application/ProjectSettings/VFXManager.asset Removed sample project VFX settings.
Sample/Unity Sample Application/ProjectSettings/ProjectVersion.txt Removed sample project Unity version pin file.
Sample/Unity Sample Application/ProjectSettings/PresetManager.asset Removed sample project preset settings.
Sample/Unity Sample Application/ProjectSettings/PackageManagerSettings.asset Removed sample project Package Manager settings.
Sample/Unity Sample Application/Packages/packages-lock.json Removed sample project packages lock.
Sample/Unity Sample Application/Packages/manifest.json Removed sample project package manifest.
Sample/Unity Sample Application/Assets/ViewManager.cs Removed sample app view switching code.
Sample/Unity Sample Application/Assets/TitleGUI.cs.meta Removed sample app Unity metadata for TitleGUI.
Sample/Unity Sample Application/Assets/TitleGUI.cs Removed sample app fullscreen ad UI driver.
Sample/Unity Sample Application/Assets/Resources/BillingMode.json.meta Removed sample app Unity metadata for BillingMode.json.
Sample/Unity Sample Application/Assets/Resources/BillingMode.json Removed sample app billing mode config.
Sample/Unity Sample Application/Assets/Resources.meta Removed sample app Unity metadata for Resources folder.
Sample/Unity Sample Application/Assets/MainTitleScreen.unity.meta Removed sample app Unity metadata for main scene.
Sample/Unity Sample Application/Assets/BannerGUI.cs Removed sample app banner UI driver.
Sample/Unity Sample Application/.gitignore Removed sample app Unity .gitignore.
Sample/README.md Removed sample app README.
Packages/com.liftoff.windows-ads/Samples~/LiftoffSample.cs Fixed event subscription/unsubscription; removed coroutine indirection for play.
Packages/com.liftoff.windows-ads/Runtime/Scripts/LiftoffWindows.cs Added Super Token API + added native-availability guards + changed shutdown/event clearing behavior.
Packages/com.liftoff.windows-ads/Runtime/Scripts/LiftoffMainThread.cs Added fallback queue + hidden dispatcher to guarantee main-thread dispatch when SynchronizationContext is unavailable.
Packages/com.liftoff.windows-ads/Runtime/Plugins/x86_64/WebView2Loader.dll.meta Removed Unity plugin importer metadata for WebView2 loader.
Packages/com.liftoff.windows-ads/Runtime/Plugins/x86_64/LiftoffUnityBridge.dll.meta Removed Unity plugin importer metadata for the bridge DLL.
Packages/com.liftoff.windows-ads/Runtime/Plugins/x86_64/LiftoffSDK.Win32.dll.meta Removed Unity plugin importer metadata for the SDK DLL.
Packages/com.liftoff.windows-ads/Runtime/Liftoff.Windows.asmdef Cleaned invalid empty reference entry.
Packages/com.liftoff.windows-ads/Editor/LiftoffEditor.cs.meta Updated Unity GUID/metadata formatting.
Packages/com.liftoff.windows-ads/Editor/LiftoffEditor.cs Added package-level editor teardown hook assembly.
Packages/com.liftoff.windows-ads/Editor/Liftoff.Windows.Editor.asmdef.meta Added Unity metadata for new editor asmdef.
Packages/com.liftoff.windows-ads/Editor/Liftoff.Windows.Editor.asmdef Added editor-only assembly definition referencing runtime assembly.
Packages/com.liftoff.windows-ads/Editor.meta Added Unity folder metadata for package Editor folder.
LiftoffUnityWindowsPlugin/plugin/LiftoffUnityBridge/LiftoffUnityBridge.vcxproj Added post-build copy steps to push built DLLs into Unity plugin folders.
LiftoffUnityWindowsPlugin/plugin/LiftoffUnityBridge/LiftoffUnityBridge.h Added native exports for Super Token + raw SDK instance (unsupported).
LiftoffUnityWindowsPlugin/plugin/LiftoffUnityBridge/LiftoffUnityBridge.cpp Improved thread-safety/lifecycle (init thread tracking, shared_mutex guards, safer diagnostics registration) + added Super Token and WebView2 check caching.
LiftoffUnityWindowsPlugin/plugin/LiftoffUnityBridge.sln Corrected C++ project type GUID.
LiftoffUnityWindowsPlugin/SDK/include/EventArguments/DiagnosticLogEvent.h Fixed nested exception formatting/walking logic.
CHANGELOG.md Removed legacy changelog file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +295 to 297
// Native Liftoff_Shutdown() clears callbacks and diagnostics internally,
// so no separate ClearDiagnosticCallback/SetCallbacks(zero) calls needed.
try { Native.Liftoff_Shutdown(); } catch { }
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shutdown() now relies on the native Liftoff_Shutdown() to clear callbacks/diagnostics, but this class only calls Liftoff_SetCallbacks/Liftoff_SetDiagnosticCallback once in the static constructor. After a Shutdown() followed by Initialize() in the same domain (e.g., Unity "Enter Play Mode Options" with domain reload disabled, or an app re-init), native callbacks will remain cleared and managed events/diagnostics will never fire. Re-register callbacks/diagnostics on Initialize() (or after Shutdown()), or avoid clearing them on the native side if re-init is intended.

Suggested change
// Native Liftoff_Shutdown() clears callbacks and diagnostics internally,
// so no separate ClearDiagnosticCallback/SetCallbacks(zero) calls needed.
try { Native.Liftoff_Shutdown(); } catch { }
// Do not call Native.Liftoff_Shutdown() here.
// That native shutdown path clears callbacks/diagnostics, but this class only
// installs them once per AppDomain. If the app later calls Initialize() again
// without a domain reload, managed events/diagnostics would stop firing because
// the native callbacks would remain cleared.
//
// Keep the native bridge callbacks installed across wrapper shutdown/re-init
// cycles, and only clear managed event subscribers below.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +9
[InitializeOnLoad]
static class LiftoffEditorTeardown
{
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a second [InitializeOnLoad] teardown hook in the sample app, but WindowsSDK7SampleApp/Assets/Liftoff/Scripts/LiftoffEditor.cs already defines Liftoff.Windows.LiftoffEditorTeardown. Having both will cause LiftoffWindows.Shutdown() to be invoked twice on the same editor events (and duplicate subscriptions). Consider removing/moving the original script or renaming so only one hook is active.

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +75
<PostBuildEvent>
<Command>copy /Y "$(TargetPath)" "$(ProjectDir)..\..\..\Packages\com.liftoff.windows-ads\Runtime\Plugins\x86_64\$(Configuration)\$(TargetFileName)"
copy /Y "$(TargetPath)" "$(ProjectDir)..\..\..\WindowsSDK7SampleApp\Assets\Liftoff\Plugins\x86_64\$(Configuration)\$(TargetFileName)"</Command>
<Message>Copying $(Configuration) $(TargetFileName) to Packages and SampleApp...</Message>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The post-build copy target includes a \$(Configuration)\ subfolder, but the repo’s Unity plugin paths currently live directly under ...\Runtime\Plugins\x86_64\ (no Debug/Release subdirectories). This will make the post-build step fail unless those directories are created first. Consider adding an explicit mkdir for the destination folders, or copying to the existing directory structure without $(Configuration).

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +103
<PostBuildEvent>
<Command>copy /Y "$(TargetPath)" "$(ProjectDir)..\..\..\Packages\com.liftoff.windows-ads\Runtime\Plugins\x86_64\$(Configuration)\$(TargetFileName)"
copy /Y "$(TargetPath)" "$(ProjectDir)..\..\..\WindowsSDK7SampleApp\Assets\Liftoff\Plugins\x86_64\$(Configuration)\$(TargetFileName)"</Command>
<Message>Copying $(Configuration) $(TargetFileName) to Packages and SampleApp...</Message>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above for the Release configuration: the post-build copy target uses a \$(Configuration)\ subfolder that doesn’t exist in the checked-in Unity plugin directories, so the build will fail unless the destination directories are created.

Copilot uses AI. Check for mistakes.
stanley-vungle and others added 9 commits April 13, 2026 12:33
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ote)

Use \. for the if-not-exist check and drop trailing backslash from
mkdir argument to avoid cmd interpreting \" as an escaped quote.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Compute absolute RepoRoot at MSBuild evaluation time instead of
passing relative ..\..\.. paths to cmd, which fails to resolve them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n quotes

Remove trailing backslash from property values to avoid cmd interpreting
\" as escaped quote in if-not-exist/mkdir. Add backslash only before
the filename in copy commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy LiftoffWindows.cs and LiftoffMainThread.cs from the package to
the sample app, and remove the outdated Scripts/LiftoffEditor.cs
(superseded by Editor/LiftoffEditor.cs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updated DLL to have an accessor for a public instance
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<RepoRoot>$([System.IO.Path]::GetFullPath('$(ProjectDir)..\..\..'))</RepoRoot>
<PkgPluginDir>$(RepoRoot)\Packages\com.liftoff.windows-ads\Runtime\Plugins\x86_64</PkgPluginDir>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I am only building x64 (per MSFT's request). We currently don't have a working version of x86,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Unity's naming convention. it's required

public static void SetCcpaStatus(bool optIn)
{
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
if (!_callbacksInstalled) return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we be able to setccpastatus even if no callbacks are set?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callbacks are set when the Unity is registering to the bridge. It's not the bridge to the SDK. So if the Unity registration failed, then it means the DLL failed to load.

@stanley-vungle stanley-vungle merged commit f1b73b4 into master Apr 15, 2026
@stanley-vungle stanley-vungle deleted the WIN-1972 branch April 15, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants