Статьи

Использование IndexedDB для управления активами 3D WebGL

В этой статье я хотел бы поделиться всем, чему я научился при разработке поддержки IndexedDB в нашем игровом движке 3D WebGL Babylon.JS . Действительно, начиная с версии 1.4.x, мы теперь поддерживаем хранение и загрузку сцен JSON, содержащих наши 3d-сетки и их текстуры .PNG или .JPG в виде BLOB-объектов из IndexedDB.

Эта статья основана на моем собственном опыте по этой теме. Он основан на том, как я решил различные проблемы, с которыми я столкнулся при работе с IDB. Затем вы найдете некоторые пояснения и советы о том, на что следует обратить внимание при работе с IndexedDB. Я также поделюсь, как и почему мы используем его в нашем движке 3d WebGL. Тем не менее, эта статья может быть полезна для всех, кто смотрит на IndexedDB в целом. 3D-игры просто послужат иллюстрацией его использования.

Введение в IndexedDB

IndexedDB — это нереляционная база данных, использующая механизм ключей / значений . Это БД noSQL. Вы могли видеть это как третье поколение хранилища, обработанного браузером. Первый — печенье, второй — локальное хранилище.

HTML5 Powered with Graphics, 3D & Effects, Performance & Integration, and Offline & Storage

Это спецификация W3C , которая в настоящее время находится в Рекомендации кандидата . Это реализовано большинством современных браузеров: IE10 +, Chrome / Opera & Firefox. Более того, эта спецификация поддерживается в версии без префиксов начиная с IE10, Firefox 16 и Chrome 24 / Opera 15. Похоже, она готова к использованию! Вот почему мы используем его на нашем сайте сегодня: http://www.babylonjs.com

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

Если вы не хотите терять время на это устаревшее содержимое, вот мои 4 рекомендуемые статьи для чтения:

1 — Сама спецификация W3C : http://www.w3.org/TR/IndexedDB/ . Он действительно содержит все и его относительно легко читать. Я часто заканчивал тем, что читал спецификацию, чтобы действительно понять, как она работает, чтобы решить некоторые из моих проблем. Иногда мы просто забываем, что спецификация W3C может быть лучшей документацией. ?
2 — Работа с IndexedDB от Рэймона Камдена . Это совсем недавно, очень хорошо объяснил и идеально подходит для начинающих. Моя статья, вероятно, будет дополнять эту, так как я буду хранить изображения в виде больших двоичных объектов, которые не рассматриваются в этой статье.
3 — IndexedDB на нашем MSDN . Он содержит некоторые интересные детали и большой учебник.
4 — Использование IndexedDB на MDN . Хорошая документация как всегда на MDN.

Поэтому, пожалуйста, прочитайте хотя бы вторую ссылку, если вы еще ничего не знаете об IndexedDB.

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

Зачем использовать IndexedDB в наших игровых сценариях?

Я начал думать об использовании IndexedDB во время моих летних каникул. Я был дома со своей невероятной 2-мегабайтной ADSL-линией, и я был подавлен каждый раз, когда мне нужно было перезагрузить сцену с нашего сайта. Для загрузки некоторых сцен может потребоваться более 5 минут. Тогда мне стало интересно: « Поскольку я уже скачал все ресурсы один раз, зачем мне их повторно скачивать? »

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

Я хотел что-то лучшее для игрового процесса. Как игрок, я могу загрузить ресурсы во время первого запуска игры. Но я не хочу терять время на повторную загрузку, потому что мой браузер решил очистить часть своего кеша. Когда я играю в игру, я хочу играть в нее немедленно. Изолируя данные игры в IndexedDB, у нас меньше шансов попасть в различные сценарии очистки кэша. Мы тогда приобретаем большую независимость.

