This media is not supported in your browser
VIEW IN TELEGRAM
А знаете.. в моменте у меня просто щелкнуло и я понял как работает мультиплеер в Анриле - и на деле это довольно таки интересный подход!
❤4❤🔥1🔥1
В чем суть:
В концепции Анрила хост/сервер - Держит весь мир и является источником правды. Клиенты же - синхронизируются с сервером. Это называется репликация
Да, клиенты могут отправлять ему команды, изменять данные, но последнее слово - за сервером
И в С++ (да и в блюпринтах) для UPROPERTY() существует такой параметр как Replicated который говорит, что переменная - реплецируемая, то есть, ее итоговое состояние будет жестко синхронизировано с сервером
И помните мой первый пост про мультиплеер, где был диссинхрон игрока и все жестко лагало? Это был результат моего "одиночного" мышления перед мультиплеером
Я тогда только изменял локальные данные игрока, не понимая, что изменения локальных реплецируемых данных - плохой ход, ведь Анрил все равно вернет их значения так, как это является на сервере (ведь он источник истиныа все остальные бесы хвхвхв)
Так вот что я понял - работа мультиплеера проста до безумия если быть в потоке с Анрилом!
Когда клиент, например, хочет присесть - выполянется локальное действие Crouch (bIsCrouch = true) И происходит отправка команды серверу, который у себя обновляет состояние переменной bIsCrouch для нашего игрока
И в чем соль - если не просить сервер обновить переменную, то на сервере bIsCrouch так и останется false, затем в новом пакете данных (между сервером и клиентом) будет диссинхрон нашего локального bIsCrouch = true и пакетного bIsCrouch = false, а так как макрос UPROPERTY(Replicated) обязует Анрил синхронизировать переменные, то произойдет неприятный пролаг, рассинхронизация и пошло поехало, покатилось побежало...
А благодаря тому способу, теперь мы сами по себе (локально) выставляем bIsCrouch = true и просим сервер обновить его bIsCrouch на true. А потом в следующем пакете данных приходит новый bIsCrouch = true, который и так равный локальному bIsCrouch и все будет в потоке - Сервер будет хранить истину для других клиентов, и мы получаем бесплатный и быстрый отклик
И да - можно, конечно, всю логику отдать серверу, чтобы его просить обновить переменную и ждать пока придет ответ, но... Это будет дико долго, из-за чего будет плохой отклик и вы будете чувствовать задержку вообще в любом действии во всей игре
Как-то так
Thinking in Four Dimensions. Think Cold.
В концепции Анрила хост/сервер - Держит весь мир и является источником правды. Клиенты же - синхронизируются с сервером. Это называется репликация
Да, клиенты могут отправлять ему команды, изменять данные, но последнее слово - за сервером
И в С++ (да и в блюпринтах) для UPROPERTY() существует такой параметр как Replicated который говорит, что переменная - реплецируемая, то есть, ее итоговое состояние будет жестко синхронизировано с сервером
И помните мой первый пост про мультиплеер, где был диссинхрон игрока и все жестко лагало? Это был результат моего "одиночного" мышления перед мультиплеером
Я тогда только изменял локальные данные игрока, не понимая, что изменения локальных реплецируемых данных - плохой ход, ведь Анрил все равно вернет их значения так, как это является на сервере (ведь он источник истины
Так вот что я понял - работа мультиплеера проста до безумия если быть в потоке с Анрилом!
Когда клиент, например, хочет присесть - выполянется локальное действие Crouch (bIsCrouch = true) И происходит отправка команды серверу, который у себя обновляет состояние переменной bIsCrouch для нашего игрока
И в чем соль - если не просить сервер обновить переменную, то на сервере bIsCrouch так и останется false, затем в новом пакете данных (между сервером и клиентом) будет диссинхрон нашего локального bIsCrouch = true и пакетного bIsCrouch = false, а так как макрос UPROPERTY(Replicated) обязует Анрил синхронизировать переменные, то произойдет неприятный пролаг, рассинхронизация и пошло поехало, покатилось побежало...
А благодаря тому способу, теперь мы сами по себе (локально) выставляем bIsCrouch = true и просим сервер обновить его bIsCrouch на true. А потом в следующем пакете данных приходит новый bIsCrouch = true, который и так равный локальному bIsCrouch и все будет в потоке - Сервер будет хранить истину для других клиентов, и мы получаем бесплатный и быстрый отклик
И да - можно, конечно, всю логику отдать серверу, чтобы его просить обновить переменную и ждать пока придет ответ, но... Это будет дико долго, из-за чего будет плохой отклик и вы будете чувствовать задержку вообще в любом действии во всей игре
Как-то так
Thinking in Four Dimensions. Think Cold.
❤3👍3❤🔥1🔥1🤯1
Forwarded from Shoko - UI Framework (Root Tool)
Черт возьми, оно работает...
знали бы вы мое волнение сейчас
Когда я только начинал проектировать Shoko, то много внимания уделял про архитектуру и кроссплатформенность до уровня Embedded
И знаете? Оно работает!
Слева - Raspberry Pi 3B (1GB RAM) + LCD Shield (Linux)
Справа - Мой ПК (Windows 11)
И абсолютно не меняя код (только конфиг), я могу переключаться между платформами, ха
Compile-time UI начинает жить!
знали бы вы мое волнение сейчас
Когда я только начинал проектировать 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/
Емае, как же я кайфую от алгоритма, просто блаженство
https://habr.com/ru/articles/984184/
❤2🔥1
Я просто сейчас в диком сегфолте
Ведь реально.. Это мини-версия статичной рефлексии из С++26!
Которую я случайным образом повторил на С++17
Вот до чего доводит долго сидеть в Райдере по 10 часов 💀
Ведь реально.. Это мини-версия статичной рефлексии из С++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. кнопки лишь нарисованы, это не виджеты, сорьки :(
И... Да, она работает, и прекрасно выполняет свою функцию: быстрый рендер + легкий перенос на любую платформу
Конкретно данный билд собран на базе 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