Fusion 2.1 and Meta Core XR SDK
For the time being, there is a compatibility issue between Fusion 2.1 and the Meta Core XR SDK package (com.meta.xr.sdk.core). You can follow the instructions on this page to have a temporary workaround, until the Meta SDK is updated for Fusion 2.1 compatibility.
Don’t hesitate to contact us for any further help on this matter.
Compatibility issue
The Meta Core XR SDK includes references to Fusion SDK, to be able to provide building blocks setting up a networked scene.
The Fusion 2.1 version introduces breaking changes to its API, and the Meta team has not yet had the opportunity to reflect those changes in their SDK.
Here are the API changes impacting the Meta integration:
- The interface method
INetworkRunnerCallbacks.OnReliableDataReceivedhad a signature change - The
NetworkTransform.DisableSharedModeInterpolationproperty does not exist anymore (this feature is integrated in config flags now) - Fusion now allows using the full underlying Photon Realtime API. Due to that, some functions now depends on the
Photon.Realtimemodule, so modules using it have to add this reference in their asmdef.
We are in discussion with the Meta team to be sure that an incoming version of the Meta XR Core SDK will be compatible with Fusion 2.1.
In the meantime, to be able to use the Meta Core XR SDK alongside Fusion 2.1, some changes are required on the package com.meta.xr.sdk.core.
How to apply changes
The com.meta.xr.sdk.core is by default installed in the Library\PackageCache folder.
Editing the package in this location is not totally possible (notably, editing asmdef files in the inspector), so a solution, to make the required modifications simpler to apply, is to embed the package.
For more information about embedding packages, you can read those links:
- https://docs.unity3d.com/Manual/upm-embed.html,
- https://docs.unity3d.com/Manual/upm-api.html#Embed,
- https://docs.unity3d.com/ScriptReference/PackageManager.Client.Embed.html
Script to embed a package
To easily embed the package, you can add this script to your project: PackageEmbedding.cs (see source code and the end of this document)
Then, right-click on the "Meta XR Core SDK" package in your project view, and finally select "Move as local embedded package". The package will move from the Library\PackageCache folder, to the Packages folder, and modifications will be easier to apply.
To place it back, close the editor, then delete the Packages\com.meta.xr.sdk.core folder.
Note: alternatively, you can go to the package manager view, choose the "Meta XR Core SDK" package, and then select "Remove", however Unity will fail cleaning up the folder completely while running, as the OVRPlugin.dll is already loaded. So closing the editor first is a cleaner approach.
Changes to apply
In Meta SDK 78.0.0 or 81.0.0, 4 files need to be edited before being able to run Fusion 2.1 successfully alongside the Meta XR Core SDK.
The recommend approach is to:
- Embed the Meta XR Core SDK package
- Install Fusion 2.1 (this will display errors)
- Add the
Photon.realtimereference in the 2 asmdef to edit - Edit the 2 files to change
- Restart Unity. Don't be surprised if the editor reports errors when launching the first time. Ignore the message, as Unity needs to complete the launch to verify that the project compiles correctly and that there are no more errors.
- If needed, install the appropriate version of Photon Voice
Meta.XR.MultiplayerBlocks.Fusion.asmdef
File to edit location
Packages/com.meta.xr.sdk.core/Scripts/BuildingBlocks/MultiplayerBlocks/PhotonFusion/Meta.XR.MultiplayerBlocks.Fusion.asmdef
Initial content of the section to edit
"references": [
"Oculus.Interaction",
"Oculus.Platform",
"Oculus.VR",
"Oculus.AvatarSDK2",
"PhotonVoice.Fusion",
"PhotonRealtime",
"PhotonVoice",
"PhotonVoice.API",
"Meta.XR.BuildingBlocks",
"Meta.XR.MultiplayerBlocks.Shared",
"Fusion.Unity",
"Fusion.Addons.Physics"
],
Modified content
Alternative in editor edition:
- if the package is embedded, instead of manually editing the file, you can edit the asmdef file directly in the Unity inspector Window in "Assembly definition references", to add the
Photon.Realtimeasmdef reference. - Note that you will need to have Fusion installed, to be able to list the Photon.Realtime asmdef successfully in the inspector
"references": [
"Oculus.Interaction",
"Oculus.Platform",
"Oculus.VR",
"Oculus.AvatarSDK2",
"PhotonVoice.Fusion",
"PhotonRealtime",
"PhotonVoice",
"PhotonVoice.API",
"Meta.XR.BuildingBlocks",
"Meta.XR.MultiplayerBlocks.Shared",
"Fusion.Unity",
"Fusion.Addons.Physics",
"Photon.Realtime"
],
Meta.XR.MultiplayerBlocks.Fusion.Editor.asmdef
File to edit location
Packages/com.meta.xr.sdk.core/Editor/BuildingBlocks/BlockData/MultiplayerBlocks/PhotonFusion/Meta.XR.MultiplayerBlocks.Fusion.Editor.asmdef
Initial content of the section to edit
"references": [
"Meta.XR.BuildingBlocks",
"Meta.XR.BuildingBlocks.Editor",
"Oculus.Interaction",
"Oculus.Interaction.OVR.Editor",
"Oculus.VR",
"Oculus.VR.Editor",
"PhotonVoice",
"PhotonRealtime",
"PhotonVoice.Fusion",
"Meta.XR.MultiplayerBlocks.Fusion",
"Fusion.Unity.Editor",
"Fusion.Unity",
"Fusion.Addons.Physics",
"Meta.XR.MultiplayerBlocks.Shared.Editor",
"Meta.XR.MultiplayerBlocks.Shared"
],
Modified content
Alternative in editor edition:
- if the package is embedded, instead of manually editing the file, you can edit the asmdef file directly in the Unity inspector Window in "Assembly definition references", to add the
Photon.Realtimeasmdef reference. - Note that you will need to have Fusion installed, to be able to list the Photon.Realtime asmdef successfully in the inspector
"references": [
"Meta.XR.BuildingBlocks",
"Meta.XR.BuildingBlocks.Editor",
"Oculus.Interaction",
"Oculus.Interaction.OVR.Editor",
"Oculus.VR",
"Oculus.VR.Editor",
"PhotonVoice",
"PhotonRealtime",
"PhotonVoice.Fusion",
"Meta.XR.MultiplayerBlocks.Fusion",
"Fusion.Unity.Editor",
"Fusion.Unity",
"Fusion.Addons.Physics",
"Meta.XR.MultiplayerBlocks.Shared.Editor",
"Meta.XR.MultiplayerBlocks.Shared",
"Photon.Realtime"
],
FusionBBEvents.cs
File to edit location
Packages/com.meta.xr.sdk.core/Scripts/BuildingBlocks/MultiplayerBlocks/PhotonFusion/NetworkRunner/Scripts/FusionBBEvents.cs
Initial code of the section to edit
void INetworkRunnerCallbacks.OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key,
ArraySegment<byte> data)
{
OnReliableDataReceived?.Invoke(runner, player, key, data);
}
Modified code
#if FUSION_2_1
void INetworkRunnerCallbacks.OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key,
ReadOnlySpan<byte> data)
{
OnReliableDataReceived?.Invoke(runner, player, key, data.ToArray());
}
#else
void INetworkRunnerCallbacks.OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key,
ArraySegment<byte> data)
{
OnReliableDataReceived?.Invoke(runner, player, key, data);
}
#endif
NetworkedGrabbableObjectFusionInstallationRoutine.cs
File to edit location
Packages/com.meta.xr.sdk.core/Editor/BuildingBlocks/BlockData/MultiplayerBlocks/PhotonFusion/NetworkedGrabbableObject/Scripts/NetworkedGrabbableObjectFusionInstallationRoutine.cs
Initial code of the section to edit
networkTransform.DisableSharedModeInterpolation = true; // otherwise object cannot be grabbed by ISDK
Modified code
#if FUSION_2_1
// otherwise object cannot be grabbed by ISDK
networkTransform.ConfigFlags = NetworkTransform.NetworkTransformFlags.DisableSharedModeInterpolation;
#else
// otherwise object cannot be grabbed by ISDK
networkTransform.DisableSharedModeInterpolation = true;
#endif
Photon Voice
If Photon Voice is needed, the Photon Voice Realtime 5 version should be used instead of the version available on the asset store.
It can be found in the "Complementary download" section of the Fusion 2.1 download page: https://doc.photonengine.com/fusion/current/getting-started/preview-2-1/download-2-1#matching-downloads
PackageEmbedding.cs source
#if UNITY_EDITOR
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace Photon.PackageTooling
{
/*
* Allows to move an existing package as an embedded package
* See https://docs.unity3d.com/Manual/upm-embed.html, https://docs.unity3d.com/Manual/upm-api.html#Embed and https://docs.unity3d.com/ScriptReference/PackageManager.Client.Embed.html
*
* Inspired by https://github.com/liortal53/upm-embed/blob/master/Assets/EmbedPackage/Scripts/EmbedPackage.cs
*/
public static class PackageEmbedding
{
static EmbedRequest EmbeddingRequest;
static RemoveRequest RemoveEmbeddingRequest;
public static bool IsAnyRequestPending()
{
return EditorApplication.isCompiling || EmbeddingRequest != null || RemoveEmbeddingRequest != null;
}
public static void EmbedPackage(string packageName)
{
if (IsAnyRequestPending())
{
Debug.LogError("Editor busy, or an embedding-related request is already in progress: please ait and retry");
return;
}
Debug.Log($"Installed package '{packageName}' is being moved to the Packages folder of the project as an embedded package ...");
EmbeddingRequest = UnityEditor.PackageManager.Client.Embed(packageName);
EditorApplication.update += EmbedProgress;
}
private static void EmbedProgress()
{
if (EmbeddingRequest.IsCompleted)
{
switch (EmbeddingRequest.Status)
{
case StatusCode.Success:
Debug.Log($"Package successfully embedded (id: {EmbeddingRequest.Result.packageId}. " +
$" Uninstalling it from the package manager will remove this custom version and return to the original one." +
$" Alternatively, to un-embed it, simply delete the embedded version version folder in your project Packages folder ({Path.GetFullPath(EmbeddingRequest.Result.assetPath)}):" +
$" the repository package will re reinstalled in its unedited version");
AssetDatabase.Refresh();
Debug.LogError("Database refreshed");
break;
case StatusCode.Failure:
Debug.Log($"Unable to embed package: {EmbeddingRequest.Error.message}");
break;
}
EditorApplication.update -= EmbedProgress;
EmbeddingRequest = null;
}
}
public static bool TryGetFirstSelectedAssetPath(out string selectedItemPath, out string selectedItemFullPath)
{
// Source: https://docs.unity3d.com/ScriptReference/Selection.GetFiltered.html and https://discussions.unity.com/t/selection-activeobject-on-items-on-left-side-of-project-window/664382/6
selectedItemPath = null;
selectedItemFullPath = null;
UnityEngine.Object[] selectedAssets = Selection.GetFiltered(typeof(object), SelectionMode.Assets);
foreach (var selectedAsset in selectedAssets)
{
selectedItemPath = AssetDatabase.GetAssetPath(selectedAsset);
selectedItemFullPath = Path.GetFullPath(selectedItemPath);
return true;
}
return false;
}
public static void UnEmbedPackage(string packageName, string absolutePath)
{
if (IsAnyRequestPending())
{
Debug.LogError("Editor busy, or an embedding-related request is already in progress: please ait and retry");
return;
}
Debug.Log($"Embedded package '{packageName}' will be deleted and replaced by the original unembedded package version");
Debug.LogError("Will erase " + absolutePath);
if(TrySafeDeleteFolder(absolutePath) == false)
{
Debug.LogError("Failed to erase embedded version");
}
else
{
// Deletion succeeded
Debug.LogError("Deletion ok");
AssetDatabase.Refresh();
Debug.LogError("Database refreshed");
}
}
public static bool TrySafeDeleteFolder(string path)
{
var folderInfo = new DirectoryInfo(path);
if (TrySafeDeleteFolder(folderInfo, dryRun: true) == false)
{
Debug.LogError($"Deletion of {path} aborted: no files were edited");
return false;
} else
{
Debug.LogError("Dry run of deletion ok");
}
return TrySafeDeleteFolder(folderInfo);
}
public static bool TrySafeDeleteFolder(DirectoryInfo folderInfo, bool dryRun = false)
{
if (folderInfo.Exists == false)
{
return false;
}
FileInfo[] fileInfos = folderInfo.GetFiles();
foreach (var fileInfo in fileInfos)
{
if (fileInfo.IsReadOnly)
{
Debug.LogError($"{fileInfo} is read-only");
return false;
}
if (dryRun == false)
{
try
{
fileInfo.Delete();
}
catch (Exception e)
{
Debug.LogError($"Unable to delete file {fileInfo}: {e.GetType()} {e.Message}");
return false;
}
}
else
{
// Dry run
try
{
// Check if the file is used. See https://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in-use
using (FileStream stream = fileInfo.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
stream.Close();
}
}
catch (Exception e)
{
Debug.LogError($"[Preliminary check fail] File {fileInfo} deletion won't be possible: [{e.GetType()}] {e.Message} (dry-run only: no files were edited)");
return false;
}
}
}
DirectoryInfo[] subFolderInfos = folderInfo.GetDirectories();
foreach(var subFolderInfo in subFolderInfos)
{
if (TrySafeDeleteFolder(subFolderInfo, dryRun: dryRun) == false)
{
return false;
}
}
if(dryRun == false)
{
try
{
folderInfo.Delete();
}
catch (Exception e)
{
Debug.LogError($"Unable to delete folder {folderInfo}: {e.GetType()} {e.Message}");
}
}
return true;
}
public static void EmbedPackageFolder(string folderLocalPath, string folderGlobalPath, bool backupOriginalPath = false)
{
if (folderLocalPath.StartsWith("Assets") == false) return;
string packageFile = Path.Join(folderLocalPath, "package.json");
if (File.Exists(packageFile) == false) return;
string packageName = Path.GetFileName(folderLocalPath);
string originalPathFilePath = Path.Join(folderLocalPath, "OriginalFolderPath.log");
string projectFolder = Path.GetDirectoryName(Application.dataPath);
string newLocationPath = Path.Join(projectFolder, "Packages", packageName);
Debug.LogError($"Will move {folderGlobalPath} to {newLocationPath}");
var folderInfo = new DirectoryInfo(folderLocalPath);
if (TrySafeDeleteFolder(folderInfo, dryRun: true) == false)
{
Debug.LogError($"Moving of {folderLocalPath} aborted: no files were edited");
return;
}
if (Directory.Exists(newLocationPath))
{
Debug.LogError($"Moving of {folderLocalPath} aborted: folder {newLocationPath} already exists");
return;
}
if (backupOriginalPath && File.Exists(originalPathFilePath) == false)
{
// Store original path in case of revert needs
var originalPathFile = File.CreateText(originalPathFilePath);
originalPathFile.WriteLine(folderLocalPath);
originalPathFile.Close();
}
try
{
Directory.Move(folderGlobalPath, newLocationPath);
AssetDatabase.Refresh();
Debug.LogError("Database refreshed");
}
catch (Exception e)
{
Debug.LogError("Unable to convert folder as an embedded package (close your editor an any app that could have access to it)) " + e.Message);
throw e;
}
}
#region Embed menu item
[MenuItem("Assets/Move as local embedded package", false, 10_000)]
public static void PackageEmbeddingAction()
{
if (TryGetFirstSelectedAssetPath(out string selectedItemPath, out string selectedItemFullPath))
{
string packageName = Path.GetFileName(selectedItemPath);
PackagePresenceCheck.LookForPackage(packageName, (packageInfo) => {
if (packageInfo == null)
{
Debug.LogError($"Package {packageName} not found");
}
else if (packageInfo.isDirectDependency == false)
{
Debug.LogError($"Error: Client.Embed cannot embed indirect dependency. Install {packageInfo.name} directly, not automatically as a dependency, to be able to embed it");
}
else
{
switch (packageInfo.source)
{
case PackageSource.BuiltIn:
Debug.LogError($"Error: built-in package embedding not supported ({packageInfo.name})");
break;
case PackageSource.Embedded:
Debug.LogError($"Package {packageInfo.name} is already embedded. Uninstalling it from the package manager will remove this custom version and return to the original one");
break;
}
}
EmbedPackage(packageName);
});
}
}
[MenuItem("Assets/Move as local embedded package", true)]
public static bool PackageEmbeddingValidation()
{
if (TryGetFirstSelectedAssetPath(out string selectedItemPath, out string selectedItemFullPath))
{
if (selectedItemPath.StartsWith("Packages"))
{
string packageName = Path.GetFileName(selectedItemPath);
// It is a package: already embedded ?
if (selectedItemFullPath.Contains("PackageCache") == false)
{
// Already an embedded package
//Debug.Log($"Already embedded package: {packageName}");
return false;
}
// Non-embedded package (not a garantee we can embed it: built-in and dependency packages are not embeddable
//Debug.Log($"Non embedded pacakge {packageName} at path {selectedItemPath}");
return true;
}
return false;
}
return false;
}
#endregion
}
/*
* Package Lookup methods
*/
public class PackagePresenceCheck
{
string[] packageNames = null;
UnityEditor.PackageManager.Requests.ListRequest request;
public delegate void ResultDelegate(Dictionary<string, UnityEditor.PackageManager.PackageInfo> packageInfoByPackageName);
ResultDelegate resultCallback;
public delegate void SingleResultDelegate(UnityEditor.PackageManager.PackageInfo packageInfo);
SingleResultDelegate singleResultCallback;
Dictionary<string, UnityEditor.PackageManager.PackageInfo> results = new Dictionary<string, UnityEditor.PackageManager.PackageInfo>();
public static void LookForPackage(string packageToSearch, SingleResultDelegate packageLookupCallback = null, string defineToAddIfDetected = null)
{
var packageCheck = new PackagePresenceCheck(packageToSearch, (packageInfo) => {
var group = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
if (packageInfo != null)
{
if (string.IsNullOrEmpty(defineToAddIfDetected) == false && defines.Contains(defineToAddIfDetected) == false) {
defines = $"{defines};{defineToAddIfDetected}";
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
}
packageLookupCallback(packageInfo);
}
else
{
if (string.IsNullOrEmpty(defineToAddIfDetected) == false && defines.Contains(defineToAddIfDetected) == true)
{
Debug.LogError($"Project define symbols include {defineToAddIfDetected}, while package {packageToSearch} is not installed anymore: it should be removed in the Player settings");
}
packageLookupCallback(null);
}
});
}
public PackagePresenceCheck(string[] packageNames, ResultDelegate resultCallback, bool useOfflineMode = true)
{
this.packageNames = packageNames;
this.resultCallback = resultCallback;
request = UnityEditor.PackageManager.Client.List(offlineMode: useOfflineMode, includeIndirectDependencies: true);
EditorApplication.update += Progress;
}
public PackagePresenceCheck(string packageName, SingleResultDelegate resultCallback, bool useOfflineMode = true)
{
this.packageNames = new string[] { packageName };
this.singleResultCallback = resultCallback;
request = UnityEditor.PackageManager.Client.List(offlineMode: useOfflineMode, includeIndirectDependencies: true);
EditorApplication.update += Progress;
}
void Progress()
{
if (request.IsCompleted)
{
bool singleResultCallbackReturned = false;
results = new Dictionary<string, UnityEditor.PackageManager.PackageInfo>();
if (request.Status == StatusCode.Success)
{
foreach (var info in request.Result)
{
foreach(var checkedPackageName in packageNames)
{
if (info.name == checkedPackageName)
{
results[checkedPackageName] = info;
if (singleResultCallback != null)
{
singleResultCallbackReturned = true;
singleResultCallback(info);
}
break;
}
}
}
}
if (singleResultCallback != null && singleResultCallbackReturned == false)
{
singleResultCallback(null);
}
if (resultCallback != null)
resultCallback(results);
EditorApplication.update -= Progress;
}
}
}
}
#endif
Back to top