Цель

Запустить Doom на PostgreSQL и познакомимся с основами написания нативных расширений для PostgreSQL.

Попробовать

Исходный код тут: https://github.com/DreamNik/pg_doom. Для удобства весь процесс реализован в виде Docker образа. Для работы придётся найти и вручную подложить файл doom.wad, который защищён авторским правом и не является свободно распространяемым.

git clone https://github.com/DreamNik/pg_doom
cd pg_doom
<вручную поместите Ваш файл doom.wad в под-директорию pg_doom>
docker build --tag pg_doom --file docker/Dockerfile .
docker run --rm --interactive --tty pg_doom

Управление - кнопками A, S, D, W, F, E.

Архитектура решения

Решение будет состоять из:

  • расширения pg_doom, которое будет работать внутри СУБД;

  • bash-скрипта, который будет работать как интерфейс ввода вывода.

Расширение будет предоставлять две новые функции в языке SQL. Первая - будет передавать нажатые клавиши, вторая - получать "картинку" для отображения. А скрипт будет, в свою очередь, считывать нажатые кнопки, передавая их как аргумент первой функции, а после вызывать вторую функцию и отображать её результат.

Подготовка

Для того, чтобы написать расширение нам потребуются:

  • компьютер с ОС Debian;

  • установленный PostgreSQL с набором для разработки;

  • компилятор языка C и набор утилит GNU Make.

В статье используется ОС Debian, но можно использовать и любую другую ОС семейства Linux с соответствующей адаптацией некоторых шагов. Windows тоже подойдёт, но там шаги подготовки совсем другие.

Итак, открываем консоль и ставим необходимые пакеты:

export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y \
	git \
	build-essentials \
	postgresql

Создание расширения

Исходный код расширения для PostgreSQL будет состоять из:

  • файла с метаданными расширения - pg_doom.control.

  • файлов с SQL кодом инициализации расширения в базе - pg_doom.sql;

  • файла сборки расширения - Makefile;

  • файлов с исходным кодом - pg_doom.c и другие.

В статье приведён далеко не весь исходный код. Весь исходный код можно посмотреть в репозитории pg_doom.

Файл pg_doom.control

Этот файл используется PostgreSQL для определения состава расширения и куда и как его загружать.

comment         = 'Provides ability to play the game.'
default_version = '1.0'
relocatable     = false
module_pathname = '$libdir/pg_doom'
schema          = doom

Из интересного здесь это module_pathname - путь, указывающий на собранный бинарный модуль.

Файл pg_doom--1.0.sql

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

CREATE PROCEDURE doom.input(
    IN  chars      TEXT,
    IN  duration   INTEGER)
AS 'MODULE_PATHNAME', 'pg_doom_input' LANGUAGE C;

CREATE FUNCTION doom.screen(
    IN  width      INTEGER DEFAULT 320,
    IN  height     INTEGER DEFAULT 200,
    OUT lineNumber INTEGER,
    OUT lineText   TEXT)
RETURNS SETOF RECORD
AS 'MODULE_PATHNAME', 'pg_doom_screen' LANGUAGE C;

В файле используется ключевое значение MODULE_PATHNAME в качестве имени модуля функции. Это значение подменяется на фактический адрес загружаемого модуля (библиотеки), которое указано в control файле.

Файл Makefile

Файл используется для компиляции и установки расширения. В начале файла задаются имя и описание расширения:

MODULE_big = pg_doom
EXTENSION  = pg_doom
PGFILEDESC = pg_doom

Далее задаётся список файлов данных, которые будут установлены вместе с расширением

DATA = pg_doom--1.0.sql pg_doom.control

Далее, задаём список объектных файлов, которые необходимо собрать. То есть, задаётся не список исходных файлов, а список артефактов сборки. Из перечисленных объектных файлов и будет собрана библиотека.

OBJS = pg_doom.c ...

Вызов компилятора и скрипты сборки установлены в системе и могут быть подключены при помощи механизма PGXS. Для получения путей в системе присутствует утилита pg_config.

PG_CONFIG = pg_config
PGXS     := $(shell $(PG_CONFIG) --pgxs)
bindir   := $(shell $(PG_CONFIG) --bindir)
include $(PGXS)

Файлы C

В файлах размещается исходный код функций, которые мы объявили в sql файле.

В общем случае, чтобы собранная библиотека загрузилась как расширение необходимо:

  • вызвать макрос PG_MODULE_MAGIC;

  • для каждой экспортируемой функции вызвать макрос PG_FUNCTION_INFO_V1(my_function_name);

  • все экспортируемые функции должны иметь сигнатуру
    Datum my_function_name( FunctionCallInfo fcinfo );

  • определить две функции - void _PG_init(void) и void _PG_fini(void);

Подробное описание функций и их состав можно посмотреть в репозитории с исходным кодом расширения.

Интеграция игры

Для сборки ядра игры необходим пропатченный исходный код, в котором исправлены некоторые конструкции языка, которые мешали оригинальному коду компилироваться и запускаться под современными 64-битными системами. Исходный код пропатченного ядра можно найти тут.

Для запуска игры нужен файл doom.wad. Он содержит все медиаданные игры, но, к сожалению, не является свободно распространяемым в отличие от ядра игры. Его можете взять из директории оригинальной игры или получить любым другим легальным способом.

Для интеграции игры реализована в файле doom.c. При первом вызове создаётся отдельный поток, в котором вызывается функция D_DoomMain, которая представляет собой основной цикл игры.

В процессе работы цикла игры вызываются функции, которые управляют вводом-выводом игры:

  • I_InitGraphics;

  • I_ShutdownGraphics;

  • I_SetPalette;

  • I_StartTic;

  • I_ReadScreen;

  • I_InitNetwork.

