using System; using System.Collections.Generic; using System.Reflection; using Unity.Multiplayer.Center.Common; using Unity.Multiplayer.Center.Questionnaire; using Unity.Multiplayer.Center.Recommendations; using Unity.Multiplayer.Center.Window.UI; using UnityEngine; namespace Unity.Multiplayer.Center.Analytics { internal static class AnalyticsUtils { // hard-coded to avoid recomputing every time / resizing arrays public const int NumNetcodePackage = 2; public const int NumHostingPackages = 1; /// /// From the recommendation view data (which contains the packages that the user sees and the user's selection), /// create the list of packages that will be sent to the analytics backend. /// /// The recommendation view data as shown in the recommendation tab /// The packages views /// The list of packages to be sent along with the installation event. public static Package[] GetPackagesWithAnalyticsFormat(RecommendationViewData data, SolutionsToRecommendedPackageViewData solutionToPackageData) { var selectedNetcode = RecommendationUtils.GetSelectedNetcode(data); var selectedHostingModel = RecommendationUtils.GetSelectedHostingModel(data); var packages = solutionToPackageData.GetPackagesForSelection(selectedNetcode.Solution, selectedHostingModel.Solution); var packageCount = NumNetcodePackage + NumHostingPackages + packages.Length; var result = new Package[packageCount]; var resultIndex = 0; AddSolutionPackages(data.NetcodeOptions, result, ref resultIndex); AddSolutionPackages(data.ServerArchitectureOptions, result, ref resultIndex); AddRecommendedPackages(packages, result, ref resultIndex); Debug.Assert(resultIndex == packageCount, $"Expected {packageCount} packages, got {resultIndex}"); return result; } /// /// Fetches all the inspector name attributes of the Preset enum and returns the displayNames /// Important! It assumes the enum values are 0, ... , N /// /// The array of preset names. The index in the array is the integer value of the enum value public static string[] GetPresetFullNames() { var t = typeof(Preset); var values = Enum.GetValues(t); var array = new string[values.Length]; foreach (var value in values) { var preset = (Preset) value; var index = (int)preset; var asString = value.ToString(); var memInfo = t.GetMember(asString); var attribute = memInfo[0].GetCustomAttribute(false); if (attribute != null) { array[index] = attribute.displayName; } else { Debug.LogError($"Could not fetch the full name of the preset value {asString}"); array[index] = asString; } } return array; } /// /// Converts AnswerData to game specs, providing the knowledge of the display names. /// It assumes there is exactly one answer in the answer list at this point. /// /// The answer data of the user /// Mapping answer id to display name /// Mapping question id to display name /// The list of game spec that will be consumed by the analytics backend public static GameSpec[] ToGameSpecs(AnswerData data, IReadOnlyDictionary answerIdToAnswerName, IReadOnlyDictionary questionIdToQuestionName) { var result = new GameSpec[data.Answers.Count]; for (var i = 0; i < result.Length; ++i) { var answer = data.Answers[i]; var answerId = answer.Answers[0]; // TODO: make sure that this always exists result[i] = new GameSpec() { QuestionId = answer.QuestionId, QuestionText = questionIdToQuestionName[answer.QuestionId], AcceptsMultipleAnswers = false, // TODO: add test that verifies this assumption AnswerId = answerId, AnswerText = answerIdToAnswerName[answerId] }; } return result; } /// /// Creates the mapping from question id to question display name /// /// The questionnaire data /// The mapping public static IReadOnlyDictionary GetQuestionDisplayNames(QuestionnaireData questionnaireData) { var dictionary = new Dictionary(); foreach (var question in questionnaireData.Questions) { dictionary[question.Id] = question.Title; } return dictionary; } /// /// Creates the mapping from answer id to answer display name /// /// The questionnaire data /// The mapping public static IReadOnlyDictionary GetAnswerDisplayNames(QuestionnaireData questionnaireData) { var dictionary = new Dictionary(); foreach (var question in questionnaireData.Questions) { foreach (var answer in question.Choices) { dictionary[answer.Id] = answer.Title; } } return dictionary; } static void AddSolutionPackages(RecommendedSolutionViewData[] options, Package[] result, ref int resultIndex) { foreach (var t in options) { if(string.IsNullOrEmpty(t.MainPackage?.PackageId)) continue; result[resultIndex] = new Package() { PackageId = t.MainPackage.PackageId, SelectedForInstall = t.Selected && t.RecommendationType != RecommendationType.Incompatible, IsRecommended = t.RecommendationType is RecommendationType.MainArchitectureChoice, IsAlreadyInstalled = t.MainPackage.IsInstalledAsProjectDependency }; ++resultIndex; } } static void AddRecommendedPackages(RecommendedPackageViewData[] packageViewDatas, Package[] result, ref int resultIndex) { foreach (var viewData in packageViewDatas) { result[resultIndex] = new Package() { PackageId = viewData.PackageId, // TODO: remove hidden? SelectedForInstall = viewData.Selected && viewData.RecommendationType != RecommendationType.Incompatible, IsRecommended = viewData.RecommendationType.IsRecommendedPackage(), IsAlreadyInstalled = viewData.IsInstalledAsProjectDependency }; ++resultIndex; } } } }