[Перевод] Что я думаю о Lua после релиза проекта на 60,000 строчек кода?
Перевод статьи опубликованной на Medium, которая недавно широко обсуждалась на Hacker News и в Reddit.
Привет! Это Олег из Luden.io. Мы решили основательно поболтать о языке программирования Lua с Иваном Трусовым, ведущим программистом видеоигры Craftomation 101. В игре около 60,000 строк кода на Lua и сделана она на игровом движке Defold.
Я попросил Ваню рассказать о реальных проблемах и показать реальный код, а не «гипотетический код, тщательно подготовленный для публики, чтобы продемонстрировать не то, как мы это делаем, а как мы думаем, что это должно быть сделано».
Craftomation 101 — это игра о самореплицирующихся роботах, которых можно программировать с помощью визуального программирования. Эти роботы, КрафтоМаты, создают (и едят!) ресурсы, нужные для терраформирования замерзшей планеты. Игра недавно была выпущена в раннем доступе для Windows, macOS и Linux на платформах Steam, itch.io и GOG. Кроме того, доступна бесплатная демоверсия, которую можно сыграть в веб-браузере.
Игра была хорошо принята игроками и в настоящее время имеет оценку пользователей «Очень положительная» на Steam. Более того, через два месяца после запуска несколько образовательных организаций начали использовать её на уроках информатики и в лагерях программирования (что нас очень вдохновляет и радует!).
Олег Чумаков: Ваня, во-первых, почему Lua? Почему мы не рассматривали возможность использования TypeScript, Haxe, обычного C++ или чего-то другого в начале разработки?
Движок Defold поддерживает C++ и Lua, но также существует открытый набор инструментов TypeScript + Defold [https://ts-defold.dev/] и библиотека поддержки Haxe для Defold [https://github.com/hxdefold/hxdefold].
Движок Defold был выбран для проекта, потому что он обеспечивает очень легковесные веб-сборки, совместимые с Chromebook и в тоже время позволяет делать нативные сборки для ПК, консолей и мобильных платформ. Это важно, так как наши игры используются в образовательных организациях, где много Chromebook. (видимо, имеются ввиду образовательные организации в США, потому что у нас Chromebook’ов в школах я не видел — примечание переводчика)
Иван Трусов: Я спросил Диму…
Дмитрий — наш коллега и ветеран в работе с движком Defold — сказал мне, что с Lua всё в порядке. Я спросил его, тот ли самый это язык, который мы используем для написания конфигов для Redis. Он заверил меня, что всё будет отлично, так как он уже релизил множество проектов с его использованием.
Поэтому я доверился Диме…
Иван Трусов — ведущий программист Craftomation 101
Мой предыдущий опыт был с C++ (я думал, что знаю C++, но работа с ClickHouse показала мне обратное), Python (хотя я понимал, что Python не очень популярен для клиентов в разработке игр) и Java. Так что мой стек был вне основных движков для разработки игр. Кроме того, Defold предоставлял нативную поддержку Lua и C++, что упростило выбор.
Я слышал, что Lua также известен среди разработчиков игр как легко интегрируемый, крошечный язык для любых доморощенных движков, особенно когда вам нужно предоставить больше свободы геймдизайнерам для настройки сложных игровых систем, таких как способности или эффекты заклинаний.
С другой стороны, никуда не деться от внезапных спайков в профайлере, когда обнаруживается что геймдизайнеры используют какую-то «темную магию» внутри своих Lua-скриптов.
Но ладно, это не про наш случай, мы планировали писать всю игровую логику на Lua.
О.Ч.: Итак, вы начали разработку на Lua. Помнишь, что тебя больше всего удивило в начале?
И.Т.: Конечно, отсутствие операции инкремента, отсутствие инструкции «continue» и индексы массивов, начинающиеся с 1 вместо 0. Эти различия могут быть очень внезапными, особенно при переключении между двумя языками для разных проектов/скриптов. Я до сих пор иногда пишу [array.length-1], чтобы получить индекс последнего элемента; сложно привыкнуть к этому после многих лет работы с C++ и Python.
Также, в мои университетские годы, я проходил два курса по Haskell. Удивительно, но я вижу некоторые преимущества в применении подходов из функциональных языков к Lua. Например, создание некаррированной функции в 4 этапа, затем её свёртка и декорирование.
Имею ввиду, что Lua, вероятно, был вдохновлен функциональными подходами и скриптовыми языками одновременно. Вот почему некоторые функциональные шаблоны могут привнести новые идеи в жизнь разработчика на Lua.
Эти функциональные флешбеки были для меня довольно удивительными. Я могу иллюстрировать это чем-то вроде вот этого:
local pref = item_struct.node_tree["item_prefab/root"] and "item_prefab" or "group_prefab"
local item_root = item_struct.node_tree[pref.."/root"] or item_struct.node_tree["root"]
local name_node = item_struct.node_tree[pref.."/name"] or item_struct.node_tree["root"]
local placefolder_node = item_struct.node_tree["group_prefab/placeholder_text"]
С синтаксическим сахаром, так сказать, «на Haskell вайбе»
local pref = "group_prefab"
if item_struct.node_tree["item_prefab/root"] then
pref = "item_prefab"
end
local item_root = item_struct.node_tree[pref.."/root"]
if item_root == nil then
item_root = item_struct.node_tree["root"]
end
local name_node = item_struct.node_tree[pref.."/name"]
if name_node == nil then
name_node = item_struct.node_tree["root"]
end
local placefolder_node = item_struct.node_tree["group_prefab/placeholder_text"]
Без синтаксического сахара, так сказать, «на Haskell вайбе»
Вот немного более сложная иллюстрация «Haskell вайбов» в Lua с каррированием функции и альтернативным решением для сравнения.
О.Ч.: А как насчет отсутствия классов в Lua?
И.Т.: Да на самом деле, я всегда любил простые решения и никогда не был фанатом ООП. Чтобы создать хороший класс, нужно многое понимать про его будущее использование и принципы ООП в целом. Я до сих пор морщусь при воспоминании о прошлых собеседованиях, на которых задавались вопросы типа: «Расскажите о трех принципах ООП». Возможно, некоторые компании до сих пор спрашивают об этом на интервью. Но это их выбор, я не могу их осуждать.
Организация кода в Lua была для меня довольно интуитивной. Это было еще понятнее благодаря простому подходу «все является таблицей». Но этот фундаментальный принцип не то чтобы был мне анонсирован заранее, я пришел к нему, когда наткнулся на баг «таблица в таблице не является таблицей, а ссылкой на таблицу» и пытался его исправить. Я просто присвоил некоторое значение с помощью »=», и после этого моя таблица была передана по ссылке, а не скопирована, так что две части игры начали редактировать одну и ту же таблицу, и я получил «интересные и неожиданные» результаты.
Также, у нас действительно есть таблица внутри самой игры! Таблица элементов.
Проиллюстрирую кодом. У меня была функция, приведенная ниже, и я хотел создать таблицу для очереди, в которую я бы записывал и затем сортировал роботов (не помню, для чего конкретно была нужна очередь). Итак, я создал таблицу размером 100×1, задав {x=0, y=0} в качестве значения по умолчанию.
Однако, как видно ниже, значение по умолчанию присваивается через »=», без копирования его для каждого элемента. В результате, я долго не мог понять, что происходит и почему моя сортировка роботов постоянно выдаёт какую-то ерунду.
Оказалось, что таблица содержала 100 ссылок на одни и те же координаты. Каждый раз, когда я переписывал x и y в элементе этой таблицы, я на самом деле переписывал их в одной таблице, и ссылка на эту таблицу назначалась всем значениям в таблице. Таким образом, в конечном итоге я сортировал 100 одинаковых элементов.
function M.createUnorderedTable(x,y,width, height, spacer) -- spacer is the default value in which I put the table
local t = {}
for i = x, width do
t[i] = {}
for j = y, height do
t[i][j] = spacer -- and here the same link of the link to this table is assigned to each newly created element
end
end
return t
end
В конце концов, я исправил это следующим образом, но в процессе я усвоил важный урок о Lua.
function M.createUnorderedTable(x,y,width, height, spacer)
local t = {}
for i = x, width do
t[i] = {}
for j = y, height do
if type(spacer) == "table" then
t[i][j] = M.copy_table(spacer)
else
t[i][j] = spacer
end
end
end
return t
end
О.Ч.: Хорошо, а что ты думаешь о…
И.Т.: Ещё кое-что о таблицах, пожалуйста!
В конце концов, я считаю, что написание игровой логики (или бизнес логики как принято говорить) намного удобнее со всеми этими таблицами в Lua, чем в C++. При сравнении с Python на базовом уровне (я имею в виду сам язык, а не все замечательные пэкэджи, такие как PyTorch, NumPy и тысячи других), я полагаю, что можно считать Lua упрощённой версией Python.
Практически всё, что легко реализовать в Python, также легко реализовать в Lua. Однако, когда дело доходит до разделения кода на модули или пакеты, Python более удобен.
О.Ч.: Хорошо, я понял насчёт таблиц, я хочу спросить о…
И.Т.: Самое последнее о таблицах, пожалуйста!
Есть ещё одна сторона у «всё является таблицей» — вам не нужно принимать сложные решения при выборе структуры, так как никаких других вариантов не предусмотрено!
Теперь я закончил говорить о таблицах, спасибо.
О.Ч.: Ты общался с другими разработчиками Lua по поводу этого «нюанса» с таблицами?
И.Т.: Я вернулся к Дмитрию и спросил его, правильно ли я понял концепцию «всё является таблицей» и, если да, почему Lua был спроектирована именно так. Дмитрий сказал мне, что Lua был создана в Папском католическом университете Рио-де-Жанейро и что для Папских католических университетов приемлемо проектировать языки программирования таким образом.
О.Ч.: Что ты думаешь о производительности?
И.Т.: Просто по ощущениям, разработка на Lua создаёт впечатление, что это язык «как бы быстрый». В Craftomation 101 есть очень мало мест, где мы действительно сталкиваемся с проблемами производительности из-за скорости языка, так что наш случай может быть не совсем показательным. Вот почему я называю его «как бы быстрый», а не просто «быстрый». Существует множество тестов производительности, которые показывают, насколько он быстрый в цифрах.
Есть варианты писать код на C++ в Defold, такие как создание модуля или модификация самого движка (код доступен на GitHub). Для меня это был потенциальный обходной путь для будущих моментов, когда мне, возможно, придётся решать вычислительно сложные задачи.
Я всегда начинаю с реализации модуля на Lua, будучи готовым переписать его на C++, если профайлер покажет значительные проблемы с загрузкой процессора. Однако проблемы были не такими большими, и применение некоторых знаний об алгоритмах к коду на Lua часто решало или минимизировало их.
Игра относительно проста с технической точки зрения по сравнению с такими проектами, как Warnament (ещё один наш проект на Defold, в котором куча модулей на C++). Кроме того, существуют нативно реализованные модули на C/C++ в экосистеме Defold, такие как A* pathfinding.
О.Ч.: С твоим опытом работы с C++ и Java, не возникало ли у тебя ощущение, что «я не знаю о проблемах, пока они не возникнут во время выполнения», потому что нет этапа компиляции и нет уверенности, которую дают строго типизированные компилируемые языки?
И.Т.: Да, конечно, но это не было большой проблемой, так как в то время я много работал с Python. Речь шла не только о бизнес-логике, написанной на Python. Это было около трёх лет машинного обучения с использованием Python. Проблемы, скрытые в огромных объёмах данных (и весь код, написанный для обработки «грязных» данных), ещё менее контролируемы, чем проблемы, скрытые в интерпретируемом коде.
О.С.: Ты использовал линтеры или еще что для анализа кода?
И.Т.: На самом деле, я большой поклонник использования стандартного «базового» набора инструментов до тех пор, пока не найду что-то, что действительно меня зацепит. В первые шесть месяцев я даже писал код, используя встроенный редактор кода Defold, потому что Дмитрий сказал мне, что это хороший способ работать с кодом (у него даже есть подсветка синтаксиса!).
Встроенный редактор кода Defold
После этого я переключился на VS Code (автор этой статьи является большим поклонником Sublime и не может не добавить этот комментарий тут: Sublime — лучший!). Однако, должен признаться, что у меня не было большого опыта в использовании линетров, поэтому простое гугление «VS Code Lua Linter» и установка того, что я нашёл по первой ссылке, не сильно изменили мой опыт программирования.
Но базовый пакет Lua для VS Code предоставляет проверку типов, которая полезно подсвечивает некоторые проблемы в коде, что довольно полезно.
VS Code с кодом на Lua
О.С.: Я помню один звонок, когда ты сказал, что ненавидишь Lua и предложил переключиться на TypeScript. В тот момент в проекте было около 60,000 строк кода. Хорошая же идея добавить какие-то инструменты для статического анализа, когда у тебя столько кода?
И.Т.: Инструменты статического анализа или TypeScript всегда были у меня в голове, но на заднем плане, как «планы на будущее». В периоды разработки, когда QA нас не очень сильно тестировали и я не был уверен в стабильности игры, это было более актуальным. Когда QA взялись за дело серьезно, я исправил ряд проблем типа «это вектор, но почему-то ему была присвоена строка». В итоге игра оказалась в гораздо более стабильном состоянии, чем я ожидал.
О.Ч.: Для следующего проекта, который может быть таким же большим или даже больше, ты хочешь начинать сразу с TypeScript?
И.Т.: Не думаю. Уровень поддержки TypeScript в Defold может быть одной из серьёзных проблем для меня — он неофициальный, и даже с лучшей поддержкой сообщества нельзя быть уверенным, что все обновления движка будут реализованы, или что у вас будет время реализовать их самостоятельно.
Две главные вещи, которые я хотел бы иметь в любой «альтернативе Lua», хорошо известны:
Я хочу быть в курсе проблем в моём коде на этапе компиляции, а не во время выполнения.
Я хочу организовать мою кодовую базу удобным и простым способом. В динамически типизированных языках, таких как Lua, существует такая большая гибкость в организации кодовой базы и разработке архитектуры, что это иногда может быть менее удобно, чем системы, которые навязывают определённый стиль.
О.Ч.: Ходят слухи, что Luau рассматривается для интеграции в Defold. Код, который валиден в Lua 5.1, также валиден в Luau. Что думаешь?
Luau — это последовательно (gradually) типизированный язык, производный от Lua, созданный командой Roblox.
И.Т.: На самом деле, я никогда не пробовал Luau, и, насколько я знаю, есть проблема с обновлением версии Lua до 5.2 в Defold. Lua 5.2 включает операцию инкремента, но что я действительно хочу, так это Lua 5.4 (дорогая команда Defold, ну пожалуйста!).
Ну да ладно, мы тут про Luau говорим. Думаю, Luau может облегчить жизнь, но не кардинально, потому что это нечто среднее между Lua и строго типизированными языками. Похожая ситуация случилась с TypeScript, который родился между JavaScript и строго типизированными языками.
Идеальный сценарий — это официальная реализация TypeScript в Defold или, если можно помечтать, другой язык, который с самого начала был разработан как строго типизированный.
О.Ч.: Какой именно?
И.Т.: Однажды мои университетские друзья спросили меня, почему нельзя писать код на Go для Defold. Я спросил, почему они вообще пришли к такой идее? Они ответили, что потому что Go — такой крутой язык, почему бы и нет! Это был, конечно, гипотетический сумасшедший вопрос, связанный с каким-то гипотетическим будущим, где у нас есть системы, не зависящие от языка и можно просто брать «крутой язык» и дальше какой-нибудь WebAssembly (который внутри Defold кстати есть) сам все сделает.
Благодаря Unity, C# сейчас является одним из лидеров в нашей индустрии. Однако, я не уверен, что его можно интегрировать в Defold, не пожертвовав легковесностью движка.
О.Ч.: Что в Craftomation 101 могло бы быть сделано лучше с помощью строго типизированного языка?
И.Т.: Например, у нас есть огромная карта с множеством интерактивных объектов. Игрок может взаимодействовать с ними, и наши роботы, КрафтоМаты, также могут взаимодействовать с объектами. Кроме того, они могут искать конкретные объекты, чтобы добывать из них ресурсы.
Все объекты на карте являются параметризированными объектами, хранящимися в массиве, как сущности в ECS. Существует множество типов объектов, таких как огонь, дерево, огонь на дереве, костёр и т.д. Чтобы координировать всё это в строго типизированном языке, я бы интуитивно использовал ООП или ECS и обрабатывал конкретные типы с помощью специальных менеджеров, контроллеров или систем. Альтернативно, я мог бы создать большой менеджер-обработчик с серией IF-выражений.
В Lua всё это тоже возможно, но так как это не так очевидно, разработчик может начать скучать по лёгкости реализации, которая есть в языках вроде C#.
Вот код для «if-селектора», который является абсолютным чемпионом по производству багов в проекте.
local collisionObj = collision_utils.checkCollision(curElement, collision_utils.tilesToPos(tileX, tileY), 0, { mm.mobs, em.elements, em.chests, sm.buildings, nil, dm.droppoints, objm.objects, {rm.Rocket} , {settings.command_center,settings.sulfur_center},settings.brewerys, {volcano_manager.object}}, MULT, true)
validAction = false
if not collisionObj or curElement.type == "command" then
validAction = em.onDrop(curElement,tileX, tileY)
elseif collisionObj.type == 1 then -- threw an element on the robot
if settings.objectiveMetainf and settings.objectiveMetainf.progress and settings.objectiveMetainf.progress["execute_feed"] and settings.objectiveMetainf.progress["execute_feed"] < settings.objectiveMetainf.obj["execute_feed"] then
settings.objectiveMetainf.progress["execute_feed"] = settings.objectiveMetainf.progress["execute_feed"] + 1
if settings.objectiveMetainf.obj.element and settings.objectiveMetainf.obj.element ~= curElement.type then
settings.objectiveMetainf.progress["execute_feed"] = settings.objectiveMetainf.progress["execute_feed"] - 1
end
end
validAction = mm.eatElement(collisionObj.obj, curElement, nil) -- We don’t send commands, this is hand feeding :)
elseif collisionObj.type == 6 then
validAction = dm.Eat(collisionObj.obj, curElement)
elseif collisionObj.type == 2 then -- if the cell is occupied, we try to mix it, or return it back
validAction = true
if tryToCombine(curElement, collisionObj.obj) == false then
validAction = false
else
end
elseif collisionObj.type == 3 and not collisionObj.obj.frozen then -- if there is a chest, then we try to put it in it
validAction = em.dropToChest(collisionObj.obj, curElement)
end
elseif collisionObj.type == 4 then
validAction = sm.inHandler(collisionObj.obj, curElement)
elseif collisionObj.type == 5 then -- point of sale
validAction = em.inHandler(collisionObj.obj, curElement)
elseif collisionObj.type == 7 then -- fireplace or tree
validAction = objm.inHandler(collisionObj.obj, curElement, false)
if validAction then
stats.statEvent(stats.insert, curElement.type)
end
if not validAction then
local optionalColl =collision_utils.checkCollision(collisionObj.obj, collision_utils.tilesToPos(tileX, tileY), 0, {objm.objects}, SINGLE, true)
if optionalColl then
validAction = objm.inHandler(optionalColl.obj, curElement, false)
if validAction then
stats.statEvent(stats.insert, curElement.type)
end
end
end
elseif collisionObj.type == 8 then -- Rocket
validAction = rm.inHandler(collisionObj.obj, curElement)
elseif collisionObj.type == 11 then -- Volcano
validAction = volcano_manager.inHandler(curElement)
end
В Lua есть метатаблицы, которые в определённой мере могут служить альтернативой классам. Однако, с моей точки зрения, лучше либо начинать с метатаблиц с самого начала, либо вовсе игнорировать их, нежели пытаться ввести подход с метатаблицами в существующую кодовую базу.
Существует огромная библиотека UI для Defold под названием Druid, которая реализована с использованием метатаблиц и имеет некоторые черты ООП. Кажется, что использование ООП в Lua может быстро и неожиданно привести разработчиков к ситуации, когда добавление новых функций прерващается в сложнейший процесс.
О.Ч.: Есть ли что-то особенное в использовании Defold и Lua с точки зрения CI?
И.Т: Меня очень удивила скорость сборки моего проекта с Defold. Помню, как мои друзья, кто делает игры на Unreal Engine, рассказывали о долгом процессе сборки. У нас в студии есть другие проекты на Unity, и там все билдится довольно быстро, по сравнению с Unreal Engine, но время сборки в Defold ощущается как чистая магия.
Даже сейчас, с огромным проектом, среднее время сборки составляет 4 минуты. Мы используем GitHub Actions и локальный раннер для сборки игры. Используя Mac Mini с процессором M1 Apple Silicon в качестве нашего локального раннера, мы можем собирать все платформы, используя всего одну машину.
Полная матрица из 25 сборок для всех платформ
О.Ч.: Есть мысли о следующем проекте?
И.Т.: Прежде всего, впереди ещё полный релиз Craftomation 101; роадмап всё ещё очень большой. Для следующего проекта на Lua и Defold я бы использовал гораздо более модульную организацию кода. Из-за текущей организации кода я использовал много «сообщений» (messages — способ общения между игровыми объектами в Defold). Думаю, в будущем мне не стоит так сильно на них полагаться.
Ещё одна вещь, которую я хочу сделать, — это создать более атомарные менеджеры игровых объектов, потому что они стали слишком большими, и сделать их более независимыми.
Юнит-тесты и другие автоматизированные проверки это супер, но, как это часто бывает с созданием видеоигр, их использование довольно изолировано. Мне очень нравится идея автоматизировать проигрывание реплеев или сценариев игроков, я бы обязательно попробовал это сделать.
О.Ч.: Порекомендуешь какие-нибудь материалы про Lua?
И.Т.:
Есть несколько официальных книг о Lua на официальном сайте.
Также, думаю, полезно почитать что-то о Haskell, Clojure или даже OCaml.
И, конечно, хотя это и не конкретно про Lua, классическое произведение «Game Programming Patterns» — настоящая сокровищница.
О.Ч.: Табы или пробелы?
И.Т.: Табы!
О.Ч.: Какую клавиатуру используешь для работы?
И.Т.: Я использую клавиатуру Dark Project One. Также хочу упомянуть свою легендарную мышь, потому что она со мной со школы и никогда еще не подводила, это Bloody TL70. Еще недавно я купил Logi MX Master 3S.
Также на моём рабочем столе всегда есть очень полезный куб.
О.Ч.: Раскроешь какой-нибудь секрет?
Иван раскрыл секрет, но он немного шокирует. Пожалуйста, подумайте, прежде чем скролить страницу вниз, чтобы узнать, что это за секрет. Для вашей безопасности я вставляю длинное изображение роадмапа игры ниже, чтобы вы случайно не проскроллили до раскрытия секрета. Я вас предупредил!
Секрет: Я предпочитаю эту светлую цветовую схему для VS Code и переключился на тёмную только для того, чтобы сделать скриншоты для этой статьи.
P.S. Я не могу закончить эту статью, не выразив нашу бесконечную любовь и огромную благодарность всем нашим игрокам — вы такие замечательные, и ТОЛЬКО благодаря вам у нас есть вдохновение делать наши игровые проекты: программисты, преподаватели, родители, студенты, инженеры и все любознательные люди со всего света ❤️
Специальный бонус: мы попросили всемогущего Björn Ritzl из Defold прокомментировать эту статью, и он любезно согласился:
Björn Ritzl: «Возможно, имеет смысл прокомментировать использование других языков:
Мы больше не рассматриваем возможность использования Luau (Roblox).
Вместо этого мы решили добавить поддержку универсальных транспилеров в наши инструменты (редактор и командную строку).
Это позволит разработчикам создавать свои собственные расширения для транспиляции с других языков в Lua и выполнять их с использованием стандартных Lua ВМ, включенных в Defold.
Чтобы показать, как это делается, мы решили предоставить официальную поддержку для Teal, типизированного диалекта Lua, который транспилируется в стандартный Lua код. Так ошибки типов буду видны во время сборки, если вы используете файлы Teal (файлы с расширением .tl). Это все еще начальная стадия, и у нас нет подсветки синтаксиса и LSP поддержки для Teal, но уже сейчас это полезно.
Еще одно важное нововведение: у нас теперь есть встроенная поддержка Lua LSP в редакторе, с использованием известного Lua LSP вместе с LuaCheck. Это даст вам статический анализ кода с подсказками во время написания кода и ошибки сборки, если что-то не так с синтаксисом Lua.
Наконец, мы также переписываем публичную часть C++ SDK, которая сейчас используется для написания нативных расширений. Сейчас мы работаем над C SDK и расширяем набор доступных функций SDK для того, чтобы можно было писать целые игры нативно.
Этот обновленный C SDK будет использоваться для генерации C++ SDK, используемого для нативных расширений сегодня, и также будет использоваться для создания C# привязок для тех, кто хочет писать игры или код расширений, используя C#. Использование C# будет полностью опциональным и ни в коем случае не увеличит размер движка или не ухудшит производительность для тех, кто выберет его не использовать.
C++ и Lua по-прежнему останутся основными языками в Defold.
Транспилированные языки, такие как Teal, и компилируемые, такие как C#, будут опциональными.»