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);
}
}
}
}
}