𝐑𝐨𝐨𝐭𝐓𝐨𝐨𝐥 𝐁𝐥𝐨𝐠
194 subscribers
570 photos
48 videos
5 files
60 links
ニャン
Download Telegram
А часики то тикают...
😁3💯1😭1
Forwarded from Shoko - UI Framework (Root Tool)
Черт возьми, оно работает...
знали бы вы мое волнение сейчас

Когда я только начинал проектировать Shoko, то много внимания уделял про архитектуру и кроссплатформенность до уровня Embedded

И знаете? Оно работает!

Слева - Raspberry Pi 3B (1GB RAM) + LCD Shield (Linux)
Справа - Мой ПК (Windows 11)

И абсолютно не меняя код (только конфиг), я могу переключаться между платформами, ха

Compile-time UI начинает жить!
🎉4❤2💯1
Если бы массивы были искусством, то это был бы Black-White Array

Емае, как же я кайфую от алгоритма, просто блаженство

https://habr.com/ru/articles/984184/
❤2🔥1
Моя честная реакция когда в школе упомянули Указательное местоимение:
🤣5❤1
This media is not supported in your browser
VIEW IN TELEGRAM
❤3🤩2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
❤1
This media is not supported in your browser
VIEW IN TELEGRAM
Я просто сейчас в диком сегфолте

Ведь реально.. Это мини-версия статичной рефлексии из С++26!

Которую я случайным образом повторил на С++17

Вот до чего доводит долго сидеть в Райдере по 10 часов 💀
❤4👻2
Forwarded from Shoko - UI Framework (Root Tool)
Что ж, вот и первая неожиданная технодемка Shoko с тестом FShokoRenderer и FShokoPlatformRenderer!

И... Да, она работает, и прекрасно выполняет свою функцию: быстрый рендер + легкий перенос на любую платформу

Конкретно данный билд собран на базе RayLib

Скачать: Shoko - Compile-time UI Framework [Rendering Tech Demo]

А также: Исходный код фреймворка

p.s. кнопки лишь нарисованы, это не виджеты, сорьки :(
❤3❤‍🔥1🤯1💯1
Рефлексия там, где ее не должно быть

Немного ночной пост о том, как я в Shoko реализовал собственную статическую рефлексию на С++17
❤‍🔥2🤩1
Начнем с того, что рефлексия - способность программы видеть себя, знать какие в ней существуют поля, методы, иерархия. Обычно все это достигается тяжелым рантаймом, как пример: Unreal Engine (крайне тежелая рефлексия), Qt (псевдо, через Meta-Object System), LVGL (примитивная, Сишная), Godot (Ага, он тоже на С++)

Но что обьединяет все эти проекты? Использование внешних инструментов или макросов. Покажу на пару примерах из моего опыта:

Unreal Engine:
UPROPERTY()
bool bValue = true;

^ UPROPERTY() - Маркер для UHT (Unreal Header Tool), который запускается перед компиляцией, парсит код и генерирует те самые файлы *.generated.h с метаданными. Это классическая рантайм-рефлексия

Qt:
class User : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
};

^ Q_PROPERTY - макрос-указатель для утилиты MOC, которая создает таблицу мета-объектов для рантайме. По сути это тоже рантайм-рефлексия

LVGL:
enum {
MY_PROP_VALUE = LV_PROPERTY_ID(LV_PROPERTY_ID_TYPE_INT, 1),
MY_PROP_COLOR = LV_PROPERTY_ID(LV_PROPERTY_ID_TYPE_COLOR, 2),
};

^ А тут вообще макросный ад: Большое раскрытие макроса LV_PROPERTY_ID + каждое свойство должно быть пронумеровано (А если их там 20+? 40? Шанс ошибиться - как 2 пальца об асфальт). И все же - все данные будут храниться в рантайме = рантайм-рефлексия
❤4💯1
Теперь Shoko

Когда я начинал писать свой Compile-time фреймворк, то столкнулся с проблемой: мне нужна рефлексия, чтобы понимать "что за обьект лежит передо мной"

Идея была тупо стырить собственный Root Header Tool из своего движка Root Engine (его вообще хоть кто-нибудь помнит?), впихнуть его как дополнительный процесс перед сборкой, и итогом получить тяжелую, но рабочую, рантайм-рефлексию

"Но подождите, это же Compile-time фреймворк!" - Так и крикнул я себе и бросил идею с внешними инструментами. Что тогда? Регистрация переменных через макросы? Нууу..... я слишком ленив :]

Поэтому спустя некоторое время я подружился с компилятором и нашел то, что искал - статическая рефлексия без рантайм оверхеда. Начнем по порядку:

====== GUTID ======

GUTID - Global Unique Type Identifier - Глобальный идентификатор, который хранит уникальный номер типа виджета прямо в его начале. По сути - это просто 1 байтовое число uint8. Существуют 2 версии GUTID: LocalGUTID и StaticGUTID

LocalGUTID - Локальная переменная, которая находится вначале базового класса виджета и служит его началом (т.е. находится прямо под указателем на виджет) - он идет в рантайм

StaticGUTID - Статическая переменная, присущая каждому классу, то есть, для SButton можно легко получить StaticGUTID с помощью SButton::StaticGUTID, а также StaticGUTID помечен как static constexpr - что значит он не идет в рантайм

Это дает мне возможность узнать GUTID класса, просто обратившисть как SButton::StaticGUTID. Но как сделать обратно?

