Portal: Solver с открытым исходным кодом?!
ДА! Вы не ослышались - Portal Solver теперь open-source!
Но чтобы вы меньше тратили время на билд проекта (почти 1.5к ассетов, я не шучу 💀)
Я решил скомпилировать все за вас! Так что успейте опробовать скомпилированные исходники Портал Солвера БЕСПЛАТНО!
Ссылочка: тык
ДА! Вы не ослышались - Portal Solver теперь open-source!
Но чтобы вы меньше тратили время на билд проекта (почти 1.5к ассетов, я не шучу 💀)
Я решил скомпилировать все за вас! Так что успейте опробовать скомпилированные исходники Портал Солвера БЕСПЛАТНО!
Ссылочка: тык
🤩3👀1
𝐑𝐨𝐨𝐭𝐓𝐨𝐨𝐥 𝐁𝐥𝐨𝐠
Искусство Построения Архитектуры Проекта Глава 4: "Квантовая запутанность" классов и "теория большого #includ'а" Вспомним: - Глава 2 - #include "Path/File.h" - открывает файл по пути и жестко заменяет себя на его содержимое - Глава 3 - Forward Declaration…
Forward Declaration на реальном примере
Как всегда работал (да.. в 2:25 ночи) и встретил распространенную/привычную проблему - в одной структуре я ссылаюсь (упоминаю) другую структуру, что находится чуть ниже
Что делать? Forward Declaration!
вот так примеры с учебника реально встречаются на работе))
Don’t Just Learn - Understand It. Think Cold
Как всегда работал (да.. в 2:25 ночи) и встретил распространенную/привычную проблему - в одной структуре я ссылаюсь (упоминаю) другую структуру, что находится чуть ниже
Что делать? Forward Declaration!
вот так примеры с учебника реально встречаются на работе))
Don’t Just Learn - Understand It. Think Cold
❤3👍1🔥1
Что ж, взял себе наконец-таки новый (второй) блокнот для Portal: Solver
А что с первым? А первый я взял ещё в прошлом году, когда только Альфа начиналась...
И сейчас он весь исписан расчётами, концептами камер, архитектур кода, систем/подсистем, и вообще темный лес☠️
Теперь, как говорится, начнем с чистого листа
А что с первым? А первый я взял ещё в прошлом году, когда только Альфа начиналась...
И сейчас он весь исписан расчётами, концептами камер, архитектур кода, систем/подсистем, и вообще темный лес☠️
Теперь, как говорится, начнем с чистого листа
❤2💯2🔥1
Искусство Построения Архитектуры Проекта
Глава 6: Enumerating the world!
Enum, enum, енум... что такое этот ваш enum? По сути - более красивая замена чисел на понятные слова.
Его синтаксис прост до безобразия:
Что можно сразу выделить:
- EnumName - название нашего перечисления
- EnumType - тип перечисления. По дефолту это int, но могут быть любые другие целочисленные типы (поговорим чуть позже)
Теперь можно обращаться к перечислению как: if(EnumName::A == EnumName::B);, или, EnumName MyEnumVar = EnumName::A;
Прикольно, да? Но что находится под enum? При компиляции компилятор просто идёт по элементам сверху вниз. Если значение у элемента не указано - берёт значение предыдущего и прибавляет к нему +1 (И да, по умолчанию первый элемент равен 0) То есть:
Но каков предел и размерность значений? А вот именно тут ключевую роль играет EnumType (по умолчанию - int). Но можно, например, enum class EnumName : uint8_t - 8 битное целое число без знака (от 0 до 255)
Но плюсы на то плюсы, что их гибкость позволяет задавать элементам любые значения. Разберем конкретный кейс:
И теперь при компиляции, код OK примет значение 200, NotFound - 404, а Something (как мы сказали выше) будет равен 405, так как компилятор даст ему значение относительно значения предыдущего элемента, да +1 (404 + 1 = 405)
А благодаря удобству enum, теперь мы можем создавать функции ECode GetCode(); и возвращать значения с помощью return ECode::OK; - удобно? Ещё как! И это одна из причин, почему enum активно используют в архитектуре
Да, конечно вышло суховато... но примите базу. В следующей главе более откровенно поговорим про хитрости использования enum
Enumerate The World. Think Cold.
Глава 6: Enumerating the world!
Enum, enum, енум... что такое этот ваш enum? По сути - более красивая замена чисел на понятные слова.
Его синтаксис прост до безобразия:
enum class EnumName : EnumType
{
A, B, C
};
Что можно сразу выделить:
- EnumName - название нашего перечисления
- EnumType - тип перечисления. По дефолту это int, но могут быть любые другие целочисленные типы (поговорим чуть позже)
Теперь можно обращаться к перечислению как: if(EnumName::A == EnumName::B);, или, EnumName MyEnumVar = EnumName::A;
Прикольно, да? Но что находится под enum? При компиляции компилятор просто идёт по элементам сверху вниз. Если значение у элемента не указано - берёт значение предыдущего и прибавляет к нему +1 (И да, по умолчанию первый элемент равен 0) То есть:
enum class EnumName : EnumType
{
// Начинаем счет с нуля
A, // 0
B, // 1
C // 2
};
Но каков предел и размерность значений? А вот именно тут ключевую роль играет EnumType (по умолчанию - int). Но можно, например, enum class EnumName : uint8_t - 8 битное целое число без знака (от 0 до 255)
Но плюсы на то плюсы, что их гибкость позволяет задавать элементам любые значения. Разберем конкретный кейс:
enum class ECode // int
{
OK = 200,
NotFound = 404,
Something // 405
};
И теперь при компиляции, код OK примет значение 200, NotFound - 404, а Something (как мы сказали выше) будет равен 405, так как компилятор даст ему значение относительно значения предыдущего элемента, да +1 (404 + 1 = 405)
А благодаря удобству enum, теперь мы можем создавать функции ECode GetCode(); и возвращать значения с помощью return ECode::OK; - удобно? Ещё как! И это одна из причин, почему enum активно используют в архитектуре
Да, конечно вышло суховато... но примите базу. В следующей главе более откровенно поговорим про хитрости использования enum
Enumerate The World. Think Cold.
❤4❤🔥1💯1
C++ - самый, сука, константный язык
Глядите, у нас есть базовый пример:
^ Указатель на x
Можно менять указатель
Можно менять значение
^ Указатель на константное значение
Можно менять указатель
Нельзя менять значение
^ Константный указатель на значение
Нельзя менять указатель
Можно менять значение
Что ж.. а теперь барабанная дробь:
^ Константный указатель на константное значение
Нельзя менять указатель
Нельзя менять значение
Но тише, тише, это только начало:
^ Указатель на константный указатель на значение
Можно менять указатель
Нельзя менять значение указателя p
Можно менять само значение x (через
^ Константный указатель на константный указатель на константное значение
Нельзя менять указатель
Нельзя менять значение указателя p
Нельзя менять само значение x
Ладно, хватит! Я еще не стал душнить про
указатель на указатель на константный указатель на константный указатель на x
C++ штука такая. Живите с этим :)
Глядите, у нас есть базовый пример:
int x = 5;
int* px = &x;
^ Указатель на x
Можно менять указатель
Можно менять значение
const int* px = &x;
^ Указатель на константное значение
Можно менять указатель
Нельзя менять значение
int* const px = &x;
^ Константный указатель на значение
Нельзя менять указатель
Можно менять значение
Что ж.. а теперь барабанная дробь:
const int* const px = &x;
^ Константный указатель на константное значение
Нельзя менять указатель
Нельзя менять значение
Но тише, тише, это только начало:
int* const* ppx = &px;
^ Указатель на константный указатель на значение
Можно менять указатель
Нельзя менять значение указателя p
Можно менять само значение x (через
**ppx)const int* const* ppx = &px;
^ Константный указатель на константный указатель на константное значение
Нельзя менять указатель
Нельзя менять значение указателя p
Нельзя менять само значение x
Ладно, хватит! Я еще не стал душнить про
int* const* const** ppppx....указатель на указатель на константный указатель на константный указатель на x
C++ штука такая. Живите с этим :)
❤4👀1
this->CelebrateBirthday();
//TODO: :tada effect
И вот.. Подходит мой8-й год в С++ 17-й год жизни, и знаете? Я счастлив что встретил вас всех, с кем остались, а с кем, конечно, разошлись
И правда, еще 2 года назад, до начала Portal: Solver и всего этого я думал что к 16-17 годам буду как те самые подростки на вписках - алкоголь, курение и всякое подобное. Считал что моя жизнь увязнет в этом, но....
Сейчас проект, цель, будущее - и правда, это не произошло бы без всех вас.
Безусловно в общей картине влияние каждого - как капля в море, но именно существование этого, как кирпичик за кирпичиком, объединило всех нас здесь, в этом скромном, но теплом, окружении. И без вас - этот день не был бы таким приятным ;)
Спасибо
//TODO: :tada effect
И вот.. Подходит мой
И правда, еще 2 года назад, до начала Portal: Solver и всего этого я думал что к 16-17 годам буду как те самые подростки на вписках - алкоголь, курение и всякое подобное. Считал что моя жизнь увязнет в этом, но....
Сейчас проект, цель, будущее - и правда, это не произошло бы без всех вас.
Безусловно в общей картине влияние каждого - как капля в море, но именно существование этого, как кирпичик за кирпичиком, объединило всех нас здесь, в этом скромном, но теплом, окружении. И без вас - этот день не был бы таким приятным ;)
Спасибо
❤5🎉3
This media is not supported in your browser
VIEW IN TELEGRAM
⚡️ Portal: Solver запустили на Raspberry Pi 3B 2016...
Оптимизация не перестает удивлять
Что дальше?)
Оптимизация не перестает удивлять
Что дальше?)
😱4❤🔥2💯1
Экстравагантное использование препроцессора для избежания виртуализации
Когда я говорил про гибкость С++, я в буквальном смысле имел ввиду - С++ очень гибкий. И недавное, чисто обдумывая препроцессинг, мой мозг заметил одну интересную вещь....
Представим задачу с реверсивной зависимостью:
И потом где-то, где-то далеко, мы реализуем:
^ В целом неплохо, да? Но что меня, как Ардуинщика, смущает - виртуальные функции...
Во первых: само их существование - убивает меня, как человека, умеющего чувствовать железо
И второе (самое очевидное): - если другой разработчик банально забудет реализовать функцию из интерфейса?
И, относительно недавно заметил одну интересную вещь: Если мы сделаем файл с обьявлением функций, а затем просто будем подключат его и реализовывать все функции? Покажу на практике:
^ Мы просто объявили функции в безымянном, для компилятора, файле (Напомню - компилятор работает только с .cpp файлами)
А вот затем, в целевом файле, делаем такой фокус:
Иии, самое интересное, реализацию делаем напрямую в .cpp:
По сути, что мы сделали? При проходе препроцессингом, строка #include "RenderDeclaration.h" раскроется с содержимым внутри RenderDeclaration.h, то есть:
^ А затем уже линковщик подловит все обьявления функций с их реализациями в OpenGLRender.cpp и все будет хорошо
Но хорошо, вы скажите, это не гибко и вообще очень плохо. Но я вас спрошу: "Какая, черт побери, гибкость в фундаменте архитектуры, которая ну никак не меняется?!"
По сути, виртуализация - это очень тяжелая штука. Каждая виртуальная функция уже КАК МИНИМУМ занимает от 4 до 8 байт! (4 байта на 32 битной машине. 8 байт на 64 битной машине. Но не суть)
Например вы пишете движок, вы 1 раз реализовали класс для определенной графики - и забыли. Вам не нужно каждый раз менять что-то, особенно динамично - это не имеет смысла так такового (а оверхед будет иметь)
"А если человек хочет реализовать свой вариант графики?" - тогда он просто создаст новый класс, подключит файл с функциями, IDE ему сразу предложит/подскажет какие функции надо реализовать - реализуете и работаете. Все! А чтобы применить графику, достаточно (к примеру):
А затем, уже где-то под капотом движка:
И, если кто не понял, при проходе препроцессингом RENDER_CLASS заменится на ROpenGLRender и мы получим строку ROpenGLRender RenderClass;
И да, если я создам класс RVulkanRender, реализую функции, обновлю конфиг как #define RENDER_CLASS RVulkanRender, то получу тот же гибкий функционал при минимальных затратах. Никаких виртуальных функций, виртуальных таблиц и прочего
А в завершении могу добавить: При обновлении движка, создателю достаточно добавить новое обьявление в RenderDeclaration.h, а затем пользователь (работающий на движке) просто дополнит свой класс RVulkanRender новой функцией и все будет как раньше - без всякого "ой, я случайно забыл переопределить функцию"
What you don't use, you don't pay for. Think Cold.
Когда я говорил про гибкость С++, я в буквальном смысле имел ввиду - С++ очень гибкий. И недавное, чисто обдумывая препроцессинг, мой мозг заметил одну интересную вещь....
Представим задачу с реверсивной зависимостью:
// RenderInterface.h
class IRender
{
virtual void SetupScreen();
virtual void ClearScreen();
virtual void UpdateScreen();
};
И потом где-то, где-то далеко, мы реализуем:
// OpenGLRender.h
class ROpenGLRender : IRender
{
virtual void SetupScreen() override;
virtual void ClearScreen() override;
virtual void UpdateScreen() override;
};
^ В целом неплохо, да? Но что меня, как Ардуинщика, смущает - виртуальные функции...
Во первых: само их существование - убивает меня, как человека, умеющего чувствовать железо
И второе (самое очевидное): - если другой разработчик банально забудет реализовать функцию из интерфейса?
И, относительно недавно заметил одну интересную вещь: Если мы сделаем файл с обьявлением функций, а затем просто будем подключат его и реализовывать все функции? Покажу на практике:
// RenderDeclaration.h
void SetupScreen();
void ClearScreen();
void UpdateScreen();
^ Мы просто объявили функции в безымянном, для компилятора, файле (Напомню - компилятор работает только с .cpp файлами)
А вот затем, в целевом файле, делаем такой фокус:
// OpenGLRender.h
class ROpenGLRender
{
#include "RenderDeclaration.h"
};
Иии, самое интересное, реализацию делаем напрямую в .cpp:
// OpenGLRender.cpp
void ROpenGLRender::SetupScreen() { ... }
void ROpenGLRender::ClearScreen() { ... }
void ROpenGLRender::UpdateScreen() { ... }
По сути, что мы сделали? При проходе препроцессингом, строка #include "RenderDeclaration.h" раскроется с содержимым внутри RenderDeclaration.h, то есть:
// OpenGLRender.h
class ROpenGLRender
{
// #include "RenderDeclaration.h"
void SetupScreen();
void ClearScreen();
void UpdateScreen();
};
^ А затем уже линковщик подловит все обьявления функций с их реализациями в OpenGLRender.cpp и все будет хорошо
Но хорошо, вы скажите, это не гибко и вообще очень плохо. Но я вас спрошу: "Какая, черт побери, гибкость в фундаменте архитектуры, которая ну никак не меняется?!"
По сути, виртуализация - это очень тяжелая штука. Каждая виртуальная функция уже КАК МИНИМУМ занимает от 4 до 8 байт! (4 байта на 32 битной машине. 8 байт на 64 битной машине. Но не суть)
Например вы пишете движок, вы 1 раз реализовали класс для определенной графики - и забыли. Вам не нужно каждый раз менять что-то, особенно динамично - это не имеет смысла так такового (а оверхед будет иметь)
"А если человек хочет реализовать свой вариант графики?" - тогда он просто создаст новый класс, подключит файл с функциями, IDE ему сразу предложит/подскажет какие функции надо реализовать - реализуете и работаете. Все! А чтобы применить графику, достаточно (к примеру):
// Config.h
#define RENDER_CLASS ROpenGLRender
А затем, уже где-то под капотом движка:
// Engine.cpp
#include "Config.h"
void Engine::SetupRender()
{
RENDER_CLASS RenderClass;
RenderClass.SetupScreen();
RenderClass.ClearScreen();
RenderClass.UpdateScreen();
/* Other... */
}
И, если кто не понял, при проходе препроцессингом RENDER_CLASS заменится на ROpenGLRender и мы получим строку ROpenGLRender RenderClass;
И да, если я создам класс RVulkanRender, реализую функции, обновлю конфиг как #define RENDER_CLASS RVulkanRender, то получу тот же гибкий функционал при минимальных затратах. Никаких виртуальных функций, виртуальных таблиц и прочего
А в завершении могу добавить: При обновлении движка, создателю достаточно добавить новое обьявление в RenderDeclaration.h, а затем пользователь (работающий на движке) просто дополнит свой класс RVulkanRender новой функцией и все будет как раньше - без всякого "ой, я случайно забыл переопределить функцию"
What you don't use, you don't pay for. Think Cold.
❤4👍2🔥1👀1
Теория многопоточности - это просто
И я серьёзно: кто хочет вкатиться в теорию многопоточности - не поленитесь и почитайте эту шикарную статью на Хабре:
https://habr.com/ru/articles/974198/
И я серьёзно: кто хочет вкатиться в теорию многопоточности - не поленитесь и почитайте эту шикарную статью на Хабре:
https://habr.com/ru/articles/974198/
❤1🔥1🤩1💯1