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

407 lines
17 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.Generic;
using Oculus.Interaction.PoseDetection.Debug;
using System.Linq;
using UnityEngine;
using UnityEngine.Assertions;
using System.Threading.Tasks;
using System.Collections;
namespace Oculus.Interaction.PoseDetection
{
/// <summary>
/// Chains together a number of <see cref="IActiveState"/>s into a sequence.
/// </summary>
/// <remarks>
/// The Sequence._stepsToActivate field contains an optional list of IActiveState's which must be 'activated' in
/// order.
/// The sequence can progress from Step N to N + 1 when: <see cref="ActivationStep.MinActiveTime"/> <= "Time step
/// N active for" <= <see cref="ActivationStep.MaxStepTime"/>, and:
///
/// Step N just became inactive OR
/// Step N is the last step OR
/// Step N+1 is active
///
/// Note that once the sequence has moved on to the next step, the previous step does not need to remain active.
/// Each step has three fields:
/// <see cref="ActivationStep.ActiveState"/>: The IActiveState that is used to determine if the conditions of this step are fulfilled
/// <see cref="ActivationStep.MinActiveTime"/>: How long (in seconds) the IActiveState of this step must be contiguously active before moving
/// on to the next step. If the ActiveState drops out of being active for even a single frame
/// the countdown is reset.
/// <see cref="ActivationStep.MaxStepTime"/>: If the elapsed time that the sequence is spent waiting for this step to reach its MinActiveTime
/// exceeds this value then the whole sequence is reset back to the beginning.
///
/// Once all steps are complete the Sequence.Active becomes true. It will remain true as long as RemainActiveWhile
/// is true. If _remainActiveCooldown > 0, Sequence.Active will remain active even after RemainActiveWhile becomes
/// false until the cooldown timer is met. The timer is reset if RemainActiveWhile becomes true again.
/// </remarks>
public class Sequence : MonoBehaviour,
IActiveState, ITimeConsumer
{
/// <summary>
/// Wrapper for <see cref="IActiveState"/>s which are to be incorporated into a <see cref="Sequence"/>.
/// </summary>
/// <remarks>
/// This type is exclusively intended to be used as part of a <see cref="Sequence"/>, and details of its
/// intended usage and expected behavior are documented in the remarks for that type.
/// </remarks>
[Serializable]
public class ActivationStep
{
[Tooltip("The IActiveState that is used to determine if the conditions of this step are fulfilled.")]
[SerializeField, Interface(typeof(IActiveState))]
private UnityEngine.Object _activeState;
/// <summary>
/// The <see cref="IActiveState"/> whose activation is the condition for completing an ActivationStep of
/// the containing <see cref="Sequence"/>.
/// </summary>
/// <remarks>
/// For a detailed overview of the function of ActivationSteps and their role in Sequences, please see the
/// documentation for <see cref="Sequence"/>.
/// </remarks>
public IActiveState ActiveState { get; private set; }
[SerializeField]
[Tooltip("This step must be consistently active for this amount of time before continuing to the next step.")]
private float _minActiveTime;
/// <summary>
/// The minimum amount of time for which <see cref="ActiveState"/> must be active in order for this
/// ActivationStep to be considered completed.
/// </summary>
/// <remarks>
/// For a detailed overview of the function of ActivationSteps and their role in Sequences, please see the
/// documentation for <see cref="Sequence"/>.
/// </remarks>
public float MinActiveTime => _minActiveTime;
[SerializeField]
[Tooltip(
"Maximum time that can be spent waiting for this step to complete, before the whole sequence is abandoned. " +
"This value must be greater than minActiveTime, or zero. This value is ignored if zero, and for the first step in the list.")]
private float _maxStepTime;
/// <summary>
/// The maximum amount of time a <see cref="Sequence"/> can remain in this ActivationStep (without proceeding
/// to the next one) before the Sequence "fails out" and must start again from the beginning.
/// </summary>
/// <remarks>
/// For a detailed overview of the function of ActivationSteps and their role in Sequences, please see the
/// documentation for <see cref="Sequence"/>.
/// </remarks>
public float MaxStepTime => _maxStepTime;
/// <summary>
/// Basic constructor for ActivationStep. Should not be called directly.
/// </summary>
/// <remarks>
/// This empty constructor is typically invoked by Unity logic when deserializing a serialized-and-saved
/// ActivationStep.
/// </remarks>
public ActivationStep()
{
}
/// <summary>
/// Constructor for constructing new ActivationSteps programmatically.
/// </summary>
/// <param name="activeState">The <see cref="IActiveState"/> to be assigned to <see cref="ActiveState"/></param>
/// <param name="minActiveTime">The float value to be assigned to <see cref="MinActiveTime"/></param>
/// <param name="maxStepTime">The float value to be assigned to <see cref="MaxStepTime"/></param>
public ActivationStep(IActiveState activeState, float minActiveTime, float maxStepTime)
{
ActiveState = activeState;
_minActiveTime = minActiveTime;
_maxStepTime = maxStepTime;
}
/// <summary>
/// Method invoked when a <see cref="Sequence"/> "moves into" this ActivationStep. This method should only ever
/// be invoked by the internal logic of its containing Sequence.
/// </summary>
public void Start()
{
if (ActiveState == null)
{
ActiveState = _activeState as IActiveState;
}
Assert.IsNotNull(ActiveState);
}
}
[Tooltip("The sequence will step through these ActivationSteps one at a " +
"time, advancing when each step becomes Active. Once all steps are active, " +
"the sequence itself will become Active.")]
[SerializeField, Optional]
private ActivationStep[] _stepsToActivate;
[Tooltip("Once the sequence is active, it will remain active as long as " +
"this IActiveState is Active.")]
[SerializeField, Optional, Interface(typeof(IActiveState))]
private UnityEngine.Object _remainActiveWhile;
[Tooltip("Sequence will not become inactive until RemainActiveWhile has " +
"been inactive for at least this many seconds.")]
[SerializeField, Optional]
private float _remainActiveCooldown;
private Func<float> _timeProvider = () => Time.time;
/// <summary>
/// Implements <see cref="ITimeConsumer.SetTimeProvider(Func{float})"/>.
/// Sets the time provider for this sequence, allowing for the default time provider (Unity's built-in Time.time)
/// to be overridden with custom behavior to enable pausing, time dilation, etc.
/// </summary>
/// <param name="timeProvider">The new time provider to be used by this instance.</param>
public void SetTimeProvider(Func<float> timeProvider)
{
_timeProvider = timeProvider;
}
private IActiveState RemainActiveWhile { get; set; }
/// <summary>
/// Returns the index of the step in <see cref="_stepsToActivate"/> whose conditions are
/// waiting to be activated.
/// If <see cref="Active"/> is true, this value will be set to the
/// size of <see cref="_stepsToActivate"/>.
/// If <see cref="_stepsToActivate"/> has no steps, this property will be 0.
/// </summary>
public int CurrentActivationStep { get; private set; }
private float _currentStepActivatedTime;
private float _stepFailedTime;
private bool _currentStepWasActive;
private float _cooldownExceededTime;
private bool _wasRemainActive;
#region Unity Lifecycle
protected virtual void Awake()
{
RemainActiveWhile = _remainActiveWhile as IActiveState;
ResetState();
}
protected virtual void Start()
{
if (_stepsToActivate == null)
{
_stepsToActivate = Array.Empty<ActivationStep>();
}
foreach (var step in _stepsToActivate)
{
step.Start();
}
}
protected virtual void Update()
{
var time = _timeProvider();
if (Active)
{
// Test for active, if RemainActiveWhile is set.
bool shouldBeActive = RemainActiveWhile != null && RemainActiveWhile.Active;
if (!shouldBeActive)
{
if (_wasRemainActive)
{
_cooldownExceededTime = time + _remainActiveCooldown;
}
if (_cooldownExceededTime <= time)
{
Active = false;
}
}
_wasRemainActive = shouldBeActive;
// No longer active; start activation condition at the beginning
if (!Active)
{
ResetState();
}
return;
}
if (CurrentActivationStep < _stepsToActivate.Length)
{
var currentStep = _stepsToActivate[CurrentActivationStep];
if (time > _stepFailedTime && CurrentActivationStep > 0 && currentStep.MaxStepTime > 0.0f)
{
// Failed to activate before max time limit reached. Start from the beginning.
ResetState();
}
bool currentStepIsActive = currentStep.ActiveState.Active;
if (currentStepIsActive)
{
if (!_currentStepWasActive)
{
// Step wasn't active, but now it is! Start the timer until next step can
// be entered.
_currentStepActivatedTime = time + currentStep.MinActiveTime;
}
}
if (time >= _currentStepActivatedTime && _currentStepWasActive)
{
// Time constraint met. Go to next step if either:
// - this step just became inactive OR
// - this is the last step OR
// - the next step is active
var nextStepIndex = CurrentActivationStep + 1;
bool thisStepCondition = !currentStepIsActive;
bool nextStepCondition = (nextStepIndex == _stepsToActivate.Length) ||
_stepsToActivate[nextStepIndex].ActiveState.Active;
if (thisStepCondition || nextStepCondition)
{
EnterNextStep(time);
}
}
_currentStepWasActive = currentStepIsActive;
}
else if (RemainActiveWhile != null)
{
Active = RemainActiveWhile.Active;
}
}
private void EnterNextStep(float time)
{
CurrentActivationStep++;
_currentStepWasActive = false;
if (CurrentActivationStep < _stepsToActivate.Length)
{
var currentStep = _stepsToActivate[CurrentActivationStep];
_stepFailedTime = time + currentStep.MaxStepTime;
return;
}
// This was the last step. Activate.
Active = true;
// In case there is no RemainActiveWhile condition, start the cooldown
// timer
_cooldownExceededTime = time + _remainActiveCooldown;
// Activate native component
NativeMethods.isdk_NativeComponent_Activate(0x5365717565446574);
}
private void ResetState()
{
CurrentActivationStep = 0;
_currentStepWasActive = false;
_currentStepActivatedTime = 0.0f;
}
#endregion
/// <summary>
/// Implements <see cref="IActiveState.Active"/>, in this case indicating whether the described sequence
/// of IActiveStates has been "recognized" as having occurred with acceptable order and timing.
/// </summary>
public bool Active { get; private set; }
static Sequence()
{
ActiveStateDebugTree.RegisterModel<Sequence>(new DebugModel());
}
private class DebugModel : ActiveStateModel<Sequence>
{
private IEnumerator GetChildrenCoroutine(Sequence sequence,
TaskCompletionSource<IEnumerable<IActiveState>> tcs)
{
while (sequence._stepsToActivate.Any(s => s.ActiveState == null))
{
// Wait for all child ActiveStates to be initialized.
yield return null;
}
List<IActiveState> children = new List<IActiveState>();
children.AddRange(sequence._stepsToActivate.Select(step => step.ActiveState));
children.Add(sequence.RemainActiveWhile);
tcs.SetResult(children.Where(c => c != null));
}
protected override Task<IEnumerable<IActiveState>> GetChildrenAsync(Sequence activeState)
{
TaskCompletionSource<IEnumerable<IActiveState>> tcs = new();
if (activeState.isActiveAndEnabled)
{
activeState.StartCoroutine(GetChildrenCoroutine(activeState, tcs));
}
else
{
tcs.SetResult(Enumerable.Empty<IActiveState>());
}
return tcs.Task;
}
}
#region Inject
/// <summary>
/// Sets the <see cref="ActivationStep"/>s for a dynamically instantiated Sequence. This method
/// exists to support Interaction SDK's dependency injection pattern and is not needed for typical Unity Editor-based
/// usage.
/// </summary>
public void InjectOptionalStepsToActivate(ActivationStep[] stepsToActivate)
{
_stepsToActivate = stepsToActivate;
}
/// <summary>
/// Sets the <see cref="IActiveState"/> for the optional "remain active while" capability (which allows a Sequence to
/// remain active pending the inactivity of the given IActiveState rather than resetting after activation) for a dynamically
/// instantiated Sequence. This method exists to support Interaction SDK's dependency injection pattern and is not needed
/// for typical Unity Editor-based usage.
/// </summary>
public void InjectOptionalRemainActiveWhile(IActiveState activeState)
{
_remainActiveWhile = activeState as UnityEngine.Object;
RemainActiveWhile = activeState;
}
[Obsolete("Use SetTimeProvider()")]
public void InjectOptionalTimeProvider(Func<float> timeProvider)
{
_timeProvider = timeProvider;
}
#endregion
}
}