If plugins or Torch call the static ctor of MyEntities too early the game doesn't break.

Freeze isn't fatal when compiled with DEBUG.  (So you can use breakpoints)
This commit is contained in:
Westin Miller
2017-10-31 20:55:48 -07:00
parent c8f42e8a48
commit 0558675132
5 changed files with 143 additions and 78 deletions

View File

@@ -203,11 +203,18 @@ namespace Torch.Server
((TorchServer)state).Invoke(() => mre.Set()); ((TorchServer)state).Invoke(() => mre.Set());
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout))) if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
{ {
#if DEBUG
Log.Error($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
#else
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread)); Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds."); throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
#endif
}
else
{
Log.Debug("Server watchdog responded");
} }
Log.Debug("Server watchdog responded");
} }
private static string DumpFrozenThread(Thread thread, int traces = 3, int pause = 5000) private static string DumpFrozenThread(Thread thread, int traces = 3, int pause = 5000)

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Sandbox;
using Sandbox.Game.Entities;
using Torch.Utils;
using VRage.Game.Common;
using VRage.Game.Components;
using VRage.Game.Entity;
using VRage.ObjectBuilders;
using VRage.Plugins;
using VRage.Utils;
namespace Torch.Patches
{
/// <summary>
/// There are places in static ctors where the registered assembly depends on the <see cref="Assembly.GetCallingAssembly"/>
/// or <see cref="MyPlugins"/>. Here we force those registrations with the proper assemblies to ensure they work correctly.
/// </summary>
internal static class ObjectFactoryInitPatch
{
#pragma warning disable 649
[ReflectedGetter(Name = "m_objectFactory", TypeName = "Sandbox.Game.Entities.MyEntityFactory, Sandbox.Game")]
private static readonly Func<MyObjectFactory<MyEntityTypeAttribute, MyEntity>> _entityFactoryObjectFactory;
#pragma warning restore 649
internal static void ForceRegisterAssemblies()
{
// static MyEntities() called by MySandboxGame.ForceStaticCtor
RuntimeHelpers.RunClassConstructor(typeof(MyEntities).TypeHandle);
{
MyObjectFactory<MyEntityTypeAttribute, MyEntity> factory = _entityFactoryObjectFactory();
ObjectFactory_RegisterFromAssemblySafe(factory, typeof(MySandboxGame).Assembly); // calling assembly
ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.GameAssembly);
ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.SandboxAssembly);
ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.UserAssembly);
}
// static MyGuiManager():
// MyGuiControlsFactory.RegisterDescriptorsFromAssembly();
// static MyComponentTypeFactory() called by MyComponentContainer.Add
RuntimeHelpers.RunClassConstructor(typeof(MyComponentTypeFactory).TypeHandle);
{
ComponentTypeFactory_RegisterFromAssemblySafe(typeof(MyComponentContainer).Assembly); // calling assembly
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.SandboxAssembly);
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.GameAssembly);
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.SandboxGameAssembly);
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.UserAssembly);
}
// static MyObjectPoolManager()
// Render, so should be fine.
}
#region MyObjectFactory Adders
private static void ObjectFactory_RegisterDescriptorSafe<TAttribute, TCreatedObjectBase>(
MyObjectFactory<TAttribute, TCreatedObjectBase> factory, TAttribute descriptor, Type type) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
{
if (factory.Attributes.TryGetValue(type, out _))
return;
if (descriptor.ObjectBuilderType != null && factory.TryGetProducedType(descriptor.ObjectBuilderType) != null)
return;
if (typeof(MyObjectBuilder_Base).IsAssignableFrom(descriptor.ProducedType) &&
factory.TryGetProducedType(descriptor.ProducedType) != null)
return;
factory.RegisterDescriptor(descriptor, type);
}
private static void ObjectFactory_RegisterFromAssemblySafe<TAttribute, TCreatedObjectBase>(MyObjectFactory<TAttribute, TCreatedObjectBase> factory, Assembly assembly) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
{
if (assembly == null)
{
return;
}
foreach (Type type in assembly.GetTypes())
{
foreach (TAttribute descriptor in type.GetCustomAttributes<TAttribute>())
{
ObjectFactory_RegisterDescriptorSafe(factory, descriptor, type);
}
}
}
#endregion
#region MyComponentTypeFactory Adders
[ReflectedGetter(Name = "m_idToType", Type = typeof(MyComponentTypeFactory))]
private static Func<Dictionary<MyStringId, Type>> _componentTypeFactoryIdToType;
[ReflectedGetter(Name = "m_typeToId", Type = typeof(MyComponentTypeFactory))]
private static Func<Dictionary<Type, MyStringId>> _componentTypeFactoryTypeToId;
[ReflectedGetter(Name = "m_typeToContainerComponentType", Type = typeof(MyComponentTypeFactory))]
private static Func<Dictionary<Type, Type>> _componentTypeFactoryContainerComponentType;
private static void ComponentTypeFactory_RegisterFromAssemblySafe(Assembly assembly)
{
if (assembly == null)
return;
foreach (Type type in assembly.GetTypes())
if (typeof(MyComponentBase).IsAssignableFrom(type))
{
ComponentTypeFactory_AddIdSafe(type, MyStringId.GetOrCompute(type.Name));
ComponentTypeFactory_RegisterComponentTypeAttributeSafe(type);
}
}
private static void ComponentTypeFactory_RegisterComponentTypeAttributeSafe(Type type)
{
Type componentType = type.GetCustomAttribute<MyComponentTypeAttribute>(true)?.ComponentType;
if (componentType != null)
_componentTypeFactoryContainerComponentType()[type] = componentType;
}
private static void ComponentTypeFactory_AddIdSafe(Type type, MyStringId id)
{
_componentTypeFactoryIdToType()[id] = type;
_componentTypeFactoryTypeToId()[type] = id;
}
#endregion
}
}

