Files
necromants-tome-7d2d-3-2/HarmonySrc/CinerariumLoad.cs
T

204 lines
8.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
/// <summary>
/// Меры нагрузки урн (решение 17.10: «общий бюджет шагов работы духов за кадр (очередь по кругу - нагрузка постоянна при
/// любом числе урн); полная анимация только ближайших ~15-20 духов; замер 100 урн»).
/// Бюджет: дел (пробный шаг + настоящий) за кадр - не больше StepsPerFrame на все урны сейва; урна, которой не досталось,
/// пробует в следующем кадре. Урна делает дело раз в CinerariumWork.StepSeconds, поэтому очередь сама идёт по кругу.
/// Анимация: двигаются (полёт, жесты, покачивание) только AnimateNearest ближайших к игроку духов, остальные замирают дома,
/// как уснувшие (CinerariumGenieMotion); работа у всех идёт.
/// Прилив (решение 17.10, «Прилив прячет некротехнологов»): ночью кровавой луны духи всех урн уходят в урны и работа стоит -
/// признак берётся раз за кадр.
/// </summary>
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();
}
}
/// <summary>Взять одно дело из бюджета кадра. _who - урна (учёт для necrourn budget).</summary>
public static bool TryTakeStep(Vector3i _who)
{
NewFrame();
if (_used >= StepsPerFrame)
{
Count(_denied, _who);
return false;
}
_used++;
Count(_granted, _who);
return true;
}
// Учёт бюджета (23.10: сборщики 600 с не получили ни одного шага, логист рядом - получал): кому дали, кому отказали.
static readonly Dictionary<Vector3i, int> _granted = new Dictionary<Vector3i, int>();
static readonly Dictionary<Vector3i, int> _denied = new Dictionary<Vector3i, int>();
static float _countSince;
static int _frames0;
static void Count(Dictionary<Vector3i, int> _d, Vector3i _who)
{
_d.TryGetValue(_who, out int n);
_d[_who] = n + 1;
}
/// <summary>Сводка с прошлого вызова и сброс (команда necrourn budget).</summary>
public static string Budget()
{
float sec = Time.time - _countSince;
int frames = Time.frameCount - _frames0;
string s = "за " + sec.ToString("0") + " с, кадров " + frames + ", лимит " + StepsPerFrame + "/кадр; дано:";
foreach (KeyValuePair<Vector3i, int> kv in _granted)
{
s += " " + kv.Key + "=" + kv.Value;
}
s += "; отказано:";
foreach (KeyValuePair<Vector3i, int> kv in _denied)
{
s += " " + kv.Key + "=" + kv.Value;
}
_granted.Clear();
_denied.Clear();
_countSince = Time.time;
_frames0 = Time.frameCount;
return s;
}
/// <summary>Идёт ли кровавый прилив (раз за кадр).</summary>
public static bool Tide
{
get
{
NewFrame();
return _tide;
}
}
// ------------------------------------------------------------------
// Ближайшие духи.
// ------------------------------------------------------------------
static float _nextRank;
static readonly HashSet<CinerariumGenieMotion> _animated = new HashSet<CinerariumGenieMotion>();
static readonly List<CinerariumGenieMotion> _sorted = new List<CinerariumGenieMotion>();
/// <summary>Двигается ли дух: среди AnimateNearest ближайших к игроку (пересчёт раз в 0.5 с, без выделений памяти).</summary>
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]);
}
}
}
/// <summary>necrofps [секунд] - средний и худший кадр за время замера (по умолчанию 5 с), число духов и урн; ответ - строкой
/// почтового ящика в лог. Для замера нагрузки урн (17.10: «замер 100 урн»).</summary>
[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<string> _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));
}
/// <summary>_uncap - на время замера снять вертикальную синхронизацию и предел кадров: при 60 fps по синхронизации
/// запас не виден (18.10, замер 100 урн).</summary>
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);
}
}
/// <summary>necrotide on|off|auto - прилив для проверок без ожидания ночи кровавой луны (SnakeMode.ForceTide).</summary>
[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<string> _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() ? "прилив" : "не прилив"));
}
}
}