/* * @author Valentin Simonov / http://va.lent.in/ */ using System; using System.Collections.Generic; using TouchScript.Gestures; using TouchScript.Utils; using TouchScript.Pointers; using UnityEngine; using UnityEngine.Profiling; namespace TouchScript.Core { /// /// Internal implementation of . /// internal sealed class GestureManagerInstance : MonoBehaviour, IGestureManager { #region Public properties /// /// Gets the instance of GestureManager singleton. /// public static IGestureManager Instance { get { if (shuttingDown) return null; if (instance == null) { if (!Application.isPlaying) return null; var objects = FindObjectsOfType(); if (objects.Length == 0) { var go = new GameObject("GestureManager Instance"); instance = go.AddComponent(); } else if (objects.Length >= 1) { instance = objects[0]; } } return instance; } } /// public IGestureDelegate GlobalGestureDelegate { get; set; } #endregion #region Private variables private static GestureManagerInstance instance; private static bool shuttingDown = false; // Upcoming changes private List gesturesToReset = new List(20); private Dictionary> pointerToGestures = new Dictionary>(10); private CustomSampler gestureSampler; #endregion #region Temporary collections // Temporary collections for update methods. // Dictionary> - pointers sorted by targets private Dictionary> pointersOnTarget = new Dictionary>(10); // Dictionary> - pointers sorted by gesture private Dictionary> pointersToDispatchForGesture = new Dictionary>(10); private List activeGesturesThisUpdate = new List(20); private Dictionary> hierarchyEndingWithCache = new Dictionary>(4); private Dictionary> hierarchyBeginningWithCache = new Dictionary>(4); #endregion #region Pools private static ObjectPool> gestureListPool = new ObjectPool>(10, () => new List(10), null, (l) => l.Clear(), "GestureManager/Gesture"); private static ObjectPool> pointerListPool = new ObjectPool>(20, () => new List(10), null, (l) => l.Clear(), "GestureManager/Pointer"); private static ObjectPool> transformListPool = new ObjectPool>(10, () => new List(10), null, (l) => l.Clear(), "GestureManager/Transform"); #endregion #region Unity private void Awake() { if (instance == null) { instance = this; } else if (instance != this) { Destroy(this); return; } gameObject.hideFlags = HideFlags.HideInHierarchy; DontDestroyOnLoad(gameObject); gestureListPool.WarmUp(20); pointerListPool.WarmUp(20); transformListPool.WarmUp(1); gestureSampler = CustomSampler.Create("[TouchScript] Update Gestures"); } private void OnEnable() { var touchManager = TouchManager.Instance; if (touchManager != null) { touchManager.FrameStarted += frameStartedHandler; touchManager.FrameFinished += frameFinishedHandler; touchManager.PointersUpdated += pointersUpdatedHandler; touchManager.PointersPressed += pointersPressedHandler; touchManager.PointersReleased += pointersReleasedHandler; touchManager.PointersCancelled += pointersCancelledHandler; } } private void OnDisable() { var touchManager = TouchManager.Instance; if (touchManager != null) { touchManager.FrameStarted -= frameStartedHandler; touchManager.FrameFinished -= frameFinishedHandler; touchManager.PointersUpdated -= pointersUpdatedHandler; touchManager.PointersPressed -= pointersPressedHandler; touchManager.PointersReleased -= pointersReleasedHandler; touchManager.PointersCancelled -= pointersCancelledHandler; } } private void OnApplicationQuit() { shuttingDown = true; } #endregion #region Internal methods internal Gesture.GestureState INTERNAL_GestureChangeState(Gesture gesture, Gesture.GestureState state) { bool recognized = false; switch (state) { case Gesture.GestureState.Idle: case Gesture.GestureState.Possible: break; case Gesture.GestureState.Began: switch (gesture.State) { case Gesture.GestureState.Idle: case Gesture.GestureState.Possible: break; default: print(string.Format("Gesture {0} erroneously tried to enter state {1} from state {2}", new object[] {gesture, state, gesture.State})); break; } recognized = recognizeGestureIfNotPrevented(gesture); if (!recognized) { if (!gesturesToReset.Contains(gesture)) gesturesToReset.Add(gesture); return Gesture.GestureState.Failed; } break; case Gesture.GestureState.Changed: switch (gesture.State) { case Gesture.GestureState.Began: case Gesture.GestureState.Changed: break; default: print(string.Format("Gesture {0} erroneously tried to enter state {1} from state {2}", new object[] {gesture, state, gesture.State})); break; } break; case Gesture.GestureState.Failed: if (!gesturesToReset.Contains(gesture)) gesturesToReset.Add(gesture); break; case Gesture.GestureState.Recognized: // Ended if (!gesturesToReset.Contains(gesture)) gesturesToReset.Add(gesture); switch (gesture.State) { case Gesture.GestureState.Idle: case Gesture.GestureState.Possible: recognized = recognizeGestureIfNotPrevented(gesture); if (!recognized) return Gesture.GestureState.Failed; break; case Gesture.GestureState.Began: case Gesture.GestureState.Changed: break; default: print(string.Format("Gesture {0} erroneously tried to enter state {1} from state {2}", new object[] {gesture, state, gesture.State})); break; } break; case Gesture.GestureState.Cancelled: if (!gesturesToReset.Contains(gesture)) gesturesToReset.Add(gesture); break; } return state; } #endregion #region Private functions private void updatePressed(IList pointers) { gestureSampler.Begin(); var activeTargets = transformListPool.Get(); var gesturesInHierarchy = gestureListPool.Get(); var startedGestures = gestureListPool.Get(); // Arrange pointers by target. var count = pointers.Count; for (var i = 0; i < count; i++) { var pointer = pointers[i]; var target = pointer.GetPressData().Target; if (target == null) continue; List list; if (!pointersOnTarget.TryGetValue(target, out list)) { list = pointerListPool.Get(); pointersOnTarget.Add(target, list); activeTargets.Add(target); } list.Add(pointer); } // Process all targets - get and sort all gestures on targets in hierarchy. count = activeTargets.Count; for (var i = 0; i < count; i++) { var target = activeTargets[i]; // Pointers that hit . var targetPointers = pointersOnTarget[target]; var targetPointersCount = targetPointers.Count; // Gestures on objects in the hierarchy from "root" to target. var gesturesOnParentsAndMe = getHierarchyEndingWith(target); // Gestures in the target's hierarchy which might affect gestures on the target. // Gestures on all parents and all children. gesturesInHierarchy.AddRange(gesturesOnParentsAndMe); gesturesInHierarchy.AddRange(getHierarchyBeginningWith(target)); var gesturesInHierarchyCount = gesturesInHierarchy.Count; for (var j = 0; j < gesturesInHierarchyCount; j++) { var gesture = gesturesInHierarchy[j]; if (gesture.State == Gesture.GestureState.Began || gesture.State == Gesture.GestureState.Changed) startedGestures.Add(gesture); } var startedCount = startedGestures.Count; var possibleGestureCount = gesturesOnParentsAndMe.Count; for (var j = 0; j < possibleGestureCount; j++) { // WARNING! Gesture state might change during this loop. // For example when one of them recognizes. var possibleGesture = gesturesOnParentsAndMe[j]; // If the gesture is not active it can't start or recognize. if (!gestureIsActive(possibleGesture)) continue; var canReceivePointers = true; // For every possible gesture in gesturesInHierarchy we need to check if it prevents gestureOnParentOrMe from getting pointers. for (var k = 0; k < startedCount; k++) { var startedGesture = startedGestures[k]; if (possibleGesture == startedGesture) continue; // This gesture has started. Is gestureOnParentOrMe allowed to work in parallel? if (canPreventGesture(startedGesture, possibleGesture)) { // activeGesture has already began and prevents gestureOnParentOrMe from getting pointers. canReceivePointers = false; break; } } if (!canReceivePointers) continue; // Filter incoming pointers for gesture. var pointersSentToGesture = pointerListPool.Get(); for (var k = 0; k < targetPointersCount; k++) { var pointer = targetPointers[k]; if (shouldReceivePointer(possibleGesture, pointer)) pointersSentToGesture.Add(pointer); } // If there are any pointers to send. if (pointersSentToGesture.Count > 0) { if (pointersToDispatchForGesture.ContainsKey(possibleGesture)) { pointersToDispatchForGesture[possibleGesture].AddRange(pointersSentToGesture); pointerListPool.Release(pointersSentToGesture); } else { // Add gesture to the list of active gestures this update. activeGesturesThisUpdate.Add(possibleGesture); pointersToDispatchForGesture.Add(possibleGesture, pointersSentToGesture); } } else { pointerListPool.Release(pointersSentToGesture); } } gesturesInHierarchy.Clear(); startedGestures.Clear(); pointerListPool.Release(targetPointers); } gestureListPool.Release(gesturesInHierarchy); gestureListPool.Release(startedGestures); transformListPool.Release(activeTargets); // Dispatch gesture events with pointers assigned to them. count = activeGesturesThisUpdate.Count; for (var i = 0; i < count; i++) { var gesture = activeGesturesThisUpdate[i]; var list = pointersToDispatchForGesture[gesture]; if (!gestureIsActive(gesture)) { pointerListPool.Release(list); continue; } var numPointers = list.Count; for (var j = 0; j < numPointers; j++) { var pointer = list[j]; List gestureList; if (!pointerToGestures.TryGetValue(pointer.Id, out gestureList)) { gestureList = gestureListPool.Get(); pointerToGestures.Add(pointer.Id, gestureList); } gestureList.Add(gesture); } gesture.INTERNAL_PointersPressed(list); pointerListPool.Release(list); } pointersOnTarget.Clear(); activeGesturesThisUpdate.Clear(); pointersToDispatchForGesture.Clear(); gestureSampler.End(); } private void updateUpdated(IList pointers) { gestureSampler.Begin(); sortPointersForActiveGestures(pointers); var count = activeGesturesThisUpdate.Count; for (var i = 0; i < count; i++) { var gesture = activeGesturesThisUpdate[i]; var list = pointersToDispatchForGesture[gesture]; if (gestureIsActive(gesture)) { gesture.INTERNAL_PointersUpdated(list); } pointerListPool.Release(list); } activeGesturesThisUpdate.Clear(); pointersToDispatchForGesture.Clear(); gestureSampler.End(); } private void updateReleased(IList pointers) { gestureSampler.Begin(); sortPointersForActiveGestures(pointers); var count = activeGesturesThisUpdate.Count; for (var i = 0; i < count; i++) { var gesture = activeGesturesThisUpdate[i]; var list = pointersToDispatchForGesture[gesture]; if (gestureIsActive(gesture)) { gesture.INTERNAL_PointersReleased(list); } pointerListPool.Release(list); } removePointers(pointers); activeGesturesThisUpdate.Clear(); pointersToDispatchForGesture.Clear(); gestureSampler.End(); } private void updateCancelled(IList pointers) { gestureSampler.Begin(); sortPointersForActiveGestures(pointers); var count = activeGesturesThisUpdate.Count; for (var i = 0; i < count; i++) { var gesture = activeGesturesThisUpdate[i]; var list = pointersToDispatchForGesture[gesture]; if (gestureIsActive(gesture)) { gesture.INTERNAL_PointersCancelled(list); } pointerListPool.Release(list); } removePointers(pointers); activeGesturesThisUpdate.Clear(); pointersToDispatchForGesture.Clear(); gestureSampler.End(); } private void sortPointersForActiveGestures(IList pointers) { var count = pointers.Count; for (var i = 0; i < count; i++) { var pointer = pointers[i]; List gestures; if (!pointerToGestures.TryGetValue(pointer.Id, out gestures)) continue; var gestureCount = gestures.Count; for (var j = 0; j < gestureCount; j++) { var gesture = gestures[j]; List toDispatch; if (!pointersToDispatchForGesture.TryGetValue(gesture, out toDispatch)) { toDispatch = pointerListPool.Get(); pointersToDispatchForGesture.Add(gesture, toDispatch); activeGesturesThisUpdate.Add(gesture); } toDispatch.Add(pointer); } } } private void removePointers(IList pointers) { var count = pointers.Count; for (var i = 0; i < count; i++) { var pointer = pointers[i]; List list; if (!pointerToGestures.TryGetValue(pointer.Id, out list)) continue; pointerToGestures.Remove(pointer.Id); gestureListPool.Release(list); } } private void resetGestures() { if (gesturesToReset.Count == 0) return; var count = gesturesToReset.Count; for (var i = 0; i < count; i++) { var gesture = gesturesToReset[i]; if (Equals(gesture, null)) continue; // Reference comparison var activePointers = gesture.ActivePointers; var activeCount = activePointers.Count; for (var j = 0; j < activeCount; j++) { var pointer = activePointers[j]; List list; if (pointerToGestures.TryGetValue(pointer.Id, out list)) list.Remove(gesture); } if (gesture == null) continue; // Unity "null" comparison gesture.INTERNAL_Reset(); gesture.INTERNAL_SetState(Gesture.GestureState.Idle); } gesturesToReset.Clear(); } private void clearFrameCaches() { foreach (var kv in hierarchyEndingWithCache) gestureListPool.Release(kv.Value); foreach (var kv in hierarchyBeginningWithCache) gestureListPool.Release(kv.Value); hierarchyEndingWithCache.Clear(); hierarchyBeginningWithCache.Clear(); } // parent <- parent <- target private List getHierarchyEndingWith(Transform target) { List list; if (hierarchyEndingWithCache.TryGetValue(target, out list)) return list; list = gestureListPool.Get(); target.GetComponentsInParent(false, list); hierarchyEndingWithCache.Add(target, list); return list; } // target <- child* private List getHierarchyBeginningWith(Transform target) { List list; if (hierarchyBeginningWithCache.TryGetValue(target, out list)) return list; list = gestureListPool.Get(); target.GetComponentsInChildren(list); hierarchyBeginningWithCache.Add(target, list); return list; } private bool gestureIsActive(Gesture gesture) { if (gesture.gameObject.activeInHierarchy == false) return false; if (gesture.enabled == false) return false; switch (gesture.State) { case Gesture.GestureState.Failed: case Gesture.GestureState.Recognized: case Gesture.GestureState.Cancelled: return false; default: return true; } } private bool recognizeGestureIfNotPrevented(Gesture gesture) { if (!shouldBegin(gesture)) return false; var gesturesToFail = gestureListPool.Get(); bool canRecognize = true; var target = gesture.transform; var gesturesInHierarchy = gestureListPool.Get(); gesturesInHierarchy.AddRange(getHierarchyEndingWith(target)); gesturesInHierarchy.AddRange(getHierarchyBeginningWith(target)); var count = gesturesInHierarchy.Count; for (var i = 0; i < count; i++) { var otherGesture = gesturesInHierarchy[i]; if (gesture == otherGesture) continue; if (!gestureIsActive(otherGesture)) continue; if (otherGesture.State == Gesture.GestureState.Began || otherGesture.State == Gesture.GestureState.Changed) { if (canPreventGesture(otherGesture, gesture)) { canRecognize = false; break; } } else if (otherGesture.State == Gesture.GestureState.Possible) { if (canPreventGesture(gesture, otherGesture)) { gesturesToFail.Add(otherGesture); } } } if (canRecognize) { count = gesturesToFail.Count; for (var i = 0; i < count; i++) { failGesture(gesturesToFail[i]); } } gestureListPool.Release(gesturesToFail); gestureListPool.Release(gesturesInHierarchy); return canRecognize; } private void failGesture(Gesture gesture) { gesture.INTERNAL_SetState(Gesture.GestureState.Failed); } private bool shouldReceivePointer(Gesture gesture, Pointer pointer) { bool result = true; if (GlobalGestureDelegate != null) result = GlobalGestureDelegate.ShouldReceivePointer(gesture, pointer); return result && gesture.ShouldReceivePointer(pointer); } private bool shouldBegin(Gesture gesture) { bool result = true; if (GlobalGestureDelegate != null) result = GlobalGestureDelegate.ShouldBegin(gesture); return result && gesture.ShouldBegin(); } private bool canPreventGesture(Gesture first, Gesture second) { bool result = true; if (GlobalGestureDelegate != null) result = !GlobalGestureDelegate.ShouldRecognizeSimultaneously(first, second); return result && first.CanPreventGesture(second); } #endregion #region Pointer events handlers private void frameFinishedHandler(object sender, EventArgs eventArgs) { resetGestures(); clearFrameCaches(); } private void frameStartedHandler(object sender, EventArgs eventArgs) { resetGestures(); } private void pointersPressedHandler(object sender, PointerEventArgs pointerEventArgs) { updatePressed(pointerEventArgs.Pointers); } private void pointersUpdatedHandler(object sender, PointerEventArgs pointerEventArgs) { updateUpdated(pointerEventArgs.Pointers); } private void pointersReleasedHandler(object sender, PointerEventArgs pointerEventArgs) { updateReleased(pointerEventArgs.Pointers); } private void pointersCancelledHandler(object sender, PointerEventArgs pointerEventArgs) { updateCancelled(pointerEventArgs.Pointers); } #endregion } }