Серёга Антонов
78 subscribers
29 photos
41 videos
60 links
Всем привет!

Меня зовут Серёга Антонов. Программирую игры и движки. Рассказываю об играх простым языком.

ВКонтакте https://vk.com/antonovcoder

YouTube https://www.youtube.com/@antonovcoder

Написать лично @bestsuperman
Download Telegram
Для большинства людей удалёнка казалась чем то не обычным до пандемии. Сейчас это вроде как обычное дело. Работодатели в IT и не только часто предлагают либо офис либо удалёнку или даже смешанный тип.

Я свою карьеру начал как flash разработчик с местного офиса. Работали тогда на израиль, но фирма распалась и я перешёл на удаленку. И тут все начало смешиваться - практически стёрлась грань работа-отдых. Я стал работать очень много, прерываясь только на поесть и поспать. Вёл сразу параллельно 4 несвязанных проекта на разных технологиях. Мне было в кайф, но тело такой режим не одобрило. Лет через 5 пришло понимание, что так продолжать нельзя. И однажды утром я просто вышел из дома и пошёл. И ходил каждый день по 1-2 часа. Через месяц я уже немного бегал. Через год я достиг своей самой лучшей формы в жизни - бегая каждый день. Так же наладил баланс работа-отдых, снизив количество, но повысив качество и стоимость моего труда. В общем ввел дисциплину. Или как коллега называет - ДНИ - Долго Непрерывно Интенсивно.

Сейчас имея 20 летний стаж работы, из которых 15 лет на удалёнке, дисциплина все еще со мной. Бег сменился на другие активности, но баланс работа-отдых это прямо база.
👍2
Время жизни дороже всего. Его невозможно дополнительно купить или забрать. Его невозможно остановить или повернуть вспять. Оно просто непрерывно течёт вперёд. Но что еще более важно - оно ограничено.

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

А можно бережно провести это время, занимаясь любимым и полезным делом. Стать уважаемым мастером своего дела. Общаться только с другими такими же людьми с совестью. Постоянно изучать что то новое. Наполнить свою жизнь смыслом. Создавать, а не потреблять.

Есть те кто нагло пытается забрать наше время и внимание. Это и богатые IT корпорации агрессивно впаривая свои интернет продукты. Это и банки подсаживают на долговую иглу. Это и всякая разная реклама лезущая отовсюду предлагающая всякую ненужную чепуху. Все хотят нашего внимания. Нашего времени. Нашей жизни.

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

А ты хозяин своего времени?
💯2
Media is too big
VIEW IN TELEGRAM
Если кому то показалось, что я пропал 😜 это не так. Я вошел в поток и всю неделю переделывал несколько раз алгоритм синхронизации. Сделал наверно десяток итераций. Пришло осознание как решать проблемы о которых я говорил в предыдущем видео.

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

ВК
Media is too big
VIEW IN TELEGRAM
Мы с вами так устроены, что погружаясь в любой практический процесс, будь то работа или какой то личный проект, мы начинаем осознавать и понимать более точно и явно теоретические основы этого процесса. Можно прочитать много книг о том как водить автомобиль, но так и не научиться этому. Поэтому практика обычно идет вместе с теорией. Я считаю, что практика первична и имеет более важное значение чем теория, потому что теория рождается из практики. На практике мы можем реально почувствовать на уровне ощущений какие либо закономерности или попробовать несколько вариантов. По другому мы загружаем контекст в голову. Много контекста. И спустя какое то время приходит полное понимание теории. Мозг как будто упорядочивает эту информацию. И тут нужно сделать шаг назад и переосмыслить то, что ты делаешь. Всегда есть места где можно что то поменять и рабочий процесс или ваше дело улучшится.

Собственно со мной это происходит регулярно и произошло и в этот раз. Попробовав много подходов в синхронизации данных мне пришло понимание теории. И в этом видео я рассказываю подробно про виды синхронизации и как они переплетаются между собой.

В предыдущем видео я показывал демо синхронизации

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

Но как известно все девайсы работают на разных частотах (60, 120, 144 и тд). Так же сейчас модно делать плавающую частоту кадров(1-120). Еще довольно часто возможна ситуация когда у нас слабый девайс и он просто не справляется с заданной частотой.

