278 lines
10 KiB
C#
278 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Multiplayer.Center.Questionnaire;
|
|
|
|
namespace Unity.Multiplayer.Center.Recommendations
|
|
{
|
|
/// <summary>
|
|
/// The source of data for the recommender system. Based on scoring and this data, the recommender system will
|
|
/// populate the recommendation view data.
|
|
/// </summary>
|
|
[Serializable]
|
|
internal class RecommenderSystemData
|
|
{
|
|
/// <summary>
|
|
/// The Unity version for which this recommendation data is valid.
|
|
/// </summary>
|
|
public string TargetUnityVersion;
|
|
|
|
/// <summary>
|
|
/// Stores all the recommended solutions. This is serialized.
|
|
/// </summary>
|
|
public RecommendedSolution[] RecommendedSolutions;
|
|
|
|
/// <summary>
|
|
/// Stores all the package details.
|
|
/// This is serialized.
|
|
/// </summary>
|
|
public PackageDetails[] Packages;
|
|
|
|
/// <summary>
|
|
/// Provides convenient access to the package details by package id.
|
|
/// </summary>
|
|
public Dictionary<string, PackageDetails> PackageDetailsById
|
|
{
|
|
get
|
|
{
|
|
if (m_PackageDetailsById != null) return m_PackageDetailsById;
|
|
m_PackageDetailsById = Utils.ToDictionary(Packages, p => p.Id);
|
|
return m_PackageDetailsById;
|
|
}
|
|
}
|
|
|
|
public Dictionary<PossibleSolution, RecommendedSolution> SolutionsByType
|
|
{
|
|
get
|
|
{
|
|
if (m_SolutionsByType != null) return m_SolutionsByType;
|
|
m_SolutionsByType = Utils.ToDictionary(RecommendedSolutions, s => s.Type);
|
|
return m_SolutionsByType;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks for incompatibility between the netcode and hosting model.
|
|
/// </summary>
|
|
/// <param name="netcode">The netcode type</param>
|
|
/// <param name="hostingModel">The hosting model</param>
|
|
/// <param name="reason">The reason for the incompatibility, filled when this function returns false.</param>
|
|
/// <returns>True if compatible (default), False otherwise</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Patch incompatibility values.
|
|
/// </summary>
|
|
/// <param name="netcode">Netcode solution</param>
|
|
/// <param name="hostingModel">Hosting model solution</param>
|
|
/// <param name="newIsCompatible">Whether we should now consider the two solutions compatible</param>
|
|
/// <param name="reason">If incompatible, why it is incompatible.</param>
|
|
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<string, PackageDetails> m_PackageDetailsById;
|
|
Dictionary<PossibleSolution, RecommendedSolution> m_SolutionsByType;
|
|
Dictionary<SolutionSelection, string> m_IncompatibleHostingModels;
|
|
}
|
|
|
|
[Serializable]
|
|
internal struct SolutionSelection
|
|
{
|
|
public PossibleSolution Netcode;
|
|
public PossibleSolution HostingModel;
|
|
public SolutionSelection(PossibleSolution netcode, PossibleSolution hostingModel)
|
|
{
|
|
Netcode = netcode;
|
|
HostingModel = hostingModel;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A possible solution and whether packages are recommended or not
|
|
/// </summary>
|
|
[Serializable]
|
|
internal class RecommendedSolution
|
|
{
|
|
/// <summary>
|
|
/// The type of solution
|
|
/// </summary>
|
|
public PossibleSolution Type;
|
|
|
|
/// <summary>
|
|
/// The name of the solution as shown in the UI.
|
|
/// </summary>
|
|
public string Title;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public string MainPackageId;// only id because scoring will impact the rest
|
|
|
|
/// <summary>
|
|
/// Url to documentation describing the solution.
|
|
/// </summary>
|
|
public string DocUrl;
|
|
|
|
/// <summary>
|
|
/// Short description of the solution.
|
|
/// </summary>
|
|
public string ShortDescription;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public RecommendedPackage[] RecommendedPackages;
|
|
|
|
/// <summary>
|
|
/// Solutions that are incompatible with this solution.
|
|
/// Typically used for netcode solutions.
|
|
/// </summary>
|
|
public IncompatibleSolution[] IncompatibleSolutions = Array.Empty<IncompatibleSolution>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores why a solution is incompatible with something and why.
|
|
/// </summary>
|
|
[Serializable]
|
|
internal class IncompatibleSolution
|
|
{
|
|
/// <summary>
|
|
/// What is incompatible.
|
|
/// </summary>
|
|
public PossibleSolution Solution;
|
|
|
|
/// <summary>
|
|
/// Why it is incompatible.
|
|
/// </summary>
|
|
public string Reason;
|
|
|
|
public IncompatibleSolution(PossibleSolution solution, string reason)
|
|
{
|
|
Solution = solution;
|
|
Reason = reason;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A package, whether it is recommended or not (context dependent), and why.
|
|
/// </summary>
|
|
[Serializable]
|
|
internal class RecommendedPackage
|
|
{
|
|
/// <summary>
|
|
/// The package id (e.g. com.unity.netcode)
|
|
/// </summary>
|
|
public string PackageId;
|
|
|
|
/// <summary>
|
|
/// Whether it is recommended or not.
|
|
/// </summary>
|
|
public RecommendationType Type;
|
|
|
|
/// <summary>
|
|
/// Why it is recommended or not.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
public string PreReleaseVersion;
|
|
|
|
/// <summary>
|
|
/// Details about the package.
|
|
/// </summary>
|
|
/// <param name="id">Package ID</param>
|
|
/// <param name="name">Package Name (for display)</param>
|
|
/// <param name="shortDescription">Short description.</param>
|
|
/// <param name="docsUrl">Link to Docs</param>
|
|
/// <param name="additionalPackages">Ids of packages that should be installed along this one, but are not formal dependencies.</param>
|
|
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<TKey, T> ToDictionary<T, TKey>(T[] array, Func<T, TKey> keySelector)
|
|
{
|
|
if (array == null) return null;
|
|
var result = new Dictionary<TKey, T>();
|
|
foreach (var item in array)
|
|
{
|
|
result[keySelector(item)] = item;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static Dictionary<SolutionSelection, string> ToDictionary(RecommendedSolution[] solutions)
|
|
{
|
|
var result = new Dictionary<SolutionSelection, string>();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |