FPS: утечка привязки cvar() в шкале некромантии; пирамида при качестве текстур ниже Full

FPS сползал 60 -> 15-30 за 10-15 минут в любом мире; перезаход сбрасывал, в режиме
змеи (пояс скрыт) тормозов не было. Замер показал: группа окон toolbelt 2 -> 23 мс/кадр
при неизменном числе элементов. Причина - ошибка игры: BindingNcalcFunctions.cvar при
каждом вычислении регистрирует новый VariableStateCVar, RegisterVariable добавляет без
проверки, новая переменная стартует с 0 и в следующем кадре снова вызывает пересчёт.
Шкала fill="{% cvar('necroNecromancyProgressCVar') / 20 }" (с 1.3.0) набирала по одной
переменной за кадр. CVarBindingLeakPatch: одна CVar регистрируется в привязке один раз.
Проверено: 15 мин, 25 убийств - FPS 55-58 ровно, toolbelt ~1 мс/кадр.

Пирамида: на Texture Quality ниже Full атлас блоков игры меньше нашей текстуры 512, и
CustomBlockPaint её пропускал. Теперь берётся мип под размер атласа; бандл necroatlas
пересобран с ignoreMipmapLimit (иначе CopyTexture отказывает и пирамида чёрная).
Проверено на Half.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-09-29 00:11:59 +03:00
co-authored by Claude Opus 5.5
parent 048307eda8
commit 888cf1ee7e
5 changed files with 58 additions and 5 deletions
+42
View File
@@ -0,0 +1,42 @@
using HarmonyLib;
namespace NecromancerTome
{
/// <summary>
/// Утечка в самой игре, на которую наступала шкала некромантии в поясе (1.3.0+,
/// fill="{% cvar('necroNecromancyProgressCVar') / 20 }" в Config/XUi_InGame/windows.xml).
///
/// Найдено 28.09 замером (NecroProfiler): FPS 60 -> 30 за 10-15 минут в любом мире, в теле;
/// группа окон toolbelt росла 2 -> 23 мс/кадр при неизменных 573 элементах; в режиме змеи пояс
/// скрыт - и тормозов нет; перезаход в мир сбрасывает.
///
/// Механика (декомпиляция 3.3): BindingNcalcFunctions.cvar при каждом вычислении выражения
/// регистрирует НОВЫЙ VariableStateCVar, а BindingInfoNcalc.RegisterVariable добавляет его в
/// список без проверки. Новая переменная начинает с 0; если шкала не пуста, её RefreshValue в
/// следующем кадре видит «изменилось» -> выражение пересчитывается -> cvar() добавляет ещё одну.
/// Плюс одна переменная за кадр, пока CVar не 0, и каждый кадр обходится весь список.
///
/// Лечение: одну и ту же CVar в одной привязке регистрируем один раз. Значение cvar() берёт
/// прямо у игрока, так что отслеживающая переменная нужна только одна - для «изменилось».
/// </summary>
[HarmonyPatch(typeof(BindingInfoNcalc), nameof(BindingInfoNcalc.RegisterVariable))]
public static class Patch_BindingInfoNcalc_RegisterVariable_CVarLeak
{
public static bool Prefix(BindingInfoNcalc __instance, BindingInfoNcalc.VariableStateAbs _var, ref bool __result)
{
if (!(_var is BindingInfoNcalc.VariableStateCVar added))
{
return true;
}
foreach (BindingInfoNcalc.VariableStateAbs existing in __instance.variables)
{
if (existing is BindingInfoNcalc.VariableStateCVar known && known.cVarName == added.cVarName)
{
__result = true;
return false;
}
}
return true;
}
}
}