Broadcaster

r4nx

Известный
Автор темы
Друг
202
261
Привет. Задумывались ли вы когда-то о возможности передавать информацию между игроками на любом расстоянии, вне зоны стрима и без сторонних серверов? Если да, то это то, что нужно.

broadcaster - это библиотека, реализующая протокол передачи данных по всему серверу. Импортируем её, вызываем одну функцию - сообщение отправлено. Определяем callback функцию, регистрируем - готово, теперь ваш скрипт может принимать сообщения. Просто? Очень.
  1. Использование
  2. Детали реализации
  3. Структуры пакетов
  4. Сессии
  5. Тип передаваемых данных
Использование
broadcaster распределяет сообщения между т.н. хендлерами (обработчиками). Хендлер - это и есть ваша callback функция. Вы указываете название хендлера при отправке сообщения, а также при регистрации callback функции. Это позволяет сразу нескольким скриптам использовать библиотеку.
[!] Обратите внимание [!] для импортирования библиотеки используется не стандартное выражение require, а функция import из moonloader. В ней нужно указывать не просто название библиотеки, а путь к ней - если ваш скрипт находится в папке moonloader, то вам необходимо делать это так:
Lua:
local broadcaster = import('lib/broadcaster.lua')
Кроме того, для корректной передачи сообщений с русскими символами понадобится модуль encoding:
Lua:
local encoding = require 'encoding'
encoding.default = 'CP1251'
u8 = encoding.UTF8

Перейдем к регистрации хендлера. Для начала определим функцию:
Lua:
function myHandler(message)
    sampAddChatMessage('New message: ' .. u8:decode(message), 0xAAAAAA)
end
Если вы хотите, чтобы регистрация проходила автоматически, это следует сделать в функции main с некоторой задержкой:
Lua:
wait(100)
broadcaster.registerHandler('myhndl', myHandler)
Рассмотрим этот момент подробнее. Первым аргументом является уникальное название хендлера. Вы должны сделать его действительно уникальным, иначе если два скрипта попробуют зарегистрировать два хендлера с одинаковыми названиями, библиотека выдаст ошибку и ни один из скриптов работать не будет. Если вы собираетесь публиковать скрипт, который использует эту библиотеку - не поленитесь оставить в комментариях название хендлера, чтобы избежать конфузов. Ограничений на длину практически нет (256 символов), но имейте ввиду - каждый символ стоит отдельного пакета, поэтому будьте экономными. Вторым аргументом передается сама callback функция.
Также перед завершением работы скрипта нужно удалить хендлеры, которые он зарегистрировал, это требуется для корректной перезагрузки скрипта:
Lua:
function onScriptTerminate(scr)
    if scr == thisScript() then
        broadcaster.unregisterHandler('myhndl')
    end
end
Отправляются сообщения проще:
Lua:
broadcaster.sendMessage(u8('hello world'), 'myhndl')
Но если вы разработчик, то есть шанс что пользователь введёт какой-нибудь необычный символ и работа завершиться ошибкой. В силу ограниченной пропускной способности я был вынужден реализовать собственную кодировку, которая состоит из букв английского и русского алфавита, цифр, а также спец. символов. Надеюсь этого хватит. Но всё же, чтобы вышеописанная ситация не произошла, следует ловить ошибки при отправке следующим образом:
Lua:
local result, returned = pcall(broadcaster.sendMessage, u8('hello world'), 'myhndl')
if not result then
    print('error occured while sending msg:\n' .. returned)
end

Детали реализации
broadcaster работает поверх битстрима. Он превращает двухбайтовый битстрим в список битов и обратно. У протокола есть 4 типа пакетов: 1 - пакет сигнала начала передачи данных, 2 - пакет сигнала завершения передачи данных, 3 - пакет данных, 4 - пакет названия хендлера. Идентификатор пакета записывается 3 первыми битами внутри каждого пакета. Опознав пакет, broadcaster распаковывает его и проводит манипуляции в зависимости от типа пакета.

Структуры пакетов
Пакет сигнала начала передачи данных


Позиция (в битах)Описание
1-3Идентификатор пакета (1)
4-7Идентификатор сессии


Пакет сигнала завершения передачи данных


1-3Идентификатор пакета (2)
4-7Идентификатор сессии


Пакет данных


1-3Идентификатор пакета (3)
4-7Идентификатор сессии
8Бит чётности
9-16Данные


Пакет названия хендлера


1-3Идентификатор пакета (4)
4-7Идентификатор сессии
8Бит чётности
9-16Название хендлера

Бит чётности - это разновидность проверки данных на наличие повреждений. Если число единиц в двоичном числе непарное, то бит чётности будет 1, если парное - 0.

Сессии
Интересным аспектом являются сессии, которые предотвращают смешывание данных, если два пользователя отправят данные одновременно. Концепт следующий: при отправке сообщения создается новая сессия, ей присвается случайный 4-битный номер. Отправляющиеся пакеты включают в себя номер сессии. Когда получателю поступает пакет сигнала начала передачи данных у него создается новая сессия. При получении последующих пакетов данных или пакетов названия хендлера, библиотека ищет подходящую сессию, если такая есть - добавляет туда полученные данные. Когда приходит пакет завершения передачи данных, broadcaster также ищет сессию, но теперь он удаляет её из списка и передает в обработчик сессий, где извлекается название хендлера и если такой зарегистрирован, данные передаются ему.
Присутствует механизм очистки старых сессий - если последнее обращение к незавершенной сессии было более 30 секунд назад, она будет удалена при получении следующего пакета.

