/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; /// /// Colocation Sessions are a way for apps to connect "colocated" users (meaning users in real-world physical proximity), /// enabling these users to share/load s to/from the session's Uuid, rather than /// with e.g. individual s, which requires manual communication of Platform user IDs. /// /// /// This feature utilizes onboard bluetooth and a shared wifi network to get colocated users connected with each other. /// Thus, users should be advised to have these device services enabled, and to remain connected to the same wifi /// network, otherwise colocation session advertisements will not be receivable.

/// Lastly, you the app developer should ensure your AndroidManifest.xml contains the following permission: /// /// ]]> /// which is automatically added when "Colocation Sessions" is enabled in your OculusProjectConfig asset /// and you run the menu bar utility "Meta > Tools > Update AndroidManifest.xml". ///
public class OVRColocationSession { /// /// An event that is invoked when a new nearby session has been discovered. /// /// /// Only invokes while in a state of "active discovery", begun by successful calls to /// and ended by successful calls to . ///

/// NOTICE: does NOT unregister listeners from this event. Leaving non-static /// listeners indefinitely attached is a potential dangling reference (GC memory leak), and/or may introduce /// undefined behaviors (including unhandled exceptions). ///

/// You should only need to sub/unsub a listener once, but ultimately listener lifetimes and validity are yours to /// consider and manage. ///
public static event Action ColocationSessionDiscovered; /// /// Data that is passed to when a nearby session has been discovered. /// /// public struct Data { /// /// The maximum number of bytes allowed for the custom section of advertisements. /// /// public const int MaxMetadataSize = 1024; /// /// A unique ID representing the colocated session, advertised by another user's call to . /// /// /// This value is identical to the Guid returned to the advertiser after they awaited . ///

/// This UUID is useful as a "groupUuid", for various sharing and loading APIs (see below). ///
/// /// /// public Guid AdvertisementUuid { get; internal set; } /// /// The user-defined data block that the advertiser passed to . /// /// public byte[] Metadata { get; internal set; } } /// /// An enum that represents the possible results from calling a public async function in . /// [OVRResultStatus] public enum Result { /// The API call succeeded Success = OVRPlugin.Result.Success, /// A Colocation Session already is advertising, a no-op occurs AlreadyAdvertising = OVRPlugin.Result.Success_ColocationSessionAlreadyAdvertising, /// Colocation Sessions are already being discovered, a no-op occurs AlreadyDiscovering = OVRPlugin.Result.Success_ColocationSessionAlreadyDiscovering, /// The API call failed with no additional details Failure = OVRPlugin.Result.Failure, /// Colocation Session is not supported Unsupported = OVRPlugin.Result.Failure_Unsupported, /// The API call operation failed OperationFailed = OVRPlugin.Result.Failure_OperationFailed, /// The result received does not represent the API call InvalidData = OVRPlugin.Result.Failure_DataIsInvalid, /// The user is not connected to a network NetworkFailed = OVRPlugin.Result.Failure_ColocationSessionNetworkFailed, /// The user does not have discovery methods such as bluetooth enabled NoDiscoveryMethodAvailable = OVRPlugin.Result.Failure_ColocationSessionNoDiscoveryMethodAvailable } /// /// Starts advertising a new colocation session with associated custom data. /// /// /// A span of bytes (or null) representing user-defined data to associate with the new Colocation Session.
/// It must be no longer than bytes.
/// This custom data is immutable per advertisement, and will be receivable by discoverers of the advertisement (via /// ; see event parameter for ). /// /// /// Returns an <,> indicating the success or failure of starting a new colocated /// session advertisement.
/// The Guid portion of the result (from or /// ) is the colocation session's UUID, which is useful as a /// "groupUuid" for various sharing and loading APIs (listed below). /// /// /// /// ///
/// /// is thrown if refers to more than /// bytes of data. /// /// Example: /// /// /// /// This method is asynchronous; use the returned wrapper to be notified of completion. ///

/// If a session advertisement is already successfully started and running, then the returned /// will be , which is a special /// "success" status.
/// In this case, is ignored even if it is new/updated data; in order to /// update your custom data, you must await before starting a new session /// advertisement. The new session would need to be re-discovered by peers, and any anchors shared to the previous /// session would need to be re-shared with the new UUID. ///
public static OVRTask> StartAdvertisementAsync(ReadOnlySpan colocationSessionData) { if (colocationSessionData.Length > Data.MaxMetadataSize) { throw new ArgumentException($"Colocation Session Advertisement can only store up to {Data.MaxMetadataSize} bytes of data"); } OVRPlugin.ColocationSessionStartAdvertisementInfo info = new OVRPlugin.ColocationSessionStartAdvertisementInfo(); unsafe { fixed (byte* colocationSessionDataPtr = colocationSessionData) { info.GroupMetadata = colocationSessionDataPtr; info.PeerMetadataCount = (uint)colocationSessionData.Length; return OVRTask.Build( OVRPlugin.StartColocationSessionAdvertisement(info, out var requestId), requestId) .ToTask(); } } } /// /// Stops the current colocation session advertisement started by . /// /// /// Returns an <> indicating the success or failure of this request to stop /// advertising. /// /// /// This method is asynchronous; use the returned wrapper to be notified of completion. /// public static OVRTask> StopAdvertisementAsync() => OVRTask.Build( OVRPlugin.StopColocationSessionAdvertisement(out var requestId), requestId) .ToResultTask(); /// /// Starts discovering nearby advertised colocated sessions. /// /// /// Returns an <> indicating the success or failure of starting /// colocated advertisement discovery. /// /// Example: /// /// /// /// Before you call this method, you need to register a listener to in /// order to actually receive discovered sessions. ///

/// This method is asynchronous; use the returned wrapper to be notified of completion. ///

/// If this method has already been invoked with a successful return status, then the returned /// of subsequent calls will be , /// which is a special "success" status indicating no-op. ///
/// public static OVRTask> StartDiscoveryAsync() => OVRTask.Build( OVRPlugin.StartColocationSessionDiscovery(out var requestId), requestId) .ToResultTask(); /// /// Stops the discovery of nearby colocation session advertisements. /// /// /// Returns an <> indicating the success or failure of this request /// to stop discovering. /// /// /// This method is asynchronous; use the returned wrapper to be notified of completion. ///

/// NOTICE: Calling this method does not affect any listeners still attached to /// . Leaving non-static listeners indefinitely attached is a potential /// dangling reference (GC memory leak), and/or may introduce undefined behaviors (including unhandled exceptions). ///
/// public static OVRTask> StopDiscoveryAsync() => OVRTask.Build( OVRPlugin.StopColocationSessionDiscovery(out var requestId), requestId) .ToResultTask(); #region Internal Impl. internal static void OnColocationSessionStartAdvertisementComplete(ulong requestId, OVRPlugin.Result result, Guid uuid) { OVRTask.SetResult(requestId, OVRResult.From(uuid, (Result)result)); } internal static void OnColocationSessionStopAdvertisementComplete(ulong requestId, OVRPlugin.Result result) { OVRTask.SetResult(requestId, OVRResult.From((Result)result)); } internal static void OnColocationSessionStartDiscoveryComplete(ulong requestId, OVRPlugin.Result result) { OVRTask.SetResult(requestId, OVRResult.From((Result)result)); } internal static void OnColocationSessionStopDiscoveryComplete(ulong requestId, OVRPlugin.Result result) { OVRTask.SetResult(requestId, OVRResult.From((Result)result)); } internal static unsafe void OnColocationSessionDiscoveryResult(ulong requestId, Guid uuid, uint metaDataCount, byte* metaDataPtr) { byte[] metaData = new byte[metaDataCount]; Marshal.Copy((IntPtr)metaDataPtr, metaData, 0, (int)metaDataCount); Data colocationSessionData = new Data() { AdvertisementUuid = uuid, Metadata = metaData, }; ColocationSessionDiscovered?.Invoke(colocationSessionData); } internal static void OnColocationSessionAdvertisementComplete(ulong requestId, OVRPlugin.Result result) { if (result != OVRPlugin.Result.Success) { Debug.LogWarning($"Colocation Session Advertisement unexpectedly completed with result: {result}"); } } internal static void OnColocationSessionDiscoveryComplete(ulong requestId, OVRPlugin.Result result) { if (result != OVRPlugin.Result.Success) { Debug.LogWarning($"Colocation Session Discovery unexpectedly completed with result: {result}"); } } #endregion Internal Impl. }