Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Фреймворк Thousand Parsec

Оригинал: "Thousand Parsec", глава из книги "The Architecture of Open Source Applications"
Авторы: Alan Laudicina and Aaron Mavrinac
Перевод: Н.Ромоданов

Перевод был сделан в соответствие с лицензией Creative Commons. С русским вариантом лицензии можно ознакомиться здесь.


21.2. Протокол Thousand Parsec

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

Сервер управляет фактическим состоянием и динамикой игры в соответствии с инструкциями, указываемыми в наборе правил. На каждом шаге клиентская программа игрока получает некоторую информацию о состоянии игры: объекты, кто ими владеет, распоряжения, которые выполняются, запасы ресурсов, состояние разработок, сообщения и все остальное, что может просматривать конкретный игрок. Игрок может выполнять определенные действия с учетом текущего состояния, например, отсылать распоряжения или создавать модели, а также отправлять их обратно на сервер для обработки при вычислении следующего шага. Все это взаимодействие оформлено в виде протокола Thousand Parsec. Интересным и вполне сознательным эффектом использования подобной архитектуры является то, что интеллектуальные клиентские программы, которые внешние по отношению к серверу / набору правил и являются лишь средством имитации в игре искусственных компьютерных игроков, функционируют по тем же самым правилам, что и клиентские программы, управляемые игроками-людьми. Т.е. они не смогут "обманывать" за счет получения недопустимого доступа к информации или за счет возможности нарушать правила.

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

Протокол Thousand Parsec предназначен для автономного использования поверх TCP/IP или туннелирования через другой протокол, например, HTTP. В нем также поддерживается шифрование SSL.

21.2.1. Основные фреймы

В протоколе предлагается несколько базовых фреймов, которые при взаимодействии между клиентской программой и сервером используются повсеместно. Ранее упомянутый фрейм Header (Заголовок) просто с помощью своих собственных двух прямых потомков — Request (Запрос) и Response (Ответ) представляет собой базу для создания всех остальных фреймов. Первый является основой для фреймов, с помощью которых соединение инициируется (в любом направлении), а второй - для фреймов, которые завершают соединение. Фреймы OK (Выполнено) и Fail (Отказано), оба являются фреймами типа Response (Ответ), возвращающими при обмене данными два значения булевой логики. Фрейм Sequence (Последовательность), который также относится к типу Response (Ответ), указывает получателю, что в ответ на его запрос должны последовать несколько фреймов.

В протоколе Thousand Parsec для адресации объектов используются числовые идентификаторы. Соответственно, есть словарь фреймов, используемый при работы с данными через эти идентификаторы. Фрейм Get With ID (Получить с идентификатором) является базовым запросом данных по такому идентификатору; также есть фрейм Get With ID and Slot (Получить с идентификатором из слота) для данных, которые находятся в "слоте" родительских данных (объект — предок), имеющих идентификатор ID (например, распоряжение по объекту). Конечно, часто бывает нужно получать последовательности идентификаторов, такие как при первоначальном заполнении состояния клиента; это делается с помощью запроса типа Get ID Sequence (Получить последовательность идентификаторов) и ответов типа ID Sequence (Последовательность идентификаторов). Общая структура запроса нескольких элементов представляет собой запрос Get ID Sequence (Получить последовательность идентификаторов) и ответ ID Sequence (Последовательность идентификаторов), за которыми следует серия запросов Get With ID (Получить с идентификатором) и соответствующих ответов, описывающих требуемые данные.

21.2.2. Игроки и игры

Прежде, чем клиентская программа сможет начать взаимодействие с игрой, нужно соблюсти некоторые формальности. Клиентская программа должна сначала передать на сервер фрейм Connect (Подключение), на который сервер может ответить OK (Выполнено) или Fail (Отказано), т.к. во фрейме Connect (Подключение) указывается версия протокола, используемого клиентской программой, и одной из причин отказа может быть несоответствие версий. Сервер может также ответить фреймом Redirect (Перенаправление) в случае перенаправления или использования серверных пулов. Далее, клиентская программа должна отправить фрейм Login (Регистрация), с помощью которого происходит идентификация и, возможно, аутентификация игрока; новые игроки на сервере могут сначала использовать фрейм Create Account (Создать аккаунт) в случае, если сервер это позволяет.

Из-за огромной вариабельности протокола Thousand Parsec, клиентская программа должна каким-то образом выяснить, какие особенности протокола поддерживаются сервером; это осуществляется с помощью запроса Get Features (Получить описание возможностей) и ответа Features (Возможности). Среди некоторых особенностей, присутствующих в ответе, могут указываться следующие:

  • Наличие туннелирования SSL и HTTP (для данного порта или другого порта).
  • Поддержка расчета свойств компонента на серверной стороне.
  • Упорядочивание последовательностей идентификаторов в ответах (по возрастанию или убыванию).

