Добрый день, сегодня я хочу продолжить рассказывать о своей библиотеке, в надежде найти сторонников идеи.
Этот пост является продолжением Transcend Library 4: Введение, точнее логичным развитием дискуссии в комментариях.

Почему я пишу новый пост вместо вскрытия кода


— имеет простую причину. Причины.
  • После очередного рефактора архитектуры(июнь 2012) библиотека не работает. Точнее половины методов просто нет. Я понимаю, что четверть решается копипастом с прошлых версий, но факт остается фактом.
  • Рефакторинг еще не завершен. Половина библиотеки переведена под новый стандарт, а половина только готовится, просто не дошли руки.
  • Код не документирован, и на данном этапе местами не ясен.
  • Да и проще будет начать разработку зная хоть в общих чертах идею, чем грызя код с нуля.


Дисклеймер


Я амбициозен, молод, глуп и самоуверен. Максимализм еще не умер и временами возникает. Но все же, после более-менее здравых рассуждений, открытие кода и выход в Open Source мне кажется более простым и реальным способом найти коллег-разработчиков, чем заработать деньги от первых продаж и нанять персонал.

Паттерны


Большинство из того что я использовал имеет под собой распространенные паттерны. Что то не является таковым, но очень распространено. В любом случае, я надеюсь что любой участок будет ясным для тех кто их не знает. Для тех кто знаком — я буду рядом приписывать название.

TL4


Асинхронность

Природа сети

Природа сети асинхронна. Здесь нет понятий «одновременно» и «мгновенно». Все эффекты от вызова функций происходят с задержками зависящими от многих причин. Ваше право дожидаться результата, или выполнять свои действия в это время(blocking and non-blocking sockets), но факт не изменится. Между тем как вы захотите открыть соединение, и как оно фактически откроется пройдет значительное время. И это плохо.
Но есть и хорошие новости, вы можете одновременно приказать совершить множество действий, и ждать какое завершится первое. При этом, количество действий не сильно влияет на время выполнения каждого.
Все это прямо просит проектировать библиотеку с поддержкой событий, но это еще не все.

Перегруженность запросов

Есть еще одна цена использования сети — каждый вызов стоит времени. Большого времени. Оно тратится на освобождение ресурса, постановление в очередь операционной системы и подобное. К примеру мы хотим сделать 1000 запросов dns. Пусть каждый вызов стоит 1ms(адекватное время, бывает и больше), это значит что мы потеряем секунду только на вызовах. В это время, наше приложение, работающее в одном потоке «просядет» по FPS, а пользователь увидит подвисание на ровном месте.
Причем фактически драйвер делает 50(число с потолка) запросов каждую секунду, для всех приложений в системе.
Я понимаю, где то может быть оправданно так поступать(и есть настройка что бы так происходило), но система разрабатывалась для realtime-приложений, а в них такое неприемлемо.

Порядок выполнения задачи

При вашем запросе OpenConnection, библиотека не делает ничего кроме выделения памяти и постановки задания в свою очередь. Все операции происходят в локальном адресном пространстве и локальном потоке, изза этого они быстрые.
Фактическое же делегирование происходит при вызове Response, который просматривает задачи и выполняет одно за другим, до timeout.
В нашем примере это позволяет после первых 50 запросов, начать устанавливать соединение, и возможно(если успеет), начать(или даже завершить) обмен информацией до получения всех 1000 адресов.
Внутреннее устройство можно сравнить с event-driven архитектурой, за исключением того что уведомление о событиях происходит не по паттерну observer.

Приоритет выполнения задания

Пусть мы играем в онлайн-игру по типу WoW. Также пусть администрация не хочет реализовывать новые предметы через патчи. Что проку скачивать миллионам игроков меч, который получит дай бог 10 задротов, а увидит дай бог 1000 прохожих?
Пусть при этом меч выпал в бою, когда бой еще не завершен. Пусть так-же голосовое общение вшито в игру.
Сразу видно несколько одновременных потоков данных: урон боссу, уровень жизней всех вокруг, их перемещения, голос и меч. (Данные отсортировал по возрастанию обьема)
Приоритеты передачи же должны быть такие: перемещения, уровень жизни, голос, урон, меч.
Было бы обидно если библиотека не предоставляла такого функционала.

Связанные задания

Фича не предусмотренна ни в релизе ни в архитектуре, но гармонично туда впишется.
Характеристики меча, текстура, модель и иконка друг без друга не имеют большого смысла. Но при этом, было бы логично передать характеристики и пиктограмму, дав возможность использовать оружие, без ожидания загрузки модели.
Текстура и модель пронаследуют приоритет родителя, при передаче, и их приоритет будет задаваться относительным смещением. Осталось подобрать коэфициенты так, что бы передача произошла своевременно.

Динамичный приоритет

Пусть наш многогигабайтный вояж подходит к концу, и имел низкий в системе приоритет. И в этот самый момент(99.9%) появилась необходимость передать несколько мегабайт срочных данных. Не смотря на то что мнимый гиг превратился в десяток килобайт.
Здесь крайне разумно было бы изменять приоритет задания в зависимости от оставшегося и буфферезированного количества данных.
Мое текущее решение в библиотеке мне не нравится, нужно поразмыслить и если получится улучшить.

Устройство памяти


Модель хранения данных

В сложной системе может потребоваться доступ к одним ресурсам из разных мест. К примеру отправить сообщение захочет chat_listener и attack_listener. Причем каждый именно своему набору соединений.
Что бы избежать дублирование и рассинхронизацию данных используется хранение ключей к базе данных в обьектах отдаваемых пользователю(ближе всего здесь Bridge, только для хранимых данных).
База данных одна на виртуальную сеть(именно поэтому операции с данными внутри одной сети потоко не безопасны), и как и все данные хранимые в ней управляется через shared_self_controlled.

Ответственность за память

Изза особенности задачи пользователь не может сохранить полный контроль над выделяемой памятью.
Идея библиотеки — «поставить на отправку и быть уверенным что оно дойдет» несколько отравляется, если еще и придется следить а когда оно дойдет и удалять.
Как ни странно, библиотека в общем случае тоже не знает когда именно следует удалить обьект. Она знает что он должен быть передан, знает когда это передача завершится, но ��ледует ли удалять его после этого? Это знает только обьект. (о боги он использует delete this)

Сборщик мусора

Громкие слова, совершенно не отражающие сути происходящего. Но отдаленно это так — как только обьект никому не нужен он удаляется. Можно сранить это с shared_ptr, за исключением что обьект является сам и контейнером и обьектом.
shared_self_conrolled дословно означает следующее. Обьект может разделяться(в отличие от простого self_controlled), и должен следить когда ему нужно самоуничтожится(в отличие от shared).
Как только обьект был передан, он не нужен библиотеке, она вычеркивает себя из списка пользователей. Если их стало ноль — к обьекту больше никто не сможет обратиться, он не нужен.

Обработка данных

Отложенное чтение

Предположим нам нужно передать фаил размером несколько мегабайт через килобайтное соединение. Следует ли выгружать фаил в память на несколько минут? Или даже так, наш фаил весит несколько гигабайт.
Смысл этого примера в том что данные не всегда стоит обрабатывать мгновенно. Лучше откладывать работу на завтра, ведь сегодня может наступить конец света, верно? ;)

Натяжение нити

Если мы захотим потянуть машинку за веревку(игрушка такая), она поедет только когда натянется вся нить. Сила будет передаваться от одного слоя к другому, равномерно. Если же в каком то месте жесткость меньше других — оно растянется, беря всю нагрузку на себя.
Когда обработчики данных выстраиваются в цепочку нас не интересует что происходит по середине. Только движение на концах.
Пусть у нас стоит обработчик decompress, который разжимает сжатые данные, и клиенту нужно 30 байт. При разжатии, для получения этого обьема использовалось только 5 байт исходных данных. Мораль в том что каждое звено знает сколько требуется от соседнего, и ничего больше. Это отражается и в библиотеке — все вычисление происходит по запросу.

Кеширование

Пусть нам нужно обработать террабайт данных, которые запрашиваются раз в несколько секунд. Логично создать такую цепочку:
загрузка => расчет => сохранение в временный фаил => загрузка из временного файла => передача
При этом в памяти будут храниться только данные необходимые для обработки, а в файле только готовые к передаче.
Глупо ждать несколько секунд что бы узнать сколько данных требуется, нужно что бы они были уже готовы.
Логичное решение очевидно — в свободное время перетягивать данные так, что бы их было минимальное количество на стадии расчет и загрузка, и максимальное в временном файле.
Для этого существуют параметры контейнера: минимизировать до, максимизировать до. (Это значит что при нарушении лимита он перестанет передавать запросы далее)
Еще один замечательный пример — прокси. Не стоит скачивать данные быстрее чем мы их можем выдать, это увеличит расход памяти на локальной машине, нас это не устраивает.

Заключение


Надеюсь данным постом я смог раскрыть немного те идеи что послужили причиной для создания библиотеки, и почему она мне кажется конкурентно-способной. Также я верю в то, что несколько смельчаков захотят принять участие в создании чего то настолько замечательного(имхо), как предложенная здесь архитектура.
Это точно не все идеи спрятанные в проекте, некоторые из них ушли в подсознание и спрятались в костном мозге, кажутся очевидными. Это только наиболее значимые, и пришедшие на ум.
«Нас учат помнить не проект человека, но идею, ибо человек слаб, и его могут поймать, его могут убить и предать забвению. Но идея и 400 лет спустя способна изменить мир.» «V значит Вендетта»
Удачных выходных.