Представляю вам перевод еще одного доклада с HaxeUp Sessions 2019 Linz, считаю что он хорошо дополняет предыдущий, т.к. продолжает тему изменений в Haxe, произошедших в 2019 году, а также немного рассказывает о его будущем.
Немного об авторе доклада: Аурел Били познакомился с Haxe, участвуя в различных гейм-джемах, и он продолжает в них участвовать (к примеру вот его игра с последнего Ludum Dare 45).
В настоящее время Аурел заканчивает обучение в Имперском колледже Лондона, что подразумевает обязательное прохождение стажировок. Первая стажировка, которую он проходил, была в удаленном офисе, дорога до которого занимала много времени. Поэтому он надеялся, что следующую практику удастся пройти удаленно.
Так получилось, что в Haxe Foundation долгое время не могли найти сотрудника на должность разработчика компилятора. Аурел решил испытать удачу и отправил письмо с заявкой об удаленной работе. Ему повезло — его приняли на шестимесячную стажировку с возможностью работать из Лондона.
При устройстве был оговорен круг задач, которыми будет заниматься Аурел (правда не все в итоге удалось реализовать).
Чем же он занимался?
Во-первых, документацией, которая находилась в печальном состоянии: были описаны все изменения в синтаксисе, новые возможности языка и компилятора, дополнены разделы, посвященные строкам, литералам и константам.
Вся документация была переведена из LaTeX в Markdown!
Во-вторых, форматирование кода стандартной библиотеки было приведено к единому стилю (т.к. над ней на протяжении более 10 лет работали разные люди с разным стилем оформления кода). Таким образом, в репозитории компилятора Haxe Аурел занял седьмое место по количеству добавленных строк кода :)
В третьих, Аурел поработал и над стандартной библиотекой и компилятором:
Например, у контейнера Map
появился новый метод clear()
, который удаляет все хранимые значения. Сделано это в первую очередь для удобства работы с контейнерами, созданными как final
переменные (то есть им нельзя присвоить новое значение, но можно модифицировать их):
Для объектов типа Date
появились методы для работы с датами в формате UTC (универсальное глобальное время). Работа над ними показала, насколько сложно реализовать единое API, одинаково работающее на всех 11 поддерживаемых в Haxe языках / платформах.
В старом компиляторе define’ы и мета-тэги были определены на OCaml, теперь же они описываются в формате JSON, что должно упростить их парсинг внешними утилитами (например, для автоматической генерации документации):
Также вы могли заметить, что на крупных проектах сервер компиляции начинает использовать много памяти.
Для решения этой проблемы Саймон Краевский и Аурел разработали бинарный формат hxb, который используется для сериализации типизированного AST. Теперь сервер компиляции может загрузить модуль в память, работать с ним до тех пор пока он нужен, а затем выгрузить его из памяти в файл в формате hxb и освободить занимаемую память.
Спецификация формата hxb доступна в отдельном репозитории, а текущая его реализация в компиляторе (с сериализатором / десериализатором) лежит в отдельной ветке Haxe. Работа над данной фичей пока что не закончена, и возможно она появится в Haxe 4.1.
Четвертым и основным направлением работы Аурела в ходе стажировки было создание нового асинхронного системного API — asys.
Потребность в его создании обусловлена тем, что существующее API не предоставляет легких способов выполнения системных операций асинхронно. Например, для работы с файлами асинхронно вам придется создать отдельный поток, в котором будут выполняться требуемые операции, и вручную управлять его состоянием. Кроме того, в текущем API не доступны все функциональные возможности для работы с UDP-сокетами, которые есть в стандартных библиотеках в других языках, нет поддержки IPC-сокетов.
При создании и реализации нового API возникает множество вопросов:
Как проектировать API? Может быть стоит взять в качестве примера какое-либо существующее? Ведь мы же не хотим создавать все с нуля, т.к. это потребует больше времени, а также может не прийтись по вкусу остальной команде и стать причиной долгих споров.
И, как уже упоминалось, актуальной для Haxe проблемой является реализация единого API для всех поддерживаемых платформ.
В качестве образца было выбрано API Node.js. Оно хорошо продумано, поддерживает необходимые системные функции и хорошо подходит для создания серверных приложений.
Но в то же время API Node.js — это Javascript API с отсутствием строгой типизации. Например, функции из модуля fs
для работы с файловой системой могут принимать в качестве путей как строки, так и объекты типа Buffer
и даже URL
. А это уже не так хорошо подходит для Haxe.
Node.js в свою очередь использует библиотеку libuv, написанную на C. Работать с API libuv из Haxe напрямую было бы уже не так удобно: например, для того, чтобы асинхронно переименовать файл, вам потребовалось бы дополнительно создать объекты типа uv_loop_t
(структура для управления циклом событий в libuv) и uv_fs_t
(структура для описания запроса к файловой системе):
В итоге API Node.js и libuv были интегрированы следующим образом (на примере интерпретатора макросов eval и метода rename
):
- взяли АPI метода из Node.js, преобразовали его в Haxe, стараясь при этом стандартизировать типы аргументов и избавиться от избыточных для Haxe аргументов. Например, аргументы-пути (тип
FilePath
) — это абстракты над строками:
- затем создали OCaml-биндинги для этого метода:
- связали OCaml и C (с помощью CFFI — C Foreign Function Interface):
- и, наконец, написали С-биндинги для вызова C-функций libuv из OCaml:
Аналогично было сделано для HashLink и Neko (пока что asys API реализован только для этих трех платформ). Как можно предположить, это потребовало много работы.
Аурел показал несколько небольших приложений, демонстрирующих работу asys API.
Первый пример — демонстрация асинхронного чтения содержимого файла. Пока что в коде явно вызываются методы для инициализации libuv (hl.Uv.init()
) и запуска цикла работы приложения (hl.Uv.run()
), связано это с тем, что работа над API еще не завершена (но в дальнейшем они будут добавляться автоматически):
Результат работы показанного кода:
Мы видим, что результаты работы вызванных методов AsyncFileSystem.readFile()
выводятся в консоль после trace’а “after call”, который в коде вызывается после попыток прочитать содержимое файлов.
Второй пример — демонстрация асинхронной работы с DNS и IP-адресами.
В новом API определить имя хоста станет гораздо проще, а также появятся вспомогательные методы для работы с IP-адресами.
Третий пример — это простой TCP эхо-сервер, для создания которого достаточно всего трех строчек кода:
Четвертый пример — это демонстрация обмена информацией между процессами:
статический метод makeFrame()
в рассматриваемом примере создает отдельные png-изображения:
а в методе main
мы запускаем процесс ffmpeg, в который будем передавать сгенерированные в makeFrame()
кадры:
и на выходе получим видео-файл:
И пятый пример — UDP video stream. Здесь как и в предыдущем примере запускается процесс ffmpeg, но в этот раз он воспроизводит видео и выводит его данные в стандартный поток вывода. Также создается UDP-сокет, который будет транслировать данные из процесса ffmpeg.
И, наконец, разбиваем данные, получаемые из ffmpeg, на меньшие “порции” и транслируем их на указанный порт:
И в результате получаем работающий видеопоток:
Обобщая вышесказанное, новое asys API включает в себя:
- методы для работы с файловой системой, включая новые функции, которых не было в стандартной библиотеке (например, для изменения разрешений), а также асинхронные версии всех функций, доступных в старой стандартной библиотеке
- поддержку асинхронной работы с TCP/UDP/IPC сокетами
- методы для работы с DNS (пока что 2 метода:
lookup
иreverse
) - а также методы для асинхронной работы с процессами.
Работа над asys API пока что не завершена, в настоящее время есть некоторые проблемы со сборщиком мусора при работе с библиотекой libuv. Pull Request с соответствующими изменениями еще не вмержен в основную ветку Haxe, в комментариях к нему приветствуются мнения по поводу имен новых методов, их сигнатур, а также документации.
Как уже упоминалось поддержка asys API реализована только для HashLink, Eval и Neko (в виде трех отдельных Pull Request’ов). У Аурела уже сформирован план, как добавить поддержку нового API для C++ и Lua. Реализация для остальных платформ потребует дополнительных исследований.
Возможно, что asys API станет доступно в Haxe 4.1 (но только для некоторых платформ).
Также Аурел рассказал о своем стороннем проекте — библиотеке ammer (который все же связан с его работой в Haxe Foundation).
Цель ammer — автоматизировать создание биндингов для библиотек на C так, чтобы их можно было использовать и в HashLink и HXCPP (в октябре 2018 года Ларс Дусе назначил вознаграждение за решение этой задачи).
Почему эта задача была актуальной? Дело в том, что хотя процесс создания биндингов для HashLink и HXCPP аналогичен, для каждой платформы придется писать свой связующий код (glue code).
Примерно то же самое Аурел делал, когда интегрировал библиотеку libuv в Haxe — для Eval, Neko и HashLink ему пришлось писать один и тот же код, который отличался только в деталях (вызов функций, отличия в работе FFI и др.):
Аналогичную работу требовалось проделать и на стороне Haxe для того, чтобы из него можно было вызывать нативные функции:
И идея ammer в том, чтобы взять Haxe-версию API, не загроможденную избыточной информацией, и сделать так, чтобы этот код каким-то образом заработал для всех платформ:
Что теперь требуется от пользователя ammer для работы с внешними библиотеками на C:
- создаете Haxe-спецификацию для библиотеки, которая по существу является экстерном для используемой библиотеки
- пишете код приложения
- компилируете проект, указав пути к заголовочным файлам и файлам C-библиотеки
- ...
- profit
Под капотом ammer выполняет при этом следующую работу:
- сопоставляет типы в зависимости от целевой платформы
- автоматически генерирует C-код для вызова нативных функций
- генерирует makefile, который используется для создания hdll, ndll файлов
В настоящий момент в ammer поддерживаются:
- простые функции
- define’ы из заголовочных файлов (в Haxe-коде к ним можно обращаться как к константам)
- указатели
Планируется поддержка:
- коллбеков (их пока что очень не хватает)
- и структур (очень нужны для работы с C-API)
Сейчас ammer работает с C++, HashLink и Eval. И Аурел уверен, что сможет добавить поддержку остальных системных платформ.
Для демонстрации возможностей ammer Аурел показал небольшое приложение, в котором выполняется интерпретатор Lua:
Используемые в нем биндинги выглядят следующим образом:
Как видно некоторые методы закомментированы, т.к. в них используются коллбеки, поддержка которых еще на реализована, но Аурел надеется, что скоро сможет исправить это.
Итак, для чего может применяться ammer:
- встраивание виртуальной машины Lua
- создание приложений на SDL
- возможна автоматизация работы с libuv (как было показано ранее, сейчас для работы с libuv требуется много кода, написанного вручную)
- и, конечно же, значительно упростится использование множества других полезных библиотек на C (таких как OpenAL, Dear-imgui и др.)
И хотя стажировка Ауреля в Haxe Foundation закончилась, он планирует продолжать работу с Haxe, т.к. его обучение в колледже еще не завершено и ему предстоит еще написать выпускную работу. Аурел уже знает чему она будет посвящена — улучшение работы сборщика мусора в HashLink. Что же, это будет интересно!