Skip to content

Модель синхронизации

Varion использует server-authoritative модель. Сервер создаёт все сетевые сущности, выдаёт им network ID и остаётся единственным источником истины. Клиенты только отображают состояние и передают ввод своего игрока. Клиенты никогда не обмениваются состоянием напрямую — весь трафик идёт через сервер.

Эта страница объясняет модель автору ресурса: как сервер стримит сущности, что такое net-owner и его миграция, как изолировать миры через dimensions и когда использовать virtual entities вместо реальных игровых сущностей.

Сервер — источник истины

ПравилоКак это работает
Сервер создаёт сущностиКаждая сетевая сущность (player, vehicle, ped, object, virtual entity) и её network ID создаются на сервере. Клиент не может создать сетевую сущность.
Сервер — маршрутизаторСервер принимает состояние от владельца сущности и ретранслирует его нужным наблюдателям. Он же выдаёт stream-in/out и миграцию владельца.
Передаёт только net-ownerСостояние сущности принимается сервером только от её net-owner (или от любого, если сущность ownerless). Чужой пакет отбрасывается.
Нет client → clientМежду клиентами нет прямого канала. Любое сообщение проходит через сервер, который решает, кому его отдать.
Remote-сущности server-authoritativeНа клиенте remote-сущности read-only. entity.setPos/setRot/destroy для чужого игрока блокируются и возвращают false.

Авторитетную логику (gameplay, инвентарь, экономику, права, перемещение) проверяйте на сервере — см. Обзор платформы, раздел Trust model.

Стриминг сущностей

Сервер решает, каких сущностей «видит» каждый игрок, через spatial hash grid:

  • Мир делится на квадратные ячейки со стороной = streamingDistance (по умолчанию 300 м).
  • Для каждого наблюдателя (observer) сервер проверяет его ячейку и восемь соседних (3×3).
  • Сущность в пределах streamingDistance и в том же dimensionstream in.
  • Сущность вышла за радиус или сменила dimension → stream out.

Что получает автор сущности

При stream-in сервер отправляет keyframe (полное состояние) плюс stream-synced meta; при stream-out — уведомление. На клиенте это превращается в события:

Событие (client)Когда
entityStreamIn / entityStreamOutСущность вошла/вышла из зоны стрима наблюдателя. Payload — сырые данные (entityId, entityType).
gameEntityCreate / gameEntityDestroyУ сущности появился/пропал игровой handle (готова к нативам).
baseObjectCreate / baseObjectRemoveПоявился/удалён base object.
worldObjectStreamIn / worldObjectStreamOutСтрим мирового объекта (обёрнутая сущность).
js
varion.on('entityStreamIn', (data) => {
  varion.log(`stream in type=${data.entityType} id=${data.entityId}`);
});

varion.on('gameEntityCreate', (entity) => {
  // handle готов — нативы можно вызывать на entity.scriptID
});

entityStreamIn сообщает, что сервер начал реплицировать сущность; gameEntityCreate наступает позже — когда игра выдала локальный handle. Нативы вызывайте после gameEntityCreate, а не сразу на entityStreamIn.

Почему delta доходит только до streamed-in

Delta-обновления (позиция, поворот, здоровье, оружие и т. д.) сервер рассылает только тем наблюдателям, которые сейчас стримят сущность. В этом суть масштабируемости: игрок на другом конце карты не тратит трафик на сущности, которых не видит. Пока сущность не in-stream у наблюдателя, он не получает её delta — только keyframe в момент stream-in.

Лимиты стрима — streamingDistance и maxStreamedEntities (см. server.toml).

Net-owner и миграция

Net-owner — клиент, который симулирует сущность и передаёт её состояние серверу. Сервер принимает состояние только от net-owner (или от любого, если сущность ownerless) и ретранслирует его streamed-in наблюдателям.

  • Каждый игрок — net-owner своего локального игрока.
  • Vehicle: net-owner — тот, кто им управляет. Пустая машина владельца не имеет либо привязана к ближайшему игроку.

Когда мигрирует (vehicle)

Владельца машин сервер пересчитывает несколько раз в секунду (~6–10 Гц) по приоритету:

  1. Driver — игрок за рулём становится владельцем.
  2. Гистерезис — если водителя нет, текущий владелец сохраняется, пока он в пределах migrationDistance (по умолчанию 150 м).
  3. Ближайший — иначе владельцем становится ближайший игрок в радиусе migrationDistance.
  4. Ownerless — если рядом никого, машина «замораживается» (без владельца).

При смене владельца:

  • Сервер вызывает событие netOwnerChange(entity, owner, oldOwner) — см. События и RPC.
  • Клиент получает netOwnerChange(entity, owner, previousOwner), но только когда локальный игрок получает или теряет владение.
