Skip to content

Commit

Permalink
Add extra debugging and possible workaround for lost grids
Browse files Browse the repository at this point in the history
  • Loading branch information
Equinox- committed Feb 28, 2024
1 parent 3fecbe5 commit c90c372
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 12 deletions.
1 change: 1 addition & 0 deletions Meds.Wrapper/Meds.Wrapper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
<Compile Include="Shim\LoggerPatches.cs" />
<Compile Include="Shim\MecPatches.cs" />
<Compile Include="Shim\MiscPatches.cs" />
<Compile Include="Shim\PatchExtensions.cs" />
<Compile Include="Shim\PatchHelper.cs" />
<Compile Include="Shim\ShimLog.cs" />
<Compile Include="Shim\VerboseCrashPatches.cs" />
Expand Down
8 changes: 3 additions & 5 deletions Meds.Wrapper/Metrics/EntityGridDatabaseMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ namespace Meds.Wrapper.Metrics
{
public static class GridDatabaseMetrics
{
private static readonly MethodInfo DatabaseProperty = AccessTools.PropertyGetter(typeof(MyInfiniteWorldPersistence), "Database");
public static readonly MethodInfo DatabaseProperty = AccessTools.PropertyGetter(typeof(MyInfiniteWorldPersistence), "Database");
public static readonly FieldInfo EntitiesField = AccessTools.Field(typeof(MyEntityGridDatabase), "Entities");
public static readonly FieldInfo ChunksField = AccessTools.Field(typeof(MyEntityGridDatabase), "Chunks");
private static readonly FieldInfo ViewsField = AccessTools.Field(typeof(MyInfiniteWorldPersistence), "m_views");
private static readonly FieldInfo ChunksField = AccessTools.Field(typeof(MyEntityGridDatabase), "Chunks");

private static readonly FieldInfo EntitiesField = AccessTools.Field(typeof(MyEntityGridDatabase), "Entities");

private static readonly FieldInfo GroupsField = AccessTools.Field(typeof(MyEntityGridDatabase), "Groups");

public static void Register()
Expand Down
4 changes: 0 additions & 4 deletions Meds.Wrapper/Shim/MiscPatches.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using Medieval.Entities.Components;
using Medieval.ObjectBuilders.Session;
using Medieval.Players;
using Meds.Wrapper.Utils;
using Sandbox.Game.Players;
using VRage;
using VRageMath;
using ZLogger;

Expand Down
30 changes: 30 additions & 0 deletions Meds.Wrapper/Shim/PatchExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Reflection;

namespace Meds.Wrapper.Shim
{
public static class PatchExtensions
{
public static bool TryFindArg(this MethodBase method, Type type, out int index)
{
if (type.IsAssignableFrom(method.DeclaringType))
{
index = 0;
return true;
}

var args = method.GetParameters();
for (var i = 0; i < args.Length; i++)
{
if (type.IsAssignableFrom(args[i].ParameterType))
{
index = i + (method.IsStatic ? 0 : 1);
return true;
}
}

index = default;
return false;
}
}
}
178 changes: 175 additions & 3 deletions Meds.Wrapper/Shim/VerboseCrashPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using HarmonyLib;
using Medieval.World.Persistence;
using Meds.Wrapper.Metrics;
using Meds.Wrapper.Utils;
using Microsoft.Extensions.Logging;
using Sandbox.Game.Entities.Entity.Stats;
using Sandbox.Game.EntityComponents.Character;
using VRage.Components;
using VRage.Game.Components;
using VRage.Game.Entity;
using VRage.Scene;
using VRage.Session;
using ZLogger;

// ReSharper disable InconsistentNaming

namespace Meds.Wrapper.Shim
{
[HarmonyPatch(typeof(MyUpdateScheduler), "ReportError")]
Expand Down Expand Up @@ -184,4 +189,171 @@ bool TryFindContainerArg(out int index)
}
}
}

