public void AddMixedNewAndExistingStates()
        {
            var statesToProcess = new NativeList <StateTransitionInfoPair <int, int, StateTransitionInfo> >(2, Allocator.TempJob);

            statesToProcess.Add(new StateTransitionInfoPair <int, int, StateTransitionInfo>(k_StateOne, k_ActionOne, k_StateOne, new StateTransitionInfo()
            {
                Probability = 1, TransitionUtilityValue = 1
            }));
            statesToProcess.Add(new StateTransitionInfoPair <int, int, StateTransitionInfo>(k_StateTwo, k_ActionTwo, k_StateFour, new StateTransitionInfo()
            {
                Probability = 1, TransitionUtilityValue = 1
            }));

            var binnedStateKeys           = GetBinnedStateKeys();
            var stateTransitionInfoLookup = m_PlanGraph.StateTransitionInfoLookup;
            var resultingStateLookup      = m_PlanGraph.ResultingStateLookup;
            var newStatesQueue            = new NativeQueue <int>(Allocator.TempJob);
            var newStatesToDestroy        = new NativeQueue <int>(Allocator.TempJob);

            var expansionJob = new GraphExpansionJob <int, int, TestStateDataContext, int>
            {
                BinnedStateKeys             = binnedStateKeys,
                NewStateTransitionInfoPairs = statesToProcess.AsDeferredJobArray(),

                ActionLookup              = m_PlanGraph.ActionLookup.AsParallelWriter(),
                ActionInfoLookup          = m_PlanGraph.ActionInfoLookup.AsParallelWriter(),
                StateTransitionInfoLookup = stateTransitionInfoLookup.AsParallelWriter(),
                ResultingStateLookup      = resultingStateLookup.AsParallelWriter(),
                NewStates        = newStatesQueue.AsParallelWriter(),
                PredecessorGraph = m_PlanGraph.PredecessorGraph.AsParallelWriter(),
                StateDataContext = new TestStateDataContext(),
                StatesToDestroy  = newStatesToDestroy.AsParallelWriter(),
            };

            // Check to ensure edges do not exist
            Assert.IsFalse(stateTransitionInfoLookup.TryGetValue(new StateTransition <int, int>(k_StateOne, k_ActionOne, k_StateOne), out _));
            Assert.IsFalse(stateTransitionInfoLookup.TryGetValue(new StateTransition <int, int>(k_StateTwo, k_ActionTwo, k_StateFour), out _));

            expansionJob.Schedule(statesToProcess, default).Complete();

            // Only one new state; One was existing
            Assert.AreEqual(1, newStatesQueue.Count);
            Assert.AreEqual(1, newStatesToDestroy.Count);

            // Action results for new edges
            Assert.IsTrue(stateTransitionInfoLookup.TryGetValue(new StateTransition <int, int>(k_StateOne, k_ActionOne, k_StateOne), out _));
            Assert.IsTrue(stateTransitionInfoLookup.TryGetValue(new StateTransition <int, int>(k_StateTwo, k_ActionTwo, k_StateFour), out _));

            // Check for added edges (forward and reverse)
            Assert.IsTrue(resultingStateLookup.TryGetFirstValue(new StateActionPair <int, int>(k_StateOne, k_ActionOne), out _, out _));
            Assert.IsTrue(resultingStateLookup.TryGetFirstValue(new StateActionPair <int, int>(k_StateTwo, k_ActionTwo), out _, out _));

            statesToProcess.Dispose();
            binnedStateKeys.Dispose();
            newStatesQueue.Dispose();
            newStatesToDestroy.Dispose();
        }
        public void AddNoStates()
        {
            var statesToProcess = new NativeList <StateTransitionInfoPair <int, int, StateTransitionInfo> >(0, Allocator.TempJob);

            var binnedStateKeys           = GetBinnedStateKeys();
            var newStatesQueue            = new NativeQueue <int>(Allocator.TempJob);
            var stateTransitionInfoLookup = m_PlanGraph.StateTransitionInfoLookup;
            var resultingStateLookup      = m_PlanGraph.ResultingStateLookup;
            var predecessorGraph          = m_PlanGraph.PredecessorGraph;
            var newStatesToDestroy        = new NativeQueue <int>(Allocator.TempJob);

            var expansionJob = new GraphExpansionJob <int, int, TestStateDataContext, int>
            {
                BinnedStateKeys             = binnedStateKeys,
                NewStateTransitionInfoPairs = statesToProcess.AsDeferredJobArray(),

                ActionLookup              = m_PlanGraph.ActionLookup.AsParallelWriter(),
                ActionInfoLookup          = m_PlanGraph.ActionInfoLookup.AsParallelWriter(),
                StateTransitionInfoLookup = stateTransitionInfoLookup.AsParallelWriter(),
                ResultingStateLookup      = resultingStateLookup.AsParallelWriter(),
                NewStates        = newStatesQueue.AsParallelWriter(),
                PredecessorGraph = predecessorGraph.AsParallelWriter(),
                StateDataContext = new TestStateDataContext(),
                StatesToDestroy  = newStatesToDestroy.AsParallelWriter(),
            };

            var stateTransitionInfosBefore = stateTransitionInfoLookup.Count();
            var resultingStateLookupBefore = resultingStateLookup.Count();
            var predecessorGraphBefore     = predecessorGraph.Count();

            expansionJob.Schedule(statesToProcess, default).Complete();

            // No new action results, states, predecessor links, etc.
            Assert.AreEqual(0, newStatesQueue.Count);
            Assert.AreEqual(0, newStatesToDestroy.Count);
            Assert.AreEqual(stateTransitionInfosBefore, stateTransitionInfoLookup.Count());
            Assert.AreEqual(resultingStateLookupBefore, resultingStateLookup.Count());
            Assert.AreEqual(predecessorGraphBefore, predecessorGraph.Count());

            statesToProcess.Dispose();
            binnedStateKeys.Dispose();
            newStatesQueue.Dispose();
            newStatesToDestroy.Dispose();
        }
        public void MatchManyExistingStates()
        {
            const int kRootState   = 0;
            const int kActionCount = 1000;

            PlanGraph <int, StateInfo, int, ActionInfo, StateTransitionInfo> planGraph = default;
            NativeMultiHashMap <int, int> binnedStateKeys = default;
            NativeQueue <int>             newStatesQueue  = default;
            NativeList <StateTransitionInfoPair <int, int, StateTransitionInfo> > statesToProcess = default;
            NativeQueue <int> newStatesToDestroy = default;

            Measure.Method(() =>
            {
                var stateTransitionInfoLookup = planGraph.StateTransitionInfoLookup;
                var resultingStateLookup      = planGraph.ResultingStateLookup;

                var expansionJob = new GraphExpansionJob <int, int, TestStateDataContext, int>
                {
                    BinnedStateKeys             = binnedStateKeys,
                    NewStateTransitionInfoPairs = statesToProcess.AsDeferredJobArray(),

                    ActionLookup              = planGraph.ActionLookup.AsParallelWriter(),
                    ActionInfoLookup          = planGraph.ActionInfoLookup.AsParallelWriter(),
                    StateTransitionInfoLookup = stateTransitionInfoLookup.AsParallelWriter(),
                    ResultingStateLookup      = resultingStateLookup.AsParallelWriter(),
                    NewStates        = newStatesQueue.AsParallelWriter(),
                    PredecessorGraph = planGraph.PredecessorGraph.AsParallelWriter(),
                    StateDataContext = new TestStateDataContext(),
                    StatesToDestroy  = newStatesToDestroy.AsParallelWriter(),
                };

                expansionJob.Schedule(statesToProcess, default).Complete();
            }).SetUp(() =>
            {
                // One root node and all children nodes of a single depth
                planGraph = PlanGraphUtility.BuildTree(kActionCount, 1, 1);
                planGraph.ExpandBy(kActionCount, kActionCount);

                newStatesQueue     = new NativeQueue <int>(Allocator.TempJob);
                newStatesToDestroy = new NativeQueue <int>(Allocator.TempJob);

                // Extend graph by one depth with the same number of actions / resulting states that loop back on themselves
                statesToProcess = new NativeList <StateTransitionInfoPair <int, int, StateTransitionInfo> >(kActionCount, Allocator.TempJob);
                for (var i = 0; i < kActionCount; i++)
                {
                    statesToProcess.Add(new StateTransitionInfoPair <int, int, StateTransitionInfo>(kRootState, i, i, new StateTransitionInfo()
                    {
                        Probability = 1, TransitionUtilityValue = 1
                    }));
                }

                binnedStateKeys = GetBinnedStateKeys(planGraph);
            }).CleanUp(() =>
            {
                planGraph.Dispose();
                newStatesQueue.Dispose();
                statesToProcess.Dispose();
                binnedStateKeys.Dispose();
                newStatesToDestroy.Dispose();
            }).WarmupCount(1).MeasurementCount(30).IterationsPerMeasurement(1).Run();

            PerformanceUtility.AssertRange(4.3, 6.25);
        }