o2 dev
108 subscribers
49 photos
4 videos
25 files
54 links
About o2 engine development
加入频道
Для удобной работы с нативными данным в ScriptValue есть еще несколько функций:

bool IsObjectContainer() const; - чтобы понять хранится ли какой-то объект внутри впринципе
const Type* GetObjectContainerType() const; - чтобы получить тип хранимого объекта
void* GetContainingObject() const; - получить сырой указатель на объект
отдельный момент про владение нативным объектом. Пока что у меня сделано не очень хорошо, тк владение не регламентировано жестко и могут возникнуть проблемы

Суть в том, что объект создается из нативной части, память управляется вручную. При этом есть GC в JS, который тоже как-то управляет памятью. Соответственно могут быть ситуации, когда GS должен удалить нативный объект, икогда не должен. По сути это определяется тем, владеет ли ScriptValue нативным объектом или нет. Если владеет, то его судьба полностью подвластна GC. Если нет - то ScriptValue просто хранит поинтер на объект, но никак его не удаляет.

Отсюда могут возникнуть проблемы. Например, прокинули объект в скрипты и убили его. Скрипт не узнает об этом. Или наоборот, скрипт владеет объектом, а мы его прибили из нативного кода.

Сейчас владение объектов по сути на совести разработчика. Но, на мой взгляд, есть более правильный способ владения объектом - через умные указатели. Если скрипт владеет объектом, то он держит сильную ссылку, если нет - слабую. Таким образом и скрипт, и нативный код защищены от непредвиденного удаления объекта

Такой подход я сделаю когда весь движок переведу на умные указатели. Да да, у меня ручное управление паматью, и оно мне не нравится
так, прокинули объект в скрипт. Но из скрипта с ним ничего не сделать, тк это просто объект без полей и плюсовые поля класса и методы никак не соотносятся со скриптом. Разберем сначала как работает прокидывание полей класса в скрипт

По сути нам нужно в объект добавить проперти, который маппится на указатель поля из нативного объекта. Либо эта проперти является оберткой над паркой setter/getter.

Чтобы сделать кастомизируемое проперти, нужно использовать специальную функцию jerry_define_own_property. Она добавляет проперти в объект, но с неким описанием как это поле работает - jerry_property_descriptor_t

Оно включает в себя параметры конфигурации поля, в котором можно задать сеттер и геттер поля. Их мы и заиспользуем. Эти сеттер и геттер - тоже скриптовые значения, которые должны быть функциями с определенной сигнатурой

Для этого определим указатели на нативные функции, к которым прицепим нативный контейнер с интерфейсом сеттера или геттера, в котором уже будем работать с указателем на поле нативного объекта. Подробнее можно глянуть в функции SetPropertyWrapper()

В результате этих операций мы получаем проперти в объекте, который вызывает специальные сеттер и геттер, которые уже работают с указателем на значение
Рядом же есть реализация проперти не через поинтер, а через функции
Так, у нас теперь есть объект с полями. Откуда они там зарегистрируются - попозже, вкратце через кодген. Теперь нам нужны функции

Здесь jerry API тоже нас не балует удобством и предоставляет интерфейс биндинга функции в скриптовое значение. Статичной функции... Снова пишем обертки!

По факту мы действительно биндим одну единственную статичную функцию на все типы функций, но саму функцию запихиваем через нативный объект. Да, скриптовое значение, которое является функцией, может еще и держать поинтер на кастомные данные.

Делаем уже по привычному пути и даже используем тот же интерфейс для хранения обертки над функцией. А в статичной функции обращаемся к этому контейнеру и вызываем функцию

Пока все просто. Но еще нужно передать параметры! Тут все интересно. Ведь на стороне JS список параметров - это массив jerry_value_t . На стороне С++ - это конкретная сигнатура функции. Пахнет магией шаблонов

При вызове функции из контейнера нам нужно упаковать параметры из JS в tuple<> для передачи в нативную функцию. Каждый отдельный параметр мы просто кастим через оператор каста в ScriptValue. А чтобы их все проитерировать, мы итерируем параметры из сигнатуры нативной функции через шаблоны - UnpackArgs

Эта функция принимает в качестве параметров шаблона индекс параметра! и тип аргументов. Внутри рекурсия с приростом индекса параметра и ображение к элементу tuple через std::get<idx>.

Оборачиваем всякой шелухой на удаление ссылок из типов, проверяем как именно должна вызываться фукнция - с возвращаемым значением или нет, и вызываем нативную функцию через std::apply(). В нее передаем указатель на функцию и tuple с параметрами