Аналогичным образом с помощью запроса Get Games (Получить игры) и последовательности ответов Game (Игра), клиентская программа будет проинформирована о том, какие игры активны на сервере. В отдельном фрейме Game (Игра) содержится следующая информация об игре:

  • Длинное (описательное) название игры.
  • Список поддерживаемых версий протокола.
  • Тип и версия сервера.
  • Название и версия набора правил.
  • Список возможных конфигураций сетевых соединений.
  • Несколько дополнительных элементов (количество игроков, количество объектов, особенности администрирования, комментарий, номер текущего шага и т.д.).
  • Базовый URL для медиаресурсов, используемых в игре.

Для игрока, конечно, важно знать, против кого он играет (или, может быть, в зависимости от обстоятельств, вместе с кем он играет), и для этого имеется набор фреймов. Обмен осуществляется с использованием общепринятой схемы последовательности элементов с помощью запроса Get Player IDs (Получить идентификатры игроков) и серии запросов Get Player Data (Получить данные об игроке) и ответов Player Data (Данные об игроке). Во фрейме Player Data (Данные об игроке) указывается имя и раса игрока.

Участие в игре также контролируется через протокол. Когда игрок закончит выполнение действий, он может проинформировать о готовности к следующему шагу с помощью запроса Finished Turn (Шаг закончен); переход на следующий шаг вычисляется после того, как это сделают все игроки. Переход на следующий шаг ограничен по времени, установленном на сервере, так что игроки, которые отвечают медленно или не отвечают, не могут участвовать в игре; клиентская программа обычно делает запрос Get Time Remaining (Получить оставшееся время) и по локальному таймеру отслеживает время, установленное в соответствие с ответом сервера Time Remaining (Оставшееся время).

Наконец, в протоколе Thousand Parsec поддерживается работа с сообщениями, предназначенными для различных целей: передаче сообщений для всех игроков, игровых уведомлений для отдельного игрока, связи между отдельными игроками. Есть варианты, которые организованы в виде контейнеров "форум", в которых можно указывать порядок и видимость сообщений; последовательность элементов обмена сообщений, которая последует далее, состоит из запроса Get Board IDs (Получить идентификаторы форумов), ответа List of Board IDs (Список идентификаторов форумов) и серии запросов Get Board (Получить форум) и ответов Board (Форум).

Как только клиентская программа получит информации, что для нее на форуме есть сообщение, она может выдать запросы Get Message (Получить сообщение) с тем того, чтобы получить сообщения из соответствующего сектора форума, т. е. в Get Message (Получить сообщение) используется базовый фрейм Get With ID and Slot (Получить с идентификатором и слотом); сервер отвечает фреймами Message (Сообщение), в которых содержится тема сообщения и само сообщение, указывается шаг, на котором было создано сообщение, и ссылки на любые другие субъекты, указанные в сообщении. В дополнение к обычному набору элементов, которые есть в протоколе Thousand Parsec (игроки, предметы и т.п.), также имеются некоторые специальные ссылки, в том числе приоритет сообщения, действия игрока и статус распоряжения. Естественно, клиентская программа также может с помощью фрейма Post Message (Послать сообщение), который является транспортом для фрейма Messsage (Сообщение), отправлять сообщения, а также удалять эти сообщения с помощью фрейма Remove Message (Удалить сообщение), базирующегося на фрейме GetMessage (Получить сообщение).

21.2.3. Объекты, распоряжения и ресурсы

Основная часть процесса взаимодействия со Вселенной осуществляется через серию фреймов, с помощью которых реализовываются функции объектов, распоряжений и ресурсов.

Физическое состояние Вселенной или, по крайней мере той ее части, которой игрок управляет или может видеть, необходимо получать через соединение, т. е. с помощью клиентской программы. Клиентская программа обычно отправляет запрос Get Object IDs т. е. Get ID Sequence (Получить идентификаторы объектов, т. е. Получить последовательность идентификаторов), на который сервер отвечает фреймом List of Object IDs (Список идентификаторов объектов). Затем клиентская программа может запросить подробности об отдельных объектах с помощью запросов Get Object by ID (Получить объект по идентификатору), ответом на которые являются фреймы Object (Объект), в которых содержатся эти подробности, опять же при условии, что их разрешено видеть игроку, такие как их тип, название, размер, положение, скорость, объекты, которые находятся внутри рассматриваемого объекта, допустимые типы распоряжений и текущие распоряжения. В протоколе также предусмотрен запрос Get Object IDs by Position (Получить идентификаторы объектов, находящихся в позиции), который позволяет клиентской программе находить все объекты в пределах указанной сферы пространства.