[HarmonyPatch(typeof(MySessionPersistence), "VRage.Session.IMyScenePersistence.AddEntity")]
[AlwaysPatch]
public static class EntityPersistencePatch
{
private static bool AttemptRepair = false;

private static readonly FieldInfo EntityPersistence = AccessTools.Field(typeof(MySessionPersistence), "m_entityPersistence");
private static readonly MethodInfo EntityId = AccessTools.PropertyGetter(typeof(MyEntity), nameof(MyEntity.Id));

private static readonly MethodInfo EntityPersistenceRemove =
AccessTools.Method(EntityPersistence.FieldType, "Remove", new[] { typeof(EntityId) });

private static readonly MethodInfo GetComponent = AccessTools.Method(typeof(MySessionPersistence), "GetComponent", new[] { typeof(int) });

private static readonly MethodInfo GetEntityPersistence =
AccessTools.Method(typeof(MySessionPersistence), "VRage.Session.IMyScenePersistence.GetEntityPersistence", new[] { typeof(MyEntity) });

private static readonly MethodInfo MonitorEnter = AccessTools.Method(typeof(Monitor), nameof(Monitor.Enter), new[] { typeof(object) });
private static readonly MethodInfo MonitorExit = AccessTools.Method(typeof(Monitor), nameof(Monitor.Exit), new[] { typeof(object) });

private static readonly FieldInfo EntityTrackerLoaded = AccessTools.Field(GridDatabaseMetrics.EntitiesField.FieldType, "Loaded");
private static readonly Type ChunkObjectData = EntityTrackerLoaded.FieldType.GenericTypeArguments[1];

private static readonly MethodInfo EntityTrackerLoadedRemove = AccessTools.Method(EntityTrackerLoaded.FieldType,
nameof(IDictionary<int, int>.Remove),
new[] { typeof(EntityId) });

private static readonly MethodInfo EntityTrackerLoadedTryGetValue = AccessTools.Method(EntityTrackerLoaded.FieldType,
nameof(IDictionary<int, int>.TryGetValue),
new[] { typeof(EntityId), ChunkObjectData.MakeByRefType() });

private static readonly MethodInfo ChunkTrackerOnObjectRemoved = AccessTools.Method(GridDatabaseMetrics.ChunksField.FieldType, "OnObjectRemove", new[]
{
typeof(EntityId),
ChunkObjectData.MakeByRefType()
});

private static readonly MethodInfo LogWarningMethod = AccessTools.Method(
typeof(EntityPersistencePatch),
nameof(LogWarning),
new[] { typeof(MyEntity), typeof(IMyPersistenceComponent) });

private static readonly FieldInfo AttemptRepairField = AccessTools.Field(typeof(EntityPersistencePatch), nameof(AttemptRepairField));

public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator ilg)
{
var normalCode = ilg.DefineLabel();
if (EntityPersistence != null && GetComponent != null && GetEntityPersistence != null)
{
var currComp = ilg.DeclareLocal(typeof(IMyPersistenceComponent));
// var currComp = this.GetEntityPersistence(entity)
yield return new CodeInstruction(OpCodes.Ldarg_0);
yield return new CodeInstruction(OpCodes.Ldarg_1);
yield return new CodeInstruction(OpCodes.Callvirt, GetEntityPersistence);
yield return new CodeInstruction(OpCodes.Dup);
yield return new CodeInstruction(OpCodes.Stloc, currComp);
// if (currComp != null) {
yield return new CodeInstruction(OpCodes.Brfalse, normalCode);

// LogWarning(entity, currComp)
yield return new CodeInstruction(OpCodes.Ldarg_1);
yield return new CodeInstruction(OpCodes.Ldloc, currComp);
yield return new CodeInstruction(OpCodes.Call, LogWarningMethod);

// if (AttemptRepair) {
yield return new CodeInstruction(OpCodes.Ldsfld, AttemptRepairField);
yield return new CodeInstruction(OpCodes.Brfalse, normalCode);

// var iwp = currComp as MyInfiniteWorldPersistence;
var iwp = ilg.DeclareLocal(typeof(MyInfiniteWorldPersistence));
yield return new CodeInstruction(OpCodes.Ldloc, currComp);
yield return new CodeInstruction(OpCodes.Isinst, typeof(MyInfiniteWorldPersistence));
yield return new CodeInstruction(OpCodes.Dup);
yield return new CodeInstruction(OpCodes.Stloc, iwp);
// if (iwp != null) {
yield return new CodeInstruction(OpCodes.Brfalse, normalCode);

// m_entityPersistence.Remove(entity.Id);
yield return new CodeInstruction(OpCodes.Ldarg_0);
yield return new CodeInstruction(OpCodes.Ldfld, EntityPersistence);
yield return new CodeInstruction(OpCodes.Ldarg_1);
yield return new CodeInstruction(OpCodes.Callvirt, EntityId);
yield return new CodeInstruction(OpCodes.Callvirt, EntityPersistenceRemove.GetBaseDefinition());
yield return new CodeInstruction(OpCodes.Pop);

// var db = iwp.Database;
var db = ilg.DeclareLocal(typeof(MyEntityGridDatabase));
yield return new CodeInstruction(OpCodes.Ldloc, iwp);
yield return new CodeInstruction(OpCodes.Callvirt, GridDatabaseMetrics.DatabaseProperty);
yield return new CodeInstruction(OpCodes.Stloc, db);

// Monitor.Enter(db);
yield return new CodeInstruction(OpCodes.Ldloc, db);
yield return new CodeInstruction(OpCodes.Call, MonitorEnter);
// try {
yield return new CodeInstruction(OpCodes.Nop).WithBlocks(new ExceptionBlock(ExceptionBlockType.BeginExceptionBlock));

// var entities = db.Entities;
var entities = ilg.DeclareLocal(GridDatabaseMetrics.EntitiesField.FieldType);
yield return new CodeInstruction(OpCodes.Ldloc, db);
yield return new CodeInstruction(OpCodes.Ldfld, GridDatabaseMetrics.EntitiesField);
yield return new CodeInstruction(OpCodes.Stloc, entities);

// ChunkObjectData chunkObjectData;
var chunkObjectData = ilg.DeclareLocal(ChunkObjectData);

// if (entities.Loaded.TryGetValue(entity.Id, out chunkObjectData)) {
var labelWasNotLoaded = ilg.DefineLabel();
yield return new CodeInstruction(OpCodes.Ldloc, entities);
yield return new CodeInstruction(OpCodes.Ldfld, EntityTrackerLoaded);
yield return new CodeInstruction(OpCodes.Ldarg_1);
yield return new CodeInstruction(OpCodes.Callvirt, EntityId);
yield return new CodeInstruction(OpCodes.Ldloca, chunkObjectData);
yield return new CodeInstruction(OpCodes.Callvirt, EntityTrackerLoadedTryGetValue.GetBaseDefinition());
yield return new CodeInstruction(OpCodes.Brfalse, labelWasNotLoaded);

// db.Chunks.OnObjectRemove(entity.Id, ref chunkObjectData);
yield return new CodeInstruction(OpCodes.Ldloc, db);
yield return new CodeInstruction(OpCodes.Ldfld, GridDatabaseMetrics.ChunksField);
yield return new CodeInstruction(OpCodes.Ldarg_1);
yield return new CodeInstruction(OpCodes.Callvirt, EntityId);
yield return new CodeInstruction(OpCodes.Ldloca, chunkObjectData);
yield return new CodeInstruction(OpCodes.Callvirt, ChunkTrackerOnObjectRemoved);

// entities.Loaded.Remove(entity.Id);
yield return new CodeInstruction(OpCodes.Ldloc, entities);
yield return new CodeInstruction(OpCodes.Ldfld, EntityTrackerLoaded);
yield return new CodeInstruction(OpCodes.Ldarg_1);
yield return new CodeInstruction(OpCodes.Callvirt, EntityId);
yield return new CodeInstruction(OpCodes.Callvirt, EntityTrackerLoadedRemove.GetBaseDefinition());
yield return new CodeInstruction(OpCodes.Pop);

// } // if (loaded.TryGetValue(...))
yield return new CodeInstruction(OpCodes.Nop).WithLabels(labelWasNotLoaded);

// } finally {
yield return new CodeInstruction(OpCodes.Ldloc, db).WithBlocks(new ExceptionBlock(ExceptionBlockType.BeginFinallyBlock));
// Monitor.Exit(db);
yield return new CodeInstruction(OpCodes.Call, MonitorExit);
yield return new CodeInstruction(OpCodes.Endfinally).WithBlocks(new ExceptionBlock(ExceptionBlockType.EndExceptionBlock));
// } // lock(db)

// } // if (iwp != null);
// } // if (currComp != null);
// } // if (AttemptRepair)
}

yield return new CodeInstruction(OpCodes.Nop).WithLabels(normalCode);
foreach (var instruction in instructions)
yield return instruction;
}

