// The version of the search loop for Sets with no Relations. internal bool TryCombinationsWithoutRelations(GroupSearchData searchData, PreviousSetMatches previousMatches) { var done = false; var duplicateCount = 0; if (previousMatches == null || previousMatches.Count == 0) { do { var hypothesis = m_MetaEnumerator.Current; // make sure we don't have any contexts that are matches for more than one query that claims data if (AnyDuplicateOrInvalid(hypothesis)) { duplicateCount++; continue; } // Since we have no Relations, our Set rating is derived entirely from member ratings searchData.MatchBuffer.Add(hypothesis, AverageMemberRating(hypothesis)); // If our very first attempt has no duplicates, it's *guaranteed* to be the best possible match. // This is because we are iterating through member ratings in order of descending rating. if (duplicateCount == 0) { done = true; } // If we had any duplicates before our first valid match, we don't have that guarantee, // because some other alternate combination may be better, so we should continue iterating }while (m_MetaEnumerator.MoveNext() && !done); } else { do { var hypothesis = m_MetaEnumerator.Current; // the first hypothesis being used by a previous match has the same effect on whether // it is guaranteed to be our best possible match as if it had duplicates within itself if (AnyDuplicateOrInvalid(hypothesis) || previousMatches.AssignmentPreviouslyUsed(hypothesis)) { duplicateCount++; continue; } searchData.MatchBuffer.Add(hypothesis, AverageMemberRating(hypothesis)); if (duplicateCount == 0) { done = true; } }while (m_MetaEnumerator.MoveNext() && !done); } return(searchData.MatchBuffer.Count > 0); }
/// <summary> /// Choose the best match we found while searching that hasn't been previously used by this set. /// </summary> /// <param name="buffer">The buffer containing every valid match found during the search</param> /// <param name="previous">A record of what previous matches for this set have used</param> /// <param name="hasRelations">Whether this Set has any Relations or not</param> /// <returns>The index into the match buffer where our chosen match is</returns> int BestAvailableMatch(SetMatchesBuffer buffer, PreviousSetMatches previous, bool hasRelations) { var chosenIndex = 0; if (hasRelations) { var tiesLeft = 0; var tieCount = 0; var tieIndex = 0; // for sets with Relations, we filter out duplicates and unavailable data // only for the highest-rated combinations we want to use, instead of for every combination do { if (tiesLeft == 0) { // start by finding all matches that tie for best, then try choosing between them. // if we run out of ties for the current best, we evaluate all the ties for the next best var ratingToCount = buffer.FindHighestRated(ref m_RatingTieIndices); // if we've looped until we discover all matches are unavailable, return -1 to indicate failure if (ratingToCount.Key == 0f) { return(-1); } tieCount = ratingToCount.Value; tiesLeft = ratingToCount.Value; tieIndex = ChoiceBehavior == TieChoiceBehavior.First ? 0 : UnityEngine.Random.Range(0, tiesLeft - 1); } else { tiesLeft--; tieIndex = tieIndex == tieCount - 1 ? 0 : tieIndex + 1; } chosenIndex = m_RatingTieIndices[tieIndex]; }while (!previous.IsCombinationAvailable(buffer, chosenIndex)); } else { // matches for sets without relations have already been filtered for availability and duplicates var chosenKvp = buffer.ChooseHighestRated(m_RatingTieIndices, ChoiceBehavior); chosenIndex = chosenKvp.Key; } return(chosenIndex); }
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; } }