Running Doom inside PostgreSQL

Target

Run doom on PostgreSQL and get acquainted with the basics of writing native extensions for PostgreSQL.

try

Source code here: https://github.com/DreamNik/pg_doom. For convenience, the entire process is implemented as a Docker image. To work, you will have to find and manually attach the file doom.wadwhich is copyrighted and not freely distributed.

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

Control – buttons A, S, D, W, F, E.

Solution architecture

The solution will consist of:

  • pg_doom extension, which will work inside the DBMS;

  • bash script that will act as an I/O interface.

The extension will provide two new features in the SQL language. The first – will transmit the pressed keys, the second – will receive a “picture” for display. And the script will, in turn, read the pressed buttons, passing them as an argument to the first function, and then call the second function and display its result.

Preparation

In order to write an extension, we need:

  • computer with Debian OS;

  • installed PostgreSQL with development kit;

  • C compiler and GNU Make toolkit.

This article uses the Debian OS, but you can use any other OS of the Linux family with appropriate adaptation of some steps. Windows is also suitable, but the preparation steps are completely different there.

So, open the console and install the necessary packages:

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

Create an extension

The PostgreSQL extension source code will consist of:

  • file with extension metadata – pg_doom.control.

  • files with SQL extension initialization code in the database – pg_doom.sql;

  • extension assembly file – Makefile;

  • source files – pg_doom.c and others.

The article does not provide the entire source code. All source code can be found in the repository pg_doom.

pg_doom.control file

This file is used by PostgreSQL to determine what the extension contains and where and how to load it.

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

Of the interesting things here is module_pathname – path pointing to the assembled binary module.

File pg_doom–1.0.sql

This file is executed when the extension is loaded into the database. If necessary, tables, views, triggers, functions and other structures necessary for the extension to work are created in such files. We need to provide only two functions in the database schema – data input and data output:

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;

Key value used in file MODULE_PATHNAME as the function module name. This value is replaced by the actual address of the loaded module (library), which is specified in the control file.

Makefile

The file is used to compile and install the extension. At the beginning of the file, the name and description of the extension are specified:

MODULE_big = pg_doom
EXTENSION  = pg_doom
PGFILEDESC = pg_doom

Next, a list of data files that will be installed along with the extension is specified.

DATA = pg_doom--1.0.sql pg_doom.control

Next, we set the list of object files that need to be built. That is, not a list of source files is specified, but a list of assembly artifacts. The library will be built from the listed object files.

OBJS = pg_doom.c ...

The compiler invocation and build scripts are installed on the system and can be connected using the mechanism PGXS. To get the paths in the system there is a utility pg_config.

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

C files

The files contain the source code of the functions that we declared in the sql file.

In general, in order for the compiled library to be loaded as an extension, you need to:

  • call macro PG_MODULE_MAGIC;

  • call a macro for each exported function PG_FUNCTION_INFO_V1(my_function_name);

  • all exported functions must have a signature
    Datum my_function_name( FunctionCallInfo fcinfo );

  • define two functions – void _PG_init(void) And void _PG_fini(void);

A detailed description of the functions and their composition can be found in the repository with the source code of the extension.

Game integration

To build the game core, you need a patched source code that fixes some language constructs that prevented the original code from compiling and running under modern 64-bit systems. The source code for the patched kernel can be found here.

The file is required to run the game. doom.wad. It contains all the game’s media, but, unfortunately, is not freely distributed, unlike the core of the game. You can take it from the directory of the original game or get it in any other legal way.

For integration of the game implemented in the file doom.c. At the first call, a separate thread is created in which the function is called D_DoomMainwhich represents the main loop of the game.

During the game loop, the following functions are called that control the game’s I/O:

  • I_InitGraphics;

  • I_ShutdownGraphics;

  • I_SetPalette;

  • I_StartTic;

  • I_ReadScreen;

  • I_InitNetwork.

When the game starts normally, these functions are implemented in the game’s I/O drivers. But in our driver extension, we do not compile, and the functions are defined to interact with structures that are accessible from the declared functions pg_doom_input And pg_doom_screen.

Compilation

We start the assembly and installation into the system using typical make calls:

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

Server start

If PostgreSQL is not running on the system, then you can create a temporary instance and run it:

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

Extension download

To start the game, create and configure the database:

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;

Game launch

For a “comfortable” game, we need a wrapper script. It should deal with I / O, similar to that of a normal game. To do this, we need to read the pressed buttons and display the picture on the screen. Before starting, you need to prepare the terminal:

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

And then run the loop:

{
    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'

In the loop, we dynamically generate textual SQL commands and send them to stdin of the psql utility that connects to the database. Its output is then formatted and displayed on the screen. The update rate and input-lag is highly dependent on the capabilities of the computer and the player.

Conclusion

With the help of PostgreSQL extensions, you can extend the capabilities of the PostgreSQL DBMS almost unlimitedly.

Thank you for your attention!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *