Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Форум / База данных игровых объектов: архитекстура системы сериализации и репликации произвольных объектов

База данных игровых объектов: архитекстура системы сериализации и репликации произвольных объектов

Страницы: 1 2 3 4 5 6 7 Следующая »
DelfigamerПостоялецwww30 мая 201812:00#0
*clickbait intensifies*

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

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

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

В стандартной форме, каждая сущность характеризуется логическим типом и физическим типом.
Логический тип сообщает о назначении сущности и придаёт смысл хранимым данным. Большинство логических типов соответствуют классам в коде программы - Texture, Mesh, Actor, Pawn, Level. Кроме того, своими собственными типами объекта снабжены примитивные типы - числа, строки, null и прочие.
Физический тип - это метод описания сущности в стандартной форме.
Система определяет фиксированное множество физических типов:
1. Целое число.
2. Вещественное число.
[upd: с точки зрения формата, все числа являются равноправными]
3. Блоб - набор байт произвольной длины, который следует хранить и передавать как есть. Текст хранится в виде блоба в кодировке UTF-8. Кроме того, файлы внешних форматов (например, текстуры в DDS и звуки в FLAC), логически, так же являются сущностями-блобами. Физически, они могут быть как встроены внутрь сериализации, так и лежать отдельным файлом, доступным для непосредственной работы с другими программами.
Примеры:

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

<assets/text/dialog2c.txt>
@b64(
    TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0
    aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1
    c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0
    aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdl
    LCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
)
4. Агрегат является основным методом представления объектов в стандартной форме. Логически, агрегат является множеством кортежей <имя, логический тип, сущность>. Например, летящий снаряд может быть описан следующим образом:
{
  position:Location = {
    x:float = -1.5260e+3,
    y:float = 1.5190e+3,
    z:float = -4.8720e+2,
    r1:float = -1.1510e-1,
    r2:float = -4.8800e-1,
    r3:float = -1.0430e-1,
  },
  velocity:Vector3 = {
    x:float = 4.2390e+2,
    y:float = 2.6680e+2,
    z:float = 2.0430e+2,
  },
  shooter:Actor = [fde1990b60169b774d3a264ac3bbb200],
  team:TeamIndex = 1,
}
5. Список предназначен для хранения коллекций объектов. Каждый элемент списка - это пара <логический тип, сущность>. Элементы списка образуют порядок, что позволяет использовать его для хранения массивов. Конкретный метод отсчёта элементов не определяется.
[upd: списки записываются как частный случай агрегата с пустыми именами]
6. Ссылка, собственно, ссылкается на другую сущность в базе данных по её имени.

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

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

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

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

Для сериализации и десериализации определяется несколько форматов:
1. Текстовый формат - наподобие приведённого в примерах выше. В нём стандартная форма сериализуется в виде текстовых синтаксических конструкций.
2. Двоичный формат - содержит ту же информацию, что и текстовый. В частности, для каждого агрегата сохраняются полная структура с именами и типами полей, что позволяет сохранять совместимость между версиями и облегчает редактирование объектов сторонними программами - лучше, когда моддинг есть и несложный, чем когда он есть, но жутко геморройный.
3. Сетевой формат - базируется на двоичном, однако, в ходе соединения, разрешается создавать и определять сокращения для постоянных элементов. Эти сокращения являются частью контекста соединения.

Репликация происходит в виде обмена сообщениями-командами между компьютерами. Каждое сообщение обладает собственным логическим типом, который определяет действие, совершаемое в ответ на сообщение; то есть, основной метод репликации - это remote procedure call, а синхронизация состояний уже реализуется через него. Логически, сообщение представляется в виде сущности. Эта сущность сериализуется в сетевой формат, передаётся в виде UDP-датаграммы и десериализуется на принимающей стороне. Аргументы команды оформляются в виде полей объекта.
Дополнительно самой системой определяется специальная команда - определение сокращения. После определения, дальнейшие сообщения могут использовать это сокращение для экономии трафика.
Встроенного механизма гарантированной доставки в системе не предусмотрено. Вместо этого, встретив неизвестное обозначение в сообщении - будь то имя сокращения, идентификатор объекта или что-то ещё - клиент отправляет запрос на определение этого обозначения.
Для сообщений, критических с точки зрения игры - умер/неумер, выстрелил/невыстрелил - подтверждение следует реализовывать отдельно, с учётом актуальности этих сообщений (например, нет смысла отправлять "ты умер" после респауна).

Правка: 7 сен. 2018 6:26

denesikПостоялецwww30 мая 201812:19#1
Разве структура бд как то связана с ее передачей по сети?
DelfigamerПостоялецwww30 мая 201813:38#2
ZabПостоялецwww30 мая 201820:59#3
Напоминает структуру девэкспесовских продуктов. Ужас для программиста, который вынужден будет это сопровождать. Его жалко, тебя - нет, ибо ты сам себе такое сделал.
И что самое неприятное, бывает хуже. И очень часто бывает. По сути, дефэспресовские библиотеки едва ли не лучшие по качеству из всего, что есть для дельфей. Лучшие - не значит хорошие, значит - остальное еще хуже.
gamedevforПостоялецwww30 мая 201822:16#4
Уж лучше XML+ZIP
DelfigamerПостоялецwww31 мая 20182:18#5
Zab
> Напоминает структуру девэкспесовских продуктов.
Первый раз об этом слышу. Что это такое и где прочитать документацию?

gamedevfor
> Уж лучше XML+ZIP
Конкретный двоичный формат ортогонален.
Главная фишка системы заключается в явно описанном промежуточном представлении - вместо того, чтобы делать (возможно, через рефлексию)

void AProjectile::serialize(Archive& archive)
{
  this->postion.serialize(archive);
  this->velocity.serialize(archive);
  archive.serializereference(this->shooter);
  archive.serialize((int)this->team);
}
делается (возможно, через рефлексию)
SFEntity AProjectile::standardform(SFContext& context)
{
  SFEntity ent = SFEntity::createstruct(SFTypeTraits<AProjectile>::id));
  ent.set(NAME("position"), this->position.standardform());
  ent.set(NAME("velocity"), this->velocity.standardform());
  ent.set(NAME("shooter"), context.createreference(this->shooter));
  ent.set(NAME("team"), context.createint(this->team));
}
а форматом занимается уже отдельный код, которому достаточно знать только стандартную форму
void SFXMLEncoder::writetext(TextStream& ts, SFEntity& ent, size_t depth)
{
  switch(ent.ptype)
  {
  case SFPType::Integer:
    ts << "<integer value=\"" << ent.intvalue() << "\" />";
    break;
  case SFPType::Float:
    ts << "<float value=\"" << ent.floatvalue() << "\" />";
    break;
  case SFPType::Blob:
    ts << "<blob value=" << TextStream::enquote(ent.blobvalue()) << " />";
    break;
  case SFPType::Reference:
    ts << "<reference target=\"" << TextStream::base16(ent.idvalue()) << "\" />";
    break;
  case SFPType::List:
    ts << "<list>";
    for(auto& kv : ent.items())
    {
      SFEntity& item = kv.second;
      ts << "\n" << TextStream::indent(depth+1);
      ts << "<item ltype=\"" << TextStream::base16(item.ltype()) << "\">";
      writetext(ts, item, depth+1);
      ts << "</item>";
    }
    ts << "\n" << TextStream::indent(depth) << "</list>";
    break;
  case SFPType::Struct:
    ts << "<struct>";
    for(auto& kv : ent.items())
    {
      std::string& name = kv.first;
      SFEntity& item = kv.second;
      ts << "\n" << TextStream::indent(depth+1);
      ts << "<attr name=" << TextStream::enquote(name) << " ltype=\"" << TextStream::base16(item.ltype()) << "\">";
      writetext(ts, item, depth+1);
      ts << "</attr>";
    }
    ts << "\n" << TextStream::indent(depth) << "</struct>";
    break;
  default:
    assert(false);
  }
}

Мне кажется, за счёт такой декомпозиции система получится, наоборот, проще, чем обычно получается.

Ну и да, лично у меня C++-код уже давно обложен рефлексией и кодогенерацией, а всё остальное - это Луа; так что код для большинства классов в любом случае поддаётся автоматизации. Так что, сложность кода для каждого класса - это не проблема, зато с моим подходом можно значительно упростить код сериализатора.

ZabПостоялецwww31 мая 20184:40#6
Delfigamer
> > Напоминает структуру девэкспесовских продуктов.
> Первый раз об этом слышу. Что это такое и где прочитать документацию?
Бери исходники и читай. Какая еще документация? Это не микрософт, где все документируется. Но даже у микрософта ты до внутренней документации вряд ли доберешься, а тут ее скорее всего просто нет.
Там же программисты работают с квалификацией на уровне 2 курса института. Хорошо обученные, но на уровне 2го курса. Уровень твоей "архитектуры" такой же. По меркам дельфей это очень много, даже системные библиотеки борлада порой хуже, но по абсолютным меркам - никуда не годится, это способ убить большой проект.
skalogryzУчастникwww31 мая 20185:54#7
Добавлю ссылку на protocol buffers
DelfigamerПостоялецwww31 мая 20186:42#8
Zab
Your search - девэкспесовские продукты - did not match any documents.

