using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Scripting; namespace NecromancerTome { /// /// Меры нагрузки урн (решение 17.10: «общий бюджет шагов работы духов за кадр (очередь по кругу - нагрузка постоянна при /// любом числе урн); полная анимация только ближайших ~15-20 духов; замер 100 урн»). /// Бюджет: дел (пробный шаг + настоящий) за кадр - не больше StepsPerFrame на все урны сейва; урна, которой не досталось, /// пробует в следующем кадре. Урна делает дело раз в CinerariumWork.StepSeconds, поэтому очередь сама идёт по кругу. /// Анимация: двигаются (полёт, жесты, покачивание) только AnimateNearest ближайших к игроку духов, остальные замирают дома, /// как уснувшие (CinerariumGenieMotion); работа у всех идёт. /// Прилив (решение 17.10, «Прилив прячет некротехнологов»): ночью кровавой луны духи всех урн уходят в урны и работа стоит - /// признак берётся раз за кадр. /// public static class CinerariumLoad { public static int StepsPerFrame = 3; public static int AnimateNearest = 16; static int _frame = -1; static int _used; static bool _tide; static void NewFrame() { if (Time.frameCount != _frame) { _frame = Time.frameCount; _used = 0; _tide = SnakeMode.IsBloodTide(); } } /// Взять одно дело из бюджета кадра. public static bool TryTakeStep() { NewFrame(); if (_used >= StepsPerFrame) { return false; } _used++; return true; } /// Идёт ли кровавый прилив (раз за кадр). public static bool Tide { get { NewFrame(); return _tide; } } // ------------------------------------------------------------------ // Ближайшие духи. // ------------------------------------------------------------------ static float _nextRank; static readonly HashSet _animated = new HashSet(); static readonly List _sorted = new List(); /// Двигается ли дух: среди AnimateNearest ближайших к игроку (пересчёт раз в 0.5 с, без выделений памяти). public static bool Animated(CinerariumGenieMotion _m) { if (Time.time >= _nextRank) { _nextRank = Time.time + 0.5f; Rank(); } return _animated.Contains(_m); } static void Rank() { _animated.Clear(); EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer(); if (pl == null) { return; } Vector3 me = pl.GetPosition() - Origin.position; _sorted.Clear(); _sorted.AddRange(CinerariumGenie.Live); if (_sorted.Count > AnimateNearest) { _sorted.Sort((a, b) => (a.transform.position - me).sqrMagnitude.CompareTo((b.transform.position - me).sqrMagnitude)); } for (int i = 0; i < _sorted.Count && i < AnimateNearest; i++) { _animated.Add(_sorted[i]); } } } /// necrofps [секунд] - средний и худший кадр за время замера (по умолчанию 5 с), число духов и урн; ответ - строкой /// почтового ящика в лог. Для замера нагрузки урн (17.10: «замер 100 урн»). [Preserve] public class ConsoleCmdNecroFps : ConsoleCmdAbstract { public override string[] getCommands() => new[] { "necrofps" }; public override string getDescription() => "NecromancerTome (разработка): средний и худший кадр за N секунд"; public override bool AllowedInMainMenu => false; public override void Execute(List _params, CommandSenderInfo _senderInfo) { float sec = _params.Count > 0 ? float.Parse(_params[0], System.Globalization.CultureInfo.InvariantCulture) : 5f; bool uncap = _params.Count > 1 && _params[1] == "uncap"; SdtdConsole.Instance.Output("necrofps: замер " + sec + " с" + (uncap ? " без ограничения кадров" : "")); ThreadManager.StartCoroutine(Measure(sec, uncap)); } /// _uncap - на время замера снять вертикальную синхронизацию и предел кадров: при 60 fps по синхронизации /// запас не виден (18.10, замер 100 урн). static IEnumerator Measure(float _sec, bool _uncap) { int vs = QualitySettings.vSyncCount, fr = Application.targetFrameRate; if (_uncap) { QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; yield return new WaitForSecondsRealtime(0.5f); } float end = Time.realtimeSinceStartup + _sec; int frames = 0; float worst = 0f, sum = 0f; long gc0 = System.GC.CollectionCount(0); while (Time.realtimeSinceStartup < end) { yield return null; frames++; sum += Time.unscaledDeltaTime; worst = Mathf.Max(worst, Time.unscaledDeltaTime); } if (_uncap) { QualitySettings.vSyncCount = vs; Application.targetFrameRate = fr; } float avg = frames > 0 ? sum / frames * 1000f : 0f; Debug.Log("[NecromancerTome] mailbox: necrofps: кадров " + frames + ", средний " + avg.ToString("F1") + " мс (" + (avg > 0 ? 1000f / avg : 0f).ToString("F0") + " fps), худший " + (worst * 1000f).ToString("F1") + " мс, сборок мусора " + (System.GC.CollectionCount(0) - gc0) + ", духов " + CinerariumGenie.Live.Count + ", урн в сейве " + CinerariumRegistry.All().Count); } } /// necrotide on|off|auto - прилив для проверок без ожидания ночи кровавой луны (SnakeMode.ForceTide). [Preserve] public class ConsoleCmdNecroTide : ConsoleCmdAbstract { public override string[] getCommands() => new[] { "necrotide" }; public override string getDescription() => "NecromancerTome (разработка): прилив on|off|auto"; public override bool AllowedInMainMenu => false; public override void Execute(List _params, CommandSenderInfo _senderInfo) { string v = _params.Count > 0 ? _params[0] : ""; SnakeMode.ForceTide = v == "on" ? true : v == "off" ? (bool?)false : null; SdtdConsole.Instance.Output("necrotide: " + (SnakeMode.ForceTide.HasValue ? (SnakeMode.ForceTide.Value ? "прилив" : "нет прилива") : "по часам") + ", сейчас " + (SnakeMode.IsBloodTide() ? "прилив" : "не прилив")); } } }