====== SHOKO_GENERATED_BODY ======
#define SHOKO_GENERATED_BODY(Class)  \
static constexpr GUTID StaticGUTID = __COUNTER__; \
friend Class GetClassByGUTIDPrivate(GUTIDReflectionFlag<StaticGUTID>);

Знакомьтесь, мой макрос SHOKO_GENERATED_BODY для каждого класса (UE-style), принимающий на вход имя текущего класса, и генерирующее под него StaticGUTID и... GetClassByGUTIDPrivate ?

По порядку:
__COUNTER__ - Это макрос, который вместо себя ставит число и увеличивается на 1, то есть поставив макрос __COUNTER__ 3 раза в коде, мы получим подстановку 0, 1 и 2. Причина - это моя гарантия, что ни хэш, ни какие другие "примерные алгоритмы" не пересекут StaticGUTID моих типов. А также такая последовательность сыграет нам на руку

friend Class GetClassByGUTIDPrivate - старейший (еще до стандартизации С++) прием, когда обьявленная friend-функция вдруг неожиданно "впрыскивается" из класса в глобальное пространство. Конечно комитет кормит нас, поэтому убрал эту лазейку, но оставил ее для ADL (Argument-Dependent Lookup) - механизм С++, который позволяет компилятору искать функцию в тех пространствах имен, в которых используются аргументы - Просто держите себе в голове, что на вход она принимает GUTIDReflectionFlag<T>, а на выходе ничего не возвращет (без реализации), но возвращает тип класса

GUTIDReflectionFlag<T> - просто шаблонная структура, принимающая на вход GUTID. Но в чем подвох - GUTIDReflectionFlag<4> это НЕ ТОЖЕ САМОЕ что GUTIDReflectionFlag<5> - Это совершенно 2 разных типа! В этом и есть секрет победы!

Давайте закрепим пример для класса SButton:
static constexpr GUTID StaticGUTID = 4;
friend SButton GetClassByGUTIDPrivate(GUTIDReflectionFlag<4>);


Теперь... Мы можем получить класс просто зная GUTID, а как?

====== GetClassByGUTID() ======
Взглянем на это:
template<GUTID InGUTID>
auto GetClassByGUTID() { return GetClassByGUTIDPrivate(GUTIDReflectionFlag<InGUTID>{});
using WidgetType = decltype(GetClassByGUTID<InGUTID>());


Сейчас попробуем узнать тип виджета (WidgetType) просто имея GUTID = 4:
Когда мы пишем GetClassByGUTID()<4>, то компилятору нужно подставить auto под определенный тип. А как? Он начинает искать функцию GetClassByGUTIDPrivate с параметром GUTIDReflectionFlag<4>, чтобы узнать что она возвращает - но... неудача, он не находит в публичном пространстве нужную ему функцию с этим принимающим параметром :-(
❤3🔥1
Но подождите! ADL! Раз уж мы упомянули GUTIDReflectionFlag<4>, то почему бы ему не пройтись в том пространстве, где она реализована? А где реализована GUTIDReflectionFlag<4>? В классе.. SButton! Компилятор заходит туда и о чудо - он находит в классе SButton функцию friend SButton GetClassByGUTIDPrivate(GUTIDReflectionFlag<4>); - теперь он видит то, что она возвращает! Она же возвращает SButton! Вопрос с auto решен, ура!

А теперь... совсем небольшой шаг: decltype - Ключевое слово в С++, возволяющее узнать тип выраженя на этапе компиляции, не вычисляя и не вызывая само выражение! То есть decltype от GetClassByGUTID<4>() (в котором возвращаемый тип - SButton) просто заменится на... SButton!

А теперь просмотрите всю дорогу: У нас был тип виджета GUTID = 4, и мы просто через хитрости компилятора заставили его вернуть нам тип виджета - SButton во время компиляции

Не чудо ли?

====== В завершении ======
Да, вышло чутка тяжело на ночь глядя - но оно работает, черт возьми. А благодаря __COUNTER__, все виджеты автоматически регистрируются, и затем просто взяв последний (максимальный) __COUNTER__ и MakeIndexSequence<N> (генерирующий последовательность чисел от 0 до N) - мы с вами создаем целый ForEachWidget! И самое главное - все это Compile-time! В бинарник пойдет, ну максимум... 1 байт на каждый виджет? Ха-ха, да, знаю это много, все, отменяем меня

Но взгляните какие это возможности: Автоматическая регистрация виджета (без ручного заполнения), возможность получить класс виджета по его идентификатору, а также подвязав сюда SFINAE, можно неплохо так проверить на существование и вызвать любой метод или переменную - и это все во время компиляции! Без рантайм оверхеда, без больших таблиц мета-данных. Это буквально философия С++ "Плати только за то, что используешь" доведенная до абсолюта

P.S. А также это, можно сказать, третий способ реализации рефлексии - чисто "из коробки", не применяя MOC, UHT/RHT и другие внешние инструменты. Только вы, компилятор, и С++

Как-то так мои хорошие, спите спокойно. А реализацию рефлексии можно глянуть в моем репозитории Shoko - Compile-time UI Framework

What you don’t use, you don’t pay for. Think Cold.
❤4🔥1🤩1💯1
Заставляет задуматься...
❤‍🔥5👀3😁2🔥1
Как чувствуется писать SQL запросы в верхнем регистре:
❤‍🔥5👍1😁1
Кто-то: Безвыходных ситуаций не бывает

Безвыходная ситуация:
❤3🕊2👍1
I always come back
🔥2