Loading files
2046045a2This file is pinned to the selected release. Download the original to open it in your design tool.
using Leap.Unity.Query;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Leap.Unity.Animation {
[Serializable]
public class SwitchTree {
#region NodeRef & Node
public class NodeRef {
public Node node;
public NodeRef(Node node) {
this.node = node;
}
public NodeRef() { }
}
public struct Node {
bool _isValid;
/// <summary>
/// The MonoBehaviour for this Node represents both its Transform (via its
/// transform property) and its IPropertySwitch (because the MonoBehaviour itself
/// must implement IPropertySwitch).
/// </summary>
private MonoBehaviour _switchBehaviour;
public MonoBehaviour switchBehaviour {
get { return _switchBehaviour; }
}
public Transform transform {
get {
if (!isValid) return null;
return _switchBehaviour.transform;
}
}
public IPropertySwitch objSwitch {
get {
if (!isValid) return null;
return _switchBehaviour as IPropertySwitch;
}
}
public NodeRef parent;
public List<Node> children;
public int treeDepth;
/// <summary>
/// The number of this node's children and grandchildren.
/// </summary>
public int numAllChildren;
public int numChildren { get { return children.Count; } }
public bool hasChildren { get { return children.Count > 0; } }
public bool hasParent { get { return parent != null; } }
public bool hasSibling { get { return hasParent && parent.node.numChildren > 1; } }
public bool isValid {
get { return _isValid; }
}
public bool isOn {
get {
if (objSwitch == null) return false;
return objSwitch.GetIsOnOrTurningOn();
}
}
public bool isParentOn {
get {
return hasParent && parent.node.isOn;
}
}
public bool isOff {
get {
if (objSwitch == null) return false;
return objSwitch.GetIsOffOrTurningOff();
}
}
public bool hasNextSibling {
get {
return hasParent
&& parent.node.GetIndexOfChild(this) != parent.node.numChildren - 1;
}
}
public Node nextSibling {
get {
return parent.node.GetChild(parent.node.GetIndexOfChild(this) + 1);
}
}
public bool isNextSiblingOn {
get {
return hasNextSibling && nextSibling.isOn;
}
}
public bool hasPrevSibling {
get {
return hasParent && parent.node.GetIndexOfChild(this) != 0;
}
}
public Node prevSibling {
get {
return parent.node.GetChild(parent.node.GetIndexOfChild(this) - 1);
}
}
public bool isPrevSiblingOn {
get {
return hasPrevSibling && prevSibling.isOn;
}
}
public PrevSiblingEnumerator prevSiblings {
get { return new PrevSiblingEnumerator(this); }
}
public struct PrevSiblingEnumerator : IEnumerator<Node> {
Node node;
int curIdx;
public PrevSiblingEnumerator(Node node) {
this.node = node;
if (!node.hasParent) { curIdx = -1; }
else { curIdx = node.parent.node.GetIndexOfChild(node); }
}
public Node Current { get { return node.parent.node.GetChild(curIdx); } }
object IEnumerator.Current {
get { return Current; }
}
public void Dispose() { }
public bool MoveNext() {
curIdx -= 1;
return curIdx >= 0;
}
public PrevSiblingEnumerator GetEnumerator() { return this; }
public bool TryGetNext(out Node t) {
bool hasNext = MoveNext();
if (!hasNext) { t = default(Node); return false; }
else { t = Current; return true; }
}
public void Reset() { curIdx = node.parent.node.GetIndexOfChild(node); }
public Query<Node> Query() {
return (this as IEnumerator<Node>).Query();
}
}
public Node(MonoBehaviour switchBehaviour,
NodeRef parent = null,
int treeDepth = 0) {
this._switchBehaviour = switchBehaviour;
this.parent = parent;
this.treeDepth = treeDepth;
_isValid = true;
children = new List<Node>();
numAllChildren = 0;
constructChildren();
}
public int GetIndexOfChild(Node child) {
return children.IndexOf(child);
}
public Node GetChild(int childIdx) {
return children[childIdx];
}
private void constructChildren() {
var stack = Pool<Stack<Transform>>.Spawn();
stack.Clear();
stack.Push(this.transform);
try {
while (stack.Count > 0) {
var transform = stack.Pop();
foreach (var child in transform.GetChildren()) {
// Ignore SwitchTreeControllers, which will handle their own internal
// hierarchy.
if (child.GetComponent<SwitchTreeController>() != null) continue;
// ObjectSwitches get priority, but any Switch will do.
IPropertySwitch objSwitch = child.GetComponent<ObjectSwitch>();
if (objSwitch == null) {
objSwitch = child.GetComponent<IPropertySwitch>();
}
if (objSwitch != null) {
// Each child with a switch component gets a node with the current node
// as its parent.
var newChild = new Node((objSwitch as MonoBehaviour),
new NodeRef(this),
this.treeDepth + 1);
children.Add(newChild);
this.numAllChildren += 1 + newChild.numAllChildren;
}
else {
// This node will "inherit" any grand-children nodes whose parents are
// not _themselves_ switches as direct "children" in the switch tree.
stack.Push(child);
}
}
}
}
finally {
stack.Clear();
Pool<Stack<Transform>>.Recycle(stack);
}
}
public static bool operator ==(Node one, Node other) {
return one.Equals(other);
}
public static bool operator !=(Node one, Node other) {
return !one.Equals(other);
}
public override bool Equals(object obj) {
if (!(obj is Node)) return false;
return this.Equals((Node)obj);
}
public bool Equals(Node other) {
return isValid && this._switchBehaviour == other._switchBehaviour;
}
public override int GetHashCode() {
if (this._switchBehaviour == null) return 0;
return this._switchBehaviour.GetHashCode();
}
}
#endregion
/// <summary>
/// This is the sole object Unity serializes to serialize the switch tree; the rest
/// is generated in OnAfterDeserialize.
/// </summary>
[SerializeField]
public MonoBehaviour rootSwitchBehaviour;
/// <summary>
/// Unity also serializes the name of the currently active node; when the tree is
/// initialized, this will be the node it attempts to switch to.
/// </summary>
[SerializeField]
private string _curActiveNodeName = "";
public string curActiveNodeName {
get { return _curActiveNodeName; }
}
private Node _root;
private bool _treeReady;
public SwitchTree(Transform transform, string startingActiveNode = null) {
var objSwitch = transform.GetComponent<IPropertySwitch>();
if (objSwitch == null) {
throw new System.InvalidOperationException("Cannot build a Switch Tree for "
+ "a Transform that is not itself a "
+ "switch.");
}
rootSwitchBehaviour = (objSwitch as MonoBehaviour);
_treeReady = false;
if (startingActiveNode == null) {
_curActiveNodeName = rootSwitchBehaviour.name;
}
else {
_curActiveNodeName = startingActiveNode;
ensureTreeReady();
SwitchTo(startingActiveNode);
}
}
public int NodeCount {
get {
ensureTreeReady();
return _root.numAllChildren + 1;
}
}
private void ensureTreeReady() {
if (!_treeReady) {
initTree();
}
}
private void initTree() {
_root = new Node(rootSwitchBehaviour, null);
}
/// <summary>
/// Traverses the tree, deactivating all switch pathways that do not lead to the
/// switch node identified by this nodeName, then activating all switches along the
/// path to the switch node identified by this nodeName, but no deeper. Switches that
/// are children of the named node are also deactivated.
///
/// Returns true if the node identified by nodeName was found, false otherwise.
/// </summary>
public bool SwitchTo(string nodeName, bool immediately = false, bool toggle = false) {
ensureTreeReady();
// We have to traverse the whole tree because we don't know where the node matching
// nodeName resides. This is fine, because the contract of the tree is to maintain
// every node's state anyway, not just the ones that are currently activating or
// deactivating.
var activeNodeChain = Pool<Stack<Node>>.Spawn();
var visitedNodes = Pool<HashSet<Node>>.Spawn();
var tempNodeRef = Pool<NodeRef>.Spawn();
var curNodeRef = tempNodeRef;
curNodeRef.node = _root;
// This dictionary allows us to reverse-breadth-first traverse all nodes once;
// we build it during the depth-first traversal.
var nodesAtDepthLevel = Pool<Dictionary<int, List<Node>>>.Spawn();
int curDepth = 0, largestDepth = 0;
Node activeNode = default(Node);
try {
// Depth-first traversal.
while (curNodeRef != null) {
var node = curNodeRef.node;
// If visiting a new node, check if it's the desired active node.
if (!visitedNodes.Contains(node)) {
visitedNodes.Add(node);
// We also construct a depth-first stack of all nodes, so we
// can deactivate nodes in a predictable (reverse-depth-first) order
// post-traversal.
List<Node> nodes = null;
if (!nodesAtDepthLevel.TryGetValue(curDepth, out nodes)) {
nodesAtDepthLevel[curDepth] = nodes = Pool<List<Node>>.Spawn();
}
nodes.Add(node);
if (node.transform.name.Equals(nodeName)) {
// We've found the node we want active.
activeNode = node;
_curActiveNodeName = activeNode.switchBehaviour.name;
buildNodeChain(activeNode, activeNodeChain);
}
}
// Go deeper into children if there are any.
bool goingDown = false;
foreach (var child in node.children) {
if (!visitedNodes.Contains(child)) {
curNodeRef.node = child;
goingDown = true;
break;
}
}
if (goingDown) {
// Heading down into the unvisited child.
curDepth += 1;
if (curDepth > largestDepth) {
largestDepth = curDepth;
}
continue;
}
else {
// Head back up to the parent node.
curDepth -= 1;
curNodeRef = node.parent;
}
}
// After traversal, we have a per-depth-level dictionary of all nodes and a
// Stack of the node chain we desire to activate.
//
// Deeper nodes should deactivate before their parent nodes. Nodes in the chain
// we desire active should not deactivate, and if they are not already active,
// they should activate from the root down.
if (nodesAtDepthLevel.Count != 0) {
List<Node> depthNodes;
for (int d = largestDepth; d >= 0; d--) {
depthNodes = nodesAtDepthLevel[d];
foreach (var node in depthNodes) {
if (node.objSwitch.GetIsOnOrTurningOn()
&& !activeNodeChain.Contains(node)) {
turnOff(node, immediately);
}
}
}
while (activeNodeChain.Count > 0) {
var node = activeNodeChain.Pop();
if (toggle && activeNode.isOn && node == activeNode) {
// The current node is the target "active" node, but if we're trying to
// _toggle_ this node and the node is on, then we only desire to switch to
// its parent, and have the node itself off.
turnOff(node, immediately);
if (!node.hasParent) {
_curActiveNodeName = "";
}
else {
_curActiveNodeName = node.parent.node.switchBehaviour.name;
}
continue;
}
else {
turnOn(node, immediately);
}
// Furthermore, some nodes activate switches in their children as a part of
// their normal switching functionality. However, when the tree desires to
// switch to a specific node beneath one of these nodes, this behavior should
// be overridden.
// To do this, we need to deactivate all OTHER children when we still have a\
// child node to activate, just after activating any node.
if (activeNodeChain.Count > 0) {
var childToActivate = activeNodeChain.Pop();
foreach (var child in node.children) {
if (childToActivate == child) continue;
if (child.objSwitch.GetIsOnOrTurningOn()) {
turnOff(child, immediately);
}
}
activeNodeChain.Push(childToActivate);
}
}
}
// Return true if we were able to find our target active node.
return activeNode != default(Node);
}
finally {
Pool<NodeRef>.Recycle(tempNodeRef);
visitedNodes.Clear();
Pool<HashSet<Node>>.Recycle(visitedNodes);
foreach (var depthNodesPair in nodesAtDepthLevel) {
var nodes = depthNodesPair.Value;
nodes.Clear();
Pool<List<Node>>.Recycle(nodes);
}
nodesAtDepthLevel.Clear();
Pool<Dictionary<int, List<Node>>>.Recycle(nodesAtDepthLevel);
activeNodeChain.Clear();
Pool<Stack<Node>>.Recycle(activeNodeChain);
}
}
/// <summary>
/// If the specified state node is not active, the switch tree will switch to that
/// node. If switch tree will switch to that node's parent, deactivating the
/// specified node. (Calling this method will also deactivate any sibling nodes that
/// might have been activated out of the context of the SwitchTree.)
///
/// Returns true if the node identified by nodeName was found in the tree,
/// false otherwise.
/// </summary>
public bool ToggleState(string nodeName, bool immediately = false) {
return SwitchTo(nodeName, immediately, toggle: true);
}
/// <summary>
/// Fills the node stack with the node-parent chain such that
/// the first Pop() produces the highest parent node and further Pops()
/// produce children down the chain, ending with (and including) the
/// starting node.
/// </summary>
private static void buildNodeChain(Node node, Stack<Node> nodeStack) {
nodeStack.Clear();
var curNode = node;
while (true) {
nodeStack.Push(curNode);
if (curNode.hasParent) {
curNode = curNode.parent.node;
}
else {
break;
}
}
}
private static void turnOn(Node node, bool immediately) {
if (Application.isPlaying && !immediately) {
node.objSwitch.On();
}
else {
node.objSwitch.OnNow();
}
}
private static void turnOff(Node node, bool immediately) {
if (Application.isPlaying && !immediately) {
node.objSwitch.Off();
}
else {
node.objSwitch.OffNow();
}
}
/// <summary>
/// Returns an enumerator that traverses the switch tree depth-first. You must
/// provide a HashSet of SwitchTree.Node objects for the enumerator to track which
/// nodes it has visited already without allocating. (You can also use this, as long
/// as you don't modify it mid-traversal.)
/// </summary>
public DepthFirstEnumerator Traverse(HashSet<Node> visitedNodesCache) {
return new DepthFirstEnumerator(this, visitedNodesCache);
}
#region Enumerators
public struct DepthFirstEnumerator : IEnumerator<Node> {
private Maybe<Node> maybeCurNode;
private HashSet<Node> visitedNodes;
private SwitchTree tree;
public DepthFirstEnumerator(SwitchTree tree, HashSet<Node> useToTrackVisitedNodes) {
useToTrackVisitedNodes.Clear();
this.tree = tree;
maybeCurNode = Maybe.None;
visitedNodes = useToTrackVisitedNodes;
}
public DepthFirstEnumerator GetEnumerator() { return this; }
public Node Current { get { return maybeCurNode.valueOrDefault; } }
object IEnumerator.Current {
get { return Current; }
}
public void Dispose() { }
public bool MoveNext() {
if (!maybeCurNode.hasValue) {
maybeCurNode = Maybe.Some(tree._root);
return true;
}
var node = maybeCurNode.valueOrDefault;
visitedNodes.Add(node);
bool goingDown = false;
foreach (var child in node.children) {
if (!visitedNodes.Contains(child)) {
maybeCurNode = Maybe.Some(child);
goingDown = true;
}
}
if (goingDown) {
// We've already set maybeCurNode with the child node.
return true;
}
else if (node.hasParent) {
maybeCurNode = Maybe.Some(node.parent.node);
return MoveNext();
}
else {
return false;
}
}
public bool TryGetNext(out Node t) {
bool hasNext = MoveNext();
if (hasNext) {
t = Current;
return true;
}
else {
t = default(Node);
return false;
}
}
public void Reset() {
visitedNodes.Clear();
maybeCurNode = Maybe.None;
}
}
#endregion
}
}