Example #1
0
 internal void CleanUp()
 {
     isActive = true;
     deactivationCandidateCount = 0;
     memberCount     = 0;
     immediateParent = this;
 }
        private SimulationIsland Merge(SimulationIsland s1, SimulationIsland s2)
        {
            //Pull the smaller island into the larger island and set all members
            //of the smaller island to refer to the new island.

            //The simulation islands can be null; a connection can be a kinematic entity, which has no simulation island.
            //'Merging' a null island with an island simply gets back the island.
            if (s1 == null)
            {
                //Should still activate the island, though.
                s2.IsActive = true;
                return(s2);
            }
            if (s2 == null)
            {
                //Should still activate the island, though.
                s1.IsActive = true;
                return(s1);
            }

            //Swap if needed so s1 is the bigger island
            if (s1.memberCount < s2.memberCount)
            {
                var biggerIsland = s2;
                s2 = s1;
                s1 = biggerIsland;
            }

            s1.IsActive        = true;
            s2.immediateParent = s1;

            //This is a bit like a 'union by rank.'
            //But don't get confused- simulation islands are not a union find structure.
            //This parenting simply avoids the need for maintaining a list of members in each simulation island.
            //In the subsequent frame, the deactivation candidacy update will go through the parents and eat away
            //at the child simulation island.  Then, in a later TryToDeactivate phase, the then-empty simulation island
            //will be removed.

            //The larger one survives.
            return(s1);
        }
        ///<summary>
        /// Adds a simulation island to a member.
        ///</summary>
        ///<param name="member">Member to gain a simulation island.</param>
        ///<exception cref="Exception">Thrown if the member already has a simulation island.</exception>
        public void AddSimulationIslandToMember(SimulationIslandMember member)
        {
            if (member.SimulationIsland != null)
            {
                throw new ArgumentException("Cannot initialize member's simulation island; it already has one.");
            }
            if (member.connections.Count > 0)
            {
                SimulationIsland island = null;
                //Find a simulation starting island to live in.
                for (int i = 0; i < member.connections.Count; i++)
                {
                    for (int j = 0; j < member.connections.Elements[i].entries.Count; j++)
                    {
                        island = member.connections.Elements[i].entries.Elements[j].Member.SimulationIsland;
                        if (island != null)
                        {
                            island.Add(member);
                            break;
                        }
                    }
                    if (island != null)
                    {
                        break;
                    }
                }
                if (member.SimulationIsland == null)
                {
                    //No non-null entries in any connections.  That's weird.
                    //Maybe it's connected to a bunch of kinematics, or maybe it's a vehicle-like situation
                    //where the body is associated with a 'vehicle' connection which sometimes contains only the body.

                    //No friends to merge with.
                    SimulationIsland newIsland = islandPool.Take();
                    simulationIslands.Add(newIsland);
                    newIsland.Add(member);
                    return;
                }


                //Becoming dynamic adds a new path.
                //Merges must be attempted between its connected members.
                for (int i = 0; i < member.connections.Count; i++)
                {
                    for (int j = 0; j < member.connections.Elements[i].entries.Count; j++)
                    {
                        if (member.connections.Elements[i].entries.Elements[j].Member == member)
                        {
                            continue; //Don't bother trying to compare against ourselves.  That would cause an erroneous early-out sometimes.
                        }
                        SimulationIsland opposingIsland = member.connections.Elements[i].entries.Elements[j].Member.SimulationIsland;
                        if (opposingIsland != null)
                        {
                            if (island != opposingIsland)
                            {
                                island = Merge(island, opposingIsland);
                            }
                            //All non-null simulation islands in a single connection are guaranteed to be the same island due to previous merges.
                            //Once we find one, we can stop.
                            break;
                        }
                    }
                }
            }
            else
            {
                //No friends to merge with.
                SimulationIsland newIsland = islandPool.Take();
                simulationIslands.Add(newIsland);
                newIsland.Add(member);
            }
        }
        ///<summary>
        /// Strips a member of its simulation island.
        ///</summary>
        ///<param name="member">Member to be stripped.</param>
        public void RemoveSimulationIslandFromMember(SimulationIslandMember member)
        {
            //Becoming kinematic eliminates the member as a possible path.
            //Splits must be attempted between its connected members.
            //Don't need to split same-connection members.  Splitting one non-null entry against a non null entry in each of the other connections will do the trick.
            if (member.simulationIsland != null)
            {
                //Note that this is using the most immediate simulation island.  This is because the immediate simulation island
                //is the one who 'owns' the member; not the root parent.  The root parent will own the member in the next frame
                //after the deactivation candidacy loop runs.
                SimulationIsland island = member.simulationIsland;
                island.Remove(member);
                if (island.memberCount == 0)
                {
                    //Even though we appear to have connections, the island was only me!
                    //We can stop now.
                    //Note that we do NOT remove the island from the simulation islands list here.
                    //That would take an O(n) search.  Instead, orphan it and let the TryToDeactivate loop find it.
                    return;
                }
            }
            if (member.connections.Count > 0)
            {
                for (int i = 0; i < member.connections.Count; i++)
                {
                    //Find a member with a non-null island to represent connection i.
                    SimulationIslandMember representativeA = null;
                    for (int j = 0; j < member.connections.Elements[i].entries.Count; j++)
                    {
                        if (member.connections.Elements[i].entries.Elements[j].Member.SimulationIsland != null)
                        {
                            representativeA = member.connections.Elements[i].entries.Elements[j].Member;
                            break;
                        }
                    }

                    if (representativeA == null)
                    {
                        //There was no representative!  That means it was a connection in which
                        //no member had a simulation island.  Consider removing a dynamic box from the space
                        //while it sits on a kinematic box.  Neither object has a simulation island.
                        //In this case, simply try the next connection.
                        continue;
                    }
                    //Activate the representative. This must be performed even if no split occurs; connected objects must be activated!
                    representativeA.Activate();

                    //Split the representative against representatives from other connections.
                    for (int j = i + 1; j < member.connections.Count; j++)
                    {
                        //Find a representative for another connection.
                        SimulationIslandMember representativeB = null;
                        for (int k = 0; k < member.connections.Elements[j].entries.Count; k++)
                        {
                            if (member.connections.Elements[j].entries.Elements[k].Member.SimulationIsland != null)
                            {
                                representativeB = member.connections.Elements[j].entries.Elements[k].Member;
                                break;
                            }
                        }

                        if (representativeB == null)
                        {
                            //There was no representative!  Same idea as above.
                            //Try the next connection.
                            continue;
                        }
                        //Activate the representative. This must be performed even if no split occurs; connected objects must be activated!
                        representativeB.Activate();

                        //Try to split the representatives.
                        //Don't bother doing any deferring; this is a rare activity
                        //and it's best just to do it up front.
                        TryToSplit(representativeA, representativeB);
                    }
                }
            }
        }
        /// <summary>
        /// Tries to split connections between the two island members.
        /// </summary>
        /// <param name="member1">First island member.</param>
        /// <param name="member2">Second island member.</param>
        /// <returns>Whether a split operation was run.  This does not mean a split was
        /// successful, just that the expensive test was performed.</returns>
        private bool TryToSplit(SimulationIslandMember member1, SimulationIslandMember member2)
        {
            //Can't split if they aren't even in the same island.
            //This also covers the case where the connection involves a kinematic entity that has no
            //simulation island at all.
            if (member1.SimulationIsland != member2.SimulationIsland ||
                member1.SimulationIsland == null ||
                member2.SimulationIsland == null)
            {
                return(false);
            }


            //By now, we know the members belong to the same island and are not null.
            //Start a BFS starting from each member.
            //Two-way can complete the search quicker.

            member1Friends.Enqueue(member1);
            member2Friends.Enqueue(member2);
            searchedMembers1.Add(member1);
            searchedMembers2.Add(member2);
            member1.searchState = SimulationIslandSearchState.OwnedByFirst;
            member2.searchState = SimulationIslandSearchState.OwnedBySecond;

            while (member1Friends.Count > 0 && member2Friends.Count > 0)
            {
                SimulationIslandMember currentNode = member1Friends.Dequeue();
                for (int i = 0; i < currentNode.connections.Count; i++)
                {
                    for (int j = 0; j < currentNode.connections.Elements[i].entries.Count; j++)
                    {
                        SimulationIslandMember connectedNode;
                        if ((connectedNode = currentNode.connections.Elements[i].entries.Elements[j].Member) != currentNode &&
                            connectedNode.SimulationIsland != null) //The connection could be connected to something that isn't in the Space and has no island, or it's not dynamic.
                        {
                            switch (connectedNode.searchState)
                            {
                            case SimulationIslandSearchState.Unclaimed:
                                //Found a new friend :)
                                member1Friends.Enqueue(connectedNode);
                                connectedNode.searchState = SimulationIslandSearchState.OwnedByFirst;
                                searchedMembers1.Add(connectedNode);
                                break;

                            case SimulationIslandSearchState.OwnedBySecond:
                                //Found our way to member2Friends set; cannot split!
                                member1Friends.Clear();
                                member2Friends.Clear();
                                goto ResetSearchStates;
                            }
                        }
                    }
                }

                currentNode = member2Friends.Dequeue();
                for (int i = 0; i < currentNode.connections.Count; i++)
                {
                    for (int j = 0; j < currentNode.connections.Elements[i].entries.Count; j++)
                    {
                        SimulationIslandMember connectedNode;
                        if ((connectedNode = currentNode.connections.Elements[i].entries.Elements[j].Member) != currentNode &&
                            connectedNode.SimulationIsland != null) //The connection could be connected to something that isn't in the Space and has no island, or it's not dynamic.
                        {
                            switch (connectedNode.searchState)
                            {
                            case SimulationIslandSearchState.Unclaimed:
                                //Found a new friend :)
                                member2Friends.Enqueue(connectedNode);
                                connectedNode.searchState = SimulationIslandSearchState.OwnedBySecond;
                                searchedMembers2.Add(connectedNode);
                                break;

                            case SimulationIslandSearchState.OwnedByFirst:
                                //Found our way to member1Friends set; cannot split!
                                member1Friends.Clear();
                                member2Friends.Clear();
                                goto ResetSearchStates;
                            }
                        }
                    }
                }
            }
            //If one of the queues empties out without finding anything, it means it's isolated.  The other one will never find it.
            //Now we can do a split.  Grab a new Island, fill it with the isolated search stuff.  Remove the isolated search stuff from the old Island.


            SimulationIsland newIsland = islandPool.Take();

            simulationIslands.Add(newIsland);
            if (member1Friends.Count == 0)
            {
                //Member 1 is isolated, give it its own simulation island!
                for (int i = 0; i < searchedMembers1.Count; i++)
                {
                    searchedMembers1[i].simulationIsland.Remove(searchedMembers1[i]);
                    newIsland.Add(searchedMembers1[i]);
                }
                member2Friends.Clear();
            }
            else if (member2Friends.Count == 0)
            {
                //Member 2 is isolated, give it its own simulation island!
                for (int i = 0; i < searchedMembers2.Count; i++)
                {
                    searchedMembers2[i].simulationIsland.Remove(searchedMembers2[i]);
                    newIsland.Add(searchedMembers2[i]);
                }
                member1Friends.Clear();
            }

            //Force the system awake.
            //Technically, the members should already be awake.
            //However, calling Activate on them resets the members'
            //deactivation candidacy timers.  This prevents the island
            //from instantly going back to sleep, which could leave
            //objects hanging in mid-air.
            member1.Activate();
            member2.Activate();


ResetSearchStates:
            for (int i = 0; i < searchedMembers1.Count; i++)
            {
                searchedMembers1[i].searchState = SimulationIslandSearchState.Unclaimed;
            }
            for (int i = 0; i < searchedMembers2.Count; i++)
            {
                searchedMembers2[i].searchState = SimulationIslandSearchState.Unclaimed;
            }
            searchedMembers1.Clear();
            searchedMembers2.Clear();
            return(true);
        }
 void GiveBackIsland(SimulationIsland island)
 {
     island.CleanUp();
     islandPool.GiveBack(island);
 }