From 0fd210481ac67fc8e17cf7475e196ae646f5764b Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 1 Sep 2021 13:38:34 +0200 Subject: [PATCH] Multithread SerializationManager initialize. --- .../ResourceCache.Preload.cs | 2 +- Robust.Client/Robust.Client.csproj | 1 + Robust.Shared/CVars.cs | 16 +-- Robust.Shared/Physics/Collision/Collision.cs | 2 +- .../Physics/Collision/Shapes/EdgeShape.cs | 2 +- .../Physics/Collision/Shapes/PhysShapeAabb.cs | 2 +- .../Physics/Collision/Shapes/PolygonShape.cs | 2 +- Robust.Shared/Physics/PhysicsConstants.cs | 15 +++ Robust.Shared/Prototypes/EntityPrototype.cs | 11 +- Robust.Shared/Prototypes/PrototypeManager.cs | 9 +- .../Reflection/IReflectionManager.cs | 3 +- Robust.Shared/Reflection/ReflectionManager.cs | 90 ++++++++-------- .../SerializationManager.FlagsAndConstants.cs | 7 +- .../SerializationManager.TypeSerializers.cs | 4 +- .../Manager/SerializationManager.cs | 101 +++++++++++++----- .../PrototypeIdDictionarySerializer.cs | 2 +- Robust.UnitTesting/RobustIntegrationTest.cs | 2 +- 17 files changed, 161 insertions(+), 110 deletions(-) create mode 100644 Robust.Shared/Physics/PhysicsConstants.cs diff --git a/Robust.Client/ResourceManagement/ResourceCache.Preload.cs b/Robust.Client/ResourceManagement/ResourceCache.Preload.cs index 9c10015c9..d5b80601d 100644 --- a/Robust.Client/ResourceManagement/ResourceCache.Preload.cs +++ b/Robust.Client/ResourceManagement/ResourceCache.Preload.cs @@ -20,7 +20,7 @@ namespace Robust.Client.ResourceManagement { var sawmill = _logManager.GetSawmill("res.preload"); - if (!_configurationManager.GetCVar(CVars.TexturePreloadingEnabled)) + if (!_configurationManager.GetCVar(CVars.ResTexturePreloadingEnabled)) { sawmill.Debug($"Skipping texture preloading due to CVar value."); return; diff --git a/Robust.Client/Robust.Client.csproj b/Robust.Client/Robust.Client.csproj index 3e2128bd9..40df9da22 100644 --- a/Robust.Client/Robust.Client.csproj +++ b/Robust.Client/Robust.Client.csproj @@ -13,6 +13,7 @@ + diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 5fbd29b8b..806c043c6 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -518,17 +518,6 @@ namespace Robust.Shared public static readonly CVarDef AngularSlop = CVarDef.Create("physics.angularslop", 2.0f / 180.0f * MathF.PI); - /// - /// The radius of the polygon/edge shape skin. This should not be modified. Making - /// this smaller means polygons will have an insufficient buffer for continuous collision. - /// Making it larger may create artifacts for vertex collision. - /// - /// - /// Default is set to be 2 x linearslop. TODO Should we listen to linearslop changes? - /// - public static readonly CVarDef PolygonRadius = - CVarDef.Create("physics.polygonradius", 2 * 0.005f); - /// /// If true, it will run a GiftWrap convex hull on all polygon inputs. /// This makes for a more stable engine when given random input, @@ -583,9 +572,12 @@ namespace Robust.Shared public static readonly CVarDef ResCheckPathCasing = CVarDef.Create("res.checkpathcasing", false); - public static readonly CVarDef TexturePreloadingEnabled = + public static readonly CVarDef ResTexturePreloadingEnabled = CVarDef.Create("res.texturepreloadingenabled", true, CVar.CLIENTONLY); + public static readonly CVarDef ResTexturePreloadCache = + CVarDef.Create("res.texture_preload_cache", true, CVar.CLIENTONLY); + /* * DEBUG diff --git a/Robust.Shared/Physics/Collision/Collision.cs b/Robust.Shared/Physics/Collision/Collision.cs index c981dbda0..b661c57d3 100644 --- a/Robust.Shared/Physics/Collision/Collision.cs +++ b/Robust.Shared/Physics/Collision/Collision.cs @@ -349,7 +349,7 @@ namespace Robust.Shared.Physics.Collision internal EPCollider(IConfigurationManager configManager) { - _polygonRadius = configManager.GetCVar(CVars.PolygonRadius); + _polygonRadius = PhysicsConstants.PolygonRadius; _angularSlop = configManager.GetCVar(CVars.AngularSlop); _polygonB = new TempPolygon(configManager); } diff --git a/Robust.Shared/Physics/Collision/Shapes/EdgeShape.cs b/Robust.Shared/Physics/Collision/Shapes/EdgeShape.cs index 79a5352c0..a1f9fdab5 100644 --- a/Robust.Shared/Physics/Collision/Shapes/EdgeShape.cs +++ b/Robust.Shared/Physics/Collision/Shapes/EdgeShape.cs @@ -92,7 +92,7 @@ namespace Robust.Shared.Physics.Collision.Shapes public EdgeShape(Vector2 start, Vector2 end) { Set(start, end); - _radius = IoCManager.Resolve().GetCVar(CVars.PolygonRadius); + _radius = PhysicsConstants.PolygonRadius; } /// diff --git a/Robust.Shared/Physics/Collision/Shapes/PhysShapeAabb.cs b/Robust.Shared/Physics/Collision/Shapes/PhysShapeAabb.cs index db7da308e..50b84177c 100644 --- a/Robust.Shared/Physics/Collision/Shapes/PhysShapeAabb.cs +++ b/Robust.Shared/Physics/Collision/Shapes/PhysShapeAabb.cs @@ -69,7 +69,7 @@ namespace Robust.Shared.Physics.Collision.Shapes public PhysShapeAabb() { - _radius = IoCManager.Resolve().GetCVar(CVars.PolygonRadius); + _radius = PhysicsConstants.PolygonRadius; } public Box2 ComputeAABB(Transform transform, int childIndex) diff --git a/Robust.Shared/Physics/Collision/Shapes/PolygonShape.cs b/Robust.Shared/Physics/Collision/Shapes/PolygonShape.cs index f18e6f440..f034bb447 100644 --- a/Robust.Shared/Physics/Collision/Shapes/PolygonShape.cs +++ b/Robust.Shared/Physics/Collision/Shapes/PolygonShape.cs @@ -153,7 +153,7 @@ namespace Robust.Shared.Physics.Collision.Shapes public PolygonShape() { - _radius = IoCManager.Resolve().GetCVar(CVars.PolygonRadius); + _radius = PhysicsConstants.PolygonRadius; } public PolygonShape(float radius) diff --git a/Robust.Shared/Physics/PhysicsConstants.cs b/Robust.Shared/Physics/PhysicsConstants.cs new file mode 100644 index 000000000..bfb979361 --- /dev/null +++ b/Robust.Shared/Physics/PhysicsConstants.cs @@ -0,0 +1,15 @@ +namespace Robust.Shared.Physics +{ + internal static class PhysicsConstants + { + /// + /// The radius of the polygon/edge shape skin. This should not be modified. Making + /// this smaller means polygons will have an insufficient buffer for continuous collision. + /// Making it larger may create artifacts for vertex collision. + /// + /// + /// Default is set to be 2 x linearslop. TODO Should we listen to linearslop changes? + /// + public const float PolygonRadius = 2 * 0.005f; + } +} diff --git a/Robust.Shared/Prototypes/EntityPrototype.cs b/Robust.Shared/Prototypes/EntityPrototype.cs index 641d64499..f1a7496d2 100644 --- a/Robust.Shared/Prototypes/EntityPrototype.cs +++ b/Robust.Shared/Prototypes/EntityPrototype.cs @@ -6,10 +6,10 @@ using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Maths; +using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Shared.Prototypes @@ -18,9 +18,9 @@ namespace Robust.Shared.Prototypes /// Prototype that represents game entities. /// [Prototype("entity", -1)] - public class EntityPrototype : IPrototype, IInheritingPrototype + public class EntityPrototype : IPrototype, IInheritingPrototype, ISerializationHooks { - private readonly ILocalizationManager _loc = IoCManager.Resolve(); + private ILocalizationManager _loc = default!; private static readonly Dictionary LocPropertiesDefault = new(); @@ -155,6 +155,11 @@ namespace Robust.Shared.Prototypes Components.Add("MetaData", new MetaDataComponent()); } + void ISerializationHooks.AfterDeserialization() + { + _loc = IoCManager.Resolve(); + } + public bool TryGetComponent(string name, [NotNullWhen(true)] out T? component) where T : IComponent { if (!Components.TryGetValue(name, out var componentUnCast)) diff --git a/Robust.Shared/Prototypes/PrototypeManager.cs b/Robust.Shared/Prototypes/PrototypeManager.cs index 8fef1dae0..c2582c5ff 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.cs @@ -193,7 +193,7 @@ namespace Robust.Shared.Prototypes } } - public class PrototypeManager : IPrototypeManager, IPostInjectInit + public class PrototypeManager : IPrototypeManager { [Dependency] private readonly IReflectionManager _reflectionManager = default!; [Dependency] protected readonly IResourceManager Resources = default!; @@ -223,6 +223,7 @@ namespace Robust.Shared.Prototypes } _initialized = true; + ReloadPrototypeTypes(); } public IEnumerable EnumeratePrototypes() where T : class, IPrototype @@ -605,12 +606,6 @@ namespace Robust.Shared.Prototypes #endregion IPrototypeManager members - public void PostInject() - { - _reflectionManager.OnAssemblyAdded += (_, _) => ReloadPrototypeTypes(); - ReloadPrototypeTypes(); - } - private void ReloadPrototypeTypes() { Clear(); diff --git a/Robust.Shared/Reflection/IReflectionManager.cs b/Robust.Shared/Reflection/IReflectionManager.cs index 6e6df4803..310460d8a 100644 --- a/Robust.Shared/Reflection/IReflectionManager.cs +++ b/Robust.Shared/Reflection/IReflectionManager.cs @@ -111,5 +111,6 @@ namespace Robust.Shared.Reflection bool TryParseEnumReference(string reference, [NotNullWhen(true)] out Enum? @enum); Type? YamlTypeTagLookup(Type baseType, string typeName); + IEnumerable FindAllTypes(); } -} \ No newline at end of file +} diff --git a/Robust.Shared/Reflection/ReflectionManager.cs b/Robust.Shared/Reflection/ReflectionManager.cs index acf5d5782..063b640e7 100644 --- a/Robust.Shared/Reflection/ReflectionManager.cs +++ b/Robust.Shared/Reflection/ReflectionManager.cs @@ -33,6 +33,8 @@ namespace Robust.Shared.Reflection private readonly Dictionary _enumCache = new(); + private readonly List _getAllTypesCache = new(); + /// public IEnumerable GetAllChildren(bool inclusive = false) { @@ -42,56 +44,47 @@ namespace Robust.Shared.Reflection /// public IEnumerable GetAllChildren(Type baseType, bool inclusive = false) { - var typeLists = new List(Assemblies.Count); - try - { - // There's very little assemblies, so storing these temporarily is cheap. - // We need to do it ahead of time so that we can catch ReflectionTypeLoadException HERE, - // so whoever is using us doesn't have to handle them. - foreach (var assembly in Assemblies) - { - typeLists.Add(assembly.GetTypes()); - } - } - catch (ReflectionTypeLoadException e) - { - Logger.Error("Caught ReflectionTypeLoadException! Dumping child exceptions:"); - if (e.LoaderExceptions != null) - { - foreach (var inner in e.LoaderExceptions) - { - if (inner != null) - { - Logger.Error(inner.ToString()); - } - } - } + EnsureGetAllTypesCache(); - throw; + foreach (var type in _getAllTypesCache) + { + if (!baseType.IsAssignableFrom(type) || type.IsAbstract) + continue; + + if (baseType == type && !inclusive) + continue; + + yield return type; + } + } + + private void EnsureGetAllTypesCache() + { + if (_getAllTypesCache.Count != 0) + return; + + var totalLength = 0; + var typeSets = new List(); + + foreach (var assembly in assemblies) + { + var types = assembly.GetTypes(); + typeSets.Add(types); + totalLength += types.Length; } - foreach (var t in typeLists) - { - foreach (var type in t) - { - if (!baseType.IsAssignableFrom(type) || type.IsAbstract) - { - continue; - } + _getAllTypesCache.Capacity = totalLength; + foreach (var typeSet in typeSets) + { + foreach (var type in typeSet) + { var attribute = (ReflectAttribute?) Attribute.GetCustomAttribute(type, typeof(ReflectAttribute)); if (!(attribute?.Discoverable ?? ReflectAttribute.DEFAULT_DISCOVERABLE)) - { continue; - } - if (baseType == type && !inclusive) - { - continue; - } - - yield return type; + _getAllTypesCache.Add(type); } } } @@ -101,6 +94,7 @@ namespace Robust.Shared.Reflection public void LoadAssemblies(IEnumerable assemblies) { this.assemblies.AddRange(assemblies); + _getAllTypesCache.Clear(); OnAssemblyAdded?.Invoke(this, new ReflectionUpdateEventArgs(this)); } @@ -167,14 +161,14 @@ namespace Robust.Shared.Reflection /// public IEnumerable FindTypesWithAttribute(Type attributeType) { - var types = new List(); + EnsureGetAllTypesCache(); + return _getAllTypesCache.Where(type => Attribute.IsDefined(type, attributeType)); + } - foreach (var assembly in Assemblies) - { - types.AddRange(assembly.GetTypes().Where(type => Attribute.IsDefined(type, attributeType))); - } - - return types; + public IEnumerable FindAllTypes() + { + EnsureGetAllTypesCache(); + return _getAllTypesCache; } /// diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs b/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs index f550665f7..a89792651 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -12,9 +13,9 @@ namespace Robust.Shared.Serialization.Manager private readonly Dictionary _constantsMapping = new(); - private void InitializeFlagsAndConstants() + private void InitializeFlagsAndConstants(IEnumerable flags, IEnumerable constants) { - foreach (Type constType in _reflectionManager.FindTypesWithAttribute()) + foreach (Type constType in constants) { if (!constType.IsEnum) { @@ -37,7 +38,7 @@ namespace Robust.Shared.Serialization.Manager } } - foreach (var bitflagType in _reflectionManager.FindTypesWithAttribute()) + foreach (var bitflagType in flags) { if (!bitflagType.IsEnum) { diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.TypeSerializers.cs b/Robust.Shared/Serialization/Manager/SerializationManager.TypeSerializers.cs index 8ed96745d..197013fab 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.TypeSerializers.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.TypeSerializers.cs @@ -14,9 +14,9 @@ namespace Robust.Shared.Serialization.Manager private readonly Dictionary _genericCopierTypes = new(); private readonly Dictionary<(Type Type, Type DataNodeType), Type> _genericValidatorTypes = new(); - private void InitializeTypeSerializers() + private void InitializeTypeSerializers(IEnumerable typeSerializers) { - foreach (var type in _reflectionManager.FindTypesWithAttribute()) + foreach (var type in typeSerializers) { RegisterSerializer(type); } diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.cs b/Robust.Shared/Serialization/Manager/SerializationManager.cs index 290d5bc2b..b47fd6f5e 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -6,6 +7,7 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using System.Threading.Tasks; using JetBrains.Annotations; using Robust.Shared.IoC; using Robust.Shared.Log; @@ -41,59 +43,79 @@ namespace Robust.Shared.Serialization.Manager public void Initialize() { if (_initializing) - { throw new InvalidOperationException($"{nameof(SerializationManager)} is already being initialized."); - } if (_initialized) - { throw new InvalidOperationException($"{nameof(SerializationManager)} has already been initialized."); - } _initializing = true; DependencyCollection = IoCManager.Instance ?? throw new NullReferenceException(); - InitializeFlagsAndConstants(); - InitializeTypeSerializers(); + var flagsTypes = new ConcurrentBag(); + var constantsTypes = new ConcurrentBag(); + var typeSerializers = new ConcurrentBag(); + var meansDataDef = new ConcurrentBag(); + var implicitDataDefForInheritors = new ConcurrentBag(); - var registrations = new HashSet(); + CollectAttributedTypes(flagsTypes, constantsTypes, typeSerializers, meansDataDef, implicitDataDefForInheritors); - foreach (var baseType in _reflectionManager.FindTypesWithAttribute()) + InitializeFlagsAndConstants(flagsTypes, constantsTypes); + InitializeTypeSerializers(typeSerializers); + + // This is a bag, not a hash set. + // Duplicates are fine since the CWT<,> won't re-run the constructor if it's already in there. + var registrations = new ConcurrentBag(); + + foreach (var baseType in implicitDataDefForInheritors) { - if (!baseType.IsAbstract && !baseType.IsInterface && !baseType.IsGenericTypeDefinition) registrations.Add(baseType); - foreach (var child in _reflectionManager.GetAllChildren(baseType)) + // Inherited attributes don't work with interfaces. + if (baseType.IsInterface) { - if (child.IsAbstract || child.IsInterface || child.IsGenericTypeDefinition) continue; - registrations.Add(child); + foreach (var child in _reflectionManager.GetAllChildren(baseType)) + { + if (child.IsAbstract || child.IsGenericTypeDefinition) + continue; + + registrations.Add(child); + } + } + else if (!baseType.IsAbstract && !baseType.IsGenericTypeDefinition) + { + registrations.Add(baseType); } } - foreach (var meansAttr in _reflectionManager.FindTypesWithAttribute()) + Parallel.ForEach(_reflectionManager.FindAllTypes(), type => { - foreach (var type in _reflectionManager.FindTypesWithAttribute(meansAttr)) + foreach (var meansDataDefAttr in meansDataDef) { - registrations.Add(type); + if (type.IsDefined(meansDataDefAttr)) + registrations.Add(type); } - } + }); - foreach (var type in registrations) + var sawmill = Logger.GetSawmill(LogCategory); + + Parallel.ForEach(registrations, type => { if (type.IsAbstract || type.IsInterface || type.IsGenericTypeDefinition) { - Logger.DebugS(LogCategory, $"Skipping registering data definition for type {type} since it is abstract or an interface"); - continue; + sawmill.Debug( + $"Skipping registering data definition for type {type} since it is abstract or an interface"); + return; } if (!type.IsValueType && type.GetConstructors(BindingFlags.Instance | BindingFlags.Public) .FirstOrDefault(m => m.GetParameters().Length == 0) == null) { - Logger.DebugS(LogCategory, $"Skipping registering data definition for type {type} since it has no parameterless ctor"); - continue; + sawmill.Debug( + $"Skipping registering data definition for type {type} since it has no parameterless ctor"); + return; } DataDefinitions.GetValue(type, CreateDefinitionCallback); - } + }); var error = new StringBuilder(); @@ -110,17 +132,42 @@ namespace Robust.Shared.Serialization.Manager throw new ArgumentException($"Duplicate data field tags found in:\n{error}"); } - foreach (var type in _reflectionManager.FindTypesWithAttribute()) - { - _copyByRefRegistrations.Add(type); - } - _copyByRefRegistrations.Add(typeof(Type)); _initialized = true; _initializing = false; } + private void CollectAttributedTypes( + ConcurrentBag flagsTypes, + ConcurrentBag constantsTypes, + ConcurrentBag typeSerializers, + ConcurrentBag meansDataDef, + ConcurrentBag implicitDataDefForInheritors) + { + // IsDefined is extremely slow. Great. + Parallel.ForEach(_reflectionManager.FindAllTypes(), type => + { + if (type.IsDefined(typeof(FlagsForAttribute), false)) + flagsTypes.Add(type); + + if (type.IsDefined(typeof(ConstantsForAttribute), false)) + constantsTypes.Add(type); + + if (type.IsDefined(typeof(TypeSerializerAttribute))) + typeSerializers.Add(type); + + if (type.IsDefined(typeof(MeansDataDefinitionAttribute))) + meansDataDef.Add(type); + + if (type.IsDefined(typeof(ImplicitDataDefinitionForInheritorsAttribute), true)) + implicitDataDefForInheritors.Add(type); + + if (type.IsDefined(typeof(CopyByRefAttribute))) + _copyByRefRegistrations.Add(type); + }); + } + private static readonly ConditionalWeakTable.CreateValueCallback CreateDefinitionCallback = CreateDataDefinition; diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/Prototype/Dictionary/PrototypeIdDictionarySerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/Prototype/Dictionary/PrototypeIdDictionarySerializer.cs index 151221401..6aa28c174 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/Prototype/Dictionary/PrototypeIdDictionarySerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/Prototype/Dictionary/PrototypeIdDictionarySerializer.cs @@ -13,7 +13,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces; namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary { - [TypeSerializer] + // [TypeSerializer] public class PrototypeIdDictionarySerializer : ITypeSerializer, MappingDataNode>, ITypeSerializer, MappingDataNode>, diff --git a/Robust.UnitTesting/RobustIntegrationTest.cs b/Robust.UnitTesting/RobustIntegrationTest.cs index 936dc44e8..9a3083fc5 100644 --- a/Robust.UnitTesting/RobustIntegrationTest.cs +++ b/Robust.UnitTesting/RobustIntegrationTest.cs @@ -590,7 +590,7 @@ namespace Robust.UnitTesting (CVars.DiscordEnabled.Name, "false"), // Avoid preloading textures. - (CVars.TexturePreloadingEnabled.Name, "false"), + (CVars.ResTexturePreloadingEnabled.Name, "false"), }); GameLoop = new IntegrationGameLoop(DependencyCollection.Resolve(),