Suggestions:

  • Make sure that all words are spelled correctly.
  • Try different keywords.
  • Try more general keywords.
  • Try fewer keywords.
  • skalogryz
    Интересная альтернатива. Однако,

    Canonically, messages are serialized into a binary wire format which is compact, forward- and backward-compatible, but not self-describing (that is, there is no way to tell the names, meaning, or full datatypes of fields without an external specification). There is no defined way to include or refer to such an external specification (schema) within a Protocol Buffers file. The officially supported implementation includes an ASCII serialization format, but this format—though self-describing—loses the forward- and backward-compatibility behavior, and is thus not a good choice for applications other than debugging.

    И нет встроенной поддержки для Lua, на котором пишется значительная часть моего движка.

    Ну и подходы несколько другие. По задумке, в моей системе форматы для RPC составляются в рантайме, с учётом текущего описания. Например, в ходе инициализации сессии, сначала явным сообщением вводится сокращение:

    0x80000004 [int32] [int32] => PlacePoint{
      position:IntVector2 = {
        x:int = %1,
        y:int = %2,
      },
    }
    Затем, при отправке RPC на PlacePoint, в сообщение пишутся только 12 байт - имя сокращения плюс аргументы. Принимающая сторона разворачивает сокращение по шаблону и десериализует. Структура и имена атрибутов передаются один раз - в определении сокращения, а в дальнейшем отправляются только переменные данные.
    Получается что-то, отдалённо напоминающее JIT-компиляцию, только не для кода, а для формата данных.
    Ну а декомпозиция между редукцией в простую структуру ("стандартная форма") и сериализацией этой структуры в формат позволяет реализовывать вот такие хитрые форматы без каких-либо изменений в коде редукции/реконструкции.

    Правка: 31 мая 2018 6:45

    ZabПостоялецwww31 мая 20186:55#9
    Delfigamer
    Извини, я думаю что не смогу тебе объяснить что именно у тебя плохо. Потому как, плохо все. И если ты этого не ощущаешь даже, значит нужно больше практики.
    Все через это проходят. Вектор развития у тебя правильный, но до уровня сеньор программиста еще далеко, пока ты создаешь проблемы, а не решаешь их, на структурном уровне.
    Вот попытаешься годик посопровождать написанную на нынешнем твоем уровне программу, так и увидишь что хорошо, а что плохо. Готовые рецепты не работают, пока оценка правильности сбоит.

    Правка: 31 мая 2018 6:58

    ZabПостоялецwww31 мая 20187:06#10
    Я не знаю способов централизованного обучения "архитектуре" софта, возможно их просто нет.
    У нас и специальность то такая отсутствует, по ней не обучают, в большинстве фирм не осознают что подобный спец нужен. Американцы в некоторых университетах пытаются учить по этой специальности, но у них ничего не выходит, выпускники не могут выполнять работу.
    Часть программистов доходит до всего самостоятельно, примерно за 10 лет работы над большими проектами. Само собой, польза от них появляется и раньше, потому как если эта работа в проекте совсем не выполняется, пусть даже плохонько, - вообще беда, а она же часто не выполняется.

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

    Тут главное в развитии не останавливаться. Девять из десяти программистов останавливаются.

    Правка: 31 мая 2018 7:12

    DelfigamerПостоялецwww31 мая 20187:54#11
    Zab
    Это всё хорошо, но ты так и не ответил - что такое "девэкспесовские продукты"? Это же не плод твоего воображения, верно?

    Правка: 31 мая 2018 7:56

    ZabПостоялецwww31 мая 20188:16#12
    Devexpress - фирма, выпускающая библиотеки для дельфи. Сейчас уже не только для дельфи, но дельфи остается приоритетом.
    Наиболее качественные из доступных библиотек - все от них. В основном библиотеки интерфейсные, но не только
    DelfigamerПостоялецwww31 мая 20188:25#13
    Developer Express Inc. (DevExpress) is a software development company founded in 1998 with headquarters in Glendale, California. DevExpress initially started producing UI Controls for Borland Delphi/C++Builder and ActiveX Controls for Microsoft Visual Studio. Presently, DevExpress has products targeting developers that use Delphi/C++Builder, Visual Studio and HTML5/JavaScript technologies.

    А причём здесь геймдев, сериализация и RPC?
    ZabПостоялецwww31 мая 20188:25#14
    На самом деле, есть одна архитектурная технология, на которую можно натаскать, но мне она не нравится. Что такое RUP слышал?
    Эта технология из разряда "не можем сделать мозгами, будем брать задницей". Вместо того, чтобы бить по проблеме, можно на всякий случай помахать везде, в надежде и проблему прихлопнуть тоже. А что еще делать, если проблем мы не видим? На этот махач можно натаскать.
    99% действий выполняется вхолостую, приходится создавать большие отделы вместо найма одного человека, дополнительно снижать эффективность за счет необходимости координации. А все потому, что не могут найти того одного, который проблемы видит. Работу то выполнять надо... хотя бы так...

    RUP - rational universal process. Развивает технологию группа наверняка известного тебе Гради Буча. UML - графический язык в составе RUP, о нем наверняка тоже слышал. Однако, RUP не пошел в массы, все о нем знают, но мало кто использует в том объеме, чтобы позволял заменить квалифицированного архитектора софта.

    Страницы: 1 2 3 4 5 6 7 Следующая »

    / Форум / Программирование игр / Общее

    2001—2018 © GameDev.ru — Разработка игр