Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PieceBatcher v1.2.1
PieceBatcher.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.LowLevel; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("PieceBatcher")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.1.0")] [assembly: AssemblyInformationalVersion("1.2.1")] [assembly: AssemblyProduct("PieceBatcher")] [assembly: AssemblyTitle("PieceBatcher")] [assembly: AssemblyVersion("1.2.1.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 PieceBatcher { internal struct GroupKey : IEquatable<GroupKey> { public int MatId; public Material Mat; public int Layer; public uint RenderingLayerMask; public ShadowCastingMode Cast; public bool Receive; public LightProbeUsage Lp; public ReflectionProbeUsage Rp; public MotionVectorGenerationMode Mv; public bool Occl; public int Signature; public static GroupKey Of(MeshRenderer r, Material m, int signature) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) return new GroupKey { MatId = ((Object)m).GetInstanceID(), Mat = m, Layer = ((Component)r).gameObject.layer, RenderingLayerMask = ((Renderer)r).renderingLayerMask, Cast = ((Renderer)r).shadowCastingMode, Receive = ((Renderer)r).receiveShadows, Lp = ((Renderer)r).lightProbeUsage, Rp = ((Renderer)r).reflectionProbeUsage, Mv = ((Renderer)r).motionVectorGenerationMode, Occl = ((Renderer)r).allowOcclusionWhenDynamic, Signature = signature }; } public bool Equals(GroupKey o) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (MatId == o.MatId && Layer == o.Layer && RenderingLayerMask == o.RenderingLayerMask && Cast == o.Cast && Receive == o.Receive && Lp == o.Lp && Rp == o.Rp && Mv == o.Mv && Occl == o.Occl) { return Signature == o.Signature; } return false; } public override bool Equals(object obj) { if (obj is GroupKey o) { return Equals(o); } return false; } public override int GetHashCode() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected I4, but got Unknown return ((((((((MatId * 31 + Layer) * 31 + (int)RenderingLayerMask) * 31 + Cast) * 31 + Receive) * 31 + Lp) * 31 + Rp) * 31 + Mv) * 31 + Occl) * 31 + Signature; } } internal sealed class PartPlan { public Entry E; public MeshRenderer R; public MeshData D; public Matrix4x4 M; public Matrix4x4 Nm; public bool Flip; public GroupKey[] Keys; public LODGroup Lod; public bool Accepted; } internal sealed class Entry { public WearNTear Wnt; public GameObject Root; public Transform Tr; public Vector3 Pos; public Cell Cell; public string Prefab; public bool Combined; public bool Highlighted; public bool MaterialTouched; public bool Ineligible; public bool Checked; public bool Moved; public int Gen; public int Index; public List<PartPlan> Parts; public readonly List<Renderer> Hidden = new List<Renderer>(); public readonly List<GameObject> LodExtra = new List<GameObject>(); public readonly List<Group> Groups = new List<Group>(); public bool Excluded { get { if (!Highlighted && !MaterialTouched && !Ineligible) { return Moved; } return true; } } } internal struct RangeRef { public Entry E; public int Start; public int Count; } internal sealed class Group { public GroupKey Key; public Cell Cell; public GameObject Go; public Mesh Mesh; public int[] Master; public int VertexCount; public readonly List<RangeRef> Ranges = new List<RangeRef>(); } internal sealed class Cell { public long Key; public Vector3 Origin; public readonly List<Entry> Entries = new List<Entry>(); public readonly List<Group> Groups = new List<Group>(); public bool Dirty; public float DirtyTime; public int Version; public RebuildJob Job; } internal sealed class GroupBuilder { public GroupKey Key; public MeshData First; public int Parts; public readonly List<Vector3> Pos = new List<Vector3>(); public readonly List<Vector3> Nrm = new List<Vector3>(); public readonly List<Vector4> Tan = new List<Vector4>(); public readonly List<Color32> Col = new List<Color32>(); public readonly List<Vector4>[] Uv = new List<Vector4>[4] { new List<Vector4>(), new List<Vector4>(), new List<Vector4>(), new List<Vector4>() }; public readonly List<int> Idx = new List<int>(); public readonly List<RangeRef> Ranges = new List<RangeRef>(); public int[] Master; public void Add(PartPlan p, int sub) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) MeshData d = p.D; if (First == null) { First = d; } Matrix4x4 m = p.M; int count = Pos.Count; int count2 = Idx.Count; Vector3[] pos = d.Pos; for (int i = 0; i < pos.Length; i++) { Pos.Add(((Matrix4x4)(ref m)).MultiplyPoint3x4(pos[i])); } Vector3 val; if (d.Nrm != null) { Matrix4x4 nm = p.Nm; Vector3[] nrm = d.Nrm; for (int j = 0; j < nrm.Length; j++) { List<Vector3> nrm2 = Nrm; val = ((Matrix4x4)(ref nm)).MultiplyVector(nrm[j]); nrm2.Add(((Vector3)(ref val)).normalized); } } if (d.Tan != null) { Vector4[] tan = d.Tan; foreach (Vector4 val2 in tan) { val = ((Matrix4x4)(ref m)).MultiplyVector(new Vector3(val2.x, val2.y, val2.z)); Vector3 normalized = ((Vector3)(ref val)).normalized; Tan.Add(new Vector4(normalized.x, normalized.y, normalized.z, p.Flip ? (0f - val2.w) : val2.w)); } } if (d.Col != null) { Col.AddRange(d.Col); } for (int l = 0; l < 4; l++) { if (d.Uv[l] != null) { Uv[l].AddRange(d.Uv[l]); } } int[] array = d.Sub[sub]; if (!p.Flip) { for (int n = 0; n < array.Length; n++) { Idx.Add(array[n] + count); } } else { for (int num = 0; num + 2 < array.Length; num += 3) { Idx.Add(array[num] + count); Idx.Add(array[num + 2] + count); Idx.Add(array[num + 1] + count); } } Ranges.Add(new RangeRef { E = p.E, Start = count2, Count = Idx.Count - count2 }); Parts++; } } internal sealed class RebuildJob { public Cell Cell; public int Version; public List<PartPlan> Parts = new List<PartPlan>(); public Dictionary<Entry, HashSet<Renderer>> ToHide = new Dictionary<Entry, HashSet<Renderer>>(); public Dictionary<Entry, HashSet<Renderer>> ToHideExtra = new Dictionary<Entry, HashSet<Renderer>>(); public List<GroupBuilder> Builders = new List<GroupBuilder>(); public int MaxVertices; public volatile bool Done; public Exception Error; public double WorkerMs; public double CollectMs; } internal sealed class Watcher : MonoBehaviour { public Entry E; public int Gen; private void OnDisable() { if (!Batcher.Quitting && !Batcher.Clearing && E != null && E.Combined && E.Gen == Gen) { Batcher.OnPartDisabled(E); } } } internal static class Batcher { private sealed class LodPlan { public int Lod0Active; public int Lod0Accepted; public readonly List<Renderer> Extra = new List<Renderer>(); } internal static bool Quitting; internal static bool Clearing; private static readonly Dictionary<WearNTear, Entry> s_entries = new Dictionary<WearNTear, Entry>(); private static readonly List<Entry> s_entryList = new List<Entry>(); private static readonly Dictionary<long, Cell> s_cells = new Dictionary<long, Cell>(); private static readonly HashSet<Cell> s_dirtyCells = new HashSet<Cell>(); private static readonly HashSet<Group> s_dirtyGroups = new HashSet<Group>(); private static readonly List<RebuildJob> s_jobs = new List<RebuildJob>(); private static readonly List<Cell> s_tmpCells = new List<Cell>(); private static readonly List<Group> s_tmpGroups = new List<Group>(); private static readonly Stopwatch s_sw = new Stopwatch(); private static readonly Dictionary<string, bool> s_prefabIneligible = new Dictionary<string, bool>(StringComparer.Ordinal); private static HashSet<string> s_shaders; private static HashSet<string> s_excludePrefabs; private static Transform s_root; private static int s_validateCursor; internal static int RebuildCount; internal static int DiscardedCount; internal static double LastRebuildMs; internal static double TotalRebuildMs; internal static double TotalWorkerMs; internal static double UpdateMs; private static double s_updateAcc; private static int s_updateFrames; private static readonly Type[] s_ineligibleTypes = new Type[22] { typeof(Rigidbody), typeof(Animator), typeof(SkinnedMeshRenderer), typeof(ZSyncTransform), typeof(StaticPhysics), typeof(Floating), typeof(Door), typeof(TeleportWorld), typeof(ShieldGenerator), typeof(ItemStand), typeof(ArmorStand), typeof(Windmill), typeof(MaterialFader), typeof(Vagon), typeof(Ship), typeof(Turret), typeof(Catapult), typeof(Trap), typeof(RandomMaterialValues), typeof(MaterialVariation), typeof(MaterialVariationWorld), typeof(ToggleSwitch) }; private static bool s_forceFragments; private static readonly List<GameObject> s_fragmentsHiddenLods = new List<GameObject>(); private static readonly Dictionary<LODGroup, LodPlan> s_lods = new Dictionary<LODGroup, LodPlan>(); private static readonly Dictionary<GroupKey, int> s_counts = new Dictionary<GroupKey, int>(); private static readonly HashSet<Renderer> s_lodOther = new HashSet<Renderer>(); private static readonly Dictionary<Renderer, LODGroup> s_lod0 = new Dictionary<Renderer, LODGroup>(); private static readonly Dictionary<LODGroup, Entry> s_lodOwner = new Dictionary<LODGroup, Entry>(); private static Transform Root { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s_root == (Object)null) { s_root = new GameObject("PieceBatcher_Root").transform; } return s_root; } } internal static void OnBeforeFragments(WearNTear wnt) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) s_forceFragments = false; if ((Object)(object)wnt == (Object)null || !s_entries.TryGetValue(wnt, out var value) || !value.Combined) { return; } bool flag = false; Camera main = Camera.main; if ((Object)(object)main != (Object)null) { Plane[] array = GeometryUtility.CalculateFrustumPlanes(main); foreach (Renderer item in value.Hidden) { if ((Object)(object)item != (Object)null && GeometryUtility.TestPlanesAABB(array, item.bounds)) { flag = true; break; } } } List<GameObject> list = new List<GameObject>(value.LodExtra); Release(value); value.Parts = null; foreach (GameObject item2 in list) { if ((Object)(object)item2 != (Object)null && item2.activeSelf) { item2.SetActive(false); s_fragmentsHiddenLods.Add(item2); } } s_forceFragments = flag; } internal static void OnAfterFragments() { s_forceFragments = false; foreach (GameObject s_fragmentsHiddenLod in s_fragmentsHiddenLods) { if ((Object)(object)s_fragmentsHiddenLod != (Object)null) { s_fragmentsHiddenLod.SetActive(true); } } s_fragmentsHiddenLods.Clear(); } internal static bool ConsumeForceFragments() { bool result = s_forceFragments; s_forceFragments = false; return result; } internal static int ContainerState(Container c) { int num = 0; if ((Object)(object)c.m_open != (Object)null && c.m_open.activeSelf) { num |= 1; } if ((Object)(object)c.m_closed != (Object)null && c.m_closed.activeSelf) { num |= 2; } return num; } internal static void Register(WearNTear wnt) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)wnt == (Object)null) && !s_entries.ContainsKey(wnt)) { Entry entry = new Entry { Wnt = wnt, Root = ((Component)wnt).gameObject, Tr = ((Component)wnt).transform, Pos = ((Component)wnt).transform.position }; entry.Prefab = ((Object)entry.Root).name; int num = entry.Prefab.IndexOf("(Clone)", StringComparison.Ordinal); if (num > 0) { entry.Prefab = entry.Prefab.Substring(0, num); } entry.Cell = GetOrCreateCell(entry.Pos); entry.Cell.Entries.Add(entry); s_entries[wnt] = entry; entry.Index = s_entryList.Count; s_entryList.Add(entry); MarkDirty(entry.Cell); } } internal static void Unregister(WearNTear wnt) { if (!((Object)(object)wnt == (Object)null) && s_entries.TryGetValue(wnt, out var value)) { Release(value); value.Parts = null; s_entries.Remove(wnt); int num = s_entryList.Count - 1; if (value.Index >= 0 && value.Index <= num && s_entryList[value.Index] == value) { Entry entry = s_entryList[num]; s_entryList[value.Index] = entry; entry.Index = value.Index; s_entryList.RemoveAt(num); } else { s_entryList.Remove(value); } value.Index = -1; Cell cell = value.Cell; value.Wnt = null; value.Cell = null; cell.Entries.Remove(value); if (cell.Entries.Count == 0) { DestroyCell(cell); } else { MarkDirty(cell); } } } internal static int VariantState(WearNTear w) { int num = 0; if ((Object)(object)w.m_new != (Object)null && w.m_new.activeSelf) { num |= 1; } if ((Object)(object)w.m_worn != (Object)null && w.m_worn.activeSelf) { num |= 2; } if ((Object)(object)w.m_broken != (Object)null && w.m_broken.activeSelf) { num |= 4; } return num; } internal static void OnChanged(WearNTear wnt) { if (!((Object)(object)wnt == (Object)null) && s_entries.TryGetValue(wnt, out var value)) { Release(value); value.Parts = null; MarkDirty(value.Cell); } } internal static void OnPartDisabled(Entry e) { Release(e); e.Parts = null; if (e.Cell != null) { MarkDirty(e.Cell); } } internal static void SetHighlighted(WearNTear wnt, bool on) { if (!((Object)(object)wnt == (Object)null) && s_entries.TryGetValue(wnt, out var value) && value.Highlighted != on) { value.Highlighted = on; if (on) { Release(value); } MarkDirty(value.Cell); } } internal static void SetMaterialTouched(WearNTear wnt, bool touched) { if (!((Object)(object)wnt == (Object)null) && s_entries.TryGetValue(wnt, out var value) && value.MaterialTouched != touched) { value.MaterialTouched = touched; if (touched) { Release(value); } MarkDirty(value.Cell); } } private static long CellKey(Vector3 p, float size, out int cx, out int cz) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) cx = Mathf.FloorToInt(p.x / size); cz = Mathf.FloorToInt(p.z / size); return ((long)cx << 32) ^ (uint)cz; } private static Cell GetOrCreateCell(Vector3 p) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) float value = Plugin.CellSize.Value; int cx; int cz; long key = CellKey(p, value, out cx, out cz); if (!s_cells.TryGetValue(key, out var value2)) { value2 = new Cell { Key = key, Origin = new Vector3((float)cx * value, 0f, (float)cz * value) }; s_cells[key] = value2; } return value2; } internal static Cell FindCell(Vector3 p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) int cx; int cz; long key = CellKey(p, Plugin.CellSize.Value, out cx, out cz); s_cells.TryGetValue(key, out var value); return value; } private static void MarkDirty(Cell cell) { if (cell != null) { cell.Version++; cell.Dirty = true; cell.DirtyTime = Time.time; s_dirtyCells.Add(cell); } } internal static void MarkAllDirty(bool now = false) { foreach (Cell value in s_cells.Values) { MarkDirty(value); if (now) { value.DirtyTime = -1000f; } } } private static void DestroyCell(Cell cell) { foreach (Group group in cell.Groups) { DestroyGroupObjects(group); } cell.Groups.Clear(); cell.Version++; s_dirtyCells.Remove(cell); s_cells.Remove(cell.Key); } private static void DestroyGroupObjects(Group g) { s_dirtyGroups.Remove(g); if ((Object)(object)g.Go != (Object)null) { Object.Destroy((Object)(object)g.Go); } if ((Object)(object)g.Mesh != (Object)null) { Object.Destroy((Object)(object)g.Mesh); } g.Go = null; g.Mesh = null; } internal static void Release(Entry e) { if (!e.Combined) { return; } e.Combined = false; e.Gen++; foreach (Renderer item in e.Hidden) { if (!((Object)(object)item == (Object)null)) { item.enabled = true; Watcher component = ((Component)item).GetComponent<Watcher>(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } } } e.Hidden.Clear(); e.LodExtra.Clear(); foreach (Group group in e.Groups) { group.Ranges.RemoveAll((RangeRef x) => x.E == e); if ((Object)(object)group.Go != (Object)null) { s_dirtyGroups.Add(group); } } e.Groups.Clear(); } private static void ApplyIndices(Group g) { if ((Object)(object)g.Go == (Object)null || (Object)(object)g.Mesh == (Object)null) { return; } if (g.Ranges.Count == 0) { g.Cell?.Groups.Remove(g); DestroyGroupObjects(g); return; } int num = 0; foreach (RangeRef range in g.Ranges) { num += range.Count; } int[] array = new int[num]; int num2 = 0; foreach (RangeRef range2 in g.Ranges) { Array.Copy(g.Master, range2.Start, array, num2, range2.Count); num2 += range2.Count; } g.Mesh.SetTriangles(array, 0, false); } internal static void LateUpdate() { long timestamp = Stopwatch.GetTimestamp(); if (s_dirtyGroups.Count > 0) { s_tmpGroups.Clear(); s_tmpGroups.AddRange(s_dirtyGroups); s_dirtyGroups.Clear(); foreach (Group s_tmpGroup in s_tmpGroups) { ApplyIndices(s_tmpGroup); } s_tmpGroups.Clear(); } AccountTime(timestamp); } private static void AccountTime(long t0) { s_updateAcc += (double)(Stopwatch.GetTimestamp() - t0) * 1000.0 / (double)Stopwatch.Frequency; } internal static void Update() { if (Quitting) { return; } long timestamp = Stopwatch.GetTimestamp(); s_updateFrames++; if (s_updateFrames >= 60) { UpdateMs = s_updateAcc / (double)s_updateFrames; s_updateAcc = 0.0; s_updateFrames = 0; } double num = Plugin.FrameBudgetMs.Value; s_sw.Restart(); for (int i = 0; i < s_jobs.Count; i++) { RebuildJob rebuildJob = s_jobs[i]; if (!rebuildJob.Done) { continue; } s_jobs.RemoveAt(i); i--; if (rebuildJob.Cell.Job == rebuildJob) { rebuildJob.Cell.Job = null; } if (rebuildJob.Error != null) { Plugin.Log.LogWarning((object)$"фоновая сборка ячейки {rebuildJob.Cell.Origin.x:0},{rebuildJob.Cell.Origin.z:0} не удалась: {rebuildJob.Error.GetType().Name}: {rebuildJob.Error.Message}"); } else { if (rebuildJob.Version == rebuildJob.Cell.Version && s_cells.ContainsKey(rebuildJob.Cell.Key)) { ApplyJob(rebuildJob); break; } DiscardedCount++; } } if (s_dirtyCells.Count > 0 && Plugin.Enabled.Value) { float time = Time.time; float value = Plugin.RebuildDelay.Value; s_tmpCells.Clear(); foreach (Cell s_dirtyCell in s_dirtyCells) { if (s_dirtyCell.Job == null && time - s_dirtyCell.DirtyTime >= value) { s_tmpCells.Add(s_dirtyCell); } } foreach (Cell s_tmpCell in s_tmpCells) { if (s_sw.Elapsed.TotalMilliseconds > num) { break; } s_dirtyCells.Remove(s_tmpCell); s_tmpCell.Dirty = false; StartJob(s_tmpCell); } s_tmpCells.Clear(); } ValidateSlice(); AccountTime(timestamp); } private static void ValidateSlice() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) int count = s_entryList.Count; if (count == 0) { return; } int num = count / 60 + 1; for (int i = 0; i < num; i++) { if (s_validateCursor >= s_entryList.Count) { s_validateCursor = 0; } Entry entry = s_entryList[s_validateCursor++]; if (!entry.Combined) { continue; } if ((Object)(object)entry.Tr == (Object)null) { Release(entry); entry.Parts = null; continue; } Vector3 val = entry.Tr.position - entry.Pos; if (((Vector3)(ref val)).sqrMagnitude > 0.0001f) { entry.Moved = true; Release(entry); entry.Parts = null; MarkDirty(entry.Cell); } } } private static void EnsureFilters() { if (s_shaders == null) { s_shaders = Split(Plugin.Shaders.Value); s_excludePrefabs = Split(Plugin.ExcludePrefabs.Value); } } private static HashSet<string> Split(string s) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); string[] array = (s ?? "").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { hashSet.Add(text); } } return hashSet; } private static bool IsIneligible(Entry e) { if (e.Checked) { return e.Ineligible; } e.Checked = true; EnsureFilters(); if ((Object)(object)e.Tr != (Object)null && (Object)(object)e.Tr.parent != (Object)null) { e.Ineligible = true; return true; } if (!s_prefabIneligible.TryGetValue(e.Prefab, out var value)) { value = s_excludePrefabs.Contains(e.Prefab); if (!value) { Type[] array = s_ineligibleTypes; foreach (Type type in array) { if ((Object)(object)e.Root.GetComponentInChildren(type, true) != (Object)null) { value = true; break; } } } s_prefabIneligible[e.Prefab] = value; } e.Ineligible = value; return value; } private static void StartJob(Cell cell) { long timestamp = Stopwatch.GetTimestamp(); if (!Plugin.Enabled.Value) { return; } EnsureFilters(); RebuildJob rebuildJob = new RebuildJob { Cell = cell, Version = cell.Version, MaxVertices = Plugin.MaxVertices.Value }; s_lods.Clear(); s_counts.Clear(); s_lodOwner.Clear(); foreach (Entry entry in cell.Entries) { if ((Object)(object)entry.Wnt == (Object)null || (Object)(object)entry.Root == (Object)null || entry.Excluded || IsIneligible(entry)) { continue; } if (entry.Parts != null && !PartsStillValid(entry)) { entry.Parts = null; } if (entry.Parts == null) { if (entry.Combined) { Release(entry); } entry.Parts = CollectParts(entry, cell); } else { foreach (PartPlan part in entry.Parts) { if ((Object)(object)part.Lod != (Object)null && !s_lods.ContainsKey(part.Lod)) { s_lods[part.Lod] = BuildLodPlan(part.Lod, entry); } } } foreach (PartPlan part2 in entry.Parts) { if (!((Object)(object)part2.R == (Object)null) && (!((Object)(object)part2.Lod != (Object)null) || s_lods.ContainsKey(part2.Lod))) { part2.Accepted = false; rebuildJob.Parts.Add(part2); } } } int value = Plugin.MinParts.Value; foreach (PartPlan part3 in rebuildJob.Parts) { GroupKey[] keys = part3.Keys; foreach (GroupKey key in keys) { s_counts.TryGetValue(key, out var value2); s_counts[key] = value2 + 1; } } foreach (PartPlan part4 in rebuildJob.Parts) { part4.Accepted = true; GroupKey[] keys = part4.Keys; foreach (GroupKey key2 in keys) { if (s_counts[key2] < value) { part4.Accepted = false; break; } } if (part4.Accepted && (Object)(object)part4.Lod != (Object)null) { s_lods[part4.Lod].Lod0Accepted++; } } foreach (PartPlan part5 in rebuildJob.Parts) { if (part5.Accepted && (Object)(object)part5.Lod != (Object)null) { LodPlan lodPlan = s_lods[part5.Lod]; if (lodPlan.Lod0Accepted != lodPlan.Lod0Active) { part5.Accepted = false; } } } rebuildJob.Parts.RemoveAll((PartPlan p) => !p.Accepted); foreach (PartPlan part6 in rebuildJob.Parts) { if (!rebuildJob.ToHide.TryGetValue(part6.E, out var value3)) { value3 = new HashSet<Renderer>(); rebuildJob.ToHide[part6.E] = value3; } value3.Add((Renderer)(object)part6.R); if (!((Object)(object)part6.Lod != (Object)null)) { continue; } if (!rebuildJob.ToHideExtra.TryGetValue(part6.E, out var value4)) { value4 = new HashSet<Renderer>(); rebuildJob.ToHideExtra[part6.E] = value4; } foreach (Renderer item in s_lods[part6.Lod].Extra) { value3.Add(item); value4.Add(item); } } s_lods.Clear(); s_counts.Clear(); s_lodOwner.Clear(); rebuildJob.CollectMs = (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency; cell.Job = rebuildJob; s_jobs.Add(rebuildJob); if (rebuildJob.Parts.Count == 0) { rebuildJob.Done = true; } else { ThreadPool.QueueUserWorkItem(RunJob, rebuildJob); } } private static bool PartsStillValid(Entry e) { foreach (PartPlan part in e.Parts) { if ((Object)(object)part.R == (Object)null || !((Component)part.R).gameObject.activeInHierarchy) { return false; } if (!((Renderer)part.R).enabled && (!e.Combined || !e.Hidden.Contains((Renderer)(object)part.R))) { return false; } } return true; } private static bool SameRenderers(List<Renderer> hidden, HashSet<Renderer> wanted) { if (hidden.Count != wanted.Count) { return false; } foreach (Renderer item in hidden) { if (!wanted.Contains(item)) { return false; } } return true; } private static LodPlan BuildLodPlan(LODGroup lg, Entry e) { LodPlan lodPlan = new LodPlan(); LOD[] lODs = lg.GetLODs(); if (lODs == null || lODs.Length == 0) { return lodPlan; } Renderer[] renderers = lODs[0].renderers; foreach (Renderer val in renderers) { if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy && (val.enabled || (e.Combined && e.Hidden.Contains(val)))) { lodPlan.Lod0Active++; } } for (int j = 1; j < lODs.Length; j++) { renderers = lODs[j].renderers; foreach (Renderer val2 in renderers) { if ((Object)(object)val2 != (Object)null && ((Component)val2).gameObject.activeInHierarchy && (val2.enabled || (e.Combined && e.Hidden.Contains(val2)))) { lodPlan.Extra.Add(val2); } } } return lodPlan; } private static List<PartPlan> CollectParts(Entry e, Cell cell) { //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) List<PartPlan> list = new List<PartPlan>(); GameObject root = e.Root; s_lod0.Clear(); s_lodOther.Clear(); LODGroup[] componentsInChildren = root.GetComponentsInChildren<LODGroup>(false); foreach (LODGroup val in componentsInChildren) { LOD[] lODs = val.GetLODs(); if (lODs == null || lODs.Length == 0) { continue; } s_lods[val] = BuildLodPlan(val, e); Renderer[] renderers = lODs[0].renderers; foreach (Renderer val2 in renderers) { if ((Object)(object)val2 != (Object)null) { s_lod0[val2] = val; } } for (int k = 1; k < lODs.Length; k++) { renderers = lODs[k].renderers; foreach (Renderer val3 in renderers) { if ((Object)(object)val3 != (Object)null && !s_lod0.ContainsKey(val3)) { s_lodOther.Add(val3); } } } } Matrix4x4 val4 = Matrix4x4.Translate(-cell.Origin); MeshRenderer[] componentsInChildren2 = root.GetComponentsInChildren<MeshRenderer>(false); foreach (MeshRenderer val5 in componentsInChildren2) { if (!((Renderer)val5).enabled || s_lodOther.Contains((Renderer)(object)val5) || (Object)(object)((Component)val5).GetComponent<RenderGroupSubscriber>() != (Object)null) { continue; } MeshFilter component = ((Component)val5).GetComponent<MeshFilter>(); if ((Object)(object)component == (Object)null || (Object)(object)component.sharedMesh == (Object)null) { continue; } Mesh sharedMesh = component.sharedMesh; Material[] sharedMaterials = ((Renderer)val5).sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0 || sharedMaterials.Length != sharedMesh.subMeshCount) { continue; } bool flag = true; Material[] array = sharedMaterials; foreach (Material val6 in array) { if ((Object)(object)val6 == (Object)null || (Object)(object)val6.shader == (Object)null || !s_shaders.Contains(((Object)val6.shader).name) || val6.renderQueue > 2500) { flag = false; break; } } if (!flag) { continue; } MeshData meshData = MeshCache.Get(sharedMesh); if (meshData.Ok) { Matrix4x4 m = val4 * ((Component)val5).transform.localToWorldMatrix; GroupKey[] array2 = new GroupKey[sharedMaterials.Length]; for (int l = 0; l < sharedMaterials.Length; l++) { array2[l] = GroupKey.Of(val5, sharedMaterials[l], meshData.Signature); } s_lod0.TryGetValue((Renderer)(object)val5, out var value); PartPlan obj = new PartPlan { E = e, R = val5, D = meshData, M = m }; Matrix4x4 inverse = ((Matrix4x4)(ref m)).inverse; obj.Nm = ((Matrix4x4)(ref inverse)).transpose; obj.Flip = ((Matrix4x4)(ref m)).determinant < 0f; obj.Keys = array2; obj.Lod = value; list.Add(obj); } } s_lod0.Clear(); s_lodOther.Clear(); return list; } private static void RunJob(object state) { RebuildJob rebuildJob = (RebuildJob)state; long timestamp = Stopwatch.GetTimestamp(); try { Dictionary<GroupKey, List<GroupBuilder>> dictionary = new Dictionary<GroupKey, List<GroupBuilder>>(); foreach (PartPlan part in rebuildJob.Parts) { for (int i = 0; i < part.Keys.Length; i++) { GroupKey key = part.Keys[i]; if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<GroupBuilder>()); } GroupBuilder groupBuilder = ((value.Count > 0) ? value[value.Count - 1] : null); if (groupBuilder == null || (groupBuilder.Pos.Count > 0 && groupBuilder.Pos.Count + part.D.Pos.Length > rebuildJob.MaxVertices)) { groupBuilder = new GroupBuilder { Key = key }; value.Add(groupBuilder); } groupBuilder.Add(part, i); } } foreach (List<GroupBuilder> value2 in dictionary.Values) { foreach (GroupBuilder item in value2) { if (item.Pos.Count > 0) { item.Master = item.Idx.ToArray(); rebuildJob.Builders.Add(item); } } } } catch (Exception error) { rebuildJob.Error = error; } rebuildJob.WorkerMs = (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency; Thread.MemoryBarrier(); rebuildJob.Done = true; } private static void ApplyJob(RebuildJob job) { //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) long timestamp = Stopwatch.GetTimestamp(); Cell cell = job.Cell; foreach (Entry entry in cell.Entries) { if (entry.Combined && !job.ToHide.ContainsKey(entry)) { Release(entry); } } foreach (Entry entry2 in cell.Entries) { entry2.Groups.Clear(); } List<Group> list = new List<Group>(job.Builders.Count); int num = 0; foreach (GroupBuilder builder in job.Builders) { Group obj = BuildGroup(builder, cell, list.Count); list.Add(obj); num += obj.VertexCount; } int num2 = 0; foreach (KeyValuePair<Entry, HashSet<Renderer>> item in job.ToHide) { Entry key = item.Key; if ((Object)(object)key.Wnt == (Object)null || key.Cell != cell) { continue; } job.ToHideExtra.TryGetValue(key, out var value); if (key.Combined && !SameRenderers(key.Hidden, item.Value)) { Release(key); } if (!key.Combined) { key.Combined = true; key.Pos = key.Tr.position; foreach (Renderer item2 in item.Value) { if (!((Object)(object)item2 == (Object)null)) { item2.enabled = false; key.Hidden.Add(item2); if (value != null && value.Contains(item2)) { key.LodExtra.Add(((Component)item2).gameObject); } Watcher watcher = ((Component)item2).gameObject.AddComponent<Watcher>(); watcher.E = key; watcher.Gen = key.Gen; } } } num2++; } foreach (Group group in cell.Groups) { DestroyGroupObjects(group); } cell.Groups.Clear(); cell.Groups.AddRange(list); RebuildCount++; LastRebuildMs = job.CollectMs + (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency; TotalRebuildMs += LastRebuildMs; TotalWorkerMs += job.WorkerMs; if (Plugin.Verbose.Value) { Plugin.Log.LogInfo((object)$"ячейка {cell.Origin.x:0},{cell.Origin.z:0}: деталей {cell.Entries.Count}, склеено {num2}, мешей {list.Count}, вершин {num}, главный поток {LastRebuildMs:0.0} мс, фон {job.WorkerMs:0.0} мс"); } } private static Group BuildGroup(GroupBuilder b, Cell cell, int index) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Expected O, but got Unknown //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) Mesh val = new Mesh { name = "PieceBatcher" }; val.indexFormat = (IndexFormat)(b.Pos.Count > 65535); val.SetVertices(b.Pos); int count = b.Pos.Count; if (b.Nrm.Count == count) { val.SetNormals(b.Nrm); } if (b.Tan.Count == count) { val.SetTangents(b.Tan); } if (b.Col.Count == count) { val.SetColors(b.Col); } for (int i = 0; i < 4; i++) { if (b.Uv[i].Count != count) { continue; } switch (b.First.UvDim[i]) { case 2: { List<Vector2> list2 = new List<Vector2>(count); foreach (Vector4 item in b.Uv[i]) { list2.Add(new Vector2(item.x, item.y)); } val.SetUVs(i, list2); break; } case 3: { List<Vector3> list = new List<Vector3>(count); foreach (Vector4 item2 in b.Uv[i]) { list.Add(new Vector3(item2.x, item2.y, item2.z)); } val.SetUVs(i, list); break; } default: val.SetUVs(i, b.Uv[i]); break; } } int[] array = b.Master ?? b.Idx.ToArray(); val.SetTriangles(array, 0, true); val.UploadMeshData(false); GameObject val2 = new GameObject($"PB_{cell.Origin.x:0}_{cell.Origin.z:0}_{index}"); val2.layer = b.Key.Layer; val2.transform.SetParent(Root, false); val2.transform.position = cell.Origin; val2.AddComponent<MeshFilter>().sharedMesh = val; MeshRenderer obj = val2.AddComponent<MeshRenderer>(); ((Renderer)obj).sharedMaterial = b.Key.Mat; ((Renderer)obj).shadowCastingMode = b.Key.Cast; ((Renderer)obj).receiveShadows = b.Key.Receive; ((Renderer)obj).lightProbeUsage = b.Key.Lp; ((Renderer)obj).reflectionProbeUsage = b.Key.Rp; ((Renderer)obj).motionVectorGenerationMode = b.Key.Mv; ((Renderer)obj).renderingLayerMask = b.Key.RenderingLayerMask; ((Renderer)obj).allowOcclusionWhenDynamic = b.Key.Occl; Group obj2 = new Group { Key = b.Key, Cell = cell, Go = val2, Mesh = val, Master = array, VertexCount = count }; obj2.Ranges.AddRange(b.Ranges); foreach (RangeRef range in b.Ranges) { if (!range.E.Groups.Contains(obj2)) { range.E.Groups.Add(obj2); } } return obj2; } internal static void OnEnabledChanged() { if (Plugin.Enabled.Value) { MarkAllDirty(now: true); } else { ReleaseAll(); } } private static void ReleaseAll() { foreach (Entry s_entry in s_entryList) { Release(s_entry); } foreach (Cell value in s_cells.Values) { foreach (Group group in value.Groups) { DestroyGroupObjects(group); } value.Groups.Clear(); value.Dirty = false; value.Version++; value.Job = null; } s_jobs.Clear(); s_dirtyCells.Clear(); s_dirtyGroups.Clear(); } internal static void Reset() { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) ReleaseAll(); s_shaders = null; s_excludePrefabs = null; s_prefabIneligible.Clear(); foreach (Cell value in s_cells.Values) { value.Entries.Clear(); } s_cells.Clear(); foreach (Entry s_entry in s_entryList) { s_entry.Checked = false; s_entry.Ineligible = false; s_entry.Moved = false; s_entry.Parts = null; if ((Object)(object)s_entry.Tr != (Object)null) { s_entry.Pos = s_entry.Tr.position; } s_entry.Cell = GetOrCreateCell(s_entry.Pos); s_entry.Cell.Entries.Add(s_entry); MarkDirty(s_entry.Cell); } } internal static void Clear() { Clearing = true; try { ReleaseAll(); } finally { s_entries.Clear(); s_entryList.Clear(); s_cells.Clear(); s_dirtyCells.Clear(); s_dirtyGroups.Clear(); s_jobs.Clear(); MeshCache.Clear(); s_validateCursor = 0; Clearing = false; } } internal static string Stats() { int num = 0; int num2 = 0; foreach (Entry s_entry in s_entryList) { if (s_entry.Combined) { num++; num2 += s_entry.Hidden.Count; } } int num3 = 0; int num4 = 0; foreach (Cell value in s_cells.Values) { num3 += value.Groups.Count; foreach (Group group in value.Groups) { num4 += group.VertexCount; } } string text = (Plugin.Enabled.Value ? "вкл" : "выкл"); return $"PieceBatcher {text}: деталей {s_entryList.Count}, склеено {num} (рендереров выключено {num2}), ячеек {s_cells.Count}, мешей {num3}, вершин {num4}, ждут пересборки {s_dirtyCells.Count}, в фоне {s_jobs.Count}; пересборок {RebuildCount} (выброшено {DiscardedCount}), последняя {LastRebuildMs:0.0} мс в главном потоке, всего главный {TotalRebuildMs:0} мс / фон {TotalWorkerMs:0} мс; свои Update+LateUpdate {UpdateMs:0.00} мс/кадр; мешей в кэше {MeshCache.Count}"; } internal static string CellStats(Vector3 p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Cell cell = FindCell(p); if (cell == null) { return "в этой ячейке деталей нет"; } int num = 0; int num2 = 0; int num3 = 0; foreach (Entry entry in cell.Entries) { if (entry.Combined) { num++; } if (entry.Highlighted || entry.MaterialTouched || entry.Moved) { num2++; } if (entry.Ineligible) { num3++; } } int num4 = 0; foreach (Group group in cell.Groups) { num4 += group.VertexCount; } string text = ((cell.Job != null) ? "собирается в фоне" : (cell.Dirty ? "ждёт пересборки" : "собрана")); return $"ячейка {cell.Origin.x:0},{cell.Origin.z:0} ({Plugin.CellSize.Value:0} м): деталей {cell.Entries.Count}, склеено {num}, временно сами {num2}, не подлежат {num3}, мешей {cell.Groups.Count}, вершин {num4}, {text}"; } } internal static class Diag { private const int N = 600; private static readonly float[] s_frames = new float[600]; private static int s_idx; private static int s_count; private static readonly FrameTiming[] s_timings = (FrameTiming[])(object)new FrameTiming[1]; private static bool s_timingFailed; private static int s_gcLast; private static float s_gcLastTime; public static void Tick() { s_frames[s_idx] = Time.unscaledDeltaTime; s_idx = (s_idx + 1) % 600; if (s_count < 600) { s_count++; } if (!s_timingFailed) { try { FrameTimingManager.CaptureFrameTimings(); } catch (Exception) { s_timingFailed = true; } } } public static string FrameLine() { //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) if (s_count < 10) { return "кадры: мало данных"; } float[] array = new float[s_count]; Array.Copy(s_frames, array, s_count); float num = 0f; float[] array2 = array; foreach (float num2 in array2) { num += num2; } Array.Sort(array); float num3 = num / (float)s_count; float num4 = array[Mathf.Clamp((int)(0.99f * (float)(s_count - 1)), 0, s_count - 1)]; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"кадры за {num:0.0} с: среднее {1f / num3:0} FPS ({num3 * 1000f:0.0} мс), 1% худших {1f / num4:0} FPS ({num4 * 1000f:0.0} мс)"); int num5 = GC.CollectionCount(0); if (s_gcLastTime > 0f) { stringBuilder.Append($"; сборок мусора {num5 - s_gcLast} за {Time.unscaledTime - s_gcLastTime:0} с"); } s_gcLast = num5; s_gcLastTime = Time.unscaledTime; if (!s_timingFailed) { try { if (FrameTimingManager.GetLatestTimings(1u, s_timings) != 0) { FrameTiming val = s_timings[0]; stringBuilder.Append($"; движок: CPU {val.cpuFrameTime:0.0} мс (главный поток {val.cpuMainThreadFrameTime:0.0}, поток рендера {val.cpuRenderThreadFrameTime:0.0}), GPU {val.gpuFrameTime:0.0} мс"); } } catch (Exception) { s_timingFailed = true; } } return stringBuilder.ToString(); } public static string SceneLine() { //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = 0; int num3 = LayerMask.NameToLayer("piece"); int num4 = 0; MeshRenderer[] array = Object.FindObjectsByType<MeshRenderer>((FindObjectsSortMode)0); foreach (MeshRenderer val in array) { if (((Renderer)val).enabled && ((Component)val).gameObject.activeInHierarchy) { num++; if (((Renderer)val).isVisible) { num2++; } if (((Component)val).gameObject.layer == num3) { num4++; } } } int num5 = 0; SkinnedMeshRenderer[] array2 = Object.FindObjectsByType<SkinnedMeshRenderer>((FindObjectsSortMode)0); foreach (SkinnedMeshRenderer val2 in array2) { if (((Renderer)val2).enabled && ((Component)val2).gameObject.activeInHierarchy) { num5++; } } int num6 = 0; int num7 = 0; Light[] array3 = Object.FindObjectsByType<Light>((FindObjectsSortMode)0); foreach (Light val3 in array3) { if (((Behaviour)val3).enabled && ((Component)val3).gameObject.activeInHierarchy && !(val3.intensity <= 0f)) { num6++; if ((int)val3.shadows != 0) { num7++; } } } int num8 = 0; ParticleSystem[] array4 = Object.FindObjectsByType<ParticleSystem>((FindObjectsSortMode)0); foreach (ParticleSystem val4 in array4) { if (val4.isPlaying && val4.particleCount > 0) { num8++; } } int count = Character.GetAllCharacters().Count; int totalSmoke = Smoke.GetTotalSmoke(); int count2 = WearNTear.GetAllInstances().Count; int num9 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.NrOfInstances() : 0); int num10 = 0; int num11 = 0; try { HashSet<LightLod> hashSet = AccessTools.Field(typeof(LightLod), "m_lights")?.GetValue(null) as HashSet<LightLod>; FieldInfo fieldInfo = AccessTools.Field(typeof(LightLod), "m_light"); if (hashSet != null && fieldInfo != null) { foreach (LightLod item in hashSet) { if ((Object)(object)item == (Object)null) { continue; } object? value = fieldInfo.GetValue(item); Light val5 = (Light)((value is Light) ? value : null); if (!((Object)(object)val5 == (Object)null) && ((Behaviour)val5).enabled && ((Component)val5).gameObject.activeInHierarchy) { num10++; if ((int)val5.shadows != 0) { num11++; } } } } } catch (Exception) { num10 = -1; } return $"сцена: рендереров {num} (в кадре {num2}, на слое piece {num4}), скелетных {num5}, источников света {num6} (с тенями {num7}; точечных игры {num10}, из них с тенями {num11}), частиц {num8}, существ {count}, дыма {totalSmoke}, деталей WearNTear {count2}, сетевых объектов {num9}"; } } internal static class LoopProbes { private sealed class Probe { } private static string[] s_names = new string[0]; private static long[] s_begin = new long[0]; private static double[] s_acc = new double[0]; private static long s_frameStart; private static double s_frameAcc; private static int s_frames; private static bool s_installed; private static readonly Dictionary<string, string> s_hints = new Dictionary<string, string>(StringComparer.Ordinal) { { "Initialization.PlayerUpdateTime", "ожидание начала кадра: vsync, лимит FPS или GPU" }, { "EarlyUpdate.UpdateInputManager", "ввод" }, { "EarlyUpdate.ExecuteMainThreadJobs", "задачи главного потока" }, { "FixedUpdate.PhysicsFixedUpdate", "физика PhysX" }, { "FixedUpdate.ScriptRunBehaviourFixedUpdate", "скрипты FixedUpdate: существа, синхронизация, дым" }, { "FixedUpdate.ScriptRunDelayedFixedFrameRate", "корутины FixedUpdate" }, { "PreUpdate.PhysicsUpdate", "физика: синхронизация трансформов" }, { "PreUpdate.AIUpdate", "навигация NavMesh" }, { "Update.ScriptRunBehaviourUpdate", "скрипты Update" }, { "Update.ScriptRunDelayedDynamicFrameRate", "корутины" }, { "PreLateUpdate.DirectorUpdateAnimationBegin", "анимация: старт" }, { "PreLateUpdate.DirectorUpdateAnimationEnd", "анимация: применение" }, { "PreLateUpdate.ParticleSystemBeginUpdateAll", "частицы: старт" }, { "PreLateUpdate.ScriptRunBehaviourLateUpdate", "скрипты LateUpdate" }, { "PostLateUpdate.ParticleSystemEndUpdateAll", "частицы: завершение" }, { "PostLateUpdate.UpdateAllRenderers", "рендереры: подготовка" }, { "PostLateUpdate.UpdateAllSkinnedMeshes", "скелетные меши" }, { "PostLateUpdate.UpdateAudio", "звук" }, { "PostLateUpdate.PlayerUpdateCanvases", "интерфейс" }, { "PostLateUpdate.PlayerEmitCanvasGeometry", "интерфейс: геометрия" }, { "PostLateUpdate.FinishFrameRendering", "рендер: отсечение и подача (главный поток)" }, { "PostLateUpdate.PresentAfterDraw", "показ кадра: ожидание GPU и потока рендера" }, { "PostLateUpdate.UpdateCustomRenderTextures", "пользовательские текстуры" }, { "PostLateUpdate.PhysicsSkinnedClothBeginUpdate", "ткань: старт" }, { "PostLateUpdate.PhysicsSkinnedClothFinishUpdate", "ткань: завершение" } }; public static bool Installed => s_installed; public static void Install() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) if (s_installed) { return; } PlayerLoopSystem currentPlayerLoop = PlayerLoop.GetCurrentPlayerLoop(); if (currentPlayerLoop.subSystemList == null) { return; } List<string> list = new List<string>(); for (int i = 0; i < currentPlayerLoop.subSystemList.Length; i++) { PlayerLoopSystem val = currentPlayerLoop.subSystemList[i]; if (val.subSystemList == null || val.subSystemList.Length == 0) { continue; } List<PlayerLoopSystem> list2 = new List<PlayerLoopSystem>(val.subSystemList.Length * 3); PlayerLoopSystem[] subSystemList = val.subSystemList; foreach (PlayerLoopSystem val2 in subSystemList) { if (!(val2.type == typeof(Probe))) { int count = list.Count; list.Add(((val.type != null) ? val.type.Name : "?") + "." + ((val2.type != null) ? val2.type.Name : "?")); list2.Add(Marker(count, begin: true)); list2.Add(val2); list2.Add(Marker(count, begin: false)); } } val.subSystemList = list2.ToArray(); currentPlayerLoop.subSystemList[i] = val; } s_names = list.ToArray(); s_begin = new long[s_names.Length]; s_acc = new double[s_names.Length]; s_frameStart = 0L; s_frameAcc = 0.0; s_frames = 0; PlayerLoop.SetPlayerLoop(currentPlayerLoop); s_installed = true; } public static void Uninstall() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (!s_installed) { return; } PlayerLoopSystem currentPlayerLoop = PlayerLoop.GetCurrentPlayerLoop(); if (currentPlayerLoop.subSystemList != null) { for (int i = 0; i < currentPlayerLoop.subSystemList.Length; i++) { PlayerLoopSystem val = currentPlayerLoop.subSystemList[i]; if (val.subSystemList == null) { continue; } List<PlayerLoopSystem> list = new List<PlayerLoopSystem>(val.subSystemList.Length); PlayerLoopSystem[] subSystemList = val.subSystemList; foreach (PlayerLoopSystem val2 in subSystemList) { if (val2.type != typeof(Probe)) { list.Add(val2); } } val.subSystemList = list.ToArray(); currentPlayerLoop.subSystemList[i] = val; } PlayerLoop.SetPlayerLoop(currentPlayerLoop); } s_installed = false; } private static PlayerLoopSystem Marker(int index, bool begin) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) return new PlayerLoopSystem { type = typeof(Probe), updateDelegate = (begin ? ((UpdateFunction)delegate { Begin(index); }) : ((UpdateFunction)delegate { End(index); })) }; } private static void Begin(int i) { long timestamp = Stopwatch.GetTimestamp(); if (i == 0) { if (s_frameStart != 0L) { s_frameAcc += (double)(timestamp - s_frameStart) * 1000.0 / (double)Stopwatch.Frequency; s_frames++; } s_frameStart = timestamp; } s_begin[i] = timestamp; } private static void End(int i) { s_acc[i] += (double)(Stopwatch.GetTimestamp() - s_begin[i]) * 1000.0 / (double)Stopwatch.Frequency; } public static void ResetWindow() { Array.Clear(s_acc, 0, s_acc.Length); s_frameAcc = 0.0; s_frames = 0; } public static string Report(int top = 14) { if (!s_installed) { return "пробы цикла кадра выключены (LoopProbes в конфиге)"; } if (s_frames < 10) { return "пробы цикла кадра: мало данных, подожди секунду и повтори"; } int[] array = new int[s_names.Length]; for (int i = 0; i < array.Length; i++) { array[i] = i; } Array.Sort(array, (int a, int b) => s_acc[b].CompareTo(s_acc[a])); double num = s_frameAcc / (double)s_frames; double num2 = 0.0; double[] array2 = s_acc; foreach (double num4 in array2) { num2 += num4; } num2 /= (double)s_frames; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"цикл кадра за {s_frames} кадров: кадр {num:0.0} мс, из них подсистемы {num2:0.0} мс, прочее {num - num2:0.0} мс. Самое тяжёлое: "); int num5 = 0; int[] array3 = array; foreach (int num6 in array3) { double num7 = s_acc[num6] / (double)s_frames; if (num7 < 0.05 || num5 >= top) { break; } string value; string text = (s_hints.TryGetValue(s_names[num6], out value) ? (" (" + value + ")") : ""); stringBuilder.Append(string.Format("{0}{1}{2} {3:0.00} мс", (num5 > 0) ? "; " : "", s_names[num6], text, num7)); num5++; } Array.Clear(s_acc, 0, s_acc.Length); s_frameAcc = 0.0; s_frames = 0; return stringBuilder.ToString(); } } internal static class MethodProfiler { private sealed class Acc { public long Ticks; public long Calls; public string Name; } private static readonly Dictionary<MethodBase, Acc> s_acc = new Dictionary<MethodBase, Acc>(); private static Harmony s_harmony; private static bool s_on; private static int s_startFrame; private static readonly string[] s_types = new string[75] { "MonoUpdaters", "Character", "Humanoid", "Player", "ZSyncTransform", "ZSyncAnimation", "CharacterAnimEvent", "BaseAI", "MonsterAI", "AnimalAI", "Tameable", "Procreation", "Floating", "Aoe", "EffectArea", "WaterVolume", "Smoke", "SmokeSpawner", "SmokeRenderer", "ZSFX", "VisEquipment", "FootStep", "InstanceRenderer", "LightFlicker", "LightLod", "CraftingStation", "ZNetScene", "ZNet", "ZDOMan", "ZoneSystem", "EnvMan", "Hud", "Minimap", "WearNTearUpdater", "WearNTear", "Pathfinding", "RenderGroupSystem", "MaterialMan", "TerrainLod", "ReflectionUpdate", "CinderSpawner", "ParticleMist", "MistEmitter", "DistantFogEmitter", "Vine", "GameCamera", "ClutterSystem", "Heightmap", "Fireplace", "PrivateArea", "Container", "ItemDrop", "Pickable", "Plant", "EnemyHud", "DamageText", "Chat", "MessageHud", "AudioMan", "MusicMan", "Game", "RandEventSystem", "Thunder", "ObjectFlicker", "Vagon", "Ship", "Fish", "FishingFloat", "Ragdoll", "Projectile", "AmplifyOcclusionEffect", "CameraEffects", "UpscaledFrameBuffer", "PresentManager", "SystemResourceManager" }; private static readonly string[] s_methods = new string[16] { "Update", "FixedUpdate", "LateUpdate", "CustomUpdate", "CustomFixedUpdate", "CustomLateUpdate", "UpdateAI", "StaticUpdate", "UpdateFloaters", "UpdateMaterials", "UpdateWear", "UpdateLights", "OnPreRender", "OnPostRender", "OnRenderImage", "OnPreCull" }; public static bool On => s_on; public static string Start() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown if (s_on) { return "замер уже идёт: pb prof — отчёт, pb prof off — выключить"; } s_harmony = new Harmony("qua8ion.valheim.piecebatcher.prof"); s_acc.Clear(); HarmonyMethod val = new HarmonyMethod(typeof(MethodProfiler), "Prefix", (Type[])null); HarmonyMethod val2 = new HarmonyMethod(typeof(MethodProfiler), "Postfix", (Type[])null); int num = 0; int num2 = 0; Assembly assembly = typeof(WearNTear).Assembly; string[] array = s_types; foreach (string text in array) { Type type = assembly.GetType(text); if (type == null) { continue; } string[] array2 = s_methods; foreach (string text2 in array2) { MethodInfo method; try { method = type.GetMethod(text2, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch (AmbiguousMatchException) { continue; } if (!(method == null) && !method.IsGenericMethod && !method.IsAbstract) { try { s_harmony.Patch((MethodBase)method, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); s_acc[method] = new Acc { Name = text + "." + text2 }; num++; } catch (Exception) { num2++; } } } } s_on = true; s_startFrame = Time.frameCount; return string.Format("замер методов включён: {0} методов{1}. pb prof — отчёт за время с прошлого отчёта, pb prof off — выключить", num, (num2 > 0) ? $", не удалось {num2}" : ""); } public static string Stop() { if (!s_on) { return "замер методов не включён"; } string text = Report(); s_harmony.UnpatchSelf(); s_harmony = null; s_on = false; s_acc.Clear(); return text + Environment.NewLine + "замер методов выключен"; } public static void ResetWindow() { foreach (Acc value in s_acc.Values) { value.Ticks = 0L; value.Calls = 0L; } s_startFrame = Time.frameCount; } public static string Report(int top = 25) { if (!s_on) { return "замер методов не включён: pb prof on"; } int num = Time.frameCount - s_startFrame; if (num < 10) { return "замер методов: мало кадров, подожди"; } List<Acc> list = new List<Acc>(s_acc.Values); list.Sort((Acc a, Acc b) => b.Ticks.CompareTo(a.Ticks)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"методы игры за {num} кадров (мс на кадр, вызовов на кадр; вложенные считаются дважды): "); int num2 = 0; foreach (Acc item in list) { double num3 = (double)item.Ticks * 1000.0 / (double)Stopwatch.Frequency / (double)num; if (num3 < 0.02 || num2 >= top) { break; } stringBuilder.Append(string.Format("{0}{1} {2:0.00} мс ({3:0.#})", (num2 > 0) ? "; " : "", item.Name, num3, (double)item.Calls / (double)num)); num2++; } foreach (Acc item2 in list) { item2.Ticks = 0L; item2.Calls = 0L; } s_startFrame = Time.frameCount; return stringBuilder.ToString(); } private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(MethodBase __originalMethod, long __state) { if (s_acc.TryGetValue(__originalMethod, out var value)) { value.Ticks += Stopwatch.GetTimestamp() - __state; value.Calls++; } } } internal sealed class PbCommand : ConsoleCommand { public override string Name => "pb"; public override string Help => "PieceBatcher: pb [stats|scene|loop|prof on|prof off|prof|sync|cell|on|off|rebuild]"; public override void Run(string[] args) { //IL_018a: Unknown result type (might be due to invalid IL or missing references) string text = ((args.Length != 0) ? args[0].ToLowerInvariant() : "stats"); string text2 = ((args.Length > 1) ? args[1].ToLowerInvariant() : ""); switch (text) { case "on": Plugin.Enabled.Value = true; Print("PieceBatcher включён, ячейки пересобираются"); break; case "off": Plugin.Enabled.Value = false; Print("PieceBatcher выключен, детали рисуются как в игре"); break; case "rebuild": Batcher.MarkAllDirty(now: true); Print("все ячейки будут пересобраны"); break; case "cell": Print(((Object)(object)Player.m_localPlayer != (Object)null) ? Batcher.CellStats(((Component)Player.m_localPlayer).transform.position) : "нет игрока"); break; case "scene": Print(Batcher.Stats()); Print(Diag.FrameLine()); Print(Diag.SceneLine()); break; case "loop": Print(Diag.FrameLine()); Print(LoopProbes.Report()); break; case "prof": if (text2 == "on") { Print(MethodProfiler.Start()); break; } if (text2 == "off") { Print(MethodProfiler.Stop()); break; } Print(Diag.FrameLine()); Print(MethodProfiler.Report()); break; case "sync": Print(SyncSkip.Report()); break; default: Print(Batcher.Stats()); Print(SyncSkip.Report()); Print(Diag.FrameLine()); break; } } public override List<string> CommandOptionList() { return new List<string> { "stats", "scene", "loop", "prof", "sync", "cell", "on", "off", "rebuild" }; } private static void Print(string s) { Plugin.Log.LogInfo((object)s); if ((Object)(object)Console.instance != (Object)null) { Console.instance.Print(s); } } } internal sealed class MeshData { public const int AttrNormals = 1; public const int AttrTangents = 2; public const int AttrColors = 4; public const int AttrUv0 = 8; public Vector3[] Pos; public Vector3[] Nrm; public Vector4[] Tan; public Color32[] Col; public Vector4[][] Uv = new Vector4[4][]; public int[] UvDim = new int[4]; public int[][] Sub; public int Attr; public int Signature; public bool Ok; } internal static class MeshCache { private static readonly Dictionary<int, MeshData> s_cache = new Dictionary<int, MeshData>(); private static readonly List<Vector3> s_v3 = new List<Vector3>(); private static readonly List<Vector4> s_v4 = new List<Vector4>(); private static readonly List<Color32> s_c32 = new List<Color32>(); private static readonly List<int> s_int = new List<int>(); public static int Count => s_cache.Count; public static MeshData Get(Mesh mesh) { int instanceID = ((Object)mesh).GetInstanceID(); if (s_cache.TryGetValue(instanceID, out var value)) { return value; } value = Read(mesh); s_cache[instanceID] = value; return value; } public static void Clear() { s_cache.Clear(); } private static MeshData Read(Mesh mesh) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) MeshData meshData = new MeshData(); if (!mesh.isReadable || mesh.vertexCount == 0 || mesh.subMeshCount == 0) { return meshData; } for (int i = 0; i < mesh.subMeshCount; i++) { if ((int)mesh.GetTopology(i) != 0) { return meshData; } } s_v3.Clear(); mesh.GetVertices(s_v3); meshData.Pos = s_v3.ToArray(); if (mesh.HasVertexAttribute((VertexAttribute)1)) { s_v3.Clear(); mesh.GetNormals(s_v3); if (s_v3.Count == meshData.Pos.Length) { meshData.Nrm = s_v3.ToArray(); meshData.Attr |= 1; } } if (mesh.HasVertexAttribute((VertexAttribute)2)) { s_v4.Clear(); mesh.GetTangents(s_v4); if (s_v4.Count == meshData.Pos.Length) { meshData.Tan = s_v4.ToArray(); meshData.Attr |= 2; } } if (mesh.HasVertexAttribute((VertexAttribute)3)) { s_c32.Clear(); mesh.GetColors(s_c32); if (s_c32.Count == meshData.Pos.Length) { meshData.Col = s_c32.ToArray(); meshData.Attr |= 4; } } for (int j = 0; j < 4; j++) { VertexAttribute val = (VertexAttribute)(4 + j); if (mesh.HasVertexAttribute(val)) { s_v4.Clear(); mesh.GetUVs(j, s_v4); if (s_v4.Count == meshData.Pos.Length) { meshData.Uv[j] = s_v4.ToArray(); meshData.UvDim[j] = Mathf.Clamp(mesh.GetVertexAttributeDimension(val), 2, 4); meshData.Attr |= 8 << j; } } } meshData.Sub = new int[mesh.subMeshCount][]; for (int k = 0; k < mesh.subMeshCount; k++) { s_int.Clear(); mesh.GetTriangles(s_int, k, true); meshData.Sub[k] = s_int.ToArray(); } meshData.Signature = meshData.Attr | (meshData.UvDim[0] << 8) | (meshData.UvDim[1] << 10) | (meshData.UvDim[2] << 12) | (meshData.UvDim[3] << 14); meshData.Ok = true; return meshData; } } [HarmonyPatch] internal static class Patches { [HarmonyPatch(typeof(WearNTear), "Awake")] [HarmonyPostfix] private static void WearNTear_Awake(WearNTear __instance, ZNetView ___m_nview) { if ((Object)(object)___m_nview != (Object)null && ___m_nview.GetZDO() != null) { Batcher.Register(__instance); } } [HarmonyPatch(typeof(WearNTear), "OnDestroy")] [HarmonyPrefix] private static void WearNTear_OnDestroy(WearNTear __instance) { Batcher.Unregister(__instance); } [HarmonyPatch(typeof(WearNTear), "SetHealthVisual")] [HarmonyPrefix] private static void SetHealthVisual_Prefix(WearNTear __instance, out int __state) { __state = Batcher.VariantState(__instance); } [HarmonyPatch(typeof(WearNTear), "SetHealthVisual")] [HarmonyPostfix] private static void SetHealthVisual_Postfix(WearNTear __instance, int __state) { if (__state != Batcher.VariantState(__instance)) { Batcher.OnChanged(__instance); } } [HarmonyPatch(typeof(WearNTear), "Highlight")] [HarmonyPrefix] private static void Highlight_Prefix(WearNTear __instance) { Batcher.SetHighlighted(__instance, on: true); } [HarmonyPatch(typeof(WearNTear), "ResetHighlight")] [HarmonyPostfix] private static void ResetHighlight_Postfix(WearNTear __instance) { Batcher.SetHighlighted(__instance, on: false); } [HarmonyPatch(typeof(WearNTear), "SetAshlandsMaterialValue")] [HarmonyPostfix] private static void SetAshlandsMaterialValue_Postfix(WearNTear __instance, float v) { Batcher.SetMaterialTouched(__instance, v != 0f); } [HarmonyPatch(typeof(Container), "UpdateUseVisual")] [HarmonyPrefix] private static void Container_UpdateUseVisual_Prefix(Container __instance, out int __state) { __state = Batcher.ContainerState(__instance); } [HarmonyPatch(typeof(Container), "UpdateUseVisual")] [HarmonyPostfix] private static void Container_UpdateUseVisual_Postfix(Container __instance, int __state) { if (__state != Batcher.ContainerState(__instance)) { WearNTear component = ((Component)__instance).GetComponent<WearNTear>(); if ((Object)(object)component != (Object)null) { Batcher.OnChanged(component); } } } [HarmonyPatch(typeof(WearNTear), "RPC_CreateFragments")] [HarmonyPrefix] private static void RPC_CreateFragments_Prefix(WearNTear __instance) { Batcher.OnBeforeFragments(__instance); } [HarmonyPatch(typeof(WearNTear), "RPC_CreateFragments")] [HarmonyPostfix] private static void RPC_CreateFragments_Postfix() { Batcher.OnAfterFragments(); } [HarmonyPatch(typeof(Destructible), "CreateFragments", new Type[] { typeof(GameObject), typeof(bool) })] [HarmonyPrefix] private static void CreateFragments_Prefix(ref bool visibleOnly) { if (Batcher.ConsumeForceFragments()) { visibleOnly = false; } } [HarmonyPatch(typeof(ZNetScene), "OnDestroy")] [HarmonyPrefix] private static void ZNetScene_OnDestroy() { Batcher.Clear(); SyncSkip.Clear(); } [HarmonyPatch(typeof(ZNetScene), "Awake")] [HarmonyPostfix] private static void ZNetScene_Awake() { if (Plugin.ProfilerEnabled.Value && !MethodProfiler.On) { Plugin.Log.LogInfo((object)MethodProfiler.Start()); } } [HarmonyPatch(typeof(Player), "OnSpawned")] [HarmonyPostfix] private static void Player_OnSpawned(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { MethodProfiler.ResetWindow(); LoopProbes.ResetWindow(); } } } [BepInPlugin("qua8ion.valheim.piecebatcher", "PieceBatcher", "1.2.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "qua8ion.valheim.piecebatcher"; public const string PluginName = "PieceBatcher"; public const string PluginVersion = "1.2.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> CellSize; internal static ConfigEntry<float> RebuildDelay; internal static ConfigEntry<float> FrameBudgetMs; internal static ConfigEntry<int> MinParts; internal static ConfigEntry<int> MaxVertices; internal static ConfigEntry<string> Shaders; internal static ConfigEntry<string> ExcludePrefabs; internal static ConfigEntry<bool> Verbose; internal static ConfigEntry<bool> LoopProbesEnabled; internal static ConfigEntry<bool> ProfilerEnabled; internal static ConfigEntry<bool> SyncSkipEnabled; internal static ConfigEntry<bool> SyncBatchPhysics; private Harmony _harmony; private void Awake() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Expected O, but got Unknown //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Expected O, but got Unknown //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_0368: Expected O, but got Unknown //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03a0: Unknown result type (might be due to invalid IL or missing references) //IL_03a6: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Склеивать детали построек в общие меши (та же картинка, меньше вызовов отрисовки). Переключается на лету."); CellSize = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CellSize", 16f, new ConfigDescription("Размер ячейки склейки в метрах. Меньше ячейка — точнее отсечение за кадром, но больше мешей.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(4f, 64f), Array.Empty<object>())); RebuildDelay = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RebuildDelay", 2f, new ConfigDescription("Через сколько секунд после последнего изменения в ячейке (постройка, снос, износ) она пересобирается.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 30f), Array.Empty<object>())); FrameBudgetMs = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FrameBudgetMs", 3f, new ConfigDescription("Сколько миллисекунд за кадр можно тратить на пересборку ячеек.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 50f), Array.Empty<object>())); MinParts = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MinParts", 2, new ConfigDescription("Минимум деталей одного материала в ячейке, чтобы их склеивать.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>())); MaxVertices = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxVertices", 60000, new ConfigDescription("Потолок вершин одного склеенного меша; ячейка с большим числом делится на несколько.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5000, 1000000), Array.Empty<object>())); Shaders = ((BaseUnityPlugin)this).Config.Bind<string>("Filters", "Shaders", "Custom/Piece", "Шейдеры материалов, которые можно склеивать, через запятую. Остальные детали рисуются как в игре."); ExcludePrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("Filters", "ExcludePrefabs", "", "Имена префабов, которые не склеивать, через запятую (например, piece_chest_wood)."); SyncSkipEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SyncSkip", true, "Пропускать шаг сетевой синхронизации у чужих объектов, которые стоят на месте: новых данных с сервера нет, объект уже в цели, тело спит. Шаг ничего бы не изменил; как только что-то меняется, синхронизация идёт как обычно."); SyncBatchPhysics = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SyncBatchPhysics", true, "Внутри шага сетевой синхронизации передавать физике сдвинутые объекты одним вызовом в конце шага, а не после каждого объекта. Физика видит те же позиции в тот же момент."); Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Verbose", false, "Писать в лог каждую пересборку ячейки."); LoopProbesEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LoopProbes", true, "Держать пробы в цикле кадра Unity для команды pb loop (физика, скрипты, анимация, частицы, рендер, ожидание GPU). Стоят доли микросекунды на кадр."); LoopProbesEnabled.SettingChanged += delegate { if (LoopProbesEnabled.Value) { LoopProbes.Install(); } else { LoopProbes.Uninstall(); } }; ProfilerEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "MethodProfiler", true, "Включать секундомер на методах игры при входе в мир, чтобы pb prof сразу показывал отчёт. pb prof off снимает его до следующего входа; стоит доли микросекунды на вызов."); Enabled.SettingChanged += delegate { Batcher.OnEnabledChanged(); }; CellSize.SettingChanged += delegate { Batcher.Reset(); }; Shaders.SettingChanged += delegate { Batcher.Reset(); }; ExcludePrefabs.SettingChanged += delegate { Batcher.Reset(); }; MinParts.SettingChanged += delegate { Batcher.MarkAllDirty(); }; MaxVertices.SettingChanged += delegate { Batcher.MarkAllDirty(); }; _harmony = new Harmony("qua8ion.valheim.piecebatcher"); _harmony.CreateClassProcessor(typeof(Patches)).Patch(); SyncSkip.Install(_harmony); GameObject val = new GameObject("PieceBatcher") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val); val.AddComponent<BatcherRunner>(); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PbCommand()); if (LoopProbesEnabled.Value) { try { LoopProbes.Install(); } catch (Exception ex) { Log.LogWarning((object)("пробы цикла кадра не встали: " + ex.GetType().Name + ": " + ex.Message)); } } Log.LogInfo((object)"PieceBatcher 1.2.1 загружен"); } private void OnDestroy() { LoopProbes.Uninstall(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal sealed class BatcherRunner : MonoBehaviour { private void Update() { Diag.Tick(); Batcher.Update(); } private void LateUpdate() { Batcher.LateUpdate(); } private void OnApplicationQuit() { Batcher.Quitting = true; } } internal static class SyncSkip { private sealed class State { public bool Idle; public uint Revision; public Vector3 Pos; public Quaternion Rot; } [HarmonyPatch] private static class Patches { [HarmonyPatch(typeof(ZSyncTransform), "CustomFixedUpdate")] [HarmonyPrefix] private static bool CustomFixedUpdate_Prefix(ZSyncTransform __instance, ZNetView ___m_nview, Rigidbody ___m_body) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.SyncSkipEnabled.Value) { return true; } Calls++; if (!s_states.TryGetValue(__instance, out var value) || !value.Idle) { return true; } if ((Object)(object)___m_nview == (Object)null || !___m_nview.IsValid()) { return true; } ZDO zDO = ___m_nview.GetZDO(); if (zDO.DataRevision != value.Revision || zDO.IsOwner()) { value.Idle = false; return true; } if ((Object)(object)___m_body != (Object)null && !___m_body.IsSleeping()) { value.Idle = false; return true; } Transform transform = ((Component)__instance).transform; if (transform.position != value.Pos || transform.rotation != value.Rot) { value.Idle = false; return true; } Skipped++; return false; } [HarmonyPatch(typeof(ZSyncTransform), "CustomFixedUpdate")] [HarmonyPostfix] private static void CustomFixedUpdate_Postfix(ZSyncTransform __instance, ZNetView ___m_nview, Rigidbody ___m_body, bool ___m_isKinematicBody) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.SyncSkipEnabled.Value) { return; } if (!s_states.TryGetValue(__instance, out var value)) { value = new State(); s_states[__instance] = value; } if (value.Idle || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid()) { return; } ZDO zDO = ___m_nview.GetZDO(); if (zDO.IsOwner() || ___m_isKinematicBody || __instance.m_syncScale || __instance.m_syncBodyVelocity) { return; } if (__instance.m_characterParentSync && zDO.HasOwner()) { ZDOID connectionZDOID = zDO.GetConnectionZDOID((ConnectionType)2); if (!((ZDOID)(ref connectionZDOID)).IsNone()) { return; } } if (zDO.HasOwner()) { Vector3 vec = zDO.GetVec3(ZDOVars.s_velHash, Vector3.zero); if (((Vector3)(ref vec)).sqrMagnitude > 0f) { return; } } Transform transform = ((Component)__instance).transform; if ((!__instance.m_syncPosition || !(Vector3.Distance(transform.position, zDO.GetPosition()) > 0.001f)) && (!__instance.m_syncRotation || !(Quaternion.Angle(transform.rotation, zDO.GetRotation()) > 0.001f)) && (!((Object)(object)___m_body != (Object)null) || ___m_body.IsSleeping())) { value.Idle = true; value.Revision = zDO.DataRevision; value.Pos = transform.position; value.Rot = transform.rotation; } } [HarmonyPatch(typeof(ZSyncTransform), "OnDisable")] [HarmonyPostfix] private static void OnDisable_Postfix(ZSyncTransform __instance) { s_states.Remove(__instance); } [HarmonyPatch(typeof(MonoUpdatersExtra), "CustomFixedUpdate")] [HarmonyPrefix] private static void Loop_Prefix(List<IMonoUpdater> source) { if (source == ZSyncTransform.Instances) { s_inSyncLoop = true; } } [HarmonyPatch(typeof(MonoUpdatersExtra), "CustomFixedUpdate")] [HarmonyPostfix] private static void Loop_Postfix(List<IMonoUpdater> source) { if (s_inSyncLoop) { s_inSyncLoop = false; FlushPhysicsSync(); } } [HarmonyPatch(typeof(ZSyncTransform), "SyncPosition")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> SyncPosition_Transpiler(IEnumerable<CodeInstruction> instructions) { MethodInfo target = AccessTools.Method(typeof(Physics), "SyncTransforms", (Type[])null, (Type[])null); MethodInfo replacement = AccessTools.Method(typeof(SyncSkip), "DeferPhysicsSync", (Type[])null, (Type[])null); foreach (CodeInstruction instruction in instructions) { if (target != null && CodeInstructionExtensions.Calls(instruction, target)) { instruction.opcode = OpCodes.Call; instruction.operand = replacement; TranspiledCalls++; } yield return instruction; } } } private static readonly Dictionary<ZSyncTransform, State> s_states = new Dictionary<ZSyncTransform, State>(); internal static bool Installed; internal static long Calls; internal static long Skipped; private static long s_callsAtReport; private static long s_skippedAtReport; private static int s_frameAtReport; private static bool s_inSyncLoop; private static bool s_pendingSync; internal static int TranspiledCalls; internal static long Deferred; internal static long Flushes; private static long s_deferredAtReport; private static long s_flushesAtReport; internal static void DeferPhysicsSync() { if (s_inSyncLoop && Plugin.SyncBatchPhysics.Value) { s_pendingSync = true; Deferred++; } else { Physics.SyncTransforms(); } } private static void FlushPhysicsSync() { if (s_pendingSync) { s_pendingSync = false; Physics.SyncTransforms(); Flushes++; } } internal static void Install(Harmony harmony) { try { harmony.CreateClassProcessor(typeof(Patches)).Patch(); Installed = true; if (TranspiledCalls == 0) { Plugin.Log.LogWarning((object)"в ZSyncTransform.SyncPosition не нашлось Physics.SyncTransforms(): отложенная синхронизация физики выключена"); } } catch (Exception ex) { Installed = false; Plugin.Log.LogWarning((object)("пропуск синхронизации не встал (игра изменилась?): " + ex.GetType().Name + ": " + ex.Message)); } } internal static string Report() { if (!Installed) { return "синхронизация: пропуск не установлен"; } if (!Plugin.SyncSkipEnabled.Value) { return "синхронизация: пропуск выключен в конфиге"; } long num = Calls - s_callsAtReport; long num2 = Skipped - s_skippedAtReport; long num3 = Deferred - s_deferredAtReport; long num4 = Flushes - s_flushesAtReport; int num5 = Mathf.Max(1, Time.frameCount - s_frameAtReport); s_callsAtReport = Calls; s_skippedAtReport = Skipped; s_deferredAtReport = Deferred; s_flushesAtReport = Flushes; s_frameAtReport = Time.frameCount; double num6 = ((num > 0) ? (100.0 * (double)num2 / (double)num) : 0.0); string text = ((TranspiledCalls > 0) ? $"; синхронизаций физики вместо {num3} сделано {num4} ({(double)num3 / (double)num5:0.#} → {(double)num4 / (double)num5:0.#} на кадр)" : "; отложенная синхронизация физики не установлена"); return $"синхронизация стоящих объектов: пропущено {num2} из {num} шагов ({num6:0}%), {(double)num2 / (double)num5:0.#} из {(double)num / (double)num5:0.#} на кадр, стоят на месте {CountIdle()} из {s_states.Count}{text}"; } private static int CountIdle() { int num = 0; foreach (State value in s_states.Values) { if (value.Idle) { num++; } } return num; } internal static void Clear() { s_states.Clear(); s_inSyncLoop = false; s_pendingSync = false; } } }