Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

Схемы Avro

Чтобы хранить JSON-данные в виде кортежей, мы воспользуемся популярной практикой, которая уменьшает объем данных и гарантирует корректность всех сохраняемых документов. Мы будем использовать Tarantool-модуль avro-schema, который проверяет схему JSON-документа и преобразует его в кортеж Tarantool. Кортеж будет содержать только значения полей и, следовательно, занимать значительно меньше места, чем исходный документ. В терминах avro-schema преобразование JSON-документов в кортежи называется «flattening» (уплощение), а восстановление исходных документов — «unflattening» (разуплощение).

Сначала нужно установить модуль командой tt rocks install avro-schema.

Дальнейшее использование довольно просто:

(1) Для каждой сущности нужно определить схему в синтаксисе Apache Avro schema, где перечисляются поля сущности с их именами и типами данных Avro. (2) При инициализации вызываются avro-schema.create(), создающий объекты в памяти для всех сущностей схемы, и compile(), генерирующий методы flatten/unflatten для каждой сущности. (3) В дальнейшем остается лишь вызывать методы flatten/unflatten для соответствующей сущности при получении/отправке данных этой сущности.

Вот как выглядят определения схем для сущностей player (игрок) и pokémon (покемон):

local schema = {    player = {        type="record",        name="player_schema",        fields={            {name="id", type="long"},            {name="name", type="string"},            {                name="location",                type= {                    type="record",                    name="player_location",                    fields={                        {name="x", type="double"},                        {name="y", type="double"}                    }                }            }        }    },    pokemon = {        type="record",        name="pokemon_schema",        fields={            {name="id", type="long"},            {name="status", type="string"},            {name="name", type="string"},            {name="chance", type="double"},            {                name="location",                type= {                    type="record",                    name="pokemon_location",                    fields={                        {name="x", type="double"},                        {name="y", type="double"}                    }                }            }        }    }}

А вот так мы создаем и компилируем сущности при инициализации:

-- load avro-schema module with require()local avro = require('avro_schema')-- create modelslocal ok_m, pokemon = avro.create(schema.pokemon)local ok_p, player = avro.create(schema.player)if ok_m and ok_p then    -- compile models    local ok_cm, compiled_pokemon = avro.compile(pokemon)    local ok_cp, compiled_player = avro.compile(player)    if ok_cm and ok_cp then        -- start the game        <...>    else        log.error('Schema compilation failed')    endelse    log.info('Schema creation failed')endreturn false

Что касается сущности map (карта), то вводить для нее схему было бы избыточно, поскольку в игре есть только одна карта, у нее совсем немного полей и, что важнее всего, карта используется только внутри нашей логики и никогда не передается внешним пользователям.

Далее нам потребуются методы для реализации игровой логики. Чтобы сымитировать объектно-ориентированное программирование в нашем Lua-коде, сохраним все Lua-функции и общие переменные в одной локальной переменной (назовем ее game). Это позволит обращаться к функциям и переменным внутри модуля как self.func_name или self.var_name. Например:

local game = {    -- a local variable    num_players = 0,    -- a method that prints a local variable    hello = function(self)      print('Hello! Your player number is ' .. self.num_players .. '.')    end,    -- a method that calls another method and returns a local variable    sign_in = function(self)      self.num_players = self.num_players + 1      self:hello()      return self.num_players    end}

В терминах ООП локальные переменные внутри game можно рассматривать как поля объекта, а локальные функции — как методы объекта.

Таким образом, наш игровой модуль будет содержать следующие методы:

  • catch() — для вычисления того, пойман ли покемон (помимо координат игрока и покемона, этот метод применяет фактор вероятности, поэтому не каждый покемон в пределах досягаемости игрока будет пойман);
  • respawn() — для добавления недостающих покемонов на карту, например, каждые 60 секунд (мы исходим из того, что испуганный покемон убегает, поэтому при любой попытке поимки покемон удаляется с карты, а через некоторое время возвращается обратно);
  • notify() — для записи в лог информации о пойманных покемонах (например, «Игрок 1 поймал покемона A»);
  • start() — для инициализации игры (создает спейсы базы данных, создает и компилирует схемы Avro, а также запускает respawn()).

Кроме того, удобно иметь методы для работы с хранилищем Tarantool. Например:

  • add_pokemon() — для добавления покемона в базу данных, и
  • map() — для заполнения карты всеми покемонами, хранящимися в Tarantool.

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