// ReSharper disable once UnusedMember.Local
private static void LogWarning(MyEntity entity, IMyPersistenceComponent curr)
{
var resurrect = curr is MyInfiniteWorldPersistence && AttemptRepair;
Entrypoint.LoggerFor(typeof(EntityPersistencePatch))
.ZLogWarningWithPayload(
new EntityPayload(entity),
"Entity {0} ({1}) is already persisted in {2}. {3}",
entity.EntityId,
entity.DefinitionId?.SubtypeName,
curr.GetType().Name,
resurrect ? "It will be removed and re-added." : "No repair will take place.");
}
}
}
21 changes: 21 additions & 0 deletions Meds.Wrapper/Utils/LoggingPayloads.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
using Sandbox.Game.World;
using VRage.Collections;
using VRage.Components;
using VRage.Components.Entity.CubeGrid;
using VRage.Definitions;
using VRage.Engine;
using VRage.Game;
using VRage.Game.Components;
using VRage.Game.Entity;
using VRageMath;

namespace Meds.Wrapper.Utils
Expand Down Expand Up @@ -124,6 +126,25 @@ public ComponentPayload(IComponent comp, string method = null)
}
}

public class EntityPayload
{
public long? EntityId;
public long? EntityRootId;
public string EntitySubtype;
public PositionPayload Position;
public int? BlockCount;

public EntityPayload(MyEntity entity)
{
EntityId = entity.EntityId;
EntityRootId = entity.GetTopMostParent()?.EntityId;
EntitySubtype = entity.DefinitionId?.SubtypeName;
PositionPayload.TryCreate(entity.GetPosition(), out Position);
if (entity.Components.TryGet(out MyGridDataComponent grid))
BlockCount = grid.BlockCount;
}
}

public class EntityComponentPayload
{
public PackagePayload Package;
Expand Down

0 comments on commit c90c372

Please sign in to comment.