На первой картинке выше можно увидеть наглядно проблему. Есть клиенты с разной частотой кадров (60, 120, плавающей). А фиксированная заданная частота у нас 60. Как же все таки нам синхронизировать частоту обрабатываемых кадров, что бы у всех было все одинаково?

Я сформировал такое решение:
- Заводим числовой таймер в микросекундах
- Каждый реальный кадр прибавляем к нему время прошедшее с предыдущего кадра
- Если это время превышает время фиксированного кадра - увеличиваем фиксированный кадр (или делаем расчеты)
- Повторяем до тех пор пока таймер больше времени фиксированного кадра
- Все расчеты времени ведем в микросекундах (или более точно если есть возможность)

Код с комментариями на второй картинке, а так же продублирую сюда.


/**
* frame - текущий кадр
* timer - таймер (в микросекундах)
* frame_time - фиксированное время кадра (в микросекундах)
* delta - дельта - сколько времени прошло между реальными кадрами (в микросекундах)
*/
pub fn next_frame(frame: &mut u32, timer: &mut u32, frame_time: u32, delta: u32) {
// увеличиваем таймер на прошедшее время
*timer += delta;

// далее в цикле проверяем не превысил ли наш таймер фиксированный кадр
// так как ситуация непредсказуема и дельта может быть большой
// за один реальный кадр может пройти несколько фиксированных кадров
while *timer > frame_time {
// если превысили то вычитаем его однократно
*timer -= frame_time;

// увеличиваем текущий кадр
*frame += 1;

// здесь можно вести расчет логики
// но можно делать это в любом другом месте
// учитывая сколько кадров прошло (frame)
}
}
Внезапно туман в Великом Новгороде )
Немного про софтскилы.

Если вам кто то говорит:
- Не хочу тебя обидеть

Значит он будет вас прямо сейчас обижать.

Аналогично с фразами:
- Никого не обвиняю, но
- Дело не в тебе
- Ничего личного
- Никто не виноват, вы не подумайте
- Вот тут написано плохо, давайте так не делать
- Вот это плохой ненадежный код
И тд.

Говорить кому то, что он сделал ошибку, пропустил что то, всегда тяжело для обоих сторон. Можно либо сильно обидеть человека, либо даже получить серьезный отпор, или начать спорить до потери пульса. А ошибки то мы все совершаем, всегда. Это неизбежно.

Я прошел довольно большой путь и всякое было. Признаюсь, по молодости, во многих случаях я был не прав. Потом у меня появился наставник Владимир. И мы с ним проработали много лет. Он меня научил, что если работаешь вместе над чем то, и есть какая то цель. То цель превыше всего. Главное достичь этой цели в вашем деле. Все остальное вторично. И когда вы держите цель в голове, все работают на одной волне. Все гребут в одной лодке и в одну сторону.

Вместо фраз в начале этого поста можно же сказать так:
- Слушай я изучил вот это и увидел проблему, давай вместе посмотрим?
- Смотри я вот тут вижу место где можно улучшить, ты пробовал так?
- Смотри я нашел баг, давай вместе чинить!

Собственно тут мы говорим о предмете работы. Всегда держа в голове общую цель.

Всем благ!
Новый видос с демкой в лабе )

ВК
👍1
Forwarded from Лаборатория сборки (Сергей Антонов)
Media is too big
VIEW IN TELEGRAM
Всем привет! Продолжаю делать синхронизацию на базе детерминизма. Про это я подробно рассказывал здесь. В данном видео я показываю синхронизацию более сложной стркутуры данных. Она описывает два экрана, между которыми можно переключаться. На первом экране у нас небольшая физическая симуляция и обработка столкновений. На втором экране можно редактировать текст одновременно. Данный подход синхронизации организует строго последовательный поток событий или как я называю изменений от пользователей. Этот поток синхронизируется между всеми узлами, тем самым позволяя нам расчитать точную сцену в любой момент. Кроме того в данном подходе свои изменения применяются сразу, тем самым пользователь никогда не увидит лага от своих действий. Синхронизацию текста я сделал на базе строки и позиций курсоров, которые взял из уже готового нашего контрола. Я вычисляю дифф текста и преобразую его в одну из операций - удаление или вставка. Так же есть операция перемещения курсора. Эти операции применяются последовательно на каждом клиенте. И дают идентичный результат. Более подробно в видео.
🔥2
Всем доброго утра! 😀