Вауля, мы умеем прокинуть нативную функцию из С++ в JS и вызвать ее из JS. Немножко сложнее вызываются функции класса, там нужно еще и обработать this
А как вызвать из С++ функцию из JS? Процесс идет наоборот, но немного проще.

Сначала объявляем интерфейс вызова через передачу параметров в виде скриптовых значений - InvokeRaw. В ней просто дергаем jerry API - jerry_call_function

Ну а чтобы иметь нормальный плюсовый интерфейс вызова функции, нам нужны variadic template args и их упаковка в массив ScriptValue. Для этого используем функцию PackArgs с магией итерирования по variadic templates.

В результате имеем человеческий вызов Invoke, который сам сконвертит параметры и передаст в скрипт.
Теперь, как регистрировать С++ классы в JS. Для начала нужно как-то объявить конструктор. Помните выше описывал как работают конструкторы в JS? Вот нам нужно все то же самое сделать для нативных классов:

определяем функцию, которая работает с this, подсовывая туда свежесозданный нативный объект, дополняя полями и функциями, которые маппятся на этот нативный объект

функция определяется тут. В ней мы конструируем объект и с помощью кодгена прокидываем данные о полях и функциях, используя вышеперечисленное API ScriptValue
Теперь о кодгене. Не буду углублятся, тк сейчас часть кодгена у меня переусложнена. Вкратце у меня кодген генерит шаблонный метод по перебору всех метаданных класса, в который передается какой-то класс-процессор. Есть такой процессор для сериализации, а есть для биндинга скриптов.

В этот процессор на вход попадает ScriptValue и указатель на класс. Он заполняет ScriptValue полями из класса и функциями. Заполняет только теми, у которых есть аттрибут @SCRIPTABLE
Ну вот, так в базовом виде работает скриптинг в о2. Есть автоматический биндинг классов в JS, есть удобное API по работе с JS сущностями. Из самого JS работать с нативными объектами тоже вполне удобно и выглядит вполне естественно
Теперь пару слов о редакторе. Впринципе, на базовом уровне все просто. Добавился ScriptableComponent, который держит в себе ссылку на специальный ассет скрипта и ScriptValue инстанса этого скрипта. В нем вызывает всякие OnStart, Update, OnEnabled/Disabled функции из ScriptValue. Сам инстанс обозначен как serializable. Для него написан конвертор, который просто перебирает все проперти и пишет в DataValue (обертка над json).

Чтобы отобразить все параметры в редакторе есть специальное поле редактора ScriptValueProperty, которая показывает содержимое ScriptValue и позволяет его редактировать. По умолчанию воспринимает ScriptValue как объект и вытаскивает поля из него. Но может работать с ним и как с массивом.
сорри что наспамил кому-то в телегу :)
holy shit, я начал открывать для себя возможности chat gpt 🤯 Не буду описывать свои эмоции, итак уже все в ахере... Но попробую рассказать немного о том, как получается его использовать в работе, что уже успел попробовать
для тех кто в танке, chat gpt - это нейросеть, обученная на огромном количестве текстов, способная генерировать текст с неким контекстом
для простоты - это чат-бот. Единственное что он умеет - генерировать ответы на запросы. Но в этом то вся и фишка, он умеет отвечать. Он умеет выделять смысл, обобщать, менять контекст. А еще он умеет писать код 😉
но он не умеет думать, мыслить, у него нет своего мнения
хотя, по факту, можно заставить его думать и решать простые задачки. Например я задавал ему задачку про собаку, прыгающую по разноцветным коробкам. Он понимает условия и может предложить решение, расписав по шагам.

Начал с простого примера: есть две коробки, красная и черная. Собака сидит на красной. Затем собака прыгнула на другую коробку. На какой коробке оказалась собака?

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

ChatGPT выдал верный ответ с объяснением
справелдивости ради, задача простая. Поэтому я ее усложнил и добавил еще одну коробку - желтую. И сказал что собака прыгнула так же на другую коробку. Какой тут правильный ответ? Фактически тут два ответа, либо черная, либо желтая. ChatGPT понял это и написал соответствующий ответ

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

Меня ничего не останавливало (про порно и убийства она не может писать, да), и я скормил несколько бредовых запросов, в надежде запутать нейросеть. Например, сможет ли бобер быть хорошим джедаем, и смог ли бы он использовать свой хвост в поединках на световых мечах. В целом ChatGPT справлялся с моим потоком бреда, и пытался предположить каким будет бобер-джедай.
ну ладно, вернемся к изначальному тезису, как это можно применить в работе
итак, я пишу код, придумываю системы, и мне стало интересно, насколько все это может упростить мою жизнь. Главное не разрушить, а то вдруг программисты то уже совсем не нужны стали 😂