Тип передаваемых данных
По-умолчанию, пользуясь теми методами, что я описал выше, для передачи доступны только строки. Но я специально вынес прикладной интерфейс в broadcaster.lua, поэтому вы можете спуститься чуть ниже - broadcaster/proto.lua. Там вам понадобится функция proto.sendData(), которая принимает на вход массив из 1-байтовых чисел (до 255) в качестве данных, а также такой же массив (из чисел) в качестве названия хендлера. Но как так - название-то - текст, как из него сделать числа? На помощь приходит broadcaster/encoder.lua. С помощью следующего нехитрого кода можно закодировать текст кодировкой, которую использует broadcaster:
Lua:
local encoder = require 'broadcaster.encoder'
local charset = require 'broadcaster.charset'

local encodedHandlerId = encoder.encode('myhndl', charset.MESSAGE_ENCODE)
Функция вернёт список списков - в каждом вложенном списке 16 бит в виде {0, 0, 1, 0, 1, 1, 1, ...}. Один вложенный список - 1 битстрим. Чтобы записать бит, используйте raknetBitStreamWriteBool; после записи всех битов установите оффсет записи на 16 бит с помощью raknetBitStreamSetWriteOffset.
Для того, чтобы данные приходили вам в "сыром" виде (тоже в виде таблицы, как вы их отправляли, а не строки), нужно при регистрации хендлера указать третий параметр - rawData, установить его на true.

Возможно вы заметите то огромное количество логов, которое производит библиотека. Но, к сожалению, это необходимость на стадии бета-тестирования. В случае, если обнаружите логи типа WARN, ERROR или FATAL - скидывайте moonloader.log в комментарии. broadcaster совсем не работает на samp-rp и плохо работает на advance-rp. Оставляйте отзывы о работе на других серверах.

Честно говоря уже устал писать, ниже библиотека со всеми зависимостями, а также чатик на её основе. Для более углубленной информации изучайте исходники, там везде комментарии. Спасибо @EvgeN 1137 за идею и @randazzo за тестирование.

Git репозиторий: https://github.com/r4nx/broadcaster
Распространяется под лицензией MIT (кратко: единственное требование - указание копирайтов)
 

Вложения

  • broadcaster_showcase.lua
    1.5 KB · Просмотры: 423
  • broadcaster.zip
    36.3 KB · Просмотры: 536
Последнее редактирование:

AnWu

Guardian of Order
Всефорумный модератор
4,687
5,163
а в чем причина неработоспособности на срп? сервер фильтрует пакеты?
 
  • Нравится
Реакции: qdIbp

Aniki

🐰
Администратор
1,225
1,495
Классная идея, классное исполнение, жаль что очень легко фиксится со стороны сервера, поэтому как-то даже не хочется делать что-то на основе этой библиотеки (типа полноценного мессенджера)
 
  • Нравится
  • Грустно
Реакции: AnWu и r4nx

Himura

Известный
23
11
Так это будет только в зоне стрима работать. Какой пакет ты используешь?
 

#Northn

Police Helper «Reborn» — уже ШЕСТЬ лет!
Всефорумный модератор
2,633
2,482
Код:
10:31:27.851887] (error)    broadcaster.lua: error loading module 'lua-utf8' from file 'D:\Games\GTA San Andreas\moonloader\lib\lua-utf8.dll':
    Не найден указанный модуль.
stack traceback:
    [C]: in ?
    [C]: in function 'require'
    ...an Andreas\moonloader\lib\broadcaster\encoder.lua:7: in main chunk
    [C]: in function 'require'
    ...es\GTA San Andreas\moonloader\lib\broadcaster.lua:15: in main chunk
[10:31:27.851887] (error)    broadcaster.lua: Script died due to an error. (0B224AFC)
[10:31:27.851887] (error)    broadcaster_showcase: import 'lib/broadcaster.lua' cannot be loaded.
stack traceback:
    [C]: in function 'import'
    ...n Andreas\moonloader\broadcaster_showcase (1).lua:9: in main chunk
[10:31:27.851887] (error)    broadcaster_showcase: Script died due to an error. (0B22511C)

:(
Использую твой же пример.
 

r4nx

Известный
Автор темы
Друг
202
261
Код:
10:31:27.851887] (error)    broadcaster.lua: error loading module 'lua-utf8' from file 'D:\Games\GTA San Andreas\moonloader\lib\lua-utf8.dll':
    Не найден указанный модуль.
stack traceback:
    [C]: in ?
    [C]: in function 'require'
    ...an Andreas\moonloader\lib\broadcaster\encoder.lua:7: in main chunk
    [C]: in function 'require'
    ...es\GTA San Andreas\moonloader\lib\broadcaster.lua:15: in main chunk
[10:31:27.851887] (error)    broadcaster.lua: Script died due to an error. (0B224AFC)
[10:31:27.851887] (error)    broadcaster_showcase: import 'lib/broadcaster.lua' cannot be loaded.
stack traceback:
    [C]: in function 'import'
    ...n Andreas\moonloader\broadcaster_showcase (1).lua:9: in main chunk
[10:31:27.851887] (error)    broadcaster_showcase: Script died due to an error. (0B22511C)

:(
Использую твой же пример.
lua-utf8.dll в папке lib есть?
 

r4nx

Известный
Автор темы
Друг
202
261
@dmitri4 спасибо, какой сервер? сами сообщение доставляются нормально?