Урны: бюджет дел в секунду с запасом и очередь вместо «3 за кадр» (#78)

Замер necrourn budget: тики всех урн приходят пачкой раз в ~1.4 с в порядке участков от ближних к игроку;
лимит «3 за кадр» доставался трём ближним, 11 урн (сборщики, логист, дальние) не получали шага никогда -
«стою в 19 м - не работают, подхожу - работают». Теперь 30 дел/с, запас 2 с, пополнение по реальному
времени; отказанная урна в очереди и обслуживается первой. После: 578 с, 19 урн по ~128 шагов, отказов 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-23 17:15:57 +03:00
co-authored by Claude Opus 5.5
parent a745f4eb4a
commit 108ff82914
4 changed files with 45 additions and 9 deletions
+43 -7
View File
@@ -8,7 +8,7 @@ namespace NecromancerTome
/// <summary>
/// Меры нагрузки урн (решение 17.10: «общий бюджет шагов работы духов за кадр (очередь по кругу - нагрузка постоянна при
/// любом числе урн); полная анимация только ближайших ~15-20 духов; замер 100 урн»).
/// Бюджет: дел (пробный шаг + настоящий) за кадр - не больше StepsPerFrame на все урны сейва; урна, которой не досталось,
/// Бюджет: дел (пробный шаг + настоящий) в секунду - не больше StepsPerSecond на все урны сейва (23.10, было за кадр); урна, которой не досталось,
/// пробует в следующем кадре. Урна делает дело раз в CinerariumWork.StepSeconds, поэтому очередь сама идёт по кругу.
/// Анимация: двигаются (полёт, жесты, покачивание) только AnimateNearest ближайших к игроку духов, остальные замирают дома,
/// как уснувшие (CinerariumGenieMotion); работа у всех идёт.
@@ -17,11 +17,24 @@ namespace NecromancerTome
/// </summary>
public static class CinerariumLoad
{
public static int StepsPerFrame = 3;
/// <summary>Дел в секунду на все урны (23.10, было «3 за кадр»). Замер: урна получает тик раз в ~1.4 с, и тики всех урн
/// приходят пачкой в одном кадре, в порядке участков от ближних к игроку. Лимит «3 за кадр» доставался трём ближним урнам,
/// дальние не получали шага никогда («стою в 19 м - не работают, подхожу - работают»). Теперь бюджет копится между
/// тиками (не больше StepsPerSecond за раз), и пачка обслуживает всех, у кого подошёл срок. Консоль: necrourn budget N.</summary>
public static float StepsPerSecond = 30f;
/// <summary>Запас бюджета, секунд (пачка тиков раз в ~1.4 с должна уместиться целиком).</summary>
public const float BurstSeconds = 2f;
static float _tokens;
static float _lastRefill;
/// <summary>Очередь (23.10, пользователь: «нужно иметь очередь урн»): урна, которой отказали, ждёт; пока очередь не пуста,
/// бюджет сначала ей - новая урна получит шаг, только если после всех ждущих ещё останется. Иначе ближние урны (их участки
/// идут первыми) снова забирали бы всё. Запись старше QueueForget секунд выбрасывается (урну сняли или участок выгружен).</summary>
static readonly Dictionary<Vector3i, float> _queue = new Dictionary<Vector3i, float>();
const float QueueForget = 10f;
static readonly List<Vector3i> _stale = new List<Vector3i>();
public static int AnimateNearest = 16;
static int _frame = -1;
static int _used;
static bool _tide;
static void NewFrame()
@@ -29,7 +42,23 @@ namespace NecromancerTome
if (Time.frameCount != _frame)
{
_frame = Time.frameCount;
_used = 0;
// По реальному времени с прошлого пополнения: NewFrame зовут только кадры с тиками урн (раз в ~1.4 с), и
// Time.deltaTime (один кадр) давал полшага на пачку (замер 23.10: 279 шагов за 600 с при лимите 30/с).
float now = Time.time;
_tokens = Mathf.Min(StepsPerSecond * BurstSeconds, _tokens + StepsPerSecond * Mathf.Max(0f, now - _lastRefill));
_lastRefill = now;
_stale.Clear();
foreach (KeyValuePair<Vector3i, float> kv in _queue)
{
if (now - kv.Value > QueueForget)
{
_stale.Add(kv.Key);
}
}
foreach (Vector3i p in _stale)
{
_queue.Remove(p);
}
_tide = SnakeMode.IsBloodTide();
}
}
@@ -38,12 +67,19 @@ namespace NecromancerTome
public static bool TryTakeStep(Vector3i _who)
{
NewFrame();
if (_used >= StepsPerFrame)
bool waiting = _queue.ContainsKey(_who);
// Ждущая урна - если есть хоть один шаг; новая - только если хватит и всем ждущим.
if (_tokens < 1f || (!waiting && _tokens < 1f + _queue.Count))
{
if (!waiting)
{
_queue[_who] = Time.time;
}
Count(_denied, _who);
return false;
}
_used++;
_tokens -= 1f;
_queue.Remove(_who);
Count(_granted, _who);
return true;
}
@@ -65,7 +101,7 @@ namespace NecromancerTome
{
float sec = Time.time - _countSince;
int frames = Time.frameCount - _frames0;
string s = "за " + sec.ToString("0") + " с, кадров " + frames + ", лимит " + StepsPerFrame + "/кадр; дано:";
string s = "за " + sec.ToString("0") + " с, кадров " + frames + ", лимит " + StepsPerSecond + "/с, в очереди " + _queue.Count + "; дано:";
foreach (KeyValuePair<Vector3i, int> kv in _granted)
{
s += " " + kv.Key + "=" + kv.Value;
+2 -2
View File
@@ -461,10 +461,10 @@ namespace NecromancerTome
{
if (_p.Count > 0 && _p[0] == "budget")
{
// necrourn budget [N] - кому из урн дали шаг и кому отказали с прошлого вызова; N - лимит дел на кадр (по умолчанию 3)
// necrourn budget [N] - кому из урн дали шаг и кому отказали с прошлого вызова; N - дел в секунду на все урны (30)
if (_p.Count > 1)
{
CinerariumLoad.StepsPerFrame = int.Parse(_p[1]);
CinerariumLoad.StepsPerSecond = float.Parse(_p[1], System.Globalization.CultureInfo.InvariantCulture);
}
Out("necrourn budget: " + CinerariumLoad.Budget());
return;
Binary file not shown.
Binary file not shown.