Перезагрузка ресурсов на лету
Defold позволяет выполнять горячую перезагрузку ресурсов. При разработке игры эта функция помогает значительно ускорить выполнение определенных задач. Она позволяет вам изменять код и содержимое игры во время ее работы. Типовые варианты использования:
- Для настройки параметров игрового процесса в Lua-скриптах.
- Для редактирования и настройки графических элементов (например, эффектов частиц или элементов графического интерфейса) и просмотра результатов в актуальном контексте.
- Для редактирования и настройки кода шейдера и просмотра результатов в актуальном контексте.
- Для облегчения тестирования игры путем перезапуска уровней, настройки состояния и т. д. без остановки игры.
Как сделать горячую перезагрузку
Запустите свою игру из редактора ( Project ▸ Build ).
Чтобы затем перезагрузить обновленный ресурс, просто выберите пункт меню File ▸ Hot Reload или нажмите соответствующую комбинацию на клавиатуре:

Горячая перезагрузка на устройстве
Горячая перезагрузка работает как на устройстве, так и на компьютере. Чтобы использовать её на устройстве, запустите отладочную сборку своей игры или приложение для разработки на своем мобильном устройстве, затем выберите его в качестве цели в редакторе:

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

Добавьте новые кнопки, сохраните и перезагрузите GUI-файл в горячем режиме. Теперь вы можете видеть новые кнопки на экране телефона:

При горячей перезагрузке файла движок выведет в консоль каждый перезагруженный файл ресурсов.
Перезагрузка скриптов
Любой перезагруженный файл Lua-скрипта будет повторно выполнен в среде исполнения Lua.
local my_value = 10 function update(self, dt) print(my_value) end
Изменение my_value на 11 и горячая перезагрузка файла дадут немедленный эффект:
. DEBUG:SCRIPT: 10 DEBUG:SCRIPT: 10 DEBUG:SCRIPT: 10 INFO:RESOURCE: /main/hunter.scriptc was successfully reloaded. DEBUG:SCRIPT: 11 DEBUG:SCRIPT: 11 DEBUG:SCRIPT: 11 .
Обратите внимание, что горячая перезагрузка не влияет на выполнение функций жизненного цикла. Например, вызова init() при горячей перезагрузке не произойдёт. Однако, если вы переопределите функции жизненного цикла, будут использоваться новые версии.
Перезагрузка Lua модулей
Пока вы добавляете переменные в глобальную область видимости в файле модуля, перезагрузка файла приведет к изменению этих глобальных переменных:
--- my_module.lua my_module = <> my_module.val = 10
-- user.script require "my_module" function update(self, dt) print(my_module.val) -- hot reload "my_module.lua" and the new value will print end
Распространенный паттерн модуля Lua — создать локальную таблицу, заполнить ее и затем вернуть:
--- my_module.lua local M = <> -- a new table object is created here M.val = 10 return M
-- user.script local mm = require "my_module" function update(self, dt) print(mm.val) -- will print 10 even if you change and hot reload "my_module.lua" end
Изменение и перезагрузка “my_module.lua” не изменит поведение “user.script”. См. Руководство по модулям для получения дополнительной информации о том, почему и как избежать этой ошибки.
Функция on_reload()
Каждый компонент скрипта может содержать определение функции on_reload() . Если она существует, она будет вызываться каждый раз при перезагрузке скрипта. Это полезно для проверки или изменения данных, отправки сообщений и так далее:
function on_reload(self) print(self.velocity) msg.post("/level#controller", "setup") end
Перезагрузка кода шейдера
При перезагрузке вершинных и фрагментных шейдеров код GLSL перекомпилируется графическим драйвером и загружается в GPU. Если код шейдера вызывает сбой, что легко сделать, поскольку GLSL написан на очень низком уровне, это приведет к остановке движка.
- English
- 中文 (Chinese)
- Español (Spanish)
- Français (French)
- Νεοελληνική γλώσσα (Greek)
- Język polski (Polish)
- Português (Portuguese)
- Русский (Russian)
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
Hot reload и hot restart. Отличие. Flutter

Эти две функции возможны только тогда, когда ваше приложение уже запущено.
Hot reload – отличный функционал, присутствующий во флаттере. Это самая простая и самая быстрая функция, которая помогает вам применять изменения, исправлять ошибки, создавать пользовательские интерфейсы и добавлять функции. На выполнение его функций требуется примерно одна секунда. При горячей перезагрузке не разрушает сохраненное состояние. Но ваше состояние state остается прежним (оно не откатывается к первоначальньному значению по умолчанию, как при первом запуске)
Hot restart – Намного медленнее, чем Hot reload, (однако куда быстрее, чем запуск приложения впервые) применяет все изменения, и возвращает наше состояние state к значению по умолчанию.
P.S. обе функции возможны, благодаря тому, что Flutter в режиме разработки компилируется just-in-time. (JIT – можно перевести как точно в срок, что означает, что же компиляция идет по ходу выполнения приложения, того, что нужно). Именно из-за этого можно сделать hot-reload/restart так быстро.
P.P.S. А в режиме выпуска (когда вы переходите к публикации своего приложения) ваш код компилируется за счет ahead-of-time (AOT – можно перевести как заблаговременная компиляция перед запуском приложения) в собственный код. Это долгий процесс, но зато, когда приложение запускается, то достигается лучшая производительность (т.к. весь код уже скомпилирован), минимального размера вашего файла и удаления других уже не нужных вещей, которые полезны в режиме разработки.
WebPack: как внутри устроено Hot Reloading

Наша платформа voximplant активно использует javascript. С помощью него клиенты управляют в реальном времени звонками, на нем работает наша backend логика и большинство frontend. Javascript мы любим, ценим и стараемся быть в курсе последних новостей. Сейчас наши разработчики активно экспериментируют с перспективной связкой webpack + typescript + react (кстати, для typescript мы сделали type definitions к нашему web sdk, но об этом как-нибудь в другой раз).
Особенно нам нравится «hot module replacement»: возможность при изменении исходников очень быстро отобразить изменения в браузере без перезагрузки страницы. Выглядит как магия. К сожалению, документировано тоже как магия — по словам eyeofhell, нашего технического евангелиста, «пример на офсайте — это уникальная комбинация частных случаев и особых команд, любое изменение в которых делает его неработоспособным». На наш взгляд все не так плохо, за пару вечеров вполне можно разобраться. Но и не так просто, как хотелось бы. Поэтому специально для Хабра под катом мы максимально просто и понятно расскажем как работает под капотом вся эта машинерия.
Магия как она есть
Официальный пример использования hot module replacement очень прост. Авторы предлагают создать файл style.css с одним стилем:
body
И файл entry.js, который использует основную фичу webpack, команду require, чтобы добавить .css файл к содержимому страницы. Ну и создает элемент типа input, на котором можно проверить hot module replacement:
require("./style.css"); document.write("");
Далее предлагается запустить webpack с помощью заклинания
webpack-dev-server ./entry --hot --inline --module-bind 'css=style!css'
И открыть страницу, доступную по адресу localhost:8080/bundle. После чего можно наблюдать магию hot module replacement: если ввести в поле input какой-нибудь текст, переместить курсор на один из символов этого текста, а затем поменять цвет в файле style.css — то цвет фона страницы поменяется практически сразу, при этом не потеряется введенный текст и даже позиция курсора останется прежней.
Хорошая, удобная магия. Но после начала использования возникает много вопросов:
- Что это за ./entry?
- Что делают и чем отличаются —hot и —inline?
- Что это за —module-bind такой?
- Почему, если добавить react.js, hot reload перестает работать?
Разоблачение магии

Начнем с самого простого. ./entry и —module-bind — это читерские аргументы, которые позволяют в целях демонстрации запускать webpack без конфигурационного файла webpack.config.js. Первый позиционный аргумент всего лишь имя javascript файла, являющегося «точкой входа» в программу, именно его код будет запускаться при выполнении скомпилированной bundle. Многих разработчиков смущает то, что этот аргумент не выглядит как имя файла. На самом деле это имя файла. Просто в целях экономии символов авторы примера воспользовались одной из особенностей webpack: файлы в require и в командной строке можно указывать без расширения, webpack автоматически попробует найти такой файл .js (или с другими расширениями, если это настроено в конфигурации). Аргумент —module-bind позволяет без конфигурационного файла указать используемые загрузчики, в данном случае для файлов с расширением css будет использован сначала загрузчик css-loader а затем загрузчик style-loader. Как нетрудно догадаться, суффикс -loader тоже можно не указывать, и авторы примера пользуются этим для экономии нескольких символов и запутывания читателей.
Режим работы «iframe automatic refresh» и встроенный веб сервер
На самом деле у webpack три режима работы автоматического обновления страницы. Самый простой режим называется iframe mode: он включается автоматически, если webpack запустить без ключей командной строки —inline и —hot, то есть вот так:
webpack-dev-server ./entry --module-bind 'css=style!css'
Запущенный веб сервер будет отдавать браузеру следующие страницы:
- localhost:8080/webpack-dev-server Покажет меню, в котором можно посмотреть исходик созданной в памяти bundle или открыть в браузере специальную html страницу, единственное назначение которой — выполнить javascript код bundle
- localhost:8080/webpack-dev-server/ От предыдущей ссылки отличается присутствием слеша на конце. Список файлов в папке, где запущен сервер. Клик по файлу покажет его в iframe и будет автоматически перезагружать, если файл изменится.
- localhost:8080/webpack-dev-server/bundle Та самая страница из первого пункта. Открывается в iframe и автоматически перезагружается. Будет автоматически перезагружаться при изменении любого файла, который приводит к перекомпиляции bundle
- localhost:8080/ и localhost:8080/bundle Ловушка для невнимательных. То же что во втором и третьем пункте, но файлы и bundle открываются не в iframe. Перезагружаться не будет. Зачем она? Для второго режима работы, —inline. Зачем показывать в первом режиме работы? Чтобы запутать разработчиков, конечно же. Ну и чтобы раздавать статику без iframe.
Режим работы «inline automatic refresh» и встраиваемый refresh client
Второй режим работы активируется ключом командной строки —inline и предсказуемо называется «inline» режимом. В этом режиме все несколько сложнее: в bundle добавляется модуль «refresh client», исходный код которого можно посмотреть в файле webpack-dev-server/client/index.js. Этот модуль будет загружен с помощью require перед вашим собственным кодом. Более того, если посмотреть в сгенерированный bundle (с помощью меню веб сервера, о котором я писал выше), то можно увидеть что этот require не совсем обычный:
/* WEBPACK VAR INJECTION */>.call(exports, "?http://localhost:8080"))
Это результат выполнения вот такого кода:
require("index?http://localhost:8080")
Этот слабодокументированный синтаксис «webpack resource query» позволяет передавать произвольные параметры в загружаемый через require код. В данном случае webpack-dev-server генерирует bundle, который при загрузке refresh client передает ему адрес запущенного на машине разработчика webpack-dev-server. Зачем ему адрес? Конечно же чтобы подключиться к серверу через socketio и ждать нотификации об изменениях файлов. Получив такую нотификацию, refresh client перезагрузит страницу. По сути происходит то же что и с iframe, но без iframe. Это позволяет отлаживать чувствительный к url код и используется как вспомогательный механизм для третьего, самого интересного режима работы: hot module replacement
hot module replacement: сильное колдунство для быстрой разработки
Как уже догадался внимательный читатель, третий режим работы включается добавлением ключа командной строки —hot, который возвращает нас к тому заклинанию, с которого началась эта статья. Но здесь не все так просто. «Hot module replacement» — это функциональность webpack, предназначенная не только для быстрой подгрузки изменений на машине разработчика, но и для обновления сайтов в production. При использовании ключа —hot bundle будет собран с поддержкой hot module replacement: соответствующий код и api добавляется в загрузчик webpack, за это отвечает HotModuleReplacementPlugin. Ключ —hot понимает как webpack-dev-server, так и webpack. С помощью hot module replacement api разработчик может запрашивать свой сервер на предмет «а не обновилось ли что», отсылать команду «обновись» дереву модулей и управлять тем, как модули обновляются без перезагрузки страницы.
Здесь два ключевых момента:
- Код, который узнает о факте обновления, должен написать разработчик. Webpack считает хеши модулей и предоставляет ajax api для загрузки обновления с сервера — но вызвать метод module.hot.check разработчик должен сам. Это не навязывает какой-то способ общения с сервером и позволяет разработчикам интегрировать hmr в существующие проекты: узнавать о наличии обновлений можно любым способом, начиная от кнопки «проверить обновления» с ajax запросом и заканчивая websocket подключением от страницы к backend.
- Webpack не обновляет модули сам. Он дает модулям возможность подписаться на callback module.hot.accept, module.hot.decline и module.hot.dispose чтобы реагировать на полученное от сервера обновление своего кода. К примеру, код модуля, отвечающего за загрузку css, может применить обновленные стили. А код модуля, создающего интерфейс ReactJS, вызвать новую версию render(), чтобы перерисовать себя.
Учитывая эти два момента, просто добавление кода hot module replacement ничего не даст — только увеличит размер bundle на несколько килобайт. Нужен еще код, который будет общаться с сервером, узнавать о наличии обновлений и вызывать module.hot.check. И такой код есть! webpack-dev-server, запущенный с ключом —hot, добавляет в собираемый bundle модуль «hot loader», исходник которого можно посмотреть в файле webpack/hot/dev-server.js. Этот модуль, так же как модуль «refresh client», будет загружен перед вашим кодом. Делает он интересную штуку: подписывается на dom event с именем webpackHotUpdate и при получении этого эвента использует hot module replacement api для обновления дерева модулей. Если модули не обновились (то есть в модулях либо нет кода обновления, либо код вернул статус невозможности обновиться), то hot loader перезагружает страницу целиком.
А кто же отсылает эвент webpackHotUpdate? Это делает «refresh client». Тот самый, который добавляется ключем —inline, поддерживает websocket подключение к webpack-dev-server и следит за изменениями файлов. При использовании ключа —hot, webpack-dev-server отправляет refresh client по websocket сообщение «hot», которое переключает refresh client в «hot mode». В этом режиме он перестает обновлять страницу сам, а вместо этого отсылает эвент webpackHotUpdate.
Последний вопрос: откуда берется код, который обновляет CSS стили? Как я уже написал выше, webpack сам ничего обновлять не будет и просто вызовет callback, на который может подписаться модуль. Откуда там этот callback? Сюрприз — style-loader имеет встроенную поддержку «hot module replacement». Специально для того, чтобы работал пример из документации.
Выводы
- Если hot module replacement не работает — проверьте что выбран правильный режим и что используемые loader’ы его поддерживают. «refresh client» и «hot loader» отчитываются в лог о происходящем.
- Если вместо изменения части страницы она перезагружается целиком — тоже смотрите в лог, там вам расскажут какой из модулей не смог hot module replacement.
- Технологию можно использовать не только при отладке на машине разработчика, для этого нужно будет реализовать на стороне клиента и сервера то, что за вас делает webpack-dev-server.
- Поддержку hot module replacement можно добавлять в свои модули и радоваться мгновенному обновлению страницы без перезагрузки во время разработки. Соответствующее api довольно простое и неплохо документировано.
Горячая перезагрузка компонентов в React
— Что будет, если в унитаз поезда на полном ходу бросить лом?
— Что будет, если в реакту по полном ходу заменить компонент?
И если с первым вопросом все более менее понятно, то со вторым вопросом все не так просто — тут же вылезает Webpack-dev-server, Hot Module Replacement, и React-Hot-Loader.
И все бы хорошо, но эта гремучая смесь просто не работает, спотыкаясь на первом же сложном коде, HOC, композиции, декораторе и нативных arrow функциях.
В общем третья версия Reac-hot-loader заводилась далеко не у всех.

Преамбула
На самом деле у версии 3 было 3 проблемы:
— react-proxy, который использовался для «подмены» старой версии компонента новой, и который совсем не дружил с arrow функциями, к которым контекст прибит намертво.
— для обеспечения работы bound и transpiled arrow functions babel plugin оборачивал их в промежуточные функции. С учетом того, что он это делал для ВСЕХ классов — часто это приводило к поломкам. Те вообще все не работало.
— сам принцип поиска и подмены компонентов. Для этого использовался babel plugin, который «регистрировал» все переменные верхнего уровня, таким образом что потом можно было бы понять, что «вот эта переменная», на самом деле «БольшаяКпонка» и следует обновить прокси.
Все бы хорошо, но HOC может создать класс внутри себя, а декоратор просто «декорирует» реальную переменую. Как результат — реакт-реконсилер при рендере встретит «совершенно новый» класс на месте старого и размаутит все старое дерево.
One of the most annoying things to setup with Webpack is a properly working Hot Module Replacement. Working properly means that components will keep their local state. Everything else makes HRM uselss (in my opinion).
Другими словами — без нормального preserve state нафиг этот RHL не нужен.
Мое путешествие в RHL началось полгода назад, когда я примерно неделю (это очень много) пытался понять как все эти годы люди использовали React-hot-loader, потому что использовать его невыносимо больно. Ничего кроме боли он не приносит. А потом открыл PR, который молча стал подсказывать почему и где RHL размаунтит компонент, чтобы люди наконец смогли понять почему он не работает. Чтобы хоть как-то сгладить душевные страдания.
А потом решил починить все на корню.
Встречаем версию 4!
Буду краток — версия 4 работает практически всегда. У нее все еще есть ограничения, которые вытекают из самой природы явления, но эти «ошибки» — гарант правильности работы. Пробовать можно уже сейчас, просто обновите RHL до версии 4.
Что такое HMR, RHL и все такое?
HMR — горячая замена модулей. Встроенный в webpack и parcel механизм, который позволяет обновлять отдельные модули на клиенте, при их изменении на сервере. Только не забываем — это исключительно про разработку. Без дополнительных телодвижений молча отрефрешит страницу.
RHL — react-hot-loader, комплекс мер которые направлены на то, чтобы React просто перерендерил обновленный элемент, и не делал больше ничего.
RHL и есть то нечто, что обеспечивает «правильный» HMR в том виде, в каком он и нужен.
Есть альтернативная теория, гласящая что HMR ведет к monkey-patchингу, в лучше TDD, BDD и все такое. И большая такая капля правды в ней есть.
Отличия версии 4 от версии 3
1. Вместо react-proxy используется react-stand-in — чуть более «javascriptовое» решение. Основное отличие — standin это не «враппер», и не прокси — это класс который наследуется от реального.
Это очень хитрый момент — когда настанет время заменить один компонент другим — standin просто поменяет себе прототип (точнее прототип своего прототипа).
Результат — this никогда не меняется.
Второй хитрый момент — перенос изменений сделанных в конструкторе (в том числе добавленных в конструктор бабелем). Standin инстанцирует старый и новый класс, после чего ищет изменения и пробует их повторить.
В итоге — после обновления компоненты применяются все изменения, кроме сделанных в componentWill/DidMount. Потому что mount/unmount не происходил, и именно его и требовалось избежать.
2. Вместо «регистраций», которые не дружат с HOC и декораторами используется механизм названный «hotReplacementRender».
Когда наступает момент «обновления» компоненты React-Hot-Loader сохраняет старое дерево, и начинает сам рендерить новое.
После рендера каждого элемента наступает момент «сравнения» старых и новых данных, фактически реконсилер.
Если новый компонент «очень похож» на старый — возможно это он и есть, и можно произвести замену.
Такой подход без проблем пробивает любые композиции, декораторы, и вообще любые render-props. Единственный момент, который не переживает — изменение колличества или порядка следования детей (если не заданы key), но это вытекает уже из самой природы Reactа.
3. Раньше многие люди спотыкались на моменте настройки HRM и RHL. В новой версии вся настройка ограничивается _одной_ командой.
import React from 'react'; import from 'react-hot-loader'; const MySuperApplication = () => . export default hot(module)(MySuperApplication);
Магия hot(module)(MySuperApplication) автоматически настроит HMR для текущего модуля, а HOC часть этой функции обернет MySuperApplication в AppContainer.
ВСЕ! (Плюс babel-plugin не забудьте)
Где работает? Везде — Webpack, parcel, typescript(babel-plugin не забудьте). Примеры есть на все.
Особенности работы, про которые лучше знать
1. RHL v3 оборачивал в прокси только «зарегистрированные» компоненты. v4 оборачивает абсолютно все. Теоретически это никак не должно сказываться на производительности, но всякое бывает.
2. Hot настраивает текущий модуль как self accepted. Тут надо понимать что ничего кроме «реакт-компоненты», который RHL может переживать экспортировать из модуля нельзя.
Стандарные сейчас ошибки:
— использовать hot для локальных переменных или вообще в рендере (детектим и ругаемся)
— использовать hot для HOC и функций — молча все ломается.
Hot — _только_ для компонент.
3. Если в componentDidMount вы запустили таймер, а потом изменили функцию которую этот таймер вызывает — она не обновиться. Потому что setTimeout уже получил ссылку на функцию, и поменять ее нельзя.
4. Code splitting — требуется или обернуть в hot каждый компонент который вы собираетесь динамически загружать, тогда он «сам себя» обновит, или использовать «загрузчик» который знаком с HMR/RHL.
— популярный react-lodable в принципе не подходит. Если он у вас — никаких других вариантов, крое как обернуть компоненты в hot — нет. (не забываем что это никак не сказывается на продакшене)
— loadable-components немного лучше — они попытаются обновить файл, но могут быть проблемы, так как «hotReplacementRender» будет уже выключен когда сработает import.
— 100% хорошо работает react-imported-component, так как оборачивает обображаемый компонент в AppContainer, и это «спасает» ситуацию, но его SSR немного специфичен, и подойдет не всем.
5. Я думаю это далеко не конец.
React-hot-loader это не серебрянная пуля — а достаточно узкоспециализированный инструмент, который может быть очень полезен. А может и не быть.
Время попробовать?
Прямо сейчас мы близки к выпуску RC версии. Банально больше нет багов чинить и фич добавлять. Только немного code coverage повышать.
Сейчас самое то время установить RHL себе, и попробовать его в деле. И сообщить о всех проблемах что возникнуть (конечно же!) не должны 😀