A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using Unity.Multiplayer.Center.Questionnaire;
4
5namespace Unity.Multiplayer.Center.Recommendations
6{
7 /// <summary>
8 /// The source of data for the recommender system. Based on scoring and this data, the recommender system will
9 /// populate the recommendation view data.
10 /// </summary>
11 [Serializable]
12 internal class RecommenderSystemData
13 {
14 /// <summary>
15 /// The Unity version for which this recommendation data is valid.
16 /// </summary>
17 public string TargetUnityVersion;
18
19 /// <summary>
20 /// Stores all the recommended solutions. This is serialized.
21 /// </summary>
22 public RecommendedSolution[] RecommendedSolutions;
23
24 /// <summary>
25 /// Stores all the package details.
26 /// This is serialized.
27 /// </summary>
28 public PackageDetails[] Packages;
29
30 /// <summary>
31 /// Provides convenient access to the package details by package id.
32 /// </summary>
33 public Dictionary<string, PackageDetails> PackageDetailsById
34 {
35 get
36 {
37 if (m_PackageDetailsById != null) return m_PackageDetailsById;
38 m_PackageDetailsById = Utils.ToDictionary(Packages, p => p.Id);
39 return m_PackageDetailsById;
40 }
41 }
42
43 public Dictionary<PossibleSolution, RecommendedSolution> SolutionsByType
44 {
45 get
46 {
47 if (m_SolutionsByType != null) return m_SolutionsByType;
48 m_SolutionsByType = Utils.ToDictionary(RecommendedSolutions, s => s.Type);
49 return m_SolutionsByType;
50 }
51 }
52
53 /// <summary>
54 /// Checks for incompatibility between the netcode and hosting model.
55 /// </summary>
56 /// <param name="netcode">The netcode type</param>
57 /// <param name="hostingModel">The hosting model</param>
58 /// <param name="reason">The reason for the incompatibility, filled when this function returns false.</param>
59 /// <returns>True if compatible (default), False otherwise</returns>
60 public bool IsHostingModelCompatibleWithNetcode(PossibleSolution netcode, PossibleSolution hostingModel, out string reason)
61 {
62 m_IncompatibleHostingModels ??= Utils.ToDictionary(RecommendedSolutions);
63 return !m_IncompatibleHostingModels.TryGetValue(new SolutionSelection(netcode, hostingModel), out reason);
64 }
65
66 /// <summary>
67 /// Patch incompatibility values.
68 /// </summary>
69 /// <param name="netcode">Netcode solution</param>
70 /// <param name="hostingModel">Hosting model solution</param>
71 /// <param name="newIsCompatible">Whether we should now consider the two solutions compatible</param>
72 /// <param name="reason">If incompatible, why it is incompatible.</param>
73 internal void UpdateIncompatibility(PossibleSolution netcode, PossibleSolution hostingModel, bool newIsCompatible, string reason=null)
74 {
75 Utils.UpdateIncompatibilityInSolutions(RecommendedSolutions, netcode, hostingModel, newIsCompatible, reason);
76 m_IncompatibleHostingModels = Utils.ToDictionary(RecommendedSolutions);
77 m_SolutionsByType = Utils.ToDictionary(RecommendedSolutions, s => s.Type);
78 }
79
80 Dictionary<string, PackageDetails> m_PackageDetailsById;
81 Dictionary<PossibleSolution, RecommendedSolution> m_SolutionsByType;
82 Dictionary<SolutionSelection, string> m_IncompatibleHostingModels;
83 }
84
85 [Serializable]
86 internal struct SolutionSelection
87 {
88 public PossibleSolution Netcode;
89 public PossibleSolution HostingModel;
90 public SolutionSelection(PossibleSolution netcode, PossibleSolution hostingModel)
91 {
92 Netcode = netcode;
93 HostingModel = hostingModel;
94 }
95 }
96
97 /// <summary>
98 /// A possible solution and whether packages are recommended or not
99 /// </summary>
100 [Serializable]
101 internal class RecommendedSolution
102 {
103 /// <summary>
104 /// The type of solution
105 /// </summary>
106 public PossibleSolution Type;
107
108 /// <summary>
109 /// The name of the solution as shown in the UI.
110 /// </summary>
111 public string Title;
112
113 /// <summary>
114 /// Optional package ID associated with that solution (e.g. a netcode package or the cloud code package).
115 /// Use this field if the package has to mandatorily be installed when the solution is selected.
116 /// </summary>
117 public string MainPackageId;// only id because scoring will impact the rest
118
119 /// <summary>
120 /// Url to documentation describing the solution.
121 /// </summary>
122 public string DocUrl;
123
124 /// <summary>
125 /// Short description of the solution.
126 /// </summary>
127 public string ShortDescription;
128
129 /// <summary>
130 /// The packages and the associated recommendation type.
131 /// If the Type is a netcode Type, all the possible packages should be in this array.
132 /// If the Type is a hosting model, this will contain only overrides in case a package is incompatible or
133 /// featured for the hosting model.
134 /// </summary>
135 public RecommendedPackage[] RecommendedPackages;
136
137 /// <summary>
138 /// Solutions that are incompatible with this solution.
139 /// Typically used for netcode solutions.
140 /// </summary>
141 public IncompatibleSolution[] IncompatibleSolutions = Array.Empty<IncompatibleSolution>();
142 }
143
144 /// <summary>
145 /// Stores why a solution is incompatible with something and why.
146 /// </summary>
147 [Serializable]
148 internal class IncompatibleSolution
149 {
150 /// <summary>
151 /// What is incompatible.
152 /// </summary>
153 public PossibleSolution Solution;
154
155 /// <summary>
156 /// Why it is incompatible.
157 /// </summary>
158 public string Reason;
159
160 public IncompatibleSolution(PossibleSolution solution, string reason)
161 {
162 Solution = solution;
163 Reason = reason;
164 }
165 }
166
167 /// <summary>
168 /// A package, whether it is recommended or not (context dependent), and why.
169 /// </summary>
170 [Serializable]
171 internal class RecommendedPackage
172 {
173 /// <summary>
174 /// The package id (e.g. com.unity.netcode)
175 /// </summary>
176 public string PackageId;
177
178 /// <summary>
179 /// Whether it is recommended or not.
180 /// </summary>
181 public RecommendationType Type;
182
183 /// <summary>
184 /// Why it is recommended or not.
185 /// </summary>
186 public string Reason;
187
188 public RecommendedPackage(string packageId, RecommendationType type, string reason)
189 {
190 PackageId = packageId;
191 Type = type;
192 Reason = reason;
193 }
194 }
195
196 [Serializable]
197 internal class PackageDetails
198 {
199 public string Id;
200 public string Name;
201 public string ShortDescription;
202 public string DocsUrl;
203 public string[] AdditionalPackages;
204
205 /// <summary>
206 /// In case we want to promote a specific pre-release version, this is set (by default, this is null
207 /// and the default package manager version is used).
208 /// </summary>
209 public string PreReleaseVersion;
210
211 /// <summary>
212 /// Details about the package.
213 /// </summary>
214 /// <param name="id">Package ID</param>
215 /// <param name="name">Package Name (for display)</param>
216 /// <param name="shortDescription">Short description.</param>
217 /// <param name="docsUrl">Link to Docs</param>
218 /// <param name="additionalPackages">Ids of packages that should be installed along this one, but are not formal dependencies.</param>
219 public PackageDetails(string id, string name, string shortDescription, string docsUrl, string[] additionalPackages = null)
220 {
221 Id = id;
222 Name = name;
223 ShortDescription = shortDescription;
224 DocsUrl = docsUrl;
225 AdditionalPackages = additionalPackages;
226 }
227 }
228
229 static class Utils
230 {
231 public static Dictionary<TKey, T> ToDictionary<T, TKey>(T[] array, Func<T, TKey> keySelector)
232 {
233 if (array == null) return null;
234 var result = new Dictionary<TKey, T>();
235 foreach (var item in array)
236 {
237 result[keySelector(item)] = item;
238 }
239
240 return result;
241 }
242
243 public static Dictionary<SolutionSelection, string> ToDictionary(RecommendedSolution[] solutions)
244 {
245 var result = new Dictionary<SolutionSelection, string>();
246 foreach (var recommendedSolution in solutions)
247 {
248 foreach (var incompatibleHostingModel in recommendedSolution.IncompatibleSolutions)
249 {
250 var key = new SolutionSelection(recommendedSolution.Type, incompatibleHostingModel.Solution);
251 result.Add(key, incompatibleHostingModel.Reason);
252 }
253 }
254
255 return result;
256 }
257
258 public static void UpdateIncompatibilityInSolutions(RecommendedSolution[] solutions, PossibleSolution netcode,
259 PossibleSolution hostingModel, bool newIsCompatible, string reason)
260 {
261 foreach (var recommendedSolution in solutions)
262 {
263 if (recommendedSolution.Type != netcode) continue;
264
265 var incompatibleSolution = Array.Find(recommendedSolution.IncompatibleSolutions, s => s.Solution == hostingModel);
266 if (newIsCompatible && incompatibleSolution != null)
267 {
268 recommendedSolution.IncompatibleSolutions = Array.FindAll(recommendedSolution.IncompatibleSolutions, s => s.Solution != hostingModel);
269 }
270 else if (!newIsCompatible && incompatibleSolution == null)
271 {
272 Array.Resize(ref recommendedSolution.IncompatibleSolutions, recommendedSolution.IncompatibleSolutions.Length + 1);
273 recommendedSolution.IncompatibleSolutions[^1] = new IncompatibleSolution(hostingModel, reason);
274 }
275 }
276 }
277 }
278}