Skip to content

Commit

Permalink
Add tiered backup support
Browse files Browse the repository at this point in the history
Disable double inventory replication patch since it suppresses initial inventory updates
  • Loading branch information
Equinox- committed Mar 10, 2024
1 parent 33889da commit 8c248ea
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 1 deletion.
31 changes: 31 additions & 0 deletions Meds.Shared/WrapperConfig.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using Equ;
Expand Down Expand Up @@ -35,6 +36,9 @@ public sealed class RenderedRuntimeConfig : MemberwiseEquatable<RenderedRuntimeC

[XmlElement]
public AuditConfig Audit = new AuditConfig();

[XmlElement]
public BackupConfig Backup = new BackupConfig();
}

public class AuditConfig : MemberwiseEquatable<AuditConfig>
Expand All @@ -59,6 +63,33 @@ public class AdjustmentsConfig : MemberwiseEquatable<AdjustmentsConfig>
public List<string> RequestPatch = new List<string>();
}

public class BackupConfig : MemberwiseEquatable<BackupTierConfig>
{
[XmlElement]
public bool DefaultTiers;

[XmlElement("Tier")]
public List<BackupTierConfig> Tiers = new List<BackupTierConfig>();
}

public class BackupTierConfig : MemberwiseEquatable<BackupTierConfig>
{
[XmlIgnore]
public TimeSpan Interval => TimeSpan.FromDays(Days) + TimeSpan.FromHours(Hours) + TimeSpan.FromMinutes(Minutes);

[XmlAttribute]
public double Minutes;

[XmlAttribute]
public double Hours;

[XmlAttribute]
public double Days;

[XmlAttribute]
public int Count;
}

public class MetricConfig : MemberwiseEquatable<MetricConfig>
{
[XmlElement]
Expand Down
1 change: 1 addition & 0 deletions Meds.Watchdog/ConfigRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private void WriteConfig(string target, object obj, XmlSerializer serializer)
private RenderedRuntimeConfig RenderRuntime(Configuration cfg) => new RenderedRuntimeConfig
{
Audit = cfg.Audit,
Backup = cfg.Backup,
};
}
}
3 changes: 3 additions & 0 deletions Meds.Watchdog/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public class Configuration : InstallConfiguration, IEquatable<Configuration>
[XmlElement]
public AdjustmentsConfig Adjustments = new AdjustmentsConfig();

[XmlElement]
public BackupConfig Backup = new BackupConfig();

[XmlElement]
public AuditConfig Audit = new AuditConfig();

Expand Down
4 changes: 4 additions & 0 deletions Meds.Wrapper/Entrypoint.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Meds.Shared;
using Meds.Wrapper.Shim;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Sandbox.Game.World;
using VRage.ParallelWorkers;

namespace Meds.Wrapper
Expand Down Expand Up @@ -48,6 +51,7 @@ public static void Main(string[] args)
services.AddHostedService(svc => cfg.Runtime.Refreshing(svc));
services.AddSingleton<ShimLog>();
services.AddSingleton<HealthReporter>();
services.AddSingleton<TieredBackups>();
services.AddHostedAlias<HealthReporter>();
services.AddHostedService<ServerService>();
services.AddMedsMessagePipe(cfg.Install.Messaging.WatchdogToServer, cfg.Install.Messaging.ServerToWatchdog);
Expand Down
1 change: 1 addition & 0 deletions Meds.Wrapper/Meds.Wrapper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
<Compile Include="Shim\PatchExtensions.cs" />
<Compile Include="Shim\PatchHelper.cs" />
<Compile Include="Shim\ShimLog.cs" />
<Compile Include="Shim\TieredBackups.cs" />
<Compile Include="Shim\VerboseCrashPatches.cs" />
<Compile Include="Shim\VoxelResetPatches.cs" />
<Compile Include="Utils\DefinitionForObject.cs" />
Expand Down
3 changes: 2 additions & 1 deletion Meds.Wrapper/Shim/NoDoubleReplicationPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ public static bool Prefix(ref MyObjectBuilder_EnvironmentDataProvider __result)
}

[HarmonyPatch(typeof(MyInventory), nameof(MyInventory.Serialize))]
[AlwaysPatch]
// [AlwaysPatch]
// For this to work correctly the client also needs to be updated so that it always sends the inventory changed event, even on the initial deserialization.
public static class NoDoubleReplicationInventory
{
private static readonly List<MyInventoryItem> NoItems = new List<MyInventoryItem>();
Expand Down
26 changes: 26 additions & 0 deletions Meds.Wrapper/Shim/PatchExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;

namespace Meds.Wrapper.Shim
{
Expand All @@ -26,5 +28,29 @@ public static bool TryFindArg(this MethodBase method, Type type, out int index)
index = default;
return false;
}

public static bool LoadsArg(this CodeInstruction isn, int arg)
{
var op = isn.opcode;
if (op == OpCodes.Ldarg || op == OpCodes.Ldarg_S)
{
return isn.operand switch
{
int int32 => int32 == arg,
short int16 => int16 == arg,
byte int8 => int8 == arg,
_ => false
};
}

return arg switch
{
0 => op == OpCodes.Ldarg_0,
1 => op == OpCodes.Ldarg_1,
2 => op == OpCodes.Ldarg_2,
3 => op == OpCodes.Ldarg_3,
_ => false
};
}
}
}
169 changes: 169 additions & 0 deletions Meds.Wrapper/Shim/TieredBackups.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using Meds.Shared;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sandbox.Game.World;
using ZLogger;