Синхронизация текста завела меня в дебри андроида. Начал я тестить одновременный ввод. С компа и с телефона. Если медленно делать ввод с обоих девайсов, то все прекрасно. А если быстро вводить, то тут по какой то причине курсор компа стал прыгать в сторону курсора андроида. Начал дебажить, выяснил, что проблема где то в вводе из софт клавиатуры. Начал более детально смотреть...

Тут надо небольшое отступление, о том как работает софт клавиатура. Предполагается если вы хотите сделать свой кастомный контрол текста, то есть некий интерфейс к софт клавиатуре. Ime State по сути это сам текст, а так же начало и конец выделения. Собственно есть запись этих свойств и чтение. Если мы ткнули в ваш текстовый блок, то мы передаем туда эти свойства. Потом пользователь вводит текст любым способом, который есть в вашей клавиатуры. И далее у нас есть возможность прочитать эти свойства.

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

Получается такая последовательность:
- читаем текст с клавиатуры
- вычисляем изменения
- вносим эти изменения на таймлайн
- так же вносим новые изменения на таймлайн других игроков
- пересчитываем результирующий текст
- утанавливаем новый текст в клавиатуру
И так по кругу, каждый кадр.

Так вот проблема в том, что установка и чтение асинхронны. Клавиатура очевидно работает не в нашем потоке, она как отдельная программа. При установке мы шлем ей наше состояние. Она его применяет у себя, потом обрабатывает ввод пользователя и отправляет нам результат. А мы его получаем по событию. То есть асинхронно. Причем если бы событие приходило именно на изменения пользователя, то все было бы корректно. Но проблема в том, что клавиатура присылает изменения и на нашу установку текста. И получается я отправил текст, а мне спустя кадр-два прилетает обновление, а я не знаю, это то что я отправил или это пользователь ввел? Вот к чему приводит асинхронность и плохой дизайн интерфейса.

В общем буду думать как это обойти...
👍2
Когда программист пишет новый проект, по началу это всегда легко, просто и явно. Со временем кодовая база проекта растет и становится сложной в понимании. А это понимание необходимо для дальнейшего изменения проекта. В итоге проект превращается в такую сложную логическую машину, что любые изменения могу провести к неожиданным результатам. Мало того растут неявные связи и пути этой логики. Это абсолютно неизбежно.

Мы программисты программируем логику. По сути мы перекладываем эту логику в код. А что бы нам добавить еще немного логики, нам надо сначала прочитать эту логику из кода и осознать ее. Затем поменять ее в голове и только после этого дописать код. Поэтому видеть явные взаимосвязи логики в коде это очень важно - наглядность всегда упрощает понимание. Не все языки программирования и архитектуры нам в этом помогают. Да и во всем есть свои плюсы и минусы. Достичь явность логики в коде это прямо серьезное испытание.

Еще у программистов есть прикол, когда они возвращаются к своему же написанному коду и пытаются понять его. Приходят в ужас от написанного и хотят его переписать. И переписывают, а спустя время ситуация повторяется вновь. Чтение же чужого кода дается еще сложнее. По сути нужно прочитать мысли другого человека и воссоздать его логику в голове. В этом помогает насмотренность. Нужно очень много читать и писать код. С годами понять чужой код, причем на любом языке программирования, становится все проще и проще. Абсолютное же понимание любого алгоритма достигается только когда ты сам напишешь этот алгоритм.

Продолжение в следующем посте.
В предыдущем посте я рассуждал о явности и сложности кода.

Теперь давайте посмотрим какой инструментарий мы имеем сегодня?
Посмотрим в со стороны явности.

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

События
События вызывают непонимание последовательности их выполнения. От этого очень сильно страдает явность. К тому же надо следить за удалением подписки на события. Дебаг событий превращается в ад )

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

Версионирование
Любое изменение любой библиотеки может сломать вашу программу. Вот прямо даже если 1 бит поменялся. Предполагается, что патч-минор версии служат исправлению багов и добавлению фич. Но где грань? Кто определяет, что это патч или минор? Это все ненадежно, проверено на практике многократно.

Entity component system (ECS)
Это когда у нас есть абстрактная сущность и мы можем вешать на нее любые компоненты. А так же есть системы-функции которые постоянно обрабатывают эти компоненты по отдельности либо по типам группами. Супер гибко! Но тут проблема в том, что сущности и связанные с ними компоненты создаются во время исполнения и проследить явную связь в коде невозможно. Ты как детектив будешь на доске выстраивать взаимосвязи компонентов и сущностей. Этот принцип возможно подойдет только для чувствительных к производительности систем, но явности он точно не добавляет.

Тесты
В теории сначала пишем тесты, а потом код к ним. Звучит просто, но практически всегда системы бывают на столько сложными, что ты не знаешь, что тестировать. Сначала надо написать не один прототип, что бы понять, что в итоге. Поэтому тесты хороши для сохранения устоявшихся решений и логики. Они позволяют быстро проверить не сломалось ли чего после твоего изменения. В общем тесты добавляют явность и еще служат примерами использования.

Data-driven
Это когда данные являются главными. Так же данные являются и интерфейсом для общения библиотек. Код в данном случае просто постоянно изменяет эти данные. Так как данные мы описываем четкими структурами то можно проследить где конкретное свойство используется. Так же мы можем код разбить на четкие небольшие процедуры которые берут часть данных меняют их и кладут в другую часть данных - мы явно можем увидеть это в коде. Процедуры общаются через данные. Это однозначно прибавляет явности.

В следующем посте подведу итог и расскажу к каким выводам мы пришли в Точке Сборки.
👍1
Media is too big
VIEW IN TELEGRAM
Ранее я рассказывал про проблему синхронизации одновременного ввода текста.

В итоге проблему удалось решить комбинировано. 

- Во первых добавил историю установки значения. Она убирает ивенты от наших изменений и старые значения не всплывают.
- Во вторых сузил контекст. Я передаю в Ime State все, что попало в текущее выделение в том числе текущее слово.

Более подробно показываю в видео.

ВК
👍1
В продолжение предыдущего поста.

Я писал много приложений разного типа, на основных платформах и популярных языках программирования. В основном они были написаны в ООП стиле со всеми рекомендациями из учебников. Как я говорил в прошлом посте, ясности в понимании этого кода не было. Была сильная связанность и запутанность кода, необходимо было много времени и документация, что бы понимать как примерно работает код.

В свободное время я экспериментировал со своими игровыми движками, и прощупывал разные архитектуры и подходы. Я хотел убрать сильную связанность кода между модулями, и что бы например я мог менять рендер в любое время. Например это могла быть обычная и дебажная отрисовка, или даже без отрисовки можно было бы запустить тесты без инициализации рендер системы. Мне нужно было абстрактное описание сцены, что бы оно не тянуло ничего и было самодостаточным. И несколько рендеров, что бы они смотрели на сцену и рисовали по своим принципам. Например если бы мы делали шахматы, то можно было бы сделать классический 2д рендер, а можно нарисовать реалистично в 3д с тенями, отражениями и бликами, а можно отрисовать в пиксель-арте. Это мы говорим про output, но по сути тоже самое с input. То есть все события мыши, клавиатуры и т.д. тоже хотелось бы основательно отделить. Получется у нас есть модель сцены в виде четких законченных структур, которые описывают сцену полностью и достаточно. Вы можете предложить, что все легко делается с помощью внедрения зависимостей (dependency injection). Именно так я и сделал в своем последнем движке. Но там проблема в том, что придется юзать интерфейсы, а значит об этих интерфейсах должны знать все части и модель и рендеры. К тому же интерфейсы закладывают ограничение или наоборот излишек. Мы же хотим, что бы наша модель не имела зависимостей. И тут нам приходит на помощь такая концепция как data-driven.

Далее исторически я присоединился к Точке Сборки, где задолго до этого Антон Волков разработал Дата-подход. И мы с коллегами используем его во всех программах.

В итоге, что мы имеем:
- Данные главные. Все общение систем происходит через данные.
- Мы описываем нашу модель понятными и легко читаемыми структурами данных.
- Что бы менять данные, нам нужны процедуры. В нашем понимании процедура это функция которая принимает данные и меняет их.
- Список процедур, которые меняют наши данные, мы называем - процессом.
- Процедуры могут быть вложенные друг в друга.
- Мы можем убирать или добавлять любую процедуру.
- Добавляя новую процедуру сложность кода растет линейно.
🔥6👍2