A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Reflection;
4using Unity.Multiplayer.Center.Common;
5using Unity.Multiplayer.Center.Questionnaire;
6using Unity.Multiplayer.Center.Recommendations;
7using Unity.Multiplayer.Center.Window.UI;
8using UnityEngine;
9
10namespace Unity.Multiplayer.Center.Analytics
11{
12 internal static class AnalyticsUtils
13 {
14 // hard-coded to avoid recomputing every time / resizing arrays
15 public const int NumNetcodePackage = 2;
16 public const int NumHostingPackages = 1;
17
18 /// <summary>
19 /// From the recommendation view data (which contains the packages that the user sees and the user's selection),
20 /// create the list of packages that will be sent to the analytics backend.
21 /// </summary>
22 /// <param name="data">The recommendation view data as shown in the recommendation tab</param>
23 /// <param name="solutionToPackageData">The packages views</param>
24 /// <returns>The list of packages to be sent along with the installation event.</returns>
25 public static Package[] GetPackagesWithAnalyticsFormat(RecommendationViewData data, SolutionsToRecommendedPackageViewData solutionToPackageData)
26 {
27 var selectedNetcode = RecommendationUtils.GetSelectedNetcode(data);
28 var selectedHostingModel = RecommendationUtils.GetSelectedHostingModel(data);
29 var packages = solutionToPackageData.GetPackagesForSelection(selectedNetcode.Solution, selectedHostingModel.Solution);
30 var packageCount = NumNetcodePackage + NumHostingPackages + packages.Length;
31
32 var result = new Package[packageCount];
33 var resultIndex = 0;
34
35 AddSolutionPackages(data.NetcodeOptions, result, ref resultIndex);
36 AddSolutionPackages(data.ServerArchitectureOptions, result, ref resultIndex);
37 AddRecommendedPackages(packages, result, ref resultIndex);
38
39 Debug.Assert(resultIndex == packageCount, $"Expected {packageCount} packages, got {resultIndex}");
40
41 return result;
42 }
43
44 /// <summary>
45 /// Fetches all the inspector name attributes of the Preset enum and returns the displayNames
46 /// Important! It assumes the enum values are 0, ... , N
47 /// </summary>
48 /// <returns>The array of preset names. The index in the array is the integer value of the enum value</returns>
49 public static string[] GetPresetFullNames()
50 {
51 var t = typeof(Preset);
52 var values = Enum.GetValues(t);
53 var array = new string[values.Length];
54 foreach (var value in values)
55 {
56 var preset = (Preset) value;
57 var index = (int)preset;
58 var asString = value.ToString();
59 var memInfo = t.GetMember(asString);
60 var attribute = memInfo[0].GetCustomAttribute<InspectorNameAttribute>(false);
61
62 if (attribute != null)
63 {
64 array[index] = attribute.displayName;
65 }
66 else
67 {
68 Debug.LogError($"Could not fetch the full name of the preset value {asString}");
69 array[index] = asString;
70 }
71 }
72
73 return array;
74 }
75
76 /// <summary>
77 /// Converts AnswerData to game specs, providing the knowledge of the display names.
78 /// It assumes there is exactly one answer in the answer list at this point.
79 /// </summary>
80 /// <param name="data">The answer data of the user</param>
81 /// <param name="answerIdToAnswerName">Mapping answer id to display name</param>
82 /// <param name="questionIdToQuestionName">Mapping question id to display name</param>
83 /// <returns>The list of game spec that will be consumed by the analytics backend</returns>
84 public static GameSpec[] ToGameSpecs(AnswerData data,
85 IReadOnlyDictionary<string, string> answerIdToAnswerName,
86 IReadOnlyDictionary<string, string> questionIdToQuestionName)
87 {
88 var result = new GameSpec[data.Answers.Count];
89 for (var i = 0; i < result.Length; ++i)
90 {
91 var answer = data.Answers[i];
92 var answerId = answer.Answers[0]; // TODO: make sure that this always exists
93 result[i] = new GameSpec()
94 {
95 QuestionId = answer.QuestionId,
96 QuestionText = questionIdToQuestionName[answer.QuestionId],
97 AcceptsMultipleAnswers = false, // TODO: add test that verifies this assumption
98 AnswerId = answerId,
99 AnswerText = answerIdToAnswerName[answerId]
100 };
101 }
102
103 return result;
104 }
105
106 /// <summary>
107 /// Creates the mapping from question id to question display name
108 /// </summary>
109 /// <param name="questionnaireData">The questionnaire data</param>
110 /// <returns>The mapping</returns>
111 public static IReadOnlyDictionary<string, string> GetQuestionDisplayNames(QuestionnaireData questionnaireData)
112 {
113 var dictionary = new Dictionary<string, string>();
114 foreach (var question in questionnaireData.Questions)
115 {
116 dictionary[question.Id] = question.Title;
117 }
118
119 return dictionary;
120 }
121
122 /// <summary>
123 /// Creates the mapping from answer id to answer display name
124 /// </summary>
125 /// <param name="questionnaireData">The questionnaire data</param>
126 /// <returns>The mapping</returns>
127 public static IReadOnlyDictionary<string, string> GetAnswerDisplayNames(QuestionnaireData questionnaireData)
128 {
129 var dictionary = new Dictionary<string, string>();
130 foreach (var question in questionnaireData.Questions)
131 {
132 foreach (var answer in question.Choices)
133 {
134 dictionary[answer.Id] = answer.Title;
135 }
136 }
137
138 return dictionary;
139 }
140
141 static void AddSolutionPackages(RecommendedSolutionViewData[] options, Package[] result, ref int resultIndex)
142 {
143 foreach (var t in options)
144 {
145 if(string.IsNullOrEmpty(t.MainPackage?.PackageId))
146 continue;
147
148 result[resultIndex] = new Package()
149 {
150 PackageId = t.MainPackage.PackageId,
151 SelectedForInstall = t.Selected && t.RecommendationType != RecommendationType.Incompatible,
152 IsRecommended = t.RecommendationType is RecommendationType.MainArchitectureChoice,
153 IsAlreadyInstalled = t.MainPackage.IsInstalledAsProjectDependency
154 };
155 ++resultIndex;
156 }
157 }
158
159 static void AddRecommendedPackages(RecommendedPackageViewData[] packageViewDatas, Package[] result, ref int resultIndex)
160 {
161 foreach (var viewData in packageViewDatas)
162 {
163 result[resultIndex] = new Package()
164 {
165 PackageId = viewData.PackageId,
166 // TODO: remove hidden?
167 SelectedForInstall = viewData.Selected && viewData.RecommendationType != RecommendationType.Incompatible,
168 IsRecommended = viewData.RecommendationType.IsRecommendedPackage(),
169 IsAlreadyInstalled = viewData.IsInstalledAsProjectDependency
170 };
171 ++resultIndex;
172 }
173 }
174 }
175}