Более того, мы недавно отправили в BabylonJS инкрементальный загрузчик . Это означает, что сцена будет загружена почти сразу, и мы будем загружать ресурсы по требованию в зависимости от того, куда в данный момент смотрит камера. Небольшая проблема с этим подходом заключается в том, что ресурсы (сетки геометрии и текстуры) будут сначала загружаться с веб-сервера и внедряться в 3D-движок. Мы будем страдать от задержки сети. Инкрементная геометрия не будет отображаться сразу и внезапно появится через пару секунд после того, как игрок переместит камеру. Используя наш подход IndexedDB, мы можем предварительно загрузить ресурсы в БД в фоновом режиме и загрузить их практически мгновенно с помощью инкрементного загрузчика. Затем мы удалим проблему с задержкой в ​​сети. Над этим нужно еще поработать, но теперь у нас есть все, чтобы построить его в будущей версии.

Наконец, возможность хранить активы в IndexedDB включает автономный сценарий . Теперь вы можете представить себе игру, загружающуюся из Интернета и работающую без проблем после этого! Вам просто нужно объединить API-интерфейсы HTML5 Application Cache с IndexedDB .

Чтобы проиллюстрировать это, нажмите на изображение ниже, чтобы перейти к онлайн-демонстрации:

image

Загрузите сцену « Сердце », нажмите кнопку «Назад», а затем загрузите сцену « Omega Crusher ». Сделав это, вы сохраните обе сцены в IndexedDB. Теперь попробуйте отключить сетевой адаптер, чтобы перейти в автономный режим. Вы должны быть в состоянии перейти на домашнюю страницу и запустить обе сцены даже без какого-либо сетевого подключения вообще!

Я объясню, как построить такую ​​демонстрацию в последней части этой статьи.

Понимание рабочего процесса выполнения IndexedDB и обработка исключений

Прежде всего, обратите внимание, что весь код, который я написал для Babylon.JS, доступен на GitHub здесь: babylon.database.js . Не стесняйтесь взглянуть, чтобы лучше понять ниже объяснения.

Более того, мой первый совет: зарегистрируйтесь на все возможные события, описанные в спецификации W3C, и поместите в них простой console.log () во время процесса разработки, чтобы понять конвейер выполнения.

Открытие базы данных

Давайте начнем с рассмотрения того, что произойдет / может произойти, когда вы откроете индексированную базу данных.

Drawing1

Первая ошибка, которую я сделал, состояла в том, чтобы думать, что событие onupgradeneeded не сопровождалось событием onsuccess . Я полагал, что onsuccess был поднят, только если БД уже существовала и открылась успешно. Таким образом, я помещал свой обратный вызов успеха в оба обработчика событий. Затем он был логически активирован дважды, но я ожидал, что он будет активирован только один раз. В заключение, вызовите вашу функцию обратного вызова только внутри обработчика события onsuccess .

Более того, вы можете перейти от onupgradeneeded к onerror, если при запросе пользователь отказался от доступа к БД. Например, вот запрос, отображаемый в Internet Explorer:

image

Если пользователь нажимает « не для этого сайта », вы попадете в обработчик ошибки .

Вы можете проверить мой код, прочитав BABYLON . База данных прототип . Функция openAsync () на GitHub .

Обработка хранилища блобов изображений во всех браузерах

Чтобы лучше понять эту часть, вы можете проверить мой код, содержащийся в функции BABYLON.Database.prototype._saveImageIntoDBAsync () на GitHub .

Пожалуйста, взгляните также на эту статью: Хранение изображений и файлов в IndexedDB. Автор Robert Nyman. Это немного устарело, но хорошо объясняет, как хранить изображения в IDB как тип BLOB-объектов.

Глобальная концепция моей функции — хранить текстуры наших 3d-сеток внутри IndexedDB. Для этого я сначала загружаю их, используя XHR2, и запрашиваю тип ответа как BLOB-объект . Я тогда в основном использую тот же подход, что и вышеупомянутая статья.

Однако, тестируя этот подход, я обнаружил, что IE10 + и Firefox хорошо поддерживают хранение изображений в виде больших двоичных объектов в IndexedDB, но не в Chrome. Chrome вызывает ошибку DataCloneError, если вы пытаетесь сохранить структуру BLOB- объектов в своей БД.

