Как SKTexture кэширует и использует повторно в SpriteKit?

В Stackru часто упоминается, что SpriteKit выполняет свое собственное внутреннее кэширование и повторное использование.

Если я не приложу никаких усилий для повторного использования текстур или атласов, какое поведение при кэшировании и повторном использовании я могу ожидать от SpriteKit?

1 ответ

Решение

Кэширование текстур в SpriteKit

"Короче говоря: положитесь на Sprite Kit, чтобы сделать то, что вам нужно". -@LearnCocos2D

Вот длинная история о iOS 9.

Объемные данные изображения текстуры не хранятся непосредственно на SKTextureобъект. (См. SKTexture ссылка на класс.)

  • SKTexture откладывает загрузку данных до необходимости. Создание текстуры, даже из большого файла изображения, выполняется быстро и занимает мало памяти.

  • Данные для текстуры загружаются с диска обычно при создании соответствующего узла спрайта. (Или на самом деле, когда нужны данные, например, когда size метод называется).

  • Данные для текстуры подготавливаются для рендеринга во время (первого) прохода рендеринга.

SpriteKit имеет некоторое встроенное кэширование для объемных данных изображения текстуры. Две особенности:

  1. Объемные данные изображения текстуры кэшируются SpriteKit до тех пор, пока SpriteKit не захочет избавиться от них.

    • Согласно SKTexture ссылка на класс: "Как только объект SKTexture готов к визуализации, он остается готовым, пока не будут удалены все сильные ссылки на объект текстуры".

    • В текущей iOS, она имеет тенденцию держаться дольше, возможно, даже после того, как вся сцена исчезла. Комментарий Stackru цитирует техническую поддержку Apple: "iOS освобождает память, кэшированную +textureWithImageNamed: или +imageNamed: когда считает нужным, например, когда обнаруживает состояние нехватки памяти".

    • В симуляторе, запустив тестовый проект, я смог увидеть текстурную память, восстановленную сразу после removeFromParent, Работая на физическом устройстве, память, казалось, задержалась; многократный рендеринг и освобождение текстуры не привели к дополнительному доступу к диску.

    • Интересно: может ли память рендеринга быть освобождена рано в некоторых критических для памяти ситуациях (когда текстура сохраняется, но в данный момент не отображается)?

  2. SpriteKit эффективно использует кэшированные объемные данные изображений.

    • В моих экспериментах было трудно не использовать его повторно.

    • Скажем, у вас есть текстура, отображаемая в узле спрайта, но вместо повторного использования SKTexture объект, вы звоните [SKTexture textureWithImageNamed:] для того же имени изображения. Текстура не будет идентичной указателю по отношению к исходной текстуре, но будет использовать громоздкие данные изображения.

    • Вышесказанное верно, является ли файл изображения частью атласа или нет.

    • Вышесказанное верно, была ли загружена исходная текстура с использованием[SKTexture textureWithImageNamed:] или используя [SKTextureAtlas textureNamed:],

    • Другой пример. Допустим, вы создаете объект атласа текстуры, используя [SKTextureAtlas atlasNamed:], Вы берете одну из ее текстур, используя textureNamed:и вы не сохраните атлас. Вы отображаете текстуру в узле спрайта (и, таким образом, текстура сильно сохраняется в вашем приложении), но вы не беспокоитесь об отслеживании этого конкретного SKTexture в кеше. Затем вы делаете все это снова: новый текстурный атлас, новая текстура, новый узел. Все эти объекты будут выделены заново, но они относительно легки. Между тем, и это важно: первоначально загруженные объемные данные изображения будут прозрачно распределены между экземплярами.

    • Попробуйте это: вы загружаете атлас монстров по имени, а затем берете одну из его текстур орков и визуализируете ее в узле спрайтов орков. Затем проигрыватель возвращается на домашний экран. Вы кодируете узел orc во время сохранения состояния приложения, а затем декодируете его во время восстановления состояния приложения. (Когда он кодирует, он не кодирует свои двоичные данные; вместо этого он кодирует свое имя.) В восстановленном приложении вы создаете другого орка (с новым атласом, текстурой и узлом). Будет ли этот новый орк делиться своими громоздкими данными с декодированными орками? Да. Да это orcking будет.

    • Практически единственный способ получить текстуру без повторного использования данных текстурного изображения - это инициализировать ее, используя [SKTexture textureWithImage:], Конечно, может быть UIImage будет выполнять собственное внутреннее кэширование файла изображения, но в любом случае, SKTextureберет на себя ответственность за данные, и не будет повторно использовать данные рендеринга в другом месте.

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

