189 lines
8.0 KiB
C#
189 lines
8.0 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Reflection;
|
||
|
using Unity.Multiplayer.Center.Common;
|
||
|
using Unity.Multiplayer.Center.Questionnaire;
|
||
|
using Unity.Multiplayer.Center.Recommendations;
|
||
|
using UnityEditor;
|
||
|
using UnityEngine;
|
||
|
using DisplayCondition = Unity.Multiplayer.Center.Common.DisplayCondition;
|
||
|
|
||
|
namespace Unity.Multiplayer.Center.Onboarding
|
||
|
{
|
||
|
using SectionCategoryToSectionIdToSectionType = Dictionary<OnboardingSectionCategory, Dictionary<string, Type>>;
|
||
|
|
||
|
using SectionCategoryToSectionList = Dictionary<OnboardingSectionCategory, Type[]>;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Stores the available section types in a serializable way, but for comparison purposes only.
|
||
|
/// Only the assembly qualified names are serialized in a sorted array.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
internal class AvailableSectionTypes
|
||
|
{
|
||
|
readonly SectionCategoryToSectionList m_SectionMapping;
|
||
|
|
||
|
[SerializeField]
|
||
|
string[] m_SectionTypeNames;
|
||
|
|
||
|
public IEnumerable<string> SectionTypeNames => m_SectionTypeNames;
|
||
|
|
||
|
public AvailableSectionTypes(SectionCategoryToSectionList sectionTypes)
|
||
|
{
|
||
|
m_SectionMapping = sectionTypes;
|
||
|
m_SectionTypeNames = RecommendationUtils.GetSectionTypeNamesInOrder(m_SectionMapping).ToArray();
|
||
|
}
|
||
|
|
||
|
public bool TryGetValue(OnboardingSectionCategory category, out Type[] sectionTypes)
|
||
|
{
|
||
|
return m_SectionMapping.TryGetValue(category, out sectionTypes);
|
||
|
}
|
||
|
|
||
|
public bool HaveTypesChanged(AvailableSectionTypes other)
|
||
|
{
|
||
|
return m_SectionTypeNames == null || !RecommendationUtils.AreArraysEqual(m_SectionTypeNames, other.m_SectionTypeNames);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static class SectionsFinder
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The target packages that are used to determine if "nothing is installed", which results in sections with
|
||
|
/// the display condition "NoPackageInstalled" to be displayed.
|
||
|
/// </summary>
|
||
|
static readonly string[] k_TargetPackages = {
|
||
|
"com.unity.services.core", // This is a dependency of all the services, so if any service is installed, this is installed
|
||
|
"com.unity.netcode.gameobjects", "com.unity.netcode", // Netcodes
|
||
|
};
|
||
|
|
||
|
public static AvailableSectionTypes FindSectionTypes()
|
||
|
{
|
||
|
var dico = GetSectionCategoryToSectionIdToSectionType();
|
||
|
return SortSectionsByOrder(dico);
|
||
|
}
|
||
|
|
||
|
static AvailableSectionTypes SortSectionsByOrder(SectionCategoryToSectionIdToSectionType dico)
|
||
|
{
|
||
|
var result = new SectionCategoryToSectionList();
|
||
|
foreach (var (category, idToTypeDictionary) in dico)
|
||
|
{
|
||
|
var typeAttributeList = GetOnboardingTypeAttributeList(idToTypeDictionary);
|
||
|
typeAttributeList.Sort((typeA, typeB) => typeA.Attribute.Order.CompareTo(typeB.Attribute.Order));
|
||
|
result[category] = GetOnboardingTypeArray(typeAttributeList);
|
||
|
}
|
||
|
|
||
|
return new AvailableSectionTypes(result);
|
||
|
}
|
||
|
|
||
|
static Type[] GetOnboardingTypeArray(List<(Type Type, OnboardingSectionAttribute Attribute)> typeAttributeList)
|
||
|
{
|
||
|
var typeArray = new Type[typeAttributeList.Count];
|
||
|
for (var i = 0; i < typeAttributeList.Count; i++)
|
||
|
{
|
||
|
typeArray[i] = typeAttributeList[i].Type;
|
||
|
}
|
||
|
|
||
|
return typeArray;
|
||
|
}
|
||
|
|
||
|
static List<(Type Type, OnboardingSectionAttribute Attribute)> GetOnboardingTypeAttributeList(Dictionary<string, Type> idToTypeDictionary)
|
||
|
{
|
||
|
var typeAttributeList = new List<(Type Type, OnboardingSectionAttribute Attribute)>(idToTypeDictionary.Count);
|
||
|
|
||
|
foreach (var (_, type) in idToTypeDictionary)
|
||
|
{
|
||
|
var attribute = type.GetCustomAttribute<OnboardingSectionAttribute>();
|
||
|
if (attribute != null)
|
||
|
{
|
||
|
typeAttributeList.Add((type, attribute));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return typeAttributeList;
|
||
|
}
|
||
|
|
||
|
static SectionCategoryToSectionIdToSectionType GetSectionCategoryToSectionIdToSectionType()
|
||
|
{
|
||
|
var dico = new Dictionary<OnboardingSectionCategory, Dictionary<string, System.Type>>();
|
||
|
foreach (var sectionType in TypeCache.GetTypesDerivedFrom<IOnboardingSection>())
|
||
|
{
|
||
|
if (sectionType.IsAbstract) continue;
|
||
|
|
||
|
// check if type has default constructor
|
||
|
if (sectionType.GetConstructor(System.Type.EmptyTypes) == null)
|
||
|
{
|
||
|
Debug.LogWarning($"Onboarding section type {sectionType} does not have a default constructor and will be ignored.");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var sectionAttribute = sectionType.GetCustomAttribute<OnboardingSectionAttribute>();
|
||
|
if(sectionAttribute == null)
|
||
|
continue;
|
||
|
|
||
|
if (!IsDisplayConditionFulfilledForSection(sectionAttribute))
|
||
|
continue;
|
||
|
|
||
|
if (!IsSectionSupportedBySelectedInfrastructure(sectionAttribute))
|
||
|
continue;
|
||
|
|
||
|
if (!IsSectionSupportedBySelectedNetcodeChoice(sectionAttribute))
|
||
|
continue;
|
||
|
|
||
|
if (!dico.ContainsKey(sectionAttribute.Category))
|
||
|
dico[sectionAttribute.Category] = new Dictionary<string, System.Type>();
|
||
|
|
||
|
if (dico[sectionAttribute.Category].TryGetValue(sectionAttribute.Id, out var existing))
|
||
|
{
|
||
|
var existingAttr = existing.GetCustomAttribute<OnboardingSectionAttribute>();
|
||
|
|
||
|
if (existingAttr!= null && existingAttr.Id == sectionAttribute.Id
|
||
|
&& sectionAttribute.Priority > existingAttr.Priority)
|
||
|
{
|
||
|
dico[sectionAttribute.Category][sectionAttribute.Id] = sectionType;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dico[sectionAttribute.Category][sectionAttribute.Id] = sectionType;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return dico;
|
||
|
}
|
||
|
|
||
|
static bool IsDisplayConditionFulfilledForSection(OnboardingSectionAttribute attribute)
|
||
|
{
|
||
|
return attribute.DisplayCondition switch
|
||
|
{
|
||
|
DisplayCondition.None => true,
|
||
|
DisplayCondition.PackageInstalled when string.IsNullOrEmpty(attribute.TargetPackageId)
|
||
|
=> throw new ArgumentException("PackageInstalled condition requires a target package id"),
|
||
|
DisplayCondition.PackageInstalled => PackageManagement.IsInstalled(attribute.TargetPackageId),
|
||
|
DisplayCondition.NoPackageInstalled => NothingInstalled(),
|
||
|
_ => throw new NotImplementedException($"Unknown display condition: {attribute.DisplayCondition}")
|
||
|
};
|
||
|
}
|
||
|
|
||
|
static bool IsSectionSupportedBySelectedInfrastructure(OnboardingSectionAttribute attribute)
|
||
|
{
|
||
|
if (UserChoicesObject.instance.SelectedSolutions == null) return true;
|
||
|
|
||
|
var selectedInfrastructure = UserChoicesObject.instance.SelectedSolutions.SelectedHostingModel;
|
||
|
return attribute.HostingModelDependency == SelectedSolutionsData.HostingModel.None || attribute.HostingModelDependency == selectedInfrastructure;
|
||
|
}
|
||
|
|
||
|
static bool IsSectionSupportedBySelectedNetcodeChoice(OnboardingSectionAttribute attribute)
|
||
|
{
|
||
|
if (UserChoicesObject.instance.SelectedSolutions == null) return true;
|
||
|
|
||
|
var selectedNetcode = UserChoicesObject.instance.SelectedSolutions.SelectedNetcodeSolution;
|
||
|
return attribute.NetcodeDependency == SelectedSolutionsData.NetcodeSolution.None || attribute.NetcodeDependency == selectedNetcode;
|
||
|
}
|
||
|
|
||
|
static bool NothingInstalled()
|
||
|
{
|
||
|
return !PackageManagement.IsAnyPackageInstalled(k_TargetPackages);
|
||
|
}
|
||
|
}
|
||
|
}
|