Чтобы покрыть конкретный случай Chrome, не анализируя UA (что плохо!), Я защищаю операцию сохранения. Если происходит сбой с кодом ошибки 25, я знаю, что UA не поддерживает хранение больших двоичных объектов. Поскольку я уже загрузил данные через XHR, я просто заполняю элемент изображения HTML с помощью createObjectURL . Но для будущих вызовов я устанавливаю флаг isUASupportingBlobStorage в false, чтобы указать, что кэширование изображений в IDB недоступно для этого браузера.

Я думал о том, как лучше охватить случай Chrome, используя некоторые существующие полифилы, использующие API-интерфейсы FileSystem, или кодируя изображения в base64 для хранения. Затем я обнаружил, что этот поток стекового потока обсуждает ту же проблему: хранение данных изображения для автономного веб-приложения (база данных хранилища на стороне клиента) . Но поскольку в настоящее время открыта ошибка, позволяющая реализовать это в будущей версии Chrome: выпуск 108012: IndexedDB должен поддерживать хранение объектов File / Blob, и, похоже, он скоро будет отправлен, я решил позволить Chrome вернуться к своему образу по умолчанию система кеширования.

Наконец, вы заметите, что в общем случае, в случае ошибки (ошибка XHR или чего-либо другого), я использую классический способ загрузки изображения с помощью HTML-элемента изображения и его свойства src . Таким образом, я максимально использую возможность загрузки наших текстур в процессе сохранения.

Обработка квоты достигнута

Этот заслуживает небольшой схемы, чтобы понять, что происходит! Это подтвердит вам, почему важно понимать, что IndexedDB основан на транзакциях .

Сначала поговорим о квоте по умолчанию в браузере. По умолчанию IE10 + позволяет хранить 10 МБ, прежде чем запросить у пользователя превышение этого лимита. Вы можете изменить это значение в настройках. Затем он имеет максимальный предел 250 МБ на домен, и вы не можете изменить это значение. Итак, у нас есть 2 возможных случая для достижения квоты, и нам нужно разобраться с этим в нашем коде.

image

Firefox предупредит вас, когда вы достигнете предела первой квоты в 50 МБ, тогда у него нет максимальной квоты. Для Chrome ответ менее прост, но вы можете найти способ справиться с квотами здесь: https://developers.google.com/chrome/whitepapers/storage#teven

Теперь, чтобы понять, как правильно обрабатывать квоту, давайте рассмотрим простой случай. Если вы переходите на наш веб-сайт: http://www.babylonjs.com , вы заметите, что есть несколько сцен, доступных для тестирования. Один из них называется FLAT 2009 .

image

Эта сцена имеет файл JSON с именем Flat2009.babylon размером 29 МБ . Файл сцены, конечно, является первым файлом, загружаемым механизмом. Тогда возможно, что при первом переходе на наш сайт вы сначала попробуете эту сцену. Что именно произойдет?

Он загрузит сцену JSON через запрос XHR и попытается сохранить ее в IndexedDB. Давайте возьмем IE11 в качестве браузера. Поскольку для первого предупреждения по умолчанию установлен предел в 10 МБ , этот предел будет достигнут только путем загрузки этой уникальной сцены. Моим первым предположением было то, что операция запроса на запись должна завершиться с ошибкой 29 МБ> 10 МБ. Ну, это не совсем то, что происходит. Чтобы лучше понять, пожалуйста, просмотрите следующую диаграмму:

IndexedDB_Quota

Первая строка кода создает транзакцию . Из этой транзакции мы запускаем запрос на запись, чтобы поместить новую только что загруженную сцену в хранилище « сцен ». Фактически, запрос с именем « addRequest » сначала будет успешно выполнен. Действительно, по логике, ваш браузер должен иметь возможность записывать 29 МБ сцены в БД. Но когда квота достигнута, браузер затем предложит пользователю спросить его, разрешает ли он браузеру превысить квоту по умолчанию. Если пользователь отказывается, транзакция будет прервана, и файл будет удален из БД.

Опять же, вывод такой же, как и раньше. Ваш конечный обработчик успеха должен вызываться из обработчика oncomplete транзакции, а не из обработчика onsuccess запроса.

