using System; using System.Collections.Generic; using Unity.Multiplayer.Center.Questionnaire; namespace Unity.Multiplayer.Center.Recommendations { /// /// The source of data for the recommender system. Based on scoring and this data, the recommender system will /// populate the recommendation view data. /// [Serializable] internal class RecommenderSystemData { /// /// The Unity version for which this recommendation data is valid. /// public string TargetUnityVersion; /// /// Stores all the recommended solutions. This is serialized. /// public RecommendedSolution[] RecommendedSolutions; /// /// Stores all the package details. /// This is serialized. /// public PackageDetails[] Packages; /// /// Provides convenient access to the package details by package id. /// public Dictionary PackageDetailsById { get { if (m_PackageDetailsById != null) return m_PackageDetailsById; m_PackageDetailsById = Utils.ToDictionary(Packages, p => p.Id); return m_PackageDetailsById; } } public Dictionary SolutionsByType { get { if (m_SolutionsByType != null) return m_SolutionsByType; m_SolutionsByType = Utils.ToDictionary(RecommendedSolutions, s => s.Type); return m_SolutionsByType; } } /// /// Checks for incompatibility between the netcode and hosting model. /// /// The netcode type /// The hosting model /// The reason for the incompatibility, filled when this function returns false. /// True if compatible (default), False otherwise public bool IsHostingModelCompatibleWithNetcode(PossibleSolution netcode, PossibleSolution hostingModel, out string reason) { m_IncompatibleHostingModels ??= Utils.ToDictionary(RecommendedSolutions); return !m_IncompatibleHostingModels.TryGetValue(new SolutionSelection(netcode, hostingModel), out reason); } /// /// Patch incompatibility values. /// /// Netcode solution /// Hosting model solution /// Whether we should now consider the two solutions compatible /// If incompatible, why it is incompatible. internal void UpdateIncompatibility(PossibleSolution netcode, PossibleSolution hostingModel, bool newIsCompatible, string reason=null) { Utils.UpdateIncompatibilityInSolutions(RecommendedSolutions, netcode, hostingModel, newIsCompatible, reason); m_IncompatibleHostingModels = Utils.ToDictionary(RecommendedSolutions); m_SolutionsByType = Utils.ToDictionary(RecommendedSolutions, s => s.Type); } Dictionary m_PackageDetailsById; Dictionary m_SolutionsByType; Dictionary m_IncompatibleHostingModels; } [Serializable] internal struct SolutionSelection { public PossibleSolution Netcode; public PossibleSolution HostingModel; public SolutionSelection(PossibleSolution netcode, PossibleSolution hostingModel) { Netcode = netcode; HostingModel = hostingModel; } } /// /// A possible solution and whether packages are recommended or not /// [Serializable] internal class RecommendedSolution { /// /// The type of solution /// public PossibleSolution Type; /// /// The name of the solution as shown in the UI. /// public string Title; /// /// Optional package ID associated with that solution (e.g. a netcode package or the cloud code package). /// Use this field if the package has to mandatorily be installed when the solution is selected. /// public string MainPackageId;// only id because scoring will impact the rest /// /// Url to documentation describing the solution. /// public string DocUrl; /// /// Short description of the solution. /// public string ShortDescription; /// /// The packages and the associated recommendation type. /// If the Type is a netcode Type, all the possible packages should be in this array. /// If the Type is a hosting model, this will contain only overrides in case a package is incompatible or /// featured for the hosting model. /// public RecommendedPackage[] RecommendedPackages; /// /// Solutions that are incompatible with this solution. /// Typically used for netcode solutions. /// public IncompatibleSolution[] IncompatibleSolutions = Array.Empty(); } /// /// Stores why a solution is incompatible with something and why. /// [Serializable] internal class IncompatibleSolution { /// /// What is incompatible. /// public PossibleSolution Solution; /// /// Why it is incompatible. /// public string Reason; public IncompatibleSolution(PossibleSolution solution, string reason) { Solution = solution; Reason = reason; } } /// /// A package, whether it is recommended or not (context dependent), and why. /// [Serializable] internal class RecommendedPackage { /// /// The package id (e.g. com.unity.netcode) /// public string PackageId; /// /// Whether it is recommended or not. /// public RecommendationType Type; /// /// Why it is recommended or not. /// public string Reason; public RecommendedPackage(string packageId, RecommendationType type, string reason) { PackageId = packageId; Type = type; Reason = reason; } } [Serializable] internal class PackageDetails { public string Id; public string Name; public string ShortDescription; public string DocsUrl; public string[] AdditionalPackages; /// /// In case we want to promote a specific pre-release version, this is set (by default, this is null /// and the default package manager version is used). /// public string PreReleaseVersion; /// /// Details about the package. /// /// Package ID /// Package Name (for display) /// Short description. /// Link to Docs /// Ids of packages that should be installed along this one, but are not formal dependencies. public PackageDetails(string id, string name, string shortDescription, string docsUrl, string[] additionalPackages = null) { Id = id; Name = name; ShortDescription = shortDescription; DocsUrl = docsUrl; AdditionalPackages = additionalPackages; } } static class Utils { public static Dictionary ToDictionary(T[] array, Func keySelector) { if (array == null) return null; var result = new Dictionary(); foreach (var item in array) { result[keySelector(item)] = item; } return result; } public static Dictionary ToDictionary(RecommendedSolution[] solutions) { var result = new Dictionary(); foreach (var recommendedSolution in solutions) { foreach (var incompatibleHostingModel in recommendedSolution.IncompatibleSolutions) { var key = new SolutionSelection(recommendedSolution.Type, incompatibleHostingModel.Solution); result.Add(key, incompatibleHostingModel.Reason); } } return result; } public static void UpdateIncompatibilityInSolutions(RecommendedSolution[] solutions, PossibleSolution netcode, PossibleSolution hostingModel, bool newIsCompatible, string reason) { foreach (var recommendedSolution in solutions) { if (recommendedSolution.Type != netcode) continue; var incompatibleSolution = Array.Find(recommendedSolution.IncompatibleSolutions, s => s.Solution == hostingModel); if (newIsCompatible && incompatibleSolution != null) { recommendedSolution.IncompatibleSolutions = Array.FindAll(recommendedSolution.IncompatibleSolutions, s => s.Solution != hostingModel); } else if (!newIsCompatible && incompatibleSolution == null) { Array.Resize(ref recommendedSolution.IncompatibleSolutions, recommendedSolution.IncompatibleSolutions.Length + 1); recommendedSolution.IncompatibleSolutions[^1] = new IncompatibleSolution(hostingModel, reason); } } } } }