js
// client
varion.on('netOwnerChange', (entity, owner, previousOwner) => {
  if (owner && owner.id === varion.Player.local.id) {
    // теперь эту сущность симулируем мы
  }
});

Owner vs non-owner

Net-ownerНе-owner
Состояние сущностиВаш клиент — источник; сервер ретранслирует его наблюдателямПриходит интерполированное remote-состояние
Мутация remote-сущностиЗаблокирована: setPos/setRot/destroy чужого игрока → false (server-authoritative)

Dimensions

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

  • Сервер: смена dimension игрока запускает немедленный пересчёт стрима (stream-out сущностей других dimension). Событие playerDimensionChange(player, oldDimension, newDimension).
  • Клиент: localDimension — dimension локального игрока изменился; playerDimensionChange — событие смены dimension. Точные сигнатуры — События и RPC.
js
// client
varion.on('playerDimensionChange', (player, oldDimension, newDimension) => {
  varion.log(`${player.id}: ${oldDimension} -> ${newDimension}`);
});

Virtual entities

Virtual entity — лёгкий синхронизируемый объект с позицией, но без игровой сущности: нет модели, коллизии и нативов. Сервер стримит их по тем же пространственным правилам, что и реальные сущности, но без затрат на создание игрового объекта.

Берите virtual entityБерите реальную сущность
Точки интереса, зоны, «якоря» стрима, синхронные данные с позицией, кастомная логика в радиусеНужен видимый/физический объект в мире (ped, vehicle, prop)
Тысячи дешёвых точекИгровое взаимодействие через нативы

Virtual entity создаёт и стримит сервер (server authority) — клиент их только читает; конструктора на клиенте нет.

Группы и cell key

Virtual entities объединяются в VirtualEntityGroup. Группа задаёт радиус стрима по умолчанию (maxStreamingDistance) и лимит одновременно стримингуемых сущностей (maxEntitiesInStream). Хелпер cellKey помогает автору сгруппировать позиции по ячейкам сетки.

js
// client
varion.on('virtualEntityStreamIn', (ve) => {
  varion.log(`VE ${ve.id} group=${ve.groupId} dist=${ve.streamingDistance}`);
});

varion.on('virtualEntityPositionChange', (ve) => {
  varion.log(`VE ${ve.id} -> ${ve.pos.x}, ${ve.pos.y}`);
});

const ve = varion.VirtualEntity.get(remoteId);
if (ve) varion.log(ve.isStreamedIn, ve.visible, ve.dimension);

const key = varion.VirtualEntityGroup.cellKey(0.0, 0.0, 100);

API (client, read)

varion.VirtualEntity

ЧленОписание
get(id) / getByRemoteID(id)VE по remote ID или null
allВсе известные VE
streamedInТолько in-stream VE

Свойства экземпляра: id / remoteId, groupId, group, isStreamedIn / streamed, streamingDistance (per-entity override или радиус группы), visible, dimension, pos, rot, а также meta-методы как у обычной сущности (см. Metadata).

varion.VirtualEntityGroup

ЧленОписание
get(id)Группа по ID или null
allВсе группы
cellKey(x, y, cellSize = 100)Grid-ключ "cx:cy" для группировки позиций

Свойства группы: id, maxStreamingDistance (по умолчанию 400), maxEntitiesInStream (по умолчанию 128), streamedIn (in-stream VE этой группы).

События virtual entity

СобытиеКогда
virtualEntityStreamIn / virtualEntityStreamOutVE вошла/вышла из стрима
virtualEntityPositionChangeПозиция VE в snapshot изменилась
entityStreamIn / entityStreamOutОбщий контракт; для VE entityType = 9

См. также

  • События и RPC — точные сигнатуры stream-, owner- и dimension-событий.
  • server.tomlstreamingDistance, migrationDistance, maxStreamedEntities.
  • Metadata — synced / stream-synced / local meta.
  • Streaming и Assets — стриминг файлов ресурсов, не сущностей.

Source backing

Страница основана на:

  • server/src/CServer.cpp (UpdateStreaming, UpdateVehicleOwnership, ApplyVehicleOwnerChange, RelayPlayerPresentation)
  • include/varion.h (streamingDistance, migrationDistance, maxStreamedEntities)
  • sdk/client-js-runtime/injected/world-bootstrap.js (VirtualEntity, VirtualEntityGroup)
  • sdk/client-js-runtime/injected/entities-bootstrap.js и events-bootstrap.js (stream-, owner- и dimension-события)

Varion Multiplayer Platform