Продолжая серию статей про разработку сервера для онлайн игр (адрес проекта http://my-fantasy.ru) на языке PHP в это части я хочу рассказать про безопасное добавления пользовательского кода игровых механик. В статье я опишу существующие решения для PHP , сравню скорость работы приведу видео примеры.

Для тестов будет использовано следующее железо:

  • CPU 2 ядра (2300 Mhz на ядро)

  • 4Gb Ram

  • php 8.1 

  • SSD

Напомню что наш сервер для онлайн игр сделан для realtime взаимодействия игроков (шутеров, rpg , стратегии) и тесты будут производится с нагрузкой 1000 циклов сервера в секунду (в каждом цикле выполняется разных код игровых механик в сумме который не должен быть более 1мс) и ниже показатель RPS - количество выполнений кода в секунду

Одним из языков который до сих пор используется в ряде игровых проектов в качестве второстепенного языке является LUA (на нем частенько пишутся квесты, эвенты, диалоги , балансы в мультиплеерых играх самописных движков). Это скриптовый язык похожий на Java Script по мнению Википедии. Я не буду останавливаться на подробном описании языка, скажу его скрипты можно компилировать (хотя нам это не очень важно тк сервер загружается в оперативку и крутится там после запуска) и что для PHP существует целых 2 расширения способных его встроить в ваш проект : PHP Lua и PHP LuaSandbox . Мы остановимся на последнем, так как в нем закрыта часть функционала, который был признан создателем плагина небезопасным. Этот же человек является автором движка используемый Википедией — MediaWiki.

Следующие функции запускают из под PHP код lua для выполнения в песочнице и дают следующие показатели производительности lua (perfomcance test php luasandbox)

  • call (closure, отдает фиксированную строку )  70 000 RPS (8 ядер + физ сервер дает x10 к скорости)

  • call (closure отдает вызов PHP функции сервера с параметрами  созданной с помощью registerLibrary число параметров значимо не влияет на скорость)  60 000 RPS (8 ядер + физ сервер дает x10 к скорости)

  • callFunction (аналог call но взывает не closure, a именованную глобальную функцию lua) - аналогично параметрам выше

Заметки:

  • в данной технологии PHP и LUA не имеют общего хранилища и все данные передаются в виде сообщений двусторонних не передавая ссылки (поэтому напрямую объект не передать, свойства не получить). 

  • Передавая объект из PHP в LUA приходится прибегать к registerLibrary (заранее создавать доступные в LUA функции из PHP) и мета таблицам (аналог объектов) при смене или получения значения которых вызвать указанные функции в php отдающие данные объекта (и изменяющие его)

  • В боевых условиях (кеш, синхронизация данных всех песочниц и тп) простое игровое событие (регенерация) занимает 0.17мс (6000 запросов в секунду где идет вызов LUA и 2 раза обращение в PHP)

Вот как это было реализовано в админ панели сервиса

Наш следующий претендент на код который смогут добавлять пользователи в сервис через админ панель (т.е. без доступа к исходникам сервера) является Java Script код. Всем знаком Node js , на нем делают сервера которые считаются достаточно быстрыми. Если углубиться в вопрос "почему ?" можно выделить некую конкретику:

  • Для Websocket почти всегда используется библиотеку Socket IO (грубо говоря у вас сервер не ждет отправку 100500 игрокам TCP сообщений, а одно другому WebSocket серверу, который уже рассылает всем остальным, тем самым достигается неблокируемость основного потока), которая реализует идею , которую можно переложить на другой язык (Си, Php и др.) взамен асинхронного программирования с несколькими сопрограммами (thread) и обменом данных (Shared Memory или сообщения)

  • движок интерпретатор JavaScript под названием....

    V8 от Google - движок для Java Script кода используемый в одном из самых быстрых браузерах Google Chrome. Для PHP имеется возможность библиотека по интеграции под названием V8JS

Следующие функции запускают из под PHP код Java Script для выполнения в песочнице и дают следующие показатели производительности lua (perfomcance test php v8js)

Выполнение компиляции происходит на лету (для примера, использование не рационально):

  • executeString (отдает фиксированную строку )  140 000 RPS  (8 ядер + физ сервер дает x4.5 к скорости)

  • executeString (отдает получение свойств объекта переданного из PHP)  80 000 RPS  (8 ядер + физ сервер дает x5 к скорости)

  • executeString (отдает выполнение метода объекта переданного из PHP)  40 000 RPS  (8 ядер + физ сервер дает x5 к скорости)

  • execiteString возвращающий анонимную функцию (closure) и вызываемый как метод PHP полученного объекта на 20% быстрее (за счет того что executeString  вызван единожды)

Тоже что и выше но код JS запроса заранее компилируется и его можно использовать повторно:

executeScript (отдает фиксированную строку) 600 000 RPS  (8 ядер + физ сервер дает x3.5 к скорости)

executeScript (отдает получение свойств объекта переданного из PHP) 300 000 RPS (8 ядер дают x4 прибавку к скорости)

executeScript (отдает выполнение метода объекта переданного из PHP)  60 000 RPS  (8 ядер + физ сервер дает x5 к скорости)

executeScript возвращающий анонимную функцию (closure) на 30% медленнее (в зависимости что возвращает, полагаю особенность компиляции closure)

передача параметров из PHP 700 000 RPS / свойство (8 ядер дают лишь x2.5 прибавку к скорости)

Заметки:

  • Добавлять новые значения из PHP в V8js  - медленно, для экономии времени добавление новых данных можно передать в пространство V8js объект один раз (тк он передастся по ссылке) , а в нем самом уже из PHP менять свойства (это создаст некое хранилище-посредник)

  • В данной технологии PHP и V8js делят некое общее хранилище памяти  однако в отличие от LUA объект передается по ссылке и сразу доступен.

Вывод: 

  1. В настоящее время JavaScript более популярен за счет игрового движка Phaser2D (33.000 лайков), в то время как для разработки игр где игровые механики (есть движки где LUA используется в незначительной степени для описания действий в игре) написаны на LUA используется Love (3.000 лайков). Lua имеет явный недостаток - это то что для получения свойств объекта PHP нужно вызвать некие функции (как если бы мы взвали методы объектов), но это можно решить кешируя в самом LUA значения , при изменении LUA - менять кеш, при изменении в PHP или JS - отправлять команду на изменение кеша (тк по большей части будет чтение это будет выигрышным выходом). На более мощном железе явный рывок в скорости

  2. В JavaScript на рынке много библиотек для работы с физикой, графикой, есть общая память и получение свойств объектов отрабатывает быстро без нужды что либо кешировать (хотя и это тоже можно сделать по примеру LUA). Однако вызов методов объектов PHP явно проигрывает по скорости LUA на хорошем железе, но такие методы вызываются реже (например 1 раз в 200мс) чем читаются (сотни раз в 1мс) и меняются свойства (например раз в кадр 60мс). Пример методов: добавления на карту новых объектов, добавление объекты новых событий, сохранение игрока в базу. 

  3. В LUA можно сделать некий кеш который кеширует все свойство не тратя время на обращение в PHP для их чтения, однако это сопряжено с тем что при смене свойств в JS или PHP мы должны обновлять их в LUA  однако все эти изменения не обязательно обновлять как только они появились , а отправлять пакетом при следующем запуске LUA 

  4. В таких языках как С++ язык LUA встраиваемый (как в нашем сервисе) и старые игры в тч и онлайн до сих пор используют LUA для написания часто меняющейся игровой логики (игровые мероприятия, диалоги, квесты)

В заключении я скажу, что для работы с песочницами LUA и JavaScript необходимо будет перестроить существующую архитектуру , все тесты я публикую на странице своего блога

Комментарии (4)


  1. Dima_Sharihin
    16.12.2022 08:00
    +1

    Какая версия рантайма Lua используется? Между Lua 5.1 и Luajit разница может быть очень существенная, настолько, что последний может оказаться быстрее PHP

    это то что для получения свойств объекта PHP нужно вызвать некие функции

    FFI позволяет работать с Сишными структурами памяти, как с нативными объектами Lua

    Простите за токсичность, но Windows 10 из коробки умеет записывать видео с экрана, для этого нужно нажать Win+G. Снимать скринкаст с телефона - не очень


    1. webrobot Автор
      16.12.2022 14:05

      5.1 стоит - эта версия обусловлена плагином php LuaSandbox. Плагины php уже написаны на Си и мне кажется если и улучшать то доработать его, но не понятно будет ли выигрыш в скорости в тч с ffi. С объектом работать в lua можно если сделать некие метотаблицы с сеттарами и геттарами и в целом жить можно особенное если кешировать свойства и при изменении объекта извне при следующем вызове lua передавать что изменилось...В любом случае Си я не знаю пока.


  1. FanatPHP
    17.12.2022 12:02

    Кстати про игры
    Чувак вот 3-d игру пилит!
    https://www.youtube.com/watch?v=skKy_6bFoSQ


    1. webrobot Автор
      18.12.2022 16:57

      посмотрел - используется сервер на PHP и сама игра как я понял на JS. Моя идея не делать игры , а дать возможность сделать мультиплеер в играх сделанных на любой платформе (unity, unreal engine , godot , phaser 2d и самописные в т.ч.)

      В дополнение предоставить web инструменты для работы с контентом догружаемым в игру и возможность самим дополнять из админ панели игровой сервер механиками (не погружаясь как там работает код сервера и куда что вкорячивать)

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