/*
* 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.
}