/// <summary> /// For a Set with any Relations, iterate over a portion of possible combinations of member assignments. /// For each combination that satisfies all Relations, reduce Relation & member ratings into a single rating. /// </summary> /// <param name="matchBuffer">A buffer to record valid combinations with their ratings in</param> /// <param name="relationRatings">The Relation ratings for this single Set</param> /// <param name="localRelationPairs">The local relation index pairs for this Set</param> /// <returns></returns> bool TryCombinations(SetMatchesBuffer matchBuffer, RelationRatingsData relationRatings, RelationDataPair[] localRelationPairs) { var config = RatingConfiguration; matchBuffer.Reset(); do { var hypothesis = m_MetaEnumerator.Current; // determine if a given combination of member assignments is valid according to our Relations if (!TryRateAssignmentSet(hypothesis, relationRatings, localRelationPairs, config, out var rating)) { continue; } matchBuffer.Add(hypothesis, rating); } // this call produces the next combination to rate, until we reach our iteration target while (m_MetaEnumerator.MoveNext()); // for Sets with Relations, we filter by availability and duplicates only after picking a match return(matchBuffer.Count > 0); }
void ProcessStage(List <int> workingIndices, List <int> outputIndices, int[][] allSetMemberIndices, RelationDataPair[][] allLocalRelationIndexPairs, RelationRatingsData[] allRelationRatings, float[] allSetOrderWeights, GroupSearchData[] allGroupSearchData, RelationMembership[][] allRelationMemberships, Exclusivity[] allMemberExclusivities, Dictionary <int, float>[] allMemberRatings, ref int[] allMemberAssignments) { if (workingIndices.Count == 0) { IsComplete = true; return; } if (m_TickCount == 0) { outputIndices.Clear(); m_MemberAssignments.Clear(); // Determine the order we are going to answer Groups. CalculateSolveOrder(allSetOrderWeights, AllGroupPriorities, workingIndices, m_AllOrderPairs); var setCount = m_AllOrderPairs.Count; m_SetsPerTick = Mathf.Clamp(Mathf.CeilToInt(setCount / (float)FrameBudget), 1, setCount); } // if we need to force completion, setting the end index to the last one will force all groups to solve var endIndex = NeedsCompletion ? m_AllOrderPairs.Count : Mathf.Clamp(m_SolveIndex + m_SetsPerTick, m_SolveIndex, m_AllOrderPairs.Count); for (var solveIndex = m_SolveIndex; solveIndex < endIndex; solveIndex++) { var kvp = m_AllOrderPairs[solveIndex]; var setIndex = kvp.Key; var queryId = SetMatchIds[setIndex].queryID; var memberIndices = allSetMemberIndices[setIndex]; var localPairs = allLocalRelationIndexPairs[setIndex]; var relationRatings = allRelationRatings[setIndex]; var searchData = allGroupSearchData[setIndex]; var matchBuffer = searchData.MatchBuffer; matchBuffer.Reset(); // determine how many options we should try for each member before stopping var hasRelations = localPairs.Length > 0; var searchSpaceSize = GetGroupSearchSpaceSize(memberIndices, allMemberRatings); // there's no options available for this group, skip it. if (searchSpaceSize == 0) { continue; } var portion = GetSearchPortion(searchSpaceSize, SearchSpacePortionCurve, hasRelations); GetIterationTargets(portion, searchData.MemberRatings, searchData.MetaEnumerator.TargetDepths); m_MetaEnumerator = searchData.MetaEnumerator; m_MetaEnumerator.RefreshEnumerators(); m_MetaEnumerator.Reset(); m_MetaEnumerator.MoveNext(); // get enumerator into initial valid state if (!PreviousMatches.TryGetValue(queryId, out var record)) { record = new PreviousSetMatches(memberIndices.Length); PreviousMatches.Add(queryId, record); } /* The critical loop where we search the set's combination space happens here, in either function. * There are 2 different versions of our inner combination loop. * * 1) for Sets with at least one Relation, we filter during the loop only based on whether * the combination satisfies all Relations. We only do duplicate assignment & * data availability checks later, when picking the best match. * * 2) for Sets with no Relations, we check duplicate assignment & previous use during the loop, * and anything that passes those checks is valid. */ var matchFound = hasRelations ? TryCombinations(searchData.MatchBuffer, relationRatings, localPairs) : TryCombinationsWithoutRelations(searchData, record); // if we didn't find any matches for this set during our search, give up and move on. if (!matchFound) { continue; } // if we did find matches, find the highest rated one and do any picking between ties necessary var chosenIndex = BestAvailableMatch(matchBuffer, record, hasRelations); if (chosenIndex < 0) { continue; } // take our chosen match and assign its keys to set members in a staging collection - // this isn't final assignment, since we might have to roll back later. var begin = chosenIndex * matchBuffer.SetSize; var end = begin + matchBuffer.SetSize; for (var i = begin; i < end; i++) { var assignedDataId = matchBuffer.DataIds[i]; var globalMemberIndex = memberIndices[i - begin]; m_MemberAssignments[globalMemberIndex] = assignedDataId; } matchBuffer.CombinationAtRatingIndex(chosenIndex, searchData.ClaimedCombination); // the following stages - marking data used & filling results - will use these as their working indices if (!outputIndices.Contains(setIndex)) { outputIndices.Add(setIndex); } // if this is anything except the last Set to be solved in the cycle, // we need to remove the appropriate possibilities from the remaining sets if (solveIndex >= m_AllOrderPairs.Count - 1) { continue; } for (var i = solveIndex + 1; i < m_AllOrderPairs.Count; i++) { var otherSetIndex = m_AllOrderPairs[i].Key; RemoveClaimedData(searchData.ClaimedCombination, memberIndices, allSetMemberIndices[otherSetIndex], allMemberExclusivities, allMemberRatings); } } m_SolveIndex = endIndex; m_TickCount++; if (endIndex == m_AllOrderPairs.Count) { // finalize our assignments foreach (var kvp in m_MemberAssignments) { allMemberAssignments[kvp.Key] = kvp.Value; } IsComplete = true; } }