Клиентская программа, следуя обычной схеме получения последовательности элементов, получает набор допустимых распоряжений при помощи запроса Get Order Description IDs (Получить идентификаторы описаний распоряжений) и для каждого идентификатора ID, полученного в ответе List of Order Description IDs (Список идентификаторов описаний распоряжений), отправки запроса Get Order Description (Получить описание распоряжения) и получения ответа Order Description (Описание распоряжения). На протяжении всего времени существования протокола были заметно улучшены реализация распоряжений и реализация очередей распоряжений. Первоначально каждый объект имел одну очередь распоряжений. Клиент выдавал запрос Order (Распоряжение), содержащий тип распоряжения, целевой объект и другие сведения, получал ответ Outcome (Результаты) с подробным изложением ожидаемых результатов исполнения распоряжения и после того, как распоряжение было выполнено, получал фрейм Result (Результат), содержащий фактический результат.

Во второй версии во фрейм Order (Распоряжение) было добавлено содержимое фрейма Outcome (Результаты), так как, поскольку он базируется на описании распоряжения, то для него не требуется обращаться на сервер, а фрейм Result (Результат) был полностью удален. В последней версии протокола очередь распоряжений к объектам была реструктурирована и были добавлены фреймы Get Order Queue IDs (Получить идентификаторы очередей распоряжений), List of Order Queue Ids (Список идентификаторов очередей распоряжений), Get Order Queue (Получить очередь с распоряжениями) и Order Queue (Очередь с ряспоряжениями), которые работают аналогично функциям обработки сообщений и форумов [2]. Фреймы Get Order (Получить распоряжение) и Remove Order (Удалить распоряжение), для каждого из которых также требуется запрос Get WithID Slot (Получить со слотом идентификаторов), позволяют клиентской программе получать доступ к очередям и удалять из очереди распоряжения, соответственно. Теперь фрейм Insert Order (Добавить распоряжение) выступает в роли транспорта полезной нагрузки Order (Распоряжение); это было сделано для того, чтобы позволить другому фрейму, Probe Order (Проверить состояние распоряжения), который в некоторых случаях используется клиентской программой, получать информацию, используемую локально.

Описания ресурсов также представляет собой последовательность элементов: запрос Get Resource Description IDs (Получить идентификаторы описаний ресурсов), ответ List of Resource Description IDs (Список идентификаторов описаний ресурсов) и серия запросов Get Resource Description (Получить описание ресурса) и ответов Resource Description (Описание ресурса).

21.2.4. Работа с Моделями

Работа с моделями в протоколе Thousand Parsec разделена на работу с четырьмя различными подкатегориями: категориями, компонентами, свойствами и моделями.

Категории различаются по типам различных моделей. Двумя наиболее часто используемыми типами моделей являются корабли и вооружение. Создание категории достаточно простое, поскольку категория состоит только из названия и описания; в самом фрейме Category (Категория) содержатся только эти две строки. В соответствие с набором каждая категория с помощью запроса Add Category (Добавить категорию), который является транспортом для фрейма Category (Категория), добавляется на склад моделей Design Store. Все остальное управление категориями осуществляется по обычной схеме работы с последовательностью элементов с помощью запроса Get Category IDs (Получить идентификаторы категорий) и ответа List of Category IDs (Список идентификаторов категорий).

Компоненты представляют собой различные части и модули, которые входят в состав моделей. Они могут быть всем, чем угодно, от корпуса корабля или ракеты и до ракетной установки, на которой находится ракета. Компоненты чуть сложнее, чем категории. Во фрейме Component (Компонент) содержится следующая информация:

  • Название и описание компонента.
  • Список категорий, к которым принадлежит компонент.
  • Функция Requirements (Требования), написанная на языке компонентов протокола Thousand Parsec (TPCL).
  • Список свойств и их значений.

Обратите внимание на функцию Requirements (Требования), связанную с компонентом. Поскольку компоненты являются теми частями, их которых собирается корабль, вооружение или другой конструируемый объект, необходимо гарантировать, что они действительно будут именно такими, которые можно добавить в модель. Функция Requirements (Требования) проверяет, что каждый компонент, добавляемый к модели, соответствует правилам, определенным в других ранее добавленных компонентах. Например, в наборе правил Missile and Torpedo Wars (Ракетные и торпедные войны), нельзя в корабле иметь ракету Alpha Missile в случае, если в нем нет ракетной установки Alpha Missile Tube. Эта проверка осуществляется как на стороне клиентской программы, так и на стороне сервера, именно поэтому функция должна быть целиком описана во фрейме протокола, и именно поэтому для нее был выбран компактный язык описания (язык TPCL, описываемый далее в этой главе).