// ReSharper disable InconsistentNaming

namespace Meds.Wrapper.Shim
{
public sealed class TieredBackups
{
// Dummy tier to keep the latest 5 saves in all circumstances.
private static readonly Tier TierZero = new Tier(5, TimeSpan.FromTicks(1));
private static TieredBackups Instance => Entrypoint.Instance.Services.GetRequiredService<TieredBackups>();

private readonly Refreshable<Tier[]> _tiers;
private readonly ILogger<TieredBackups> _log;

public TieredBackups(Refreshable<RenderedRuntimeConfig> cfg, ILogger<TieredBackups> log)
{
_tiers = cfg
.Map(x => x.Backup ?? new BackupConfig())
.Map(CreateTiers);
_log = log;
}

private static Tier[] CreateTiers(BackupConfig x)
{
if (x.Tiers != null && x.Tiers.Count > 0)
{
var tiers = x.Tiers.Where(y => y.Count > 0)
.Select(y => new Tier(y.Count, y.Interval))
.Where(y => y.Interval > TimeSpan.Zero)
.OrderBy(y => y.Interval)
.ToList();
if (tiers.Count > 0)
{
tiers.Insert(0, TierZero);
return tiers.ToArray();
}
}

if (x.DefaultTiers)
{
return new[]
{
TierZero,
new Tier(30, TimeSpan.FromMinutes(5)),
new Tier(10, TimeSpan.FromHours(1)),
new Tier(10, TimeSpan.FromHours(3)),
new Tier(10, TimeSpan.FromHours(6)),
new Tier(10, TimeSpan.FromHours(12)),
new Tier(10, TimeSpan.FromHours(24)),
};
}

return Array.Empty<Tier>();
}

private sealed class Tier
{
public readonly int Count;
public readonly TimeSpan Interval;

public Tier(int count, TimeSpan interval)
{
Count = count;
Interval = interval;
}
}

private void ApplyRetention(List<MySessionBackup.Backup> backups)
{
var tiers = _tiers.Current;
if (tiers.Length == 0 || backups.Count == 0)
return;
var partOfTier = new Tier[backups.Count];

foreach (var tier in tiers)
{
var currWindow = long.MaxValue;
var remaining = tier.Count;
for (var j = backups.Count - 1; j >= 0 && remaining > 0; j--)
{
var backup = backups[j];
var window = backup.Time.Ticks / tier.Interval.Ticks;
if (window == currWindow)
continue;

currWindow = window;
partOfTier[j] ??= tier;
--remaining;
}
}

for (var j = 0; j < backups.Count; j++)
{
var backup = backups[j];
var tier = partOfTier[j];
if (tier != null)
continue;

_log.ZLogInformation("Removing backup {0}", backup.DirectoryPath);

if (backup.IsArchive)
File.Delete(backup.DirectoryPath);
else
Directory.Delete(backup.DirectoryPath, true);
}
}


internal static bool Enabled => Instance._tiers.Current.Length > 0;
internal static void HookApplyRetention(List<MySessionBackup.Backup> backups) => Instance.ApplyRetention(backups);
}


[HarmonyPatch(typeof(MySessionBackup), nameof(MySessionBackup.MakeBackup), typeof(MySession), typeof(IEnumerable<string>), typeof(int))]
[AlwaysPatch]
public static class TieredBackupsDisableInternalLimit
{
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator ilg)
{
foreach (var instruction in instructions)
{
if (instruction.LoadsArg(2))
{
var originalArg = ilg.DefineLabel();
var nextCode = ilg.DefineLabel();
yield return CodeInstruction.Call(typeof(TieredBackups), "get_" + nameof(TieredBackups.Enabled))
.WithLabels(instruction.labels)
.WithBlocks(instruction.blocks);
yield return new CodeInstruction(OpCodes.Brfalse, originalArg);
yield return new CodeInstruction(OpCodes.Ldc_I4, int.MaxValue);
yield return new CodeInstruction(OpCodes.Br, nextCode);
yield return new CodeInstruction(instruction.opcode, instruction.operand).WithLabels(originalArg);
yield return new CodeInstruction(OpCodes.Nop).WithLabels(nextCode);
continue;
}

yield return instruction;
}
}
}

[HarmonyPatch(typeof(MySessionBackup), "MakeBackup")]
public static class TieredBackupsHookRetention
{
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
foreach (var instruction in instructions)
{
yield return instruction;
if (!(instruction.operand is MethodInfo { Name: "GetBackups" }))
continue;
yield return new CodeInstruction(OpCodes.Dup);
yield return CodeInstruction.Call(typeof(TieredBackups), nameof(TieredBackups.HookApplyRetention));
}
}
}
}

0 comments on commit 8c248ea

Please sign in to comment.