View File

@@ -1,74 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Sandbox;
using Sandbox.Game.Entities;
using Torch.Utils;
using VRage.Game.Common;
using VRage.Game.Components;
using VRage.Game.Entity;
using VRage.ObjectBuilders;
namespace Torch.Patches
{
/// <summary>
/// There are places in static ctors where the registered assembly depends on the <see cref="Assembly.GetCallingAssembly"/>.
/// Here we force those registrations with the proper assemblies to ensure they work correctly.
/// </summary>
internal static class RegisterFromCallingAssemblyPatch
{
#pragma warning disable 649
[ReflectedGetter(Name="m_objectFactory", TypeName = "Sandbox.Game.Entities.MyEntityFactory, Sandbox.Game")]
private static readonly Func<MyObjectFactory<MyEntityTypeAttribute, MyEntity>> _entityFactoryObjectFactory;
#pragma warning restore 649
internal static void ForceRegisterAssemblies()
{
// static MyEntities() called by MySandboxGame.ForceStaticCtor
RuntimeHelpers.RunClassConstructor(typeof(MyEntities).TypeHandle);
RegisterFromAssemblySafe(_entityFactoryObjectFactory(), typeof(MySandboxGame).Assembly);
// static MyGuiManager():
// MyGuiControlsFactory.RegisterDescriptorsFromAssembly();
// static MyComponentTypeFactory() called by MyComponentContainer.Add
// _componentFactoryRegisterAssembly(typeof(MyComponentContainer).Assembly);
// static MyObjectPoolManager()
// Render, so should be fine.
}
private static void RegisterDescriptorSafe<TAttribute, TCreatedObjectBase>(
MyObjectFactory<TAttribute, TCreatedObjectBase> factory, TAttribute descriptor, Type type) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
{
if (factory.Attributes.TryGetValue(type, out _))
return;
if (descriptor.ObjectBuilderType != null && factory.TryGetProducedType(descriptor.ObjectBuilderType) != null)
return;
if (typeof(MyObjectBuilder_Base).IsAssignableFrom(descriptor.ProducedType) &&
factory.TryGetProducedType(descriptor.ProducedType) != null)
return;
factory.RegisterDescriptor(descriptor, type);
}
private static void RegisterFromAssemblySafe<TAttribute, TCreatedObjectBase>(MyObjectFactory<TAttribute, TCreatedObjectBase> factory, Assembly assembly) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
{
if (assembly == null)
{
return;
}
foreach (Type type in assembly.GetTypes())
{
foreach (TAttribute descriptor in type.GetCustomAttributes<TAttribute>())
{
RegisterDescriptorSafe(factory, descriptor, type);
}
}
}
}
}

View File

@@ -194,7 +194,7 @@
<Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" /> <Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" />
<Compile Include="Patches\GameAnalyticsPatch.cs" /> <Compile Include="Patches\GameAnalyticsPatch.cs" />
<Compile Include="Patches\GameStatePatchShim.cs" /> <Compile Include="Patches\GameStatePatchShim.cs" />
<Compile Include="Patches\RegisterFromCallingAssemblyPatch.cs" /> <Compile Include="Patches\ObjectFactoryInitPatch.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SaveGameStatus.cs" /> <Compile Include="SaveGameStatus.cs" />
<Compile Include="Collections\KeyTree.cs" /> <Compile Include="Collections\KeyTree.cs" />

View File

@@ -254,7 +254,14 @@ namespace Torch
Debug.Assert(!_init, "Torch instance is already initialized."); Debug.Assert(!_init, "Torch instance is already initialized.");
SpaceEngineersGame.SetupBasicGameInfo(); SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings(); SpaceEngineersGame.SetupPerGameSettings();
RegisterFromCallingAssemblyPatch.ForceRegisterAssemblies(); // If the attached assemblies change (MySandboxGame.ctor => MySandboxGame.ParseArgs => MyPlugins.RegisterFromArgs)
// attach assemblies to object factories again.
ObjectFactoryInitPatch.ForceRegisterAssemblies();
GameStateChanged += (game, state) =>
{
if (state == TorchGameState.Created)
ObjectFactoryInitPatch.ForceRegisterAssemblies();
};
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null"); Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", ".")); GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));