323 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Text;
 | |
| using Unity.Multiplayer.Center.Common;
 | |
| using Unity.Multiplayer.Center.Onboarding;
 | |
| using Unity.Multiplayer.Center.Questionnaire;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace Unity.Multiplayer.Center.Recommendations
 | |
| {
 | |
|     internal static class RecommendationUtils
 | |
|     {
 | |
|         public static List<RecommendedPackageViewData> PackagesToInstall(RecommendationViewData recommendation,
 | |
|             SolutionsToRecommendedPackageViewData solutionToPackageData)
 | |
|         {
 | |
|             var packagesToInstall = new List<RecommendedPackageViewData>();
 | |
| 
 | |
|             // Can happen on first load
 | |
|             if (recommendation?.NetcodeOptions == null)
 | |
|                 return packagesToInstall;
 | |
| 
 | |
|             var selectedNetcode = GetSelectedNetcode(recommendation);
 | |
|             if (selectedNetcode == null)
 | |
|                 return packagesToInstall;
 | |
| 
 | |
|             // add features based on netcode
 | |
|             if (selectedNetcode.MainPackage != null)
 | |
|                 packagesToInstall.Add(selectedNetcode.MainPackage);
 | |
| 
 | |
|             var selectedServerArchitecture = GetSelectedHostingModel(recommendation);
 | |
|             if (selectedServerArchitecture.MainPackage != null)
 | |
|                 packagesToInstall.Add(selectedServerArchitecture.MainPackage);
 | |
| 
 | |
|             foreach (var package in solutionToPackageData.GetPackagesForSelection(selectedNetcode.Solution, selectedServerArchitecture.Solution))
 | |
|             {
 | |
|                 if (package.Selected)
 | |
|                 {
 | |
|                     packagesToInstall.Add(package);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return packagesToInstall;
 | |
|         }
 | |
| 
 | |
|         public static RecommendedSolutionViewData GetSelectedHostingModel(RecommendationViewData recommendation)
 | |
|         {
 | |
|             return GetSelectedSolution(recommendation.ServerArchitectureOptions);
 | |
|         }
 | |
| 
 | |
|         public static RecommendedSolutionViewData GetSelectedNetcode(RecommendationViewData recommendation)
 | |
|         {
 | |
|             return GetSelectedSolution(recommendation.NetcodeOptions);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Finds the first selected solution in the input array.
 | |
|         /// </summary>
 | |
|         /// <param name="availableSolutions">The available solutions.</param>
 | |
|         /// <returns>Returns the first selected solution. If no solution is selected, it returns null.</returns>
 | |
|         public static RecommendedSolutionViewData GetSelectedSolution(RecommendedSolutionViewData[] availableSolutions)
 | |
|         {
 | |
|             foreach (var solution in availableSolutions)
 | |
|             {
 | |
|                 if (solution.Selected)
 | |
|                 {
 | |
|                     return solution;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return default;
 | |
|         }
 | |
| 
 | |
|         public static PackageDetails GetPackageDetailForPackageId(string packageId)
 | |
|         {
 | |
|             var idToPackageDetailDict = RecommenderSystemDataObject.instance.RecommenderSystemData.PackageDetailsById;
 | |
| 
 | |
|             if (idToPackageDetailDict.TryGetValue(packageId, out var packageDetail))
 | |
|                 return packageDetail;
 | |
| 
 | |
|             Debug.LogError("Trying to get package detail for package id that does not exist: " + packageId);
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns all the packages passed via packageIds and their informal dependencies (stored in AdditionalPackages)
 | |
|         /// </summary>
 | |
|         /// <returns>List of PackageDetails</returns>
 | |
|         /// <param name="packages">List of package id</param>
 | |
|         /// <param name="toolTip">tooltip text</param>
 | |
|         public static void GetPackagesWithAdditionalPackages(List<RecommendedPackageViewData> packages,
 | |
|             out List<string> ids, out List<string> names, out string toolTip)
 | |
|         {
 | |
|             ids = new List<string>();
 | |
|             names = new List<string>();
 | |
|             var toolTipBuilder = new StringBuilder();
 | |
|             foreach (var package in packages)
 | |
|             {
 | |
|                 var packageDetail = GetPackageDetailForPackageId(package.PackageId);
 | |
| 
 | |
|                 var id = string.IsNullOrEmpty(package.PreReleaseVersion)? package.PackageId : $"{package.PackageId}@{package.PreReleaseVersion}";
 | |
|                 var name = string.IsNullOrEmpty(package.PreReleaseVersion)? packageDetail.Name : $"{packageDetail.Name} {package.PreReleaseVersion}";
 | |
|                 ids.Add(id);
 | |
|                 names.Add(name);
 | |
|                 toolTipBuilder.Append(name);
 | |
| 
 | |
|                 if (packageDetail.AdditionalPackages is {Length: > 0})
 | |
|                 {
 | |
|                     toolTipBuilder.Append(" + ");
 | |
|                     foreach (var additionalPackageId in packageDetail.AdditionalPackages)
 | |
|                     {
 | |
|                         var additionalPackage = GetPackageDetailForPackageId(additionalPackageId);
 | |
|                         ids.Add(additionalPackage.Id);
 | |
|                         names.Add(additionalPackage.Name);
 | |
|                         toolTipBuilder.Append(additionalPackage.Name);
 | |
|                         toolTipBuilder.Append(",");
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 toolTipBuilder.Append("\n");
 | |
|             }
 | |
| 
 | |
|             // remove last newline
 | |
|             toolTip = toolTipBuilder.ToString().TrimEnd('\n');
 | |
|             ids.Add(QuickstartIsMissingView.PackageId);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Reapplies the previous selection on the view data
 | |
|         /// </summary>
 | |
|         /// <param name="recommendation">The recommendation view data to update</param>
 | |
|         /// <param name="data">The previous selection data</param>
 | |
|         public static void ApplyPreviousSelection(RecommendationViewData recommendation, SelectedSolutionsData data)
 | |
|         {
 | |
|             if (data == null || recommendation == null)
 | |
|                 return;
 | |
| 
 | |
|             if (data.SelectedNetcodeSolution != SelectedSolutionsData.NetcodeSolution.None)
 | |
|             {
 | |
|                 foreach (var d in recommendation.NetcodeOptions)
 | |
|                 {
 | |
|                     d.Selected = Logic.ConvertNetcodeSolution(d) == data.SelectedNetcodeSolution;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (data.SelectedHostingModel != SelectedSolutionsData.HostingModel.None)
 | |
|             {
 | |
|                 foreach (var view in recommendation.ServerArchitectureOptions)
 | |
|                 {
 | |
|                     view.Selected = Logic.ConvertInfrastructure(view) == data.SelectedHostingModel;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns the packages that are of the given recommendation type
 | |
|         /// </summary>
 | |
|         /// <param name="packages">All the package view data as returned by the recommender system</param>
 | |
|         /// <param name="type">The target recommendation type</param>
 | |
|         /// <returns>The filtered list</returns>
 | |
|         public static List<RecommendedPackageViewData> FilterByType(IEnumerable<RecommendedPackageViewData> packages, RecommendationType type)
 | |
|         {
 | |
|             var filteredPackages = new List<RecommendedPackageViewData>();
 | |
| 
 | |
|             foreach (var package in packages)
 | |
|             {
 | |
|                 if (package.RecommendationType == type)
 | |
|                 {
 | |
|                     filteredPackages.Add(package);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return filteredPackages;
 | |
|         }
 | |
| 
 | |
|         public static int IndexOfMaximumScore(RecommendedSolutionViewData[] array)
 | |
|         {
 | |
|             var maxIndex = -1;
 | |
|             var maxScore = float.MinValue;
 | |
|             for (var index = 0; index < array.Length; index++)
 | |
|             {
 | |
|                 var solution = array[index];
 | |
|                 if (solution.RecommendationType == RecommendationType.Incompatible)
 | |
|                     continue;
 | |
| 
 | |
|                 if (solution.Score > maxScore)
 | |
|                 {
 | |
|                     maxScore = solution.Score;
 | |
|                     maxIndex = index;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return maxIndex;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Finds the recommended solution among the scored solutions.
 | |
|         /// </summary>
 | |
|         /// <param name="scoredSolutions">An array of tuples, where each tuple contains a PossibleSolution and its corresponding Scoring.</param>
 | |
|         /// <returns>Returns the recommended solution with the maximum total score.</returns>
 | |
|         public static PossibleSolution FindRecommendedSolution((PossibleSolution, Scoring)[] scoredSolutions)
 | |
|         {
 | |
|             var maxScore = float.MinValue;
 | |
|             PossibleSolution recommendedSolution = default;
 | |
|             foreach (var (possibleSolution, scoring) in scoredSolutions)
 | |
|             {
 | |
|                 if (scoring.TotalScore > maxScore)
 | |
|                 {
 | |
|                     maxScore = scoring.TotalScore;
 | |
|                     recommendedSolution = possibleSolution;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return recommendedSolution;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Looks through the hosting models and marks them incompatible if necessary (deselected and recommendation
 | |
|         /// type incompatible).
 | |
|         /// Note that this might leave the recommendation in an invalid state (no hosting model selected)
 | |
|         /// </summary>
 | |
|         /// <param name="recommendation">The recommendation data to modify.</param>
 | |
|         public static void MarkIncompatibleHostingModels(RecommendationViewData recommendation)
 | |
|         {
 | |
|             var netcode = GetSelectedNetcode(recommendation);
 | |
| 
 | |
|             for (var index = 0; index < recommendation.ServerArchitectureOptions.Length; index++)
 | |
|             {
 | |
|                 var hosting = recommendation.ServerArchitectureOptions[index];
 | |
|                 var isCompatible = RecommenderSystemDataObject.instance.RecommenderSystemData.IsHostingModelCompatibleWithNetcode(netcode.Solution, hosting.Solution, out _);
 | |
|                 if (!isCompatible)
 | |
|                 {
 | |
|                     hosting.RecommendationType = RecommendationType.Incompatible;
 | |
|                     hosting.Selected = false;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     hosting.RecommendationType = RecommendationType.SecondArchitectureChoice;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Finds a recommended package view by its ID from a list.
 | |
|         /// </summary>
 | |
|         /// <param name="packages">A list of Recommended Package ViewData representing the packages.</param>
 | |
|         /// <param name="id">The ID of the package to find.</param>
 | |
|         /// <returns>Returns the RecommendedPackageViewData object with the matching ID. If none is found, it returns null.</returns>
 | |
|         public static RecommendedPackageViewData FindRecommendedPackageViewById( List<RecommendedPackageViewData> packages, string id)
 | |
|         {
 | |
|             RecommendedPackageViewData featureToSet = default;
 | |
|             foreach (var package in packages)
 | |
|             {
 | |
|                 if (package.PackageId == id)
 | |
|                 {
 | |
|                     featureToSet = package;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return featureToSet;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Checks if the specific question has been answered.
 | |
|         /// </summary>
 | |
|         /// <param name="question">The question to check.</param>
 | |
|         /// <returns>Returns true if the question has been answered by the user, otherwise returns false.</returns>
 | |
|         public static bool IsQuestionAnswered(Question question)
 | |
|         {
 | |
|             return Logic.TryGetAnswerByQuestionId(UserChoicesObject.instance.UserAnswers, question.Id, out _);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Compares two arrays of type <typeparamref name="T"/> for equality.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">The type of the elements in the arrays.</typeparam>
 | |
|         /// <param name="a">The first array to compare.</param>
 | |
|         /// <param name="b">The second array to compare.</param>
 | |
|         /// <returns>
 | |
|         /// <c>true</c> if all elements are equal and they are in the same order,
 | |
|         /// <c>false</c> otherwise.
 | |
|         /// </returns>
 | |
|         public static bool AreArraysEqual<T>(T[] a, T[] b)
 | |
|         {
 | |
|             if (a.Length != b.Length)
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             for (var i = 0; i < a.Length; i++)
 | |
|             {
 | |
|                 if (!EqualityComparer<T>.Default.Equals(a[i], b[i]))
 | |
|                 {
 | |
|                     return false;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Gets the section type names in order from the provided section mapping.
 | |
|         /// </summary>
 | |
|         /// <param name="sectionMapping">A dictionary mapping OnboardingSectionCategory to SectionList types.</param>
 | |
|         /// <returns>A list of section type names sorted in ascending order.</returns>
 | |
|         public static List<string> GetSectionTypeNamesInOrder(Dictionary<OnboardingSectionCategory, Type[]> sectionMapping)
 | |
|         {
 | |
|             var sectionTypeNamesList = new List<string>();
 | |
| 
 | |
|             foreach (var sectionTypeArray in sectionMapping.Values)
 | |
|             {
 | |
|                 foreach (var sectionType in sectionTypeArray)
 | |
|                 {
 | |
|                     sectionTypeNamesList.Add(sectionType.AssemblyQualifiedName);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             sectionTypeNamesList.Sort(StringComparer.InvariantCulture);
 | |
|             return sectionTypeNamesList;
 | |
|         }
 | |
|     }
 | |
| }
 |