Вы можете просмотреть эту логику, прочитав код BABYLON.Database.prototype._saveSceneIntoDBAsync () на GitHub . Самая важная часть здесь:

  // Открыть транзакцию в базе данных 
  varaction = that.db.transaction ([ " scene " ], "readwrite" ); 
  // транзакция может быть прервана из-за ошибки QuotaExceededError 
  транзакция.onabort = функция (событие) { 
     попытаться { 
         if (event.srcElement.error.name === "QuotaExceededError" ) { 
             that.hasReachedQuota = true ; 
         } 
     } 
     catch (ex) {} 
     обратный вызов (sceneText); 
  ; 
  транзакция.oncomplete = функция (событие) { 
     обратный вызов (sceneText); 
  ; 

Необходимо проверить « QuotaExceededError », чтобы убедиться, что транзакция была прервана из-за квоты. В моем случае я устанавливаю флаг hasReachedQuota, так как больше нет необходимости пытаться выполнять дальнейшие операции записи в БД, это больше не будет работать.

Несколько советов, которые я выучил и использовал в процессе разработки

Позвольте мне поделиться с вами некоторыми советами, которые я использовал в процессе разработки и которые могут быть вам полезны.

Как очистить / удалить проиндексированные базы данных в различных браузерах

Вам, вероятно, потребуется удалить БД, созданную во время тестов, чтобы перезапустить с нуля.

Internet Explorer

Перейдите в « Свойства обозревателя » -> « Настройки » -> « Кэши и базы данных » и выберите домен, который вы хотите удалить.

image

Хром

Перейдите к chrome: // settings и перейдите в « дополнительные настройки ». Нажмите на кнопку « Очистить данные просмотра… ». Наконец, нажмите кнопку « Очистить данные просмотра » в следующей форме:

image

Или вы можете удалить папку, связанную с вашим доменным именем, прямо здесь: % AppData% \ Local \ Google \ Chrome \ User Data \ Default \ IndexedDB

Fire Fox

Вам нужно перейти в эту папку: % AppData% \ Roaming \ Mozilla \ Firefox \ Profiles \ id вашего профиля \ indexedDB и удалить папку, связанную с вашим доменным именем.

Знаете ли вы о просмотре InPrivate / Incognito?

Если вы просматриваете свой веб-сайт, используя режим браузера InPrivate или Incognito, IndexedDB будет отключен (как, например, localStorage и cookie). window.indexedDB будет неопределенным . Это может быть полезно для выполнения некоторых тестов с / без IndexedDB. Например, для меня было полезно протестировать браузер с поддержкой WebGL без включенного IndexedDB.

Как проверить, действительно ли загружены ресурсы из БД

Во время моих тестов мне всегда было интересно, работает ли логика моей базы данных и действительно ли ресурсы загружаются из моей БД, а не напрямую из Интернета. Я нашел очень простой способ проверить это: используя панель разработки F12 в IE11. Проверьте это самостоятельно:

— используя IE11, перейдите на http://www.babylonjs.com

— нажмите F12 и выберите панель « Сеть », нажмите кнопку « Всегда обновлять с сервера ». Теперь мы просим браузер обойти его кеш и всегда пытаться загрузить ресурсы с веб-сервера. Теперь нажмите кнопку « Play », чтобы начать захват:

image

— попробуйте загрузить сцену « Сердце ». В первый раз вы должны увидеть след вроде этого:

image

38 элементов загружаются с использованием запросов XHR.

— вернитесь на домашнюю страницу и перезагрузите ту же сцену. Теперь вы должны увидеть только 1 HTTP-запрос:

image

Уникальный XHR-запрос отправляется для проверки файла манифеста. Теперь мы уверены, что все остальное исходит от нашей локальной IndexedDB.

Некоторые внутренние детали IE, Firefox и Chrome

Последний совет: я нашел эту статью Аарона Пауэлла очень интересной для чтения: как браузеры хранят данные IndexedDB . Вы узнаете, что IE использует ESE ( Extensible Storage Engine ) для реализации IndexedDB, Firefox использует SQLite, а Chrome использует LevelDB .

Это также в той же статье, где я узнал, где скрыты БД Firefox и Chrome.

Как мы используем это внутри Babylon.JS

Нашей главной целью было сделать его максимально простым для использования в нашем игровом движке и как можно меньше влиять на остальную часть кода. Моя миссия заключалась в том, чтобы внедрить мою логику в две функции загрузки, которые загружают текстуры и файл сцены JSON.

Если вы хотите узнать, как включить поддержку IndexedDB с Babylon.JS, начните с простого прочтения учебника, который я написал на нашей вики: https://github.com/BabylonJS/Babylon.js/wiki/Caching -The-ресурсы-в-IndexedDB

Использование тогда очень просто. Добавьте файл .manifest в свою сцену .babylon и укажите номер версии своих ресурсов, а также, если вы хотите кэшировать сцену, текстуры или и то, и другое.

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

Большинство сцен настроены для использования в автономном режиме для сцены и их текстур на нашем сайте: www.babylonjs.com . Например, вы можете попробовать сцену « Сердце ». Сцена описана в heart.babylon, а связанный файл манифеста — heart.babylon.manifest . Одна из сцен настроена только на кеширование текстуры. Это сцена «Автомобиль ». Это потому, что размер файла JSON, TheCar.babylon , превышает 93 МБ. IE11 и Chrome не могут хранить файлы такого размера в своей БД. Затем я решил не пытаться его кешировать.

Наконец, чтобы создать полностью автономную функциональную демо-версию с использованием Babylon.JS, например, такой как: Babylon.JS offline-демо , вам необходимо связать логику нашей базы данных с API-интерфейсом HTML5 Application Cache. Я уже рассмотрел его использование для 2d canvas игры: Модернизация HTML5-игр Canvas. Часть 2. Автономный API, Drag’n’drop & File API

Подход строго одинаков для 3D-игры WebGL. В этом случае я поместил в файл манифеста HTML5 уменьшенную версию Babylon.JS, а также пару изображений, используемых на домашней странице. Более важно: я включил в него файлы .babylon.manifest. Наконец-то я получил этот простой маленький файл манифеста кэша с именем babylon.cache :

  Манифест кэша 
   Версия 1.1 
  КЭШ:  
  abylon.js 
  and.minified-1.1.1.js 
  ndex.html 
  ndex.css 
  ndex.js 
  creenshots / heart.jpg 
  creenshots / omegacrusher.jpg 
  ssets / BandeauEmbleme.png 
  ssets / Bandeauhaut.png 
  ssets / BtnAbout.png 
  ssets / BtnDownload.png 
  ssets / gradient.png 
  ssets / Logo.png 
  ssets / SpotDown.png 
  ssets / SpotLast.png 
  Cenes / Сердце / Heart.babylon.manifest 
  Cenes / SpaceDek / SpaceDek.babylon.manifest 
  СЕТЬ: 
 

Действительно, если вы не положите. Файлы babylon.manifest в манифест кэша, ошибка 404 будет возникать, когда движок попытается проверить их значения. По умолчанию Babylon.JS предполагает, что это означает, что вы хотите загрузить ресурсы из Интернета.

В заключение, благодаря нашему подходу, теперь представьте, что эта автономная демонстрация Babylon.JS представляет главное меню вашей 3d-игры, и каждая сцена — это определенный уровень вашей игры. Если вы хотите обновить только один из уровней, вам просто нужно изменить версию, включенную в связанный с ней файл .babylon.manifest . Наш 3D игровой движок будет обновлять только этот конкретный уровень в базе данных . Это то, что вы не можете сделать, только используя API кеша приложения HTML5. С AppCache нет дельта-обновлений . Вы вынуждены повторно загрузить все, что указано в файле манифеста кэша. Это будет означать, что обновление одного из уровней вашей игры будет означать полную переустановку игры из Интернета в кэш HTML5.

Я надеюсь, что наш подход и советы вдохновят некоторых из вас на то, чтобы использовать IndexedDB в Интернете! Не стесняйтесь делиться своими отзывами в комментариях.

Первоначально опубликовано: http://blogs.msdn.com/b/davrous/archive/2013/09/24/using-indexeddb-to-handle-your-3d-webgl-assets-sharing-feedbacks-amp-tips-of -babylon-js.aspx . Перепечатано здесь с разрешения автора.