Decompiled source of RunicWorldEngine v1.1.0

RunicWorldEngine.dll

Decompiled 27 minutes ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RunicWorldEngine.Contracts;
using RunicWorldEngine.Core;
using RunicWorldEngine.Integration;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Runic World Engine")]
[assembly: AssemblyDescription("Bounded Valheim world-state observability and safe save smoothing")]
[assembly: AssemblyCompany("Chazman")]
[assembly: AssemblyProduct("Runic World Engine")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RunicWorldEngine
{
	internal static class WorldEngineConfig
	{
		internal static ConfigEntry<bool> Enabled { get; private set; }

		internal static ConfigEntry<bool> LogPeriodicSummary { get; private set; }

		internal static ConfigEntry<float> SummaryIntervalSeconds { get; private set; }

		internal static ConfigEntry<bool> SmoothWorldSaves { get; private set; }

		internal static ConfigEntry<float> SaveFrameBudgetMilliseconds { get; private set; }

		internal static ConfigEntry<float> MaximumSaveDeferralSeconds { get; private set; }

		internal static void Bind(ConfigFile config)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Expected O, but got Unknown
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable bounded world-state observation and optional save smoothing. When false, no Harmony patches or runtime state are created. This never enables deletion or network sync rescheduling.");
			LogPeriodicSummary = config.Bind<bool>("Diagnostics", "LogPeriodicSummary", false, "Write rate-limited aggregate ZDO counts and traffic to the BepInEx log.");
			SummaryIntervalSeconds = config.Bind<float>("Diagnostics", "SummaryIntervalSeconds", 30f, new ConfigDescription("Seconds between optional aggregate summaries.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 600f), Array.Empty<object>()));
			SmoothWorldSaves = config.Bind<bool>("Save Smoothing", "Enabled", true, "Coalesce overlapping asynchronous world saves and defer the main-thread PrepareSave capture until a stable frame. Synchronous shutdown saves are never deferred.");
			SaveFrameBudgetMilliseconds = config.Bind<float>("Save Smoothing", "FrameBudgetMilliseconds", 24f, new ConfigDescription("Prefer to start asynchronous PrepareSave only when the previous unscaled frame was at or below this duration.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 100f), Array.Empty<object>()));
			MaximumSaveDeferralSeconds = config.Bind<float>("Save Smoothing", "MaximumDeferralSeconds", 5f, new ConfigDescription("Maximum time an asynchronous save may wait for a stable frame. The save then starts even if frames remain busy.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
		}
	}
	[BepInPlugin("chazman.RunicWorldEngine", "Runic World Engine", "1.1.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "chazman.RunicWorldEngine";

		public const string Name = "Runic World Engine";

		public const string Version = "1.1.0";

		private Harmony _harmony;

		private float _nextSummaryAt;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			WorldEngineConfig.Bind(((BaseUnityPlugin)this).Config);
			ConfigEntry<bool> enabled = WorldEngineConfig.Enabled;
			if (enabled != null && !enabled.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic World Engine is disabled; no Harmony patches or observatory state were created.");
				return;
			}
			try
			{
				_harmony = new Harmony("chazman.RunicWorldEngine");
				ObservatoryRuntime.Verify();
				_harmony.PatchAll(typeof(Plugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic World Engine v1.1.0 ready: bounded aggregate ZDO observability. Unknown data is preserved; compaction and sync changes are off.");
			}
			catch (Exception ex)
			{
				Shutdown();
				((BaseUnityPlugin)this).Logger.LogError((object)("Runic World Engine failed closed; Valheim world handling remains unchanged. " + ex));
			}
		}

		private void Update()
		{
			SaveSmoothingRuntime.Tick();
			ConfigEntry<bool> enabled = WorldEngineConfig.Enabled;
			if (enabled == null || !enabled.Value)
			{
				return;
			}
			ConfigEntry<bool> logPeriodicSummary = WorldEngineConfig.LogPeriodicSummary;
			if (logPeriodicSummary != null && logPeriodicSummary.Value)
			{
				float unscaledTime = Time.unscaledTime;
				if (!(unscaledTime < _nextSummaryAt))
				{
					_nextSummaryAt = unscaledTime + Mathf.Clamp(WorldEngineConfig.SummaryIntervalSeconds.Value, 5f, 600f);
					ZdoObservatorySnapshot current = ObservatoryRuntime.Current;
					((BaseUnityPlugin)this).Logger.LogInfo((object)($"World sample #{current.Sequence}: objects={current.TotalObjects}, peers={current.ConnectedPeers}, " + $"created={current.CreatedSincePreviousSample}, destroyed={current.DestroyedSincePreviousSample}, " + $"sent/s={current.SentLastSecond}, received/s={current.ReceivedLastSecond}, " + $"save={current.LastSaveMilliseconds:F1}ms, load={current.LastLoadMilliseconds:F1}ms."));
				}
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			_harmony = null;
			ObservatoryRuntime.Reset();
			SaveSmoothingRuntime.Reset();
		}
	}
}
namespace RunicWorldEngine.Integration
{
	[HarmonyPatch(typeof(ZDOMan), "CreateNewZDO", new Type[]
	{
		typeof(ZDOID),
		typeof(Vector3),
		typeof(int)
	})]
	internal static class ZdoCreatedPatch
	{
		private static void Postfix(ZDO __result)
		{
			ObservatoryRuntime.MarkCreated(__result);
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "HandleDestroyedZDO", new Type[] { typeof(ZDOID) })]
	internal static class ZdoDestroyedPatch
	{
		private static void Prefix(ZDOMan __instance, ZDOID __0, ref bool __state)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			__state = ObservatoryRuntime.Exists(__instance, __0);
		}

		private static void Postfix(ZDOMan __instance, ZDOID __0, bool __state)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			ObservatoryRuntime.MarkDestroyed(__state, __instance, __0);
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "UpdateStats", new Type[] { typeof(float) })]
	internal static class ZdoStatsPatch
	{
		private static void Postfix(ZDOMan __instance)
		{
			ObservatoryRuntime.Capture(__instance);
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "SaveAsync", new Type[] { typeof(BinaryWriter) })]
	internal static class ZdoSaveTimingPatch
	{
		private static void Prefix(ref long __state)
		{
			__state = ObservatoryRuntime.BeginTimedOperation();
		}

		private static Exception Finalizer(long __state, Exception __exception)
		{
			ObservatoryRuntime.EndSave(__state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "Load", new Type[]
	{
		typeof(BinaryReader),
		typeof(int)
	})]
	internal static class ZdoLoadTimingPatch
	{
		private static void Prefix(ref long __state)
		{
			__state = ObservatoryRuntime.BeginTimedOperation();
		}

		private static Exception Finalizer(long __state, Exception __exception)
		{
			ObservatoryRuntime.EndLoad(__state);
			return __exception;
		}
	}
	internal static class ObservatoryRuntime
	{
		private static readonly FieldInfo ObjectsField = AccessTools.Field(typeof(ZDOMan), "m_objectsByID");

		private static readonly FieldInfo PeersField = AccessTools.Field(typeof(ZDOMan), "m_peers");

		private static readonly FieldInfo SentField = AccessTools.Field(typeof(ZDOMan), "m_zdosSentLastSec");

		private static readonly FieldInfo ReceivedField = AccessTools.Field(typeof(ZDOMan), "m_zdosRecvLastSec");

		private static readonly ObservatoryCounter Counter = new ObservatoryCounter();

		private static long _nextCaptureTimestamp;

		private static bool _verified;

		internal static ZdoObservatorySnapshot Current => Counter.Current;

		internal static void Verify()
		{
			bool flag = PeersField != null && !PeersField.IsStatic && PeersField.FieldType.IsGenericType && PeersField.FieldType.GetGenericTypeDefinition() == typeof(List<>) && PeersField.FieldType.GetGenericArguments().Length == 1 && PeersField.FieldType.GetGenericArguments()[0].DeclaringType == typeof(ZDOMan) && PeersField.FieldType.GetGenericArguments()[0].Name == "ZDOPeer";
			if (ObjectsField == null || ObjectsField.IsStatic || ObjectsField.FieldType != typeof(Dictionary<ZDOID, ZDO>) || !flag || SentField == null || SentField.IsStatic || SentField.FieldType != typeof(int) || ReceivedField == null || ReceivedField.IsStatic || ReceivedField.FieldType != typeof(int))
			{
				throw new MissingMemberException("ZDOMan observatory fields do not match the audited Valheim 0.221.12 contract.");
			}
			_verified = true;
		}

		internal static void MarkCreated(ZDO created)
		{
			if (_verified)
			{
				ConfigEntry<bool> enabled = WorldEngineConfig.Enabled;
				if (enabled != null && enabled.Value && created != null)
				{
					Counter.MarkCreated();
				}
			}
		}

		internal static bool Exists(ZDOMan manager, ZDOID id)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (_verified)
			{
				ConfigEntry<bool> enabled = WorldEngineConfig.Enabled;
				if (enabled != null && enabled.Value && manager != null)
				{
					try
					{
						return ObjectsField.GetValue(manager) is Dictionary<ZDOID, ZDO> dictionary && dictionary.ContainsKey(id);
					}
					catch
					{
						return false;
					}
				}
			}
			return false;
		}

		internal static void MarkDestroyed(bool existedBefore, ZDOMan manager, ZDOID id)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (_verified && existedBefore && !Exists(manager, id))
			{
				Counter.MarkDestroyed();
			}
		}

		internal static void Capture(ZDOMan manager)
		{
			if (!_verified || manager == null)
			{
				return;
			}
			ConfigEntry<bool> enabled = WorldEngineConfig.Enabled;
			if (enabled == null || !enabled.Value)
			{
				return;
			}
			try
			{
				long timestamp = Stopwatch.GetTimestamp();
				if (timestamp >= _nextCaptureTimestamp)
				{
					_nextCaptureTimestamp = ((timestamp > long.MaxValue - Stopwatch.Frequency) ? long.MaxValue : (timestamp + Stopwatch.Frequency));
					int totalObjects = ((ObjectsField.GetValue(manager) is Dictionary<ZDOID, ZDO> dictionary) ? dictionary.Count : 0);
					int connectedPeers = ((PeersField.GetValue(manager) is ICollection collection) ? collection.Count : 0);
					int sentLastSecond = Convert.ToInt32(SentField.GetValue(manager));
					int receivedLastSecond = Convert.ToInt32(ReceivedField.GetValue(manager));
					Counter.Capture(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), totalObjects, connectedPeers, sentLastSecond, receivedLastSecond);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("World observatory sample failed closed: " + ex.Message));
				}
			}
		}

		internal static long BeginTimedOperation()
		{
			if (_verified)
			{
				ConfigEntry<bool> enabled = WorldEngineConfig.Enabled;
				if (enabled != null && enabled.Value)
				{
					return Stopwatch.GetTimestamp();
				}
			}
			return 0L;
		}

		internal static void EndSave(long started)
		{
			Counter.RecordSaveDuration(started);
		}

		internal static void EndLoad(long started)
		{
			Counter.RecordLoadDuration(started);
			if (ZDOMan.instance != null)
			{
				Capture(ZDOMan.instance);
			}
		}

		internal static void Reset()
		{
			Counter.Reset();
			_nextCaptureTimestamp = 0L;
			_verified = false;
		}
	}
	internal static class SaveSmoothingRuntime
	{
		private static readonly object Gate = new object();

		private static readonly FieldInfo SaveThreadField = AccessTools.Field(typeof(ZNet), "m_saveThread");

		private static readonly MethodInfo SaveWorldMethod = AccessTools.Method(typeof(ZNet), "SaveWorld", new Type[1] { typeof(bool) }, (Type[])null);

		private static bool _pending;

		private static bool _dispatching;

		private static float _requestedAt;

		private static float _eligibleAt;

		internal static bool BeforeSave(ZNet network, bool sync, Thread saveThread)
		{
			if (!(WorldEngineConfig.SmoothWorldSaves?.Value ?? true) || sync)
			{
				return true;
			}
			lock (Gate)
			{
				if (_dispatching)
				{
					return true;
				}
				float unscaledTime = Time.unscaledTime;
				bool flag = saveThread?.IsAlive ?? false;
				bool flag2 = Time.unscaledDeltaTime * 1000f > FrameBudget();
				if (!flag && !flag2)
				{
					return true;
				}
				if (!_pending)
				{
					_requestedAt = unscaledTime;
				}
				_pending = true;
				_eligibleAt = Math.Max(_eligibleAt, unscaledTime + (flag ? 0.25f : 0.05f));
				return false;
			}
		}

		internal static void Tick()
		{
			ConfigEntry<bool> enabled = WorldEngineConfig.Enabled;
			if (enabled == null || !enabled.Value)
			{
				return;
			}
			ConfigEntry<bool> smoothWorldSaves = WorldEngineConfig.SmoothWorldSaves;
			if (smoothWorldSaves != null && !smoothWorldSaves.Value)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			bool flag;
			lock (Gate)
			{
				if (!_pending || _dispatching)
				{
					return;
				}
				float unscaledTime = Time.unscaledTime;
				if (unscaledTime < _eligibleAt)
				{
					return;
				}
				if (SaveThreadField?.GetValue(instance) is Thread { IsAlive: not false })
				{
					_eligibleAt = unscaledTime + 0.25f;
					return;
				}
				bool num = Time.unscaledDeltaTime * 1000f <= FrameBudget();
				float num2 = Math.Max(0f, Math.Min(30f, WorldEngineConfig.MaximumSaveDeferralSeconds?.Value ?? 5f));
				if (!num && unscaledTime - _requestedAt < num2)
				{
					_eligibleAt = unscaledTime + 0.05f;
					return;
				}
				_pending = false;
				_dispatching = true;
				flag = true;
			}
			if (!flag)
			{
				return;
			}
			try
			{
				if (SaveWorldMethod == null)
				{
					throw new MissingMethodException("ZNet.SaveWorld(bool)");
				}
				SaveWorldMethod.Invoke(instance, new object[1] { false });
			}
			finally
			{
				lock (Gate)
				{
					_dispatching = false;
				}
			}
		}

		internal static void Reset()
		{
			lock (Gate)
			{
				_pending = false;
				_dispatching = false;
				_requestedAt = 0f;
				_eligibleAt = 0f;
			}
		}

		private static float FrameBudget()
		{
			return Math.Max(8f, Math.Min(100f, WorldEngineConfig.SaveFrameBudgetMilliseconds?.Value ?? 24f));
		}
	}
	[HarmonyPatch(typeof(ZNet), "SaveWorld", new Type[] { typeof(bool) })]
	internal static class SmoothWorldSavePatch
	{
		[HarmonyPrefix]
		private static bool Prefix(ZNet __instance, bool sync, Thread ___m_saveThread)
		{
			return SaveSmoothingRuntime.BeforeSave(__instance, sync, ___m_saveThread);
		}
	}
}
namespace RunicWorldEngine.Core
{
	internal sealed class ObservatoryCounter
	{
		private readonly object _gate = new object();

		private int _created;

		private int _destroyed;

		private long _sequence;

		private double _lastSaveMilliseconds;

		private double _lastLoadMilliseconds;

		private ZdoObservatorySnapshot _current = ZdoObservatorySnapshot.Empty;

		internal ZdoObservatorySnapshot Current
		{
			get
			{
				lock (_gate)
				{
					return _current;
				}
			}
		}

		internal void MarkCreated()
		{
			lock (_gate)
			{
				_created = SaturatingIncrement(_created);
			}
		}

		internal void MarkDestroyed()
		{
			lock (_gate)
			{
				_destroyed = SaturatingIncrement(_destroyed);
			}
		}

		internal void RecordSaveDuration(long startedTimestamp)
		{
			RecordDuration(startedTimestamp, save: true);
		}

		internal void RecordLoadDuration(long startedTimestamp)
		{
			RecordDuration(startedTimestamp, save: false);
		}

		internal ZdoObservatorySnapshot Capture(long unixMilliseconds, int totalObjects, int connectedPeers, int sentLastSecond, int receivedLastSecond)
		{
			lock (_gate)
			{
				_sequence = ((_sequence == long.MaxValue) ? long.MaxValue : (_sequence + 1));
				_current = new ZdoObservatorySnapshot(_sequence, Math.Max(0L, unixMilliseconds), Math.Max(0, totalObjects), Math.Max(0, connectedPeers), _created, _destroyed, Math.Max(0, sentLastSecond), Math.Max(0, receivedLastSecond), _lastSaveMilliseconds, _lastLoadMilliseconds);
				_created = 0;
				_destroyed = 0;
				return _current;
			}
		}

		internal void Reset()
		{
			lock (_gate)
			{
				_created = 0;
				_destroyed = 0;
				_sequence = 0L;
				_lastSaveMilliseconds = 0.0;
				_lastLoadMilliseconds = 0.0;
				_current = ZdoObservatorySnapshot.Empty;
			}
		}

		private void RecordDuration(long startedTimestamp, bool save)
		{
			if (startedTimestamp <= 0)
			{
				return;
			}
			long num = Stopwatch.GetTimestamp() - startedTimestamp;
			if (num < 0)
			{
				return;
			}
			double num2 = (double)num * 1000.0 / (double)Stopwatch.Frequency;
			lock (_gate)
			{
				if (save)
				{
					_lastSaveMilliseconds = num2;
				}
				else
				{
					_lastLoadMilliseconds = num2;
				}
			}
		}

		private static int SaturatingIncrement(int value)
		{
			if (value != int.MaxValue)
			{
				return value + 1;
			}
			return value;
		}
	}
}
namespace RunicWorldEngine.Contracts
{
	internal sealed class ZdoObservatorySnapshot
	{
		internal long Sequence { get; }

		internal long CapturedUnixMilliseconds { get; }

		internal int TotalObjects { get; }

		internal int ConnectedPeers { get; }

		internal int CreatedSincePreviousSample { get; }

		internal int DestroyedSincePreviousSample { get; }

		internal int SentLastSecond { get; }

		internal int ReceivedLastSecond { get; }

		internal double LastSaveMilliseconds { get; }

		internal double LastLoadMilliseconds { get; }

		internal static ZdoObservatorySnapshot Empty { get; } = new ZdoObservatorySnapshot(0L, 0L, 0, 0, 0, 0, 0, 0, 0.0, 0.0);

		internal ZdoObservatorySnapshot(long sequence, long capturedUnixMilliseconds, int totalObjects, int connectedPeers, int createdSincePreviousSample, int destroyedSincePreviousSample, int sentLastSecond, int receivedLastSecond, double lastSaveMilliseconds, double lastLoadMilliseconds)
		{
			Sequence = Math.Max(0L, sequence);
			CapturedUnixMilliseconds = Math.Max(0L, capturedUnixMilliseconds);
			TotalObjects = Math.Max(0, totalObjects);
			ConnectedPeers = Math.Max(0, connectedPeers);
			CreatedSincePreviousSample = Math.Max(0, createdSincePreviousSample);
			DestroyedSincePreviousSample = Math.Max(0, destroyedSincePreviousSample);
			SentLastSecond = Math.Max(0, sentLastSecond);
			ReceivedLastSecond = Math.Max(0, receivedLastSecond);
			LastSaveMilliseconds = Math.Max(0.0, lastSaveMilliseconds);
			LastLoadMilliseconds = Math.Max(0.0, lastLoadMilliseconds);
		}
	}
}