Forwarded from Shoko - UI Framework (Root Tool)
Что ж, вот и первая неожиданная технодемка Shoko с тестом FShokoRenderer и FShokoPlatformRenderer!
И... Да, она работает, и прекрасно выполняет свою функцию: быстрый рендер + легкий перенос на любую платформу
Конкретно данный билд собран на базе RayLib
Скачать: Shoko - Compile-time UI Framework [Rendering Tech Demo]
А также: Исходный код фреймворка
p.s. кнопки лишь нарисованы, это не виджеты, сорьки :(
И... Да, она работает, и прекрасно выполняет свою функцию: быстрый рендер + легкий перенос на любую платформу
Конкретно данный билд собран на базе RayLib
Скачать: Shoko - Compile-time UI Framework [Rendering Tech Demo]
А также: Исходный код фреймворка
p.s. кнопки лишь нарисованы, это не виджеты, сорьки :(
❤3❤🔥1🤯1💯1
Начнем с того, что рефлексия - способность программы видеть себя, знать какие в ней существуют поля, методы, иерархия. Обычно все это достигается тяжелым рантаймом, как пример: Unreal Engine (крайне тежелая рефлексия), Qt (псевдо, через Meta-Object System), LVGL (примитивная, Сишная), Godot (Ага, он тоже на С++)
Но что обьединяет все эти проекты? Использование внешних инструментов или макросов. Покажу на пару примерах из моего опыта:
Unreal Engine:
^ UPROPERTY() - Маркер для UHT (Unreal Header Tool), который запускается перед компиляцией, парсит код и генерирует те самые файлы *.generated.h с метаданными. Это классическая рантайм-рефлексия
Qt:
^ Q_PROPERTY - макрос-указатель для утилиты MOC, которая создает таблицу мета-объектов для рантайме. По сути это тоже рантайм-рефлексия
LVGL:
^ А тут вообще макросный ад: Большое раскрытие макроса LV_PROPERTY_ID + каждое свойство должно быть пронумеровано (А если их там 20+? 40? Шанс ошибиться - как 2 пальца об асфальт). И все же - все данные будут храниться в рантайме = рантайм-рефлексия
Но что обьединяет все эти проекты? Использование внешних инструментов или макросов. Покажу на пару примерах из моего опыта:
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 ======
Знакомьтесь, мой макрос SHOKO_GENERATED_BODY для каждого класса (UE-style), принимающий на вход имя текущего класса, и генерирующее под него StaticGUTID и... GetClassByGUTIDPrivate ?
По порядку:
Давайте закрепим пример для класса SButton:
Теперь... Мы можем получить класс просто зная GUTID, а как?
====== GetClassByGUTID() ======
Взглянем на это:
Сейчас попробуем узнать тип виджета (WidgetType) просто имея GUTID = 4:
Когда мы пишем GetClassByGUTID()<4>, то компилятору нужно подставить auto под определенный тип. А как? Он начинает искать функцию GetClassByGUTIDPrivate с параметром GUTIDReflectionFlag<4>, чтобы узнать что она возвращает - но... неудача, он не находит в публичном пространстве нужную ему функцию с этим принимающим параметром :-(
Когда я начинал писать свой 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 во время компиляции
Не чудо ли?
====== В завершении ======
Да, вышло чутка тяжело на ночь глядя - но оно работает, черт возьми. А благодаряХа-ха, да, знаю это много, все, отменяем меня
Но взгляните какие это возможности: Автоматическая регистрация виджета (без ручного заполнения), возможность получить класс виджета по его идентификатору, а также подвязав сюда SFINAE, можно неплохо так проверить на существование и вызвать любой метод или переменную - и это все во время компиляции! Без рантайм оверхеда, без больших таблиц мета-данных. Это буквально философия С++ "Плати только за то, что используешь" доведенная до абсолюта
P.S. А также это, можно сказать, третий способ реализации рефлексии - чисто "из коробки", не применяя MOC, UHT/RHT и другие внешние инструменты. Только вы, компилятор, и С++
Как-то так мои хорошие, спите спокойно. А реализацию рефлексии можно глянуть в моем репозитории Shoko - Compile-time UI Framework
What you don’t use, you don’t pay for. Think Cold.
А теперь... совсем небольшой шаг: 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
Я наконец-то снова живой, аху-
Вывод - не болейте. Вот вообще
Где я пропадал - подыхал, емае
Вот в начале этой недели еще чуток болел, горло, кашль, сопли, ну база
И ТУТ ПОДЛОВИЛ РОТОВИРУСНОЕ бл-
39.9, бесонные ночи, рвота, голодовка - База
Просто целый, мать его, галлюционный отпуск в другое измерение, миндальный коннект с создателем PHP, и много чего еще, что мне запретили говорить на орбите к границе обратно на Землю
Заранее извиняюсь всем кому не отвечал, вот)
Вывод - не болейте. Вот вообще
Где я пропадал - подыхал, емае
Вот в начале этой недели еще чуток болел, горло, кашль, сопли, ну база
И ТУТ ПОДЛОВИЛ РОТОВИРУСНОЕ бл-
39.9, бесонные ночи, рвота, голодовка - База
Просто целый, мать его, галлюционный отпуск в другое измерение, миндальный коннект с создателем PHP, и много чего еще, что мне запретили говорить на орбите к границе обратно на Землю
Заранее извиняюсь всем кому не отвечал, вот)
👍2🙏2🕊1
Вносим вклад в историю С++
^ Сказал я, и начал делать то, на что бы никогда не решился - попробовать предложить свою идею комитету ISO С++
Для тех кто не знает: У С++ нет компании-владельца. Языком занимается публичный комитет WG21 организации ISO - группа из топовых инженеров и программистов (~400 человек) которые спорят и согласуют стандарт С++ - свод правил, по которым каждый компилятор (MSVC, GCC, CLang, avr-gcc и другие...) их реализуют. Так и живём ¯\_(ツ)_/¯
И внутри комитета есть рабочие группы (Study Groups) по различным направлениям: Рефлексия, встраиваемые системы, высоконагруженные системы, безопасность, игровые движки и тд.... И SG14 как раз и является группой в области "встраиваемых, высоконагруженных систем и игровые движки", куда я, собственно говоря, и обратился со своей идеей
В чем суть моего предложения?
Те, кто работали на C#, Swift, Kotlin, JS или TS - прекрасно поймут такой прекрасный оператор как '?.', который вызывает метод объекта, если сам объект не нулевой. Такой оператор уже давно появился в этих языках и очень хорошо себя зарекомендовал
В С++, конечно, такого оператора нет, что обидно. Хотя обсуждения были, идеи были, но все возвращались к одному вопросу - что возвращать, если мы ожидаем int? 0? Магическое число? А если ожидаем объект? Что тогда?
В своей идее я ограничился только возвратом указателя и void. И да, раз уж мы работаем в контексте указателей, то логично будет писать не '?.', а '?->' (как унаследованное от ванильной стрелки '->')
А название ему: Null-Propagating Member Access Operator
Концептуально он выглядит как..
Из этого:
В это:
Если одна из веток не валидна (вернула nullptr), то продолжение функций обрывается (и возвращается nullptr)
Хотя почему я говорю концептуально? Ха, я не теоретик, поэтому форкнул Clang и на коленках реализовал этот злосчастный оператор '?->' в парсере и семантическом анализе. Теперь это не просто "идея", а уже работающий PoC (Proof of Concept)
В общем по ходу дела буду рассказывать тут что да как, а пока - оставлю пару ссылочек
GitHub: https://github.com/RootTool0/null-propagating-member-access-operator
Обсуждение в SG14: https://lists.isocpp.org/sg14/2026/02/1277.php
^ Сказал я, и начал делать то, на что бы никогда не решился - попробовать предложить свою идею комитету ISO С++
Для тех кто не знает: У С++ нет компании-владельца. Языком занимается публичный комитет WG21 организации ISO - группа из топовых инженеров и программистов (~400 человек) которые спорят и согласуют стандарт С++ - свод правил, по которым каждый компилятор (MSVC, GCC, CLang, avr-gcc и другие...) их реализуют. Так и живём ¯\_(ツ)_/¯
И внутри комитета есть рабочие группы (Study Groups) по различным направлениям: Рефлексия, встраиваемые системы, высоконагруженные системы, безопасность, игровые движки и тд.... И SG14 как раз и является группой в области "встраиваемых, высоконагруженных систем и игровые движки", куда я, собственно говоря, и обратился со своей идеей
В чем суть моего предложения?
Те, кто работали на C#, Swift, Kotlin, JS или TS - прекрасно поймут такой прекрасный оператор как '?.', который вызывает метод объекта, если сам объект не нулевой. Такой оператор уже давно появился в этих языках и очень хорошо себя зарекомендовал
В С++, конечно, такого оператора нет, что обидно. Хотя обсуждения были, идеи были, но все возвращались к одному вопросу - что возвращать, если мы ожидаем int? 0? Магическое число? А если ожидаем объект? Что тогда?
В своей идее я ограничился только возвратом указателя и void. И да, раз уж мы работаем в контексте указателей, то логично будет писать не '?.', а '?->' (как унаследованное от ванильной стрелки '->')
А название ему: Null-Propagating Member Access Operator
Концептуально он выглядит как..
Из этого:
UWorld* World = GEngine->GetWorld();
if (!World) return nullptr;
ULevel* Level = World->GetCurrentLevel();
if (!Level) return nullptr;
AActor* Actor = Level->GetActor();
return Actor;
В это:
return GEngine?->GetWorld()?->GetCurrentLevel()?->GetActor();
Если одна из веток не валидна (вернула nullptr), то продолжение функций обрывается (и возвращается nullptr)
Хотя почему я говорю концептуально? Ха, я не теоретик, поэтому форкнул Clang и на коленках реализовал этот злосчастный оператор '?->' в парсере и семантическом анализе. Теперь это не просто "идея", а уже работающий PoC (Proof of Concept)
В общем по ходу дела буду рассказывать тут что да как, а пока - оставлю пару ссылочек
GitHub: https://github.com/RootTool0/null-propagating-member-access-operator
Обсуждение в SG14: https://lists.isocpp.org/sg14/2026/02/1277.php
GitHub
GitHub - RootTool0/null-propagating-member-access-operator: Fork of LLVM/Clang with implementing the ?-> operator in C++
Fork of LLVM/Clang with implementing the ?-> operator in C++ - RootTool0/null-propagating-member-access-operator
❤3❤🔥1🔥1🤩1💯1
Заходит мужик в зоомагазин. Смотрит на рыбок, там, котов, собачек и тут резко останавливается видит обезьянку со стоимостью 10 тысяч долларов. Он спрашивает у продавца:
- Я может что-то не понимаю, или у вас ошибка, но почему эта обезьяна стоит 10 тысяч долларов?
- А, это особая обезьяна она умеет кодить на Python. Оттуда и цена.
Мужик удивился и пошёл дальше. Видит ещё одну обезьяну, со стоимостью 15 тыщ баксов.
- А эта что умеет?
- Эта обезьяна знает Python и JavaScript.
Ну, мужик удивился и пошёл дальше. Видит третью обезьяну со стоимостью 25 тысяч долларов.
- А эта что умеет?
- Она знает С, С++, C#, Java и кучу фреймворков к ним.
Мужик покивал головой, пошёл дальше и наткнулся на ещё одну обезьянку, со стоимость 100 тысяч долларов.
- А эта что, знает все языки программирования, ещё и все фреймворки к ним?
- Нет, она вообще ничего не знает, но остальные обезьянки называют её "Project Manager"
- Я может что-то не понимаю, или у вас ошибка, но почему эта обезьяна стоит 10 тысяч долларов?
- А, это особая обезьяна она умеет кодить на Python. Оттуда и цена.
Мужик удивился и пошёл дальше. Видит ещё одну обезьяну, со стоимостью 15 тыщ баксов.
- А эта что умеет?
- Эта обезьяна знает Python и JavaScript.
Ну, мужик удивился и пошёл дальше. Видит третью обезьяну со стоимостью 25 тысяч долларов.
- А эта что умеет?
- Она знает С, С++, C#, Java и кучу фреймворков к ним.
Мужик покивал головой, пошёл дальше и наткнулся на ещё одну обезьянку, со стоимость 100 тысяч долларов.
- А эта что, знает все языки программирования, ещё и все фреймворки к ним?
- Нет, она вообще ничего не знает, но остальные обезьянки называют её "Project Manager"
😁7❤1🕊1💯1🍓1
This media is not supported in your browser
VIEW IN TELEGRAM
Особенно ошибка инстанцирования шаблонов в виде полотна текста которого невозможно остановить))
❤3💯1
Ух знаете, только что с душа вышел, такая ебанутая гениальная мысль пришла: ГитГрам
В чем идея: Связать свойство репозиториев хранить информацию + их отправку изменений через коммиты как способ создания мессенджера!
То есть например я захожу в этот ГиТгРаМ через гитхаб, создаю там новый чат/сервер (просто создаю приватный репозиторий) и добавляю туда участников (контрбьютеров в этот репозиторий)
Читать сообщения легче лёгкого - просто читать гитхаб
А отправлять - отправлять коммиты (например создавать новый txt-файл с определённым id и коммитить его)
Другие участники это видят, читают этот файл, и это отображается в красивом интерфейсе мессенджера
Бред? Конечно!
Но надеюсь РКН не положит гитхаб, иначе он вообще положит всю индустрию IT )))
(хотя по идее можно Gitverse, но там коммиты платные... Ах, лишь бы денег содрать)
А еще логотип сложнее некуда: Логотип Телеграма, перекрашенный в черный цвет, а внутри белый логотип Гитхаба в виде осьминога, хаха
; Р
В чем идея: Связать свойство репозиториев хранить информацию + их отправку изменений через коммиты как способ создания мессенджера!
То есть например я захожу в этот ГиТгРаМ через гитхаб, создаю там новый чат/сервер (просто создаю приватный репозиторий) и добавляю туда участников (контрбьютеров в этот репозиторий)
Читать сообщения легче лёгкого - просто читать гитхаб
А отправлять - отправлять коммиты (например создавать новый txt-файл с определённым id и коммитить его)
Другие участники это видят, читают этот файл, и это отображается в красивом интерфейсе мессенджера
Бред? Конечно!
Но надеюсь РКН не положит гитхаб, иначе он вообще положит всю индустрию IT )))
(хотя по идее можно Gitverse, но там коммиты платные... Ах, лишь бы денег содрать)
А еще логотип сложнее некуда: Логотип Телеграма, перекрашенный в черный цвет, а внутри белый логотип Гитхаба в виде осьминога, хаха
; Р
❤2🔥2👍1🍓1