При обычном запуске игры эти функции реализованы в драйверах ввода-вывода игры. Но в нашем расширении драйвера мы не компилируем, а функции определены на взаимодействие со структурами, которые доступны из объявленных функций pg_doom_input и pg_doom_screen.

Компиляция

Запускаем сборку и установку в систему при помощи типовых вызовов make:

make -j$(nproc) && sudo make install

Запуск сервера

Если в системе не запущен PostgreSQL, то можно создать временный экземпляр и запустить его:

export PGDATA=/tmp/pg_doom_data
mkdir -p $PGDATA

initdb --no-clean --no-sync

cat >> $PGDATA/postgresql.conf <<-EOF
    listen_addresses = '127.0.0.1'
EOF

cat >> $PGDATA/pg_hba.conf <<-EOF
    host all    postgres 127.0.0.1/32 trust
    host doomdb slayer   127.0.0.1/32 trust
EOF

pg_ctl start &> /dev/null

Загрузка расширения

Для запуска игры создаём и настраиваем базу данных:

CREATE DATABASE doomdb;
CREATE EXTENSION IF NOT EXISTS pg_doom;
CREATE ROLE slayer WITH LOGIN;
GRANT USAGE ON SCHEMA doom TO slayer;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA doom TO slayer;

Запуск игры

Для "комфортной" игры нам необходим скрипт-обёртка. Он должен заниматься вводом-выводом, аналогичным как при обычной игре. Для этого нам нужно считывать нажатые кнопки и отображать картинку на экране. Перед запуском необходимо подготовить терминал:

stty -echo
clear
cols=$(expr $(tput cols  || 281) - 1)
rows=$(expr $(tput lines ||  92) - 2)

И далее запустить цикл:

{
    while true; do
        while read -n 1 -s -t 0.02 k; do
            echo "CALL doom.input('$k',10);";
        done;
        echo "SELECT '\\x1B[H';";
        echo "SELECT linetext FROM doom.screen($cols,$rows);";
        sleep 0.2;
    done;
} | psql -h 127.0.0.1 -U slayer -d doomdb -P pager=off -t -q | sed 's|\\x1B|\x1B|g'

В цикле мы динамически формируем текстовые SQL команды и отправляем их в stdin утилиты psql, которая подключается к базе данных. Её вывод затем форматируется и выводится на экран. Скорость обновления и input-lag сильно зависит от возможностей компьютера и игрока.

Заключение

При помощи расширений PostgreSQL можно практически не ограничено расширять возможности СУБД PostgreSQL.


Спасибо за внимание!

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


  1. Wesha
    06.08.2023 23:05
    -3

    Прикольно, конечно, но...


  1. hssergey
    06.08.2023 23:05
    +9

    Интересно, но они просто подключили движок дума в качестве расширения постргеса, а вся обработка по-прежнему идет через оригинальный код дума. А судя по названию статьи можно было предположить, что они прямо весь код дума портировали на PL/PGSQL, вот это было бы круто...


    1. DreamNik Автор
      06.08.2023 23:05

      Технически это возможно, но объём работ уж очень большой получается.

      Если рассматривать вариант работы без расширений, то проще взять PL/Python и запустить на нём python порт движка.
      Кстати, PostgreSQL нативно может следующие языки: PL/pgSQL, PL/Tcl, PL/Perl, PL/Python.

      Ещё одна из интересных идей - добавить язык PL/C, который как PL/PGSQL но только С :)
      И в него уже вставить оригинальные функции на С.


      1. gudvinr
        06.08.2023 23:05

        Ещё одна из интересных идей — добавить язык PL/C, который как PL/PGSQL но только С

        Так по сути разница только в том, что PL/* интерпретируемые, а "user-defined functions" на C — нет.
        Но принципиально это уже и есть PL/C.


    1. Fell-x27
      06.08.2023 23:05
      +5

      Это из разряда "DOOM запустили на цифровом тесте беременности!!!"

      А когда начинаешь читать, оказывается, что тест вскрыли, подпаялись к контроллеру, и просто выводили на его дисплейчик DOOM, запущенный на стоящем рядом ноутбуке. То есть, по сути, все свелось к "мы вывели дум на 5-баксовый дисплей с алика".


      1. DreamNik Автор
        06.08.2023 23:05

        Ага, помню такую статью - было обидно что он действительно не на самом тесте работал...


  1. DreamNik Автор
    06.08.2023 23:05
    +5

    Тут DOOM действительно внутри СУБД работает, в самом ядре. И каждая сессия имеет свой инстанс игры, который живёт внутри бэкенда сессии - ни какого "внешнего" подключения игры не происходит.

    Теоретически можно было бы даже мультиплеер запилить между разными сессиями и обмен через shared memory бэкендов.

    Почти все расширения PostgreSQL реализованы по такому принципу - подгружается библиотека которая реализует какой-то функционал. Это как комплектные из состава contrib, так и большинство внешних - pg_pathman, pg_hint_plan, timescaledb, pg_stat_kcache, postgis.


  1. warus
    06.08.2023 23:05

    библиотеку aalib изобрели заново, загрузите посмотрите возможности aalib в демо bb установка apt install bb.
    По приколу видио в терминале проигрывал, вроде и сейчас vlc в linux может так фильмы показывать.


    1. DreamNik Автор
      06.08.2023 23:05

      Я думал использовать готовую, в т.ч. aalib. Но при беглом осмотре показалось что она умеет рендерить только в терминал, т.е. работать как графическое устройство.

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

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

      Можно было попробовать адаптировать aalib, но простая своя реализация показалась проще.


  1. Travisw
    06.08.2023 23:05

    А где видео?


    1. DreamNik Автор
      06.08.2023 23:05

      В самом начале статьи:
      https://www.youtube.com/watch?v=zdQOoZgB50M