Объедините эти две точки: SpriteKit имеет встроенный кэш, в котором хранятся важные объемные данные изображений и разумно их повторно использует.

Другими словами, это просто работает.

Не обещаю. В симуляторе, на котором запущено тестовое приложение, я могу легко доказать, что SpriteKit удаляет мои данные текстуры из кэша, прежде чем я действительно с этим покончу.

Однако во время создания прототипа вы можете быть удивлены, обнаружив в своем приложении достаточно хорошее поведение, даже если вы никогда не используете один атлас или текстуру повторно.

У SpriteKit слишком кешируется атлас

SpriteKit имеет механизм кэширования специально для текстурных атласов. Это работает так:

  • Ты звонишь [SKTextureAtlas atlasNamed:] загрузить текстурный атлас. (Как упоминалось ранее, это еще не загружает объемные данные изображения.)

  • Вы сильно сохраняете атлас где-то в своем приложении.

  • Позже, если вы позвоните [SKTextureAtlas atlasNamed:] с тем же именем атласа возвращаемый объект будет идентичным указателю на сохраненный атлас. Текстуры извлечены из атласа с использованиемtextureNamed:тогда тоже будет идентичным указателю. (Обновление: текстуры не обязательно будут идентичны указателям в iOS10.)

Следует отметить, что объекты текстуры не сохраняют свои атласы.

Вы все еще хотите создать свой собственный кеш

Итак, я вижу, что вы все равно создаете свои собственные механизмы кэширования и повторного использования. Зачем ты это делаешь?

  • В конечном итоге у вас есть лучшая информация о том, когда сохранять или удалять определенные текстуры.

  • Вам может понадобиться полный контроль над временем загрузки. Например, если вы хотите, чтобы ваши текстуры появлялись сразу при первом рендеринге, вы будете использовать preload методы из SKTexture а такжеSKTextureAtlas, В этом случае вы должны сохранить ссылки на предварительно загруженные текстуры или атласы, верно? Или SpriteKit кеширует их для вас независимо? Неясно. Пользовательский атлас или кеш текстуры - хороший способ сохранить полный контроль.

  • В определенный момент оптимизации (недай бог, преждевременно!!) имеет смысл прекратить создавать новые SKTexture и / илиSKTextureAtlas объекты снова и снова, независимо от того, насколько легкий. Возможно, вы сначала создадите механизм повторного использования атласа, поскольку атласы менее легкие (в конце концов, у них есть словарь текстур). Позже вы можете создать отдельный механизм кэширования текстур для повторного использования не атласа SKTexture объекты. Или, может быть, вы никогда не найдете этот второй. В конце концов, вы заняты, а кухня сама себя не убирает, черт возьми.

Все это говорит о том, что ваше поведение при кешировании и повторном использовании, вероятно, в конечном итоге будет очень похожим на SpriteKit.

Как кэширование текстур в SpriteKit влияет на ваш собственный дизайн кэша текстур? Вот вещи (сверху), которые нужно иметь в виду:

  • Вы не можете напрямую контролировать время выпуска громоздких данных изображения при использовании именованных текстур. Вы освобождаете свои ссылки, а SpriteKit освобождает память, когда захочет.

  • Вы можете контролировать время загрузки объемных данных изображения, используя preload методы.

  • Если вы полагаетесь на внутреннее кеширование SpriteKit, то кешу атласа нужно только сохранять ссылки на SKTextureAtlas объекты, а не вернуть их. Объект атласа будет автоматически повторно использоваться в вашем приложении.

  • Точно так же ваш кеш текстуры должен сохранять только ссылки на SKTexture объекты, а не вернуть их. Объемные данные изображения будут автоматически повторно использоваться в вашем приложении. (Однако это немного меня утомляет; проверять хорошее поведение - боль.)

  • Учитывая последние два пункта, рассмотрим альтернативные варианты для одноэлементного объекта кэша. Вместо этого вы можете сохранить используемые атласы на ваших спрайтовых объектах или их контроллерах. В течение срока службы контроллера любые вызовы в вашем приложении atlasNamed: будет повторно использовать идентичный указателю атлас.

  • Два указателя идентичны SKTexture да, объекты совместно используют одну и ту же память, но из-за кэширования SpriteKit обратное утверждение не всегда верно. Если вы отлаживаете проблемы с памятью и находите два SKTextureобъекты, которые, как вы ожидали, идентичны указателям, но не являются ими, все равно могут делиться своими объемными данными изображения.

тестирование

Я новичок в инструментах, поэтому я только что измерил общее использование памяти приложения при сборке релиза с помощью инструмента Allocations.

Я обнаружил, что "All Heap & Anonymous VM" будет чередоваться между двумя стабильными значениями при последовательных запусках. Я запускал каждый тест несколько раз и в качестве результата использовал самое низкое значение памяти.

Для моего тестирования у меня есть два разных атласа с двумя изображениями в каждом; назовите атласы A и B и изображения 1 и 2. Исходные изображения являются большими (один 760 КиБ, один 950 КиБ).

Атласы загружаются с помощью [SKTextureAtlas atlasNamed:], Текстуры загружаются с помощью [SKTexture textureWithImageNamed:], В приведенной ниже таблице загрузка действительно означает "помещение узла спрайта и рендеринг".

 All Heap
& Anon VM
    (MiB)  Test
---------  ------------------------------------------------------

   106.67  baseline
   106.67  preload atlases but no nodes

   110.81  load A1
   110.81  load A1 and reuse in different two sprite nodes
   110.81  load A1 with retained atlas
   110.81  load A1,A1
   110.81  load A1,A1 with retained atlas
   110.81  load A1,A2
   110.81  load A1,A2 with retained atlas
   110.81  load A1 two different ways*
   110.81  load A1 two different ways* with retained atlas
   110.81  load A1 or A2 randomly on each tap
   110.81  load A1 or A2 randomly on each tap with retained atlas

   114.87  load A1,B1
   114.87  load A1,A2,B1,B2
   114.87  load A1,A2,B1,B2 with preload atlases

* Load A1 two different ways: Once using [SKTexture
  textureWithImageNamed:] and once using [SKTextureAtlas
  textureNamed:].

Внутренняя структура

При исследовании я обнаружил некоторые истинные факты о внутренней структуре текстур и объектов атласа в SpriteKit.

Интересно? Это зависит от того, какие вещи вас интересуют!

Структура текстуры из SKTextureAtlas

Когда атлас загружен [SKTextureAtlas atlasNamed:]проверка его текстур во время выполнения показывает некоторое повторное использование.

  • Во время сборки XCode скрипт компилирует атлас из отдельных файлов изображений в несколько больших изображений листов спрайтов (ограниченных по размеру и сгруппированных по разрешению @1x @2x @3x). Каждая текстура в атласе относится к своему изображению спрайт-листа по пути пакета, хранящемуся в_imgName_isPath установить истину).

  • Каждая текстура в атласе индивидуально идентифицируется по_subTextureNameи имеет textureRect вставить в свой лист спрайта.

  • Все текстуры в атласе, которые используют одно и то же изображение спрайт-листа, будут иметь идентичные не ноль ивара _originalTexture а также_textureCache,

  • Общий _originalTextureсама SKTexture объект, предположительно, представляет все изображение листа спрайта. Не имеет_subTextureName своего, и его textureRect является (0, 0, 1, 1),

Если атлас выпущен из памяти и затем загружен, новая копия будет иметь другое SKTexture объекты, разные _originalTextureобъекты и разные _textureCache объекты. Из того, что я мог видеть, только _imgName (то есть фактический файл изображения) соединяет новый атлас со старым атласом.

Структура текстуры не из SKTextureAtlas

Когда текстура загружается с помощью [SKTexture textureWithImageNamed:], это может быть из атласа, но, похоже, не изSKTextureAtlas,

Текстура, загруженная таким образом, имеет отличия от приведенных выше:

  • Имеет короткий _imgNameнапример, "giraffe.png" и _isPath установлено в ложь.

  • У него есть неустановленный _originalTexture,

  • У него (видимо) свой _textureCache,

Два SKTexture объекты, загруженные textureWithImageNamed: (с тем же именем изображения) не имеют ничего общего, кроме _imgName.

Тем не менее, как подробно описано выше, такая конфигурация текстуры делит объемные данные изображения с другой конфигурацией текстуры. Это означает, что кэширование выполняется близко к реальному файлу изображения.

Другие вопросы по тегам