reckfellENGINE

Поиск утечек: объекты движка и память C#

Различайте количество нативных объектов, отсоединённые Entity и управляемую память.

На этой странице

Счётчики отвечают на разные вопросы

ПоказательЧто измеряет
ObjectCountЧисло нативных объектов, зарегистрированных в ObjectDB
ObjectNodeCountEntity внутри активного EntityTree; точное имя константы SDK содержит Node
ObjectOrphanNodeCountEntity вне активного дерева, включая детей отсоединённой иерархии; диагностическая сборка
GC.GetTotalMemory(false)Приблизительный объём managed-памяти .NET, не память всего процесса

Первые три показателя читайте через Performance.GetMonitor с соответствующим значением Performance.Monitor.

ObjectOrphanNodeCount не означает только отсутствие родителя: ребёнок у отсоединённого корня тоже находится вне дерева. В сборке без DEBUG_ENABLED этот монитор возвращает 0, поэтому ноль там не доказывает отсутствие таких объектов.

Не ищите решение только во вкладке ObjectDB Profiler: наличие такого snapshot-интерфейса в поставке здесь не подтверждено. Доступные счётчики не являются графом удерживающих ссылок и не показывают все нативные буферы, GPU-память или managed-массивы.

Отсоединение, удаление и C#-ссылка

ObjectLifetimeProbe.cs
C#
using System;
using System.Collections.Generic;
using Reckfell;

public static class ObjectLifetimeProbe
{
    public static bool Run()
    {
        var retained = new List<Entity>();
        var parent = new Entity();
        var child = new Entity();
        retained.Add(child);
        parent.AddChild(child);
        parent.RemoveChild(child);
        parent.Free();
        bool detachedSurvived = ReckfellObject.IsInstanceValid(child);
        child.Free();
        bool wrapperOutlivedNative = retained[0] is not null &&
            !ReckfellObject.IsInstanceValid(retained[0]);
        retained.Clear();
        return detachedSurvived && wrapperOutlivedNative;
    }

    public static void PrintSnapshot()
    {
        double objects = Performance.GetMonitor(Performance.Monitor.ObjectCount);
        double entities = Performance.GetMonitor(Performance.Monitor.ObjectNodeCount);
        double orphans = Performance.GetMonitor(Performance.Monitor.ObjectOrphanNodeCount);
        long managed = GC.GetTotalMemory(false);
        ReckfellApi.Print($"MEMORY objects={objects}; entitiesInTree={entities}; outsideTree={orphans}; managedApproxBytes={managed}");
    }
}

Run возвращает true: RemoveChild оставляет ребёнка живым, после Free родителя он всё ещё существует. Затем Free ребёнка уничтожает нативный объект, хотя List ещё хранит C#-обёртку. Поэтому не проверяйте жизнь нативного объекта только через ссылку != null.

Если ребёнок остаётся прикреплённым, Free родителя освобождает его вместе с родителем. Отдельно удерживаемые отсоединённые объекты должен освобождать их хозяин в логике приложения. При QueueFree дождитесь выполнения отложенного удаления, прежде чем сравнивать состояние.

Подтвердить рост, а не угадать причину

Повторяйте один сценарий: открыть уровень, создать объекты, закрыть уровень и дождаться отложенных удалений. Сравните одинаковую точку нескольких циклов. Первый рост может быть загрузкой кэша или инициализацией, а стабильное количество объектов не доказывает отсутствие роста их внутренних буферов.

Обычный new byte[8192] увеличивает managed-выделения, но не добавляет объект в ObjectDB. Сохраняйте список собственных созданных объектов, если нужно установить, кто не был удалён; для цепочек удержания managed-объектов нужен отдельный .NET memory-инструмент. Одна цифра ObjectCount не даёт полного диагноза утечки.

Запустить пример · Ответственность за удаление

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.