Обращение ко всем свойствам модели происходит через фрейм Property (свойство). В каждом наборе правил задается набор свойств, используемых в игре. Они обычно включают в себя такие вещи, как количество ракетных установок определенного типа, размещаемых на корабле, или тип брони, используемый с определенным типом корпуса корабля. Точно также как и во фреймах Component (Компонент), во фреймах Property (Свойство) применяется язык TPCL. Во фрейме Property (Свойство) содержится следующая информация:

  • Наименование (отображаемое) и описание свойства.
  • Список категорий, которые обладают данным свойством.
  • Имя (правильный идентификатор языка TPCL) свойства.
  • Ранг свойства.
  • Функции Calculate (Вычислить) и Requirements (Требования), написанные на языке компонентов протокола Thousand Parsec (TPCL).

Ранг свойства используется для выявления различий в иерархии зависимостей. В языке TPCL, функция может не зависеть от тех свойств, ранг которых меньше или равен рангу данного свойства. Это означает, что если ранг защиты равен 1, а ранг скрытности равен 0, то свойство скрытности не может напрямую зависеть от свойства защиты. Такое ранжирование было реализовано в качестве средства сокращения циклических зависимостей. Функция Calculate (Вычислить), используется для того, чтобы определить, как следует отображать некоторое свойство в зависимости от различий, выявленных при измерении. В наборе правил Missile and Torpedo Wars (Ракетные и торпедные войны) для импорта в игру свойств из файла данных игры использован язык XML. На рис.21.2 показан пример свойства из этой игры.

<prop>
<CategoryIDName>Ships</CategoryIDName>
<rank value="0"/>
<name>Colonise</name>
<displayName>Can Colonise Planets</displayName>
<description>Can the ship colonise planets</description>
<tpclDisplayFunction>
    (lambda (design bits) (let ((n (apply + bits))) (cons n (if (= n 1) "Yes" "No")) ) )
</tpclDisplayFunction>
<tpclRequirementsFunction>
    (lambda (design) (cons #t ""))
</tpclRequirementsFunction>
</prop>

Рис.21.2: Пример свойств

В этом примере у нас есть свойство, принадлежащее категории кораблей Ships, имеющее ранг 0. Это свойство называется Colonise (Колонизация) и относится к возможности использовать корабли при колонизации планет. Беглый взгляд на функцию Calculate (Вычислить), указанную здесь как tpclDisplayFunction, показывает, что в зависимости от того, есть ли у корабля такое свойство, эта функция выдает в ответ на запрос либо значение «Yes» («Да»), либо значение «No» («Нет»). Такое добавление свойств позволяет разработчику наборов правил более детально управлять метриками игры, легко их сравнить и выводить их в дружественном для игрока виде.

Фактическая модель кораблей, вооружения и другие игровые артефакты создаются и изменяются с помощью фрейма Design (Модель) и связанных с ним других фреймов. При создании кораблей и вооружений обычно используются все компоненты и свойства, имеющиеся в текущем наборе правил. Поскольку в правилах для моделей уже есть TPCL функций Requirements (Требования) для компонентов и свойств, создание модели несколько упрощается. Во фрейме Design (Модель) содержится следующая информация:

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

Этот фрейм немного отличается от других. В частности, поскольку модель — это тот элемент в игре, у которого есть владелец, имеется связь каждой модели с ее владельцем. В модели с помощью счетчика также отслеживается количество экземпляров модели.

21.2.5. Администрирование сервера

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

Точно также как и в случае игрового протокола, описанного в предыдущих разделах, клиентская программа администрирования сначала договаривается о подключении (через порт, отличающийся от обычного игрового порта) и с помощью запросов Connect (Подключение) и Login (Регистрация) происходит проверки подлинности. После подключения, клиентская программа может получать журнальные сообщения и отправлять команды на сервер.

Журнальные сообщения передаются клиентской программе через фреймы Log Message (Журнальное сообщение). В них указывается уровень серьезности сообщения и его текст; в зависимости от соответствующего контекста, в клиентской программе можно выбрать отображать все получаемые журнальные сообщения, некоторые из них или вообще их не отображать.

Сервер также может отправить фрейм Command Update (Обновить команду), указывающий клиенту, что нужно заполнить или обновить его локальный набор команд; в ответ на фрейм Get Command Description IDs (Получить идентификаторы описания команд) сервер перенаправит клиентской программе список поддерживаемых команд. Затем можно получить описания каждой отдельной команды, отправив для каждой команды фрейм Get Command Description (Получить описание команды), на который сервер ответит с помощью фрейма Command Description (Описание команды).

Такой обмен данными функционально очень похож (и, по сути, изначально на нем основывался) на обмен, используемый фреймами распоряжений в основном игровом протоколе. Он позволяет в некоторой степени локально проверять команды, которые набирает пользователь, что ведет к минимизации использования сети. Протокол администрирования разрабатывался уже тогда, когда игровой протокол был уже достаточно зрелым; чтобы не начинать все с нуля, разработчики взяли из игрового протокола уже существующие функциональные возможности и в те же самые библиотеки протоколов добавили код.


Далее: 21.3. Поддерживаемые функциональные возможности