Initial Commit
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
namespace Oculus.Interaction.PoseDetection
|
||||
{
|
||||
/// <summary>
|
||||
/// Chains together a number of IActiveStates into a sequence.
|
||||
/// 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: MinActiveTime <= "Time step N active for" <= 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:
|
||||
/// ActiveState: The IActiveState that is used to determine if the conditions of this step are fulfilled
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class Sequence : MonoBehaviour, IActiveState
|
||||
{
|
||||
[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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
public float MaxStepTime => _maxStepTime;
|
||||
|
||||
public ActivationStep()
|
||||
{
|
||||
}
|
||||
|
||||
public ActivationStep(IActiveState activeState, float minActiveTime, float maxStepTime)
|
||||
{
|
||||
ActiveState = activeState;
|
||||
_minActiveTime = minActiveTime;
|
||||
_maxStepTime = maxStepTime;
|
||||
}
|
||||
|
||||
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 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;
|
||||
Func<float> _timeProvider;
|
||||
|
||||
private float _cooldownExceededTime;
|
||||
private bool _wasRemainActive;
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
RemainActiveWhile = _remainActiveWhile as IActiveState;
|
||||
|
||||
ResetState();
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
if (_timeProvider == null)
|
||||
{
|
||||
_timeProvider = () => Time.time;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
public bool Active { get; private set; }
|
||||
|
||||
static Sequence()
|
||||
{
|
||||
ActiveStateDebugTree.RegisterModel<Sequence>(new DebugModel());
|
||||
}
|
||||
|
||||
private class DebugModel : ActiveStateModel<Sequence>
|
||||
{
|
||||
protected override IEnumerable<IActiveState> GetChildren(Sequence activeState)
|
||||
{
|
||||
List<IActiveState> children = new List<IActiveState>();
|
||||
children.AddRange(activeState._stepsToActivate.Select(step => step.ActiveState));
|
||||
children.Add(activeState.RemainActiveWhile);
|
||||
return children.Where(c => c != null);
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectOptionalStepsToActivate(ActivationStep[] stepsToActivate)
|
||||
{
|
||||
_stepsToActivate = stepsToActivate;
|
||||
}
|
||||
|
||||
public void InjectOptionalRemainActiveWhile(IActiveState activeState)
|
||||
{
|
||||
_remainActiveWhile = activeState as UnityEngine.Object;
|
||||
RemainActiveWhile = activeState;
|
||||
}
|
||||
|
||||
public void InjectOptionalTimeProvider(Func<float> timeProvider)
|
||||
{
|
||||
_timeProvider = timeProvider;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user