/// <summary> /// Due to the way the Add/Remove scheduled methods work it is not possible to call entity.RemoveScheduled(a) and entity.AddScheduled(a) within the same update. /// Either one waits at least one update between the 2 calls, or uses this method which will set the child to the new parent. /// This method is threadsafe. /// </summary> /// <param name="newParent"></param> public void ChangeParent(SceneGraphEntity newParent) { if (newParent == null) { throw new ArgumentNullException(nameof(newParent), "if you want to remove the entity from a scenegraph, call RemoveScheduled"); } if (!Initialized) { throw new NotSupportedException("Changing parent is only possible if the entity was already Initialized"); } // even though initialized is set to true, it will take one more update before the entity is actually added to the scenegraph // assert that said update has occured, otherwise parent is not properly set if (!Parent.IsRegistered(this)) { throw new NotSupportedException("it takes at least one frame after the entity has been Initialized before the entity is actually registered with the parent."); } if (Parent == null) { throw new NotSupportedException("The current entity must have a parent, otherwise ChangingParent is invalid."); } // allow override of parent because we are switching parent _parentSet = false; SetParent(this, newParent); }
/// <summary> /// Adds the entity the scenegraph. /// Note that the item is not added directly but rather the next time update is called. /// Entities that are not yet initialized are also further delayed (until initialize has executed on a background thread). /// The initialize thread is executed on the first parent scenegraph root that is found. /// Due to this, entities are not guaranteed to be added in same order as method was called (e.g. a not yet initialized entity is added first /// and needs 3s to initialize on the background thread. In the meantime entities that are already Initialized can be added directly). /// To prevent this issue, never mix initialized and non-initialized entities. Both categories are guaranteed to be added in order, UNLESS they are mixed. /// This method is threadsafe. /// </summary> /// <param name="child"></param> public virtual void AddAsync(SceneGraphEntity child) { SetParent(child, this); // note that the child is not added directly but rather to another list, see UpdateCollection method for reason if (child.Initialized) { // already initialized, add right away lock (_sceneGraphEntitiesToBeAdded) { _sceneGraphEntitiesToBeAdded.Add(child); } } else { // need to initialize first, add when callback is executed // the initialize background worker guarantees us the same order as we called it, so if no entity is initialized yet they are all scheduled on the worker thread in order // and end up being added in the same order _root.InitializeInBackground(child, e => { lock (_sceneGraphEntitiesToBeAdded) { _sceneGraphEntitiesToBeAdded.Add(e); } }); } }
/// <summary> /// Scenegraph internal helper that calls initialize on the child in a background thread and runs the action after initialization. /// This function returns immediately, the actual initialization of the child is thus deferred. /// </summary> /// <param name="child"></param> /// <param name="action"></param> internal void InitializeInBackground(SceneGraphEntity child, Action <SceneGraphEntity> action) { if (child == null) { throw new ArgumentNullException(nameof(child)); } if (child.Initialized) { // no need to call initialize again, just call the function right away action(child); } _sceneGraphObjectInitializer.QueueItem(child, action); }
/// <summary> /// Sets the parent of the child entity. /// Will also navigate up the tree of the parent to find the root. /// If a null parent is provided the function will "unset" the parent. /// </summary> /// <param name="child"></param> /// <param name="parent">If null, will unset the parent, otherwise will set it. Note that a parent can only be set if it has not yet been set before.</param> private static void SetParent(SceneGraphEntity child, SceneGraphEntity parent) { if (parent != null && child._parentSet) { throw new NotSupportedException("parent has already been set for the provided node. Remove the node from the scene graph that it is attached to and then try to add it again."); } if (parent != null) { child.Parent = parent; child.FindAndSetRootNode(); child._parentSet = true; } else { child.Parent = null; child._root = null; child._parentSet = false; } }
/// <summary> /// Call to find out if the specific entity is already registered as a child of the current node. /// Note that the *Scheduled functions will not execute directly and it may take a frame (or two) for an entity to actually be registered. /// This method is threadsafe. /// </summary> /// <param name="entity"></param> /// <param name="searchEntireTree">Defaults to false. If false will only check the direct childrend of this entity. If true will search the entire tree until all leafes are reached.</param> /// <returns>True if the entity is registered, false otherwise.</returns> public bool IsRegistered(SceneGraphEntity entity, bool searchEntireTree = false) { // create a copy of the list as this function might be called on a different thread and each update can change the source collection var entities = _sceneGraphEntities.ToList(); if (entities.Contains(entity)) { return(true); } if (searchEntireTree) { foreach (var e in entities) { if (e.IsRegistered(entity, true)) { return(true); } } } return(false); }
/// <summary> /// Looks for the scenegraph this node is attached to by going up the parent chain until a scene graph is found. /// </summary> protected void FindAndSetRootNode() { SceneGraphEntity root = this; while (!(root is SceneGraphRoot)) { root = root.Parent; // don't look for the ultimate root, just look for the next highest root node // this allows each root to run its own scheduler for all its children //while (root is SceneGraph && root.Parent != null) //{ // // keep going, we support nested scene graphs // root = root.Parent; //} } if (root == null) { throw new NotSupportedException("Could not find the root of the provided parent."); } _root = (SceneGraphRoot)root; }
/// <summary> /// Removes the entity from the scenegraph. /// Note that the item is not removed immediately but rather the next time update is called. /// This method is threadsafe. /// </summary> /// <param name="child"></param> /// <returns></returns> public void RemoveAsync(SceneGraphEntity child) { // note that the child is not removed directly but rather scheduled in another list, see UpdateCollection method for reason _sceneGraphEntitiesToBeRemoved.Add(child); }