VR4RoboticArm2/VR4RoboticArm/Library/PackageCache/com.meta.xr.sdk.interaction/Runtime/Scripts/Utils/Context.cs
IonutMocanu 48cccc22ad Main2
2025-09-08 11:13:29 +03:00

261 lines
14 KiB
C#

/*
* 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.Concurrent;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
namespace Oculus.Interaction
{
/// <summary>
/// Context is a tool for coordinating the operation of disparate instances in a generalized and decoupled way:
/// different instances can collaborate with one another without directly depending on one another by sharing
/// data and conventions using a Context. A common example of this kind of collaboration is for instance
/// decoration; for detailed commentary on that usage, see <see cref="DecoratorBase{InstanceT, DecorationT}"/>.
/// </summary>
/// <remarks>
/// Conceptually, a Context is a logical scope of ownership and operation which is not bound to a specific
/// region of the implementation. Just as a code scope (the braces after an `if` or `for` statement, for example)
/// both owns and provides access to the data it contains --- for example, an `int` variable declared in a scope
/// will be automatically released when the scope end, but within the scope it can be accessed by its name --- so
/// too is a Context capable of owning and providing access to data required by the operations it supports/logically
/// "contains." Contexts are a common feature of many libraries, including examples such as
/// [System.AppContext](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-appcontext)
/// from the .NET API itself.
///
/// The Context implementation provided here is specifically designed to support a "safe singleton" pattern in
/// Unity applications. The "safe singleton" pattern aims to provide the majority of the benefits of singletons
/// (mainly simplicity) while avoiding the problems associated with global state; this is achieved because
/// "safe singletons" are not "global" to the app, but are merely "global" to a particular Context instance which
/// owns, enforces the uniqueness of, and provides access to its singletons.
///
/// The impact of this pattern on true global state is thus confined to the provided <see cref="Global"/> Context
/// instance. This instance is provided for convenience and is very valuable for quick-and-dirty work. However,
/// because this does have an (albeit confined) impact on centralized global state, development which must be
/// scalable should not rely on the <see cref="Global"/> Context. Libraries, for example, which wish to leverage
/// Context-based patterns should simply define and expose their own <see cref="Instance"/> to avoid colliding
/// with global considerations.
/// </remarks>
public class Context : MonoBehaviour
{
/// <summary>
/// Instance is a helper class for conveniently creating custom <see cref="Context"/>s using a code-centric
/// approach.
/// </summary>
/// <remarks>
/// When creating your own Context instances, there are two main patterns you can use: an Editor-centric
/// pattern and a code-centric pattern. For an Editor-centric pattern, simply attach a Context to a
/// GameObject and "wire it up" to Editor-exposed fields as you would with any other Unity Component.
/// The accessibility and lifespan, in Editor or in Code, of a Context created this way adheres to typical
/// Unity patterns. This usage is most appropriate small-scope uses --- for example, if an individual prefab
/// needs a Context specific to its own usage to faciliate operations local to the context of each prefab
/// instance.
///
/// By contrast, the Instance class is intended to help with a code-centric pattern. In this pattern, the
/// Context in question is lazily created at runtime (rather than saved as part of the scene) and thus
/// cannot be "wired up" to dependencies in the Editor. While the Context within an Instance can be
/// retrieved using Component accessors at runtime, it should typically be accessed through the Instance
/// proper, which will lazily initialize the Context instance as needed. This pattern is most appropriate
/// for large and/or long-lived usage scopes, particularly for Contexts which should not be destroyed before
/// app teardown. Destroying Instance Contexts, as well as accessing them during app teardown, involves
/// special considerations explored more deeply in the remarks on <see cref="GetInstance"/>.
/// </remarks>
public class Instance
{
private readonly string _name;
private Context _instance;
/// <summary>
/// Basic constructor. This sets the name which will be given to the lazily-created GameObject which
/// will host the underlying Context.
/// </summary>
/// <param name="name">The name which will be given to the lazily-created GameObject</param>
public Instance(string name)
{
_name = name;
}
/// <summary>
/// Retrieves the <see cref="Context"/> which underlies this Instance; if a valid one does not currently
/// exist, a new one is lazily created.
/// </summary>
/// <returns>The Context underlying this Instance</returns>
/// <remarks>
/// Note that destroying the GameObject and/or Context created by an Instance _is_ supported and will
/// successfully tear down the Context; however, if GetInstance is subsequently queried, it will simply
/// lazily create a new Context. This has particular implications on the use of Instance Contexts during
/// app teardown, where order of destruction is not guaranteed and attempts to access an existing
/// Instance Context may result in the creation of a new Context instead if the prior Context happens to
/// have been destroyed first. For this reason, if access to a specific Context is needed in teardown
/// logic (or to a lesser degree any other logic), you should locally cache a reference to the underlying
/// Context and only invoke GetInstance if the old instance is destroyed and you specifically need a
/// reference to the new one.
/// </remarks>
public Context GetInstance()
{
if (_instance == null)
{
GameObject go = new();
go.name = _name;
if (Application.isPlaying)
{
DontDestroyOnLoad(go);
}
_instance = go.AddComponent<Context>();
}
return _instance;
}
}
private static SynchronizationContext _unityMainThreadSynchronizationContext = null;
private static Queue<Action> _unityMainThreadWork = new();
private static Mutex _unityMainThreadWorkMutex = new();
/// <summary>
/// Executes the specified work on the main Unity thread.
/// </summary>
/// <param name="work">The work to execute on the main thread</param>
/// <remarks>
/// This function provides an easy way for work triggered from arbitrary threads to be performed specifically
/// on the main thread; for example, <see cref="FinalAction"/> uses this method to cause a callback invoked
/// during destruction (which can happen on any thread) to be executed on the Unity main thread.
///
/// Unity 6+ introduces improved support for asynchronous programming techniques, including a new
/// [`Awaitable.MainThreadAsync`](https://docs.unity3d.com/6000.0/Documentation/Manual/async-await-support.html)
/// which can be used for the same purpose as this method.
/// </remarks>
public static void ExecuteOnMainThread(Action work)
{
_unityMainThreadWorkMutex.WaitOne();
if (_unityMainThreadSynchronizationContext != null)
{
_unityMainThreadSynchronizationContext.Post(_ =>
{
work();
}, null);
}
else
{
_unityMainThreadWork.Enqueue(work);
}
_unityMainThreadWorkMutex.ReleaseMutex();
}
/// <summary>
/// The global <see cref="Context"/>, which can be used as a default Context for quick-and-dirty development.
/// </summary>
/// <remarks>
/// This Instance is provided for convenience and can be safely used for end-product development (i.e., for
/// the development of an individual game), but development that must incorporate or scale (i.e., library
/// development) should rely on independently Context Instances instead. See the remarks on
/// <see cref="Context"/> and <see cref="Instance"/> for a more detailed exploration of this topic.
/// </remarks>
public static Instance Global { get; } = new("Global Context");
/// <summary>
/// Event which signals the destruction of a <see cref="Context"/>, allowing dependent instances an opportunity
/// to react.
/// </summary>
public event Action WhenDestroyed;
private readonly ConcurrentDictionary<Type, object> _singletons = new();
private void Awake()
{
if (_unityMainThreadSynchronizationContext == null)
{
_unityMainThreadWorkMutex.WaitOne();
_unityMainThreadSynchronizationContext = SynchronizationContext.Current;
while (_unityMainThreadWork.TryDequeue(out var work))
{
work();
}
_unityMainThreadWorkMutex.ReleaseMutex();
}
Debug.Assert(_unityMainThreadSynchronizationContext == SynchronizationContext.Current);
}
/// <summary>
/// Retrieves a Context-local or "safe" singleton instance from this Context. If an instance of the requested
/// type already exists on this Context, that instance will be returned; otherwise, a new one will be lazily
/// created.
/// </summary>
/// <typeparam name="T">The type of the singleton to be retrieved</typeparam>
/// <returns>A Context-local singleton of the requested type</returns>
/// <remarks>
/// This method implements the "safe singleton" pattern discussed in the remarks on the <see cref="Context"/>.
/// While the name "GetOrCreate" is used to clarify that the requested instance _may_ be created during this
/// call, this method should really only be thought of as an accessor with any instantiation that may or may
/// not happen being simply an irrelevant implementation detail. Ideally, you should think of Context-local
/// singletons as aspects of the Context itself rather than as separate instances which can be created and
/// destroyed independently. Adhering to this thinking will ensure that order-of-operations and independent
/// lifecycles do not complicate the usage of Context. As long as the Context merely exists, _all_ singletons
/// on it can _always_ be accessed identically using GetOrCreateSingleton; all other considerations are the
/// purview of the singletons themselves.
/// </remarks>
public T GetOrCreateSingleton<T>() where T : class, new()
{
var type = typeof(T);
object singleton;
if (!_singletons.TryGetValue(type, out singleton))
{
singleton = new T();
_singletons.TryAdd(type, singleton);
}
return singleton as T;
}
/// <summary>
/// Retrieves a Context-local or "safe" singleton instance from this Context. If an instance of the requested
/// type already exists on this Context, that instance will be returned; otherwise, a new one will be lazily
/// created by invoking the provided <paramref name="factory"/> callback.
/// </summary>
/// <typeparam name="T">The type of the singleton to be retrieved</typeparam>
/// <param name="factory">A callback which creates a singleton of the requested type</param>
/// <returns>A Context-local singleton of the requested type</returns>
/// <remarks>
/// This method differs from <see cref="GetOrCreateSingleton{T}"/> in that it accepts a
/// <paramref name="factory"/> callback, which it will invoke to create the singleton if necessary instead
/// of simply calling `new()`. This is useful for singleton types which can only be constructed with
/// arguments, or for types with private constructors. This method implements the "safe singleton" pattern
/// discussed in the remarks on <see cref="Context"/> and further explored in the remarks on
/// <see cref="GetOrCreateSingleton{T}"/>.
/// </remarks>
public T GetOrCreateSingleton<T>(Func<T> factory) where T : class
{
var type = typeof(T);
object singleton;
if (!_singletons.TryGetValue(type, out singleton))
{
singleton = factory();
_singletons.TryAdd(type, singleton);
}
return singleton as T;
}
private void OnDestroy()
{
WhenDestroyed?.Invoke();
}
}
}