# Перезапуск скриптов — `$.script`

Движок следит за файлами игры и перезапускает QuickJS, когда они меняются. Это
тот механизм, которым живёт правка кода без перезапуска процесса:

```js
$.ready(() => {
    // Перезапуск по горячей клавише из игры.
    if ($.input.pressed('F5')) $.script.request('правлю интерфейс');
});
```

**Главное правило: перезапуск никогда не случается посреди кадра.** Запрос
только ставится, а выполняется на границе кадра — когда кадр отрисован и
JS-вызовов в нём больше не будет. Раньше рантайм уничтожался прямо в момент
обнаружения правки: если это происходило во время обработки события, кадр
оставался недоигранным.

---

## 1. Пространство имён

| Вызов | Возвращает | Смысл |
|---|---|---|
| `$.script.request(reason?)` | `bool` | попросить перезапуск; случится на границе кадра |
| `$.script.pending()` | `bool` | ждёт ли перезапуска |
| `$.script.hotReload()` | `bool` | следит ли движок за файлами (см. `--no-hot-reload`) |
| `$.script.count()` | `number` | сколько раз рантайм перезапускался за процесс |
| `$.script.error()` | `string` | последняя ошибка скрипта, пустая строка при отсутствии |

Низкоуровневые вызовы: `engine.requestReload(reason)`,
`engine.reloadPending()`, `engine.hotReload()`, `engine.reloads`.

## 2. Когда перезапуск случается сам

* файл `.js` или атлас спрайтов `*.atlas.json` в каталоге игры изменился (mtime и размер, проверка раз в 0.35 с);
* нажата **F5** в игре;
* игра позвала `$.script.request()`.

Во всех трёх случаях перезапуск откладывается до границы кадра, и в журнал
пишется причина: `правка файлов скриптов`, `F5` или текст, переданный игрой.
Первый запрос важнее последующих — если файл изменился и тут же нажали F5, в
журнале будет причина первого.

## 3. Что теряется при перезапуске

Перезапуск **уничтожает весь JS-heap**: узлы мира, подписки, таймеры, твины,
локальные переменные. Игра запускается заново с `main.js`.

Что делать с состоянием:

* `$.save.write(...)` — сохранить в файл до перезапуска;
* `$.store` — держит значения между вызовами, но **не** переживает перезапуск
  (он внутри того же JS-heap);
* `$.fs.write/readJSON` — самый надёжный способ пережить перезапуск;
* `$.script.pending()` — узнать заранее и сохранить состояние в своём `onUpdate`.

## 4. Слежение за файлами

Слежение включено по умолчанию и выключается флагом `--no-hot-reload` (например,
для релизной сборки или чтобы не дёргать диск). Проверка — раз в 0.35 секунды:
складываются mtime и размеры `.js` и `*.atlas.json` файлов в каталоге игры, что надёжнее
сравнения одного mtime на файловой системе с грубыми часами.

`$.script.hotReload()` показывает текущее состояние.

## 5. Ограничения

* **весь JS-heap теряется**: перезапуск — это повторный запуск `main.js`, а не
  частичная замена модуля. Сохранять состояние нужно вручную;
* **перезапуск не мгновенный**: он ждёт границы кадра (обычно меньше 16 мс, но
  на медленном кадре — до конца кадра);
* **неудачный перезапуск оставляет старый рантайм**: если новый скрипт не
  загрузился, движок пишет ошибку и продолжает работать со старым кодом;
* **файлы `.r2d-*` не отслеживаются**: это служебные черновики SDK (`.r2d-sdk-draft.atlas.json`, `.r2d-draft-*`), которые студии пишут рядом с данными для проверки и предпросмотра; без исключения игра перезапускалась бы со старыми данными, пока настоящий файл ещё не сохранён;
* **слежение только за `.js` и `*.atlas.json`**: правка прочего JSON
  (сохранений, данных), изображений и шейдеров перезапуска не вызывает —
  атласы включены, потому что их правит SDK (Sprite Studio), а игра читает
  их при старте;
* **подкаталоги сканируются**, но символические ссылки не разворачиваются.

## 6. Проверка

```bash
# запрос ждёт границы кадра, рантайм перезапускается, игра загружается заново
python3 tests/agent/highlevel_reload_test.py
```
