Start working on frontend to rust engine rewrite
authorunC0Rr
Tue, 06 Nov 2018 17:00:35 +0100
changeset 14143 745c73e0e644
parent 14142 11202097584f
child 14144 165e43c3ed59
Start working on frontend to rust engine rewrite
qmlfrontend/CMakeLists.txt
qmlfrontend/Page1.qml
qmlfrontend/Page1Form.ui.qml
qmlfrontend/flib.h
qmlfrontend/gameconfig.cpp
qmlfrontend/gameconfig.h
qmlfrontend/gameview.cpp
qmlfrontend/gameview.h
qmlfrontend/hwengine.cpp
qmlfrontend/hwengine.h
qmlfrontend/main.cpp
qmlfrontend/main.qml
qmlfrontend/previewimageprovider.cpp
qmlfrontend/previewimageprovider.h
qmlfrontend/qml.qrc
qmlfrontend/res/iconTime.png
qmlfrontend/runqueue.cpp
qmlfrontend/runqueue.h
qmlfrontend/team.cpp
qmlfrontend/team.h
rust/hedgewars-engine/Cargo.toml
rust/hedgewars-engine/src/lib.rs
--- a/qmlfrontend/CMakeLists.txt	Tue Nov 06 16:40:54 2018 +0300
+++ b/qmlfrontend/CMakeLists.txt	Tue Nov 06 17:00:35 2018 +0100
@@ -8,6 +8,13 @@
 
 find_package(Qt5 COMPONENTS Core Quick REQUIRED)
 
-add_executable(${PROJECT_NAME} "main.cpp" "qml.qrc")
+add_executable(${PROJECT_NAME} "main.cpp" "qml.qrc"
+    "hwengine.cpp" "hwengine.h"
+    "gameconfig.cpp" "gameconfig.h"
+    "runqueue.cpp" "runqueue.h"
+    "gameview.cpp" "gameview.h"
+    "team.cpp" "team.h"
+    "previewimageprovider.cpp" "previewimageprovider.h"
+    "flib.h")
 
 target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick)
--- a/qmlfrontend/Page1.qml	Tue Nov 06 16:40:54 2018 +0300
+++ b/qmlfrontend/Page1.qml	Tue Nov 06 17:00:35 2018 +0100
@@ -1,7 +1,24 @@
 import QtQuick 2.7
+import Hedgewars.Engine 1.0
 
 Page1Form {
+  tickButton.onClicked: {
+    item1.tick(100)
+  }
+  gameButton.onClicked: {
+    HWEngine.runQuickGame()
+  }
   button1.onClicked: {
-    console.log("Button Pressed. Entered text: " + textField1.text);
+    HWEngine.getPreview()
+  }
+
+  Connections {
+    target: HWEngine
+    onPreviewImageChanged: {
+      previewImage.source = "image://preview/image"
+    }
+    onPreviewIsRendering: {
+      previewImage.source = "qrc:/res/iconTime.png"
+    }
   }
 }
--- a/qmlfrontend/Page1Form.ui.qml	Tue Nov 06 16:40:54 2018 +0300
+++ b/qmlfrontend/Page1Form.ui.qml	Tue Nov 06 17:00:35 2018 +0100
@@ -2,23 +2,53 @@
 import QtQuick.Controls 2.0
 import QtQuick.Layouts 1.3
 
+import Hedgewars.Engine 1.0
+
 Item {
-    property alias textField1: textField1
     property alias button1: button1
+    property alias previewImage: previewImage
+    property alias gameButton: gameButton
+    width: 1024
+    height: 800
+    property alias tickButton: tickButton
+    property alias item1: item1
 
     RowLayout {
         anchors.horizontalCenter: parent.horizontalCenter
         anchors.topMargin: 20
         anchors.top: parent.top
 
-        TextField {
-            id: textField1
-            placeholderText: qsTr("Text Field")
+        Button {
+            id: button1
+            text: qsTr("Preview")
+        }
+
+        Button {
+            id: gameButton
+            text: qsTr("Game")
         }
 
         Button {
-            id: button1
-            text: qsTr("Press Me")
+            id: tickButton
+            text: qsTr("Tick")
         }
     }
+
+    Image {
+        id: previewImage
+        x: 8
+        y: 20
+        width: 256
+        height: 128
+        source: "qrc:/res/iconTime.png"
+        cache: false
+    }
+
+    GameView {
+        id: item1
+        x: 8
+        y: 154
+        width: 1008
+        height: 638
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/flib.h	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,47 @@
+#ifndef FLIB_H
+#define FLIB_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum MessageType {
+  MSG_PREVIEW,
+  MSG_PREVIEWHOGCOUNT,
+  MSG_TONET,
+  MSG_GAMEFINISHED,
+};
+
+typedef union string255_ {
+  struct {
+    unsigned char s[256];
+  };
+  struct {
+    unsigned char len;
+    unsigned char str[255];
+  };
+} string255;
+
+typedef void RunEngine_t(int argc, const char** argv);
+typedef void GameTick_t(uint32_t time_delta);
+typedef void ResizeWindow_t(uint32_t width, uint32_t height);
+typedef void ipcToEngineRaw_t(const char* msg, uint32_t len);
+typedef void ipcSetEngineBarrier_t();
+typedef void ipcRemoveBarrierFromEngineQueue_t();
+typedef bool updateMousePosition_t(int32_t centerX, int32_t centerY, int32_t x,
+                                   int32_t y);
+
+typedef void registerUIMessagesCallback_t(
+    void* context,
+    void (*)(void* context, MessageType mt, const char* msg, uint32_t len));
+typedef void flibInit_t(const char* localPrefix, const char* userPrefix);
+typedef void flibFree_t();
+typedef void passFlibEvent_t(const char* data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // FLIB_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/gameconfig.cpp	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,81 @@
+#include "gameconfig.h"
+
+GameConfig::GameConfig() : m_isPreview(true) { setPreview(m_isPreview); }
+
+const char** GameConfig::argv() const {
+  m_argv.resize(m_arguments.size());
+
+  for (int i = 0; i < m_arguments.size(); ++i)
+    m_argv[i] = m_arguments[i].data();
+
+  return m_argv.data();
+}
+
+int GameConfig::argc() const { return m_arguments.size(); }
+
+const QList<QByteArray> GameConfig::config() const {
+  QList<QByteArray> cfg = m_cfg;
+  cfg.append("\x01!");
+  return cfg;
+}
+
+void GameConfig::clear() {
+  m_arguments.clear();
+  m_cfg.clear();
+}
+
+void GameConfig::cmdSeed(const QByteArray& seed) { cfgAppend("eseed " + seed); }
+
+void GameConfig::cmdTheme(const QByteArray& theme) {
+  cfgAppend("e$theme " + theme);
+}
+
+void GameConfig::cmdMapgen(int mapgen) {
+  cfgAppend("e$mapgen " + QByteArray::number(mapgen));
+}
+
+void GameConfig::cmdTeam(const Team& team) {
+  cfgAppend("eaddteam <hash> " + team.color + " " + team.name);
+
+  for (const Hedgehog& h : team.hedgehogs()) {
+    cfgAppend("eaddhh " + QByteArray::number(h.level) + " " +
+              QByteArray::number(h.hp) + " " + h.name);
+    cfgAppend("ehat " + h.hat);
+  }
+  cfgAppend(
+      "eammloadt 9391929422199121032235111001200000000211100101011111000102");
+  cfgAppend(
+      "eammprob 0405040541600655546554464776576666666155510101115411111114");
+  cfgAppend(
+      "eammdelay 0000000000000205500000040007004000000000220000000600020000");
+  cfgAppend(
+      "eammreinf 1311110312111111123114111111111111111211111111111111111111");
+  cfgAppend("eammstore");
+}
+
+bool GameConfig::isPreview() const { return m_isPreview; }
+
+void GameConfig::setPreview(bool isPreview) {
+  m_isPreview = isPreview;
+
+  m_arguments.clear();
+
+  if (m_isPreview) {
+    m_arguments << ""
+                << "--internal"
+                << "--landpreview";
+
+  } else {
+    m_arguments << ""
+                << "--internal"
+                << "--nomusic";
+  }
+}
+
+void GameConfig::cfgAppend(const QByteArray& cmd) {
+  Q_ASSERT(cmd.size() < 256);
+
+  quint8 len = cmd.size();
+  m_cfg.append(QByteArray::fromRawData(reinterpret_cast<const char*>(&len), 1) +
+               cmd);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/gameconfig.h	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,36 @@
+#ifndef GAMECONFIG_H
+#define GAMECONFIG_H
+
+#include <QList>
+#include <QVector>
+
+#include "team.h"
+
+class GameConfig {
+ public:
+  explicit GameConfig();
+
+  const char** argv() const;
+  int argc() const;
+  const QList<QByteArray> config() const;
+
+  void clear();
+  void cmdSeed(const QByteArray& seed);
+  void cmdTheme(const QByteArray& theme);
+  void cmdMapgen(int mapgen);
+  void cmdTeam(const Team& team);
+
+  bool isPreview() const;
+  void setPreview(bool isPreview);
+
+ private:
+  mutable QVector<const char*> m_argv;
+  QList<QByteArray> m_arguments;
+  QList<QByteArray> m_cfg;
+  QList<Team> m_teams;
+  bool m_isPreview;
+
+  void cfgAppend(const QByteArray& cmd);
+};
+
+#endif  // GAMECONFIG_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/gameview.cpp	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,87 @@
+#include "gameview.h"
+
+#include <QtQuick/qquickwindow.h>
+#include <QCursor>
+#include <QTimer>
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QOpenGLShaderProgram>
+
+#include "flib.h"
+
+extern "C" {
+extern GameTick_t* flibGameTick;
+extern ResizeWindow_t* flibResizeWindow;
+extern updateMousePosition_t* flibUpdateMousePosition;
+}
+
+GameView::GameView() : m_delta(0), m_renderer(nullptr), m_windowChanged(true) {
+  connect(this, &QQuickItem::windowChanged, this,
+          &GameView::handleWindowChanged);
+}
+
+void GameView::tick(quint32 delta) {
+  m_delta = delta;
+
+  if (window()) {
+    QTimer* timer = new QTimer(this);
+    connect(timer, &QTimer::timeout, window(), &QQuickWindow::update);
+    timer->start(100);
+
+    // window()->update();
+  }
+}
+
+void GameView::handleWindowChanged(QQuickWindow* win) {
+  if (win) {
+    connect(win, &QQuickWindow::beforeSynchronizing, this, &GameView::sync,
+            Qt::DirectConnection);
+    connect(win, &QQuickWindow::sceneGraphInvalidated, this, &GameView::cleanup,
+            Qt::DirectConnection);
+
+    win->setClearBeforeRendering(false);
+
+    m_windowChanged = true;
+  }
+}
+
+void GameView::cleanup() {
+  if (m_renderer) {
+    delete m_renderer;
+    m_renderer = 0;
+  }
+}
+
+void GameView::sync() {
+  if (!m_renderer) {
+    m_renderer = new GameViewRenderer();
+    connect(window(), &QQuickWindow::beforeRendering, m_renderer,
+            &GameViewRenderer::paint, Qt::DirectConnection);
+  }
+
+  if (m_windowChanged) {
+    QSize windowSize = window()->size();
+    m_renderer->setViewportSize(windowSize * window()->devicePixelRatio());
+    m_centerX = windowSize.width() / 2;
+    m_centerY = windowSize.height() / 2;
+  }
+
+  QPoint mousePos = mapFromGlobal(QCursor::pos()).toPoint();
+  if (flibUpdateMousePosition(m_centerX, m_centerY, mousePos.x(), mousePos.y()))
+    QCursor::setPos(mapToGlobal(QPointF(m_centerX, m_centerY)).toPoint());
+
+  m_renderer->tick(m_delta);
+}
+
+GameViewRenderer::~GameViewRenderer() {}
+
+void GameViewRenderer::setViewportSize(const QSize& size) {
+  flibResizeWindow(size.width(), size.height());
+}
+
+void GameViewRenderer::paint() {
+  if (m_delta == 0) return;
+
+  flibGameTick(m_delta);
+
+  // m_window->resetOpenGLState();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/gameview.h	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,51 @@
+#ifndef GAMEVIEW_H
+#define GAMEVIEW_H
+
+#include <QQuickItem>
+
+#include <QtGui/QOpenGLFunctions>
+#include <QtGui/QOpenGLShaderProgram>
+
+class GameViewRenderer : public QObject, protected QOpenGLFunctions {
+  Q_OBJECT
+ public:
+  GameViewRenderer() : m_delta(0) {}
+  ~GameViewRenderer();
+
+  void tick(quint32 delta) { m_delta = delta; }
+  void setViewportSize(const QSize& size);
+
+ public slots:
+  void paint();
+
+ private:
+  quint32 m_delta;
+};
+
+class GameView : public QQuickItem {
+  Q_OBJECT
+
+ public:
+  GameView();
+
+  Q_INVOKABLE void tick(quint32 delta);
+
+ signals:
+  void tChanged();
+
+ public slots:
+  void sync();
+  void cleanup();
+
+ private slots:
+  void handleWindowChanged(QQuickWindow* win);
+
+ private:
+  quint32 m_delta;
+  GameViewRenderer* m_renderer;
+  bool m_windowChanged;
+  qint32 m_centerX;
+  qint32 m_centerY;
+};
+
+#endif  // GAMEVIEW_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/hwengine.cpp	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,146 @@
+#include "hwengine.h"
+
+#include <QDebug>
+#include <QLibrary>
+#include <QQmlEngine>
+#include <QUuid>
+
+#include "gameview.h"
+#include "previewimageprovider.h"
+#include "runqueue.h"
+
+extern "C" {
+RunEngine_t* flibRunEngine;
+GameTick_t* flibGameTick;
+ResizeWindow_t* flibResizeWindow;
+updateMousePosition_t* flibUpdateMousePosition;
+ipcToEngineRaw_t* flibIpcToEngineRaw;
+ipcSetEngineBarrier_t* flibIpcSetEngineBarrier;
+ipcRemoveBarrierFromEngineQueue_t* flibIpcRemoveBarrierFromEngineQueue;
+registerUIMessagesCallback_t* flibRegisterUIMessagesCallback;
+flibInit_t* flibInit;
+flibFree_t* flibFree;
+passFlibEvent_t* flibPassFlibEvent;
+}
+
+HWEngine::HWEngine(QQmlEngine* engine, QObject* parent)
+    : QObject(parent),
+      m_engine(engine),
+      m_previewProvider(new PreviewImageProvider()),
+      m_runQueue(new RunQueue(this)) {
+  qRegisterMetaType<MessageType>("MessageType");
+
+#ifdef Q_OS_WIN
+  QLibrary hwlib("./libhwengine.dll");
+#else
+  QLibrary hwlib("./libhwengine.so");
+#endif
+
+  if (!hwlib.load())
+    qWarning() << "Engine library not found" << hwlib.errorString();
+
+  flibRunEngine = (RunEngine_t*)hwlib.resolve("RunEngine");
+  flibGameTick = (GameTick_t*)hwlib.resolve("GameTick");
+  flibResizeWindow = (ResizeWindow_t*)hwlib.resolve("ResizeWindow");
+  flibUpdateMousePosition =
+      (updateMousePosition_t*)hwlib.resolve("updateMousePosition");
+  flibIpcToEngineRaw = (ipcToEngineRaw_t*)hwlib.resolve("ipcToEngineRaw");
+  flibIpcSetEngineBarrier =
+      (ipcSetEngineBarrier_t*)hwlib.resolve("ipcSetEngineBarrier");
+  flibIpcRemoveBarrierFromEngineQueue =
+      (ipcRemoveBarrierFromEngineQueue_t*)hwlib.resolve(
+          "ipcRemoveBarrierFromEngineQueue");
+  flibRegisterUIMessagesCallback = (registerUIMessagesCallback_t*)hwlib.resolve(
+      "registerUIMessagesCallback");
+  flibInit = (flibInit_t*)hwlib.resolve("flibInit");
+  flibFree = (flibFree_t*)hwlib.resolve("flibFree");
+
+  flibInit("/usr/home/unC0Rr/Sources/Hedgewars/MainRepo/share/hedgewars/Data",
+           "/usr/home/unC0Rr/.hedgewars");
+  flibRegisterUIMessagesCallback(this, &guiMessagesCallback);
+
+  m_engine->addImageProvider(QLatin1String("preview"), m_previewProvider);
+
+  connect(m_runQueue, &RunQueue::previewIsRendering, this,
+          &HWEngine::previewIsRendering);
+  connect(this, &HWEngine::gameFinished, m_runQueue, &RunQueue::onGameFinished);
+}
+
+HWEngine::~HWEngine() { flibFree(); }
+
+static QObject* hwengine_singletontype_provider(QQmlEngine* engine,
+                                                QJSEngine* scriptEngine) {
+  Q_UNUSED(scriptEngine)
+
+  HWEngine* hwengine = new HWEngine(engine);
+  return hwengine;
+}
+
+void HWEngine::exposeToQML() {
+  qDebug("HWEngine::exposeToQML");
+  qmlRegisterSingletonType<HWEngine>("Hedgewars.Engine", 1, 0, "HWEngine",
+                                     hwengine_singletontype_provider);
+  qmlRegisterType<GameView>("Hedgewars.Engine", 1, 0, "GameView");
+}
+
+void HWEngine::guiMessagesCallback(void* context, MessageType mt,
+                                   const char* msg, uint32_t len) {
+  HWEngine* obj = reinterpret_cast<HWEngine*>(context);
+  QByteArray b = QByteArray(msg, len);
+
+  qDebug() << "FLIPC in" << mt << " size = " << b.size();
+
+  QMetaObject::invokeMethod(obj, "engineMessageHandler", Qt::QueuedConnection,
+                            Q_ARG(MessageType, mt), Q_ARG(QByteArray, b));
+}
+
+void HWEngine::engineMessageHandler(MessageType mt, const QByteArray& msg) {
+  switch (mt) {
+    case MSG_PREVIEW: {
+      qDebug("MSG_PREVIEW");
+      m_previewProvider->setPixmap(msg);
+      emit previewImageChanged();
+      break;
+    }
+    case MSG_PREVIEWHOGCOUNT: {
+      qDebug("MSG_PREVIEWHOGCOUNT");
+      m_previewHedgehogsCount = static_cast<quint8>(msg.data()[0]);
+      emit previewHogCountChanged(m_previewHedgehogsCount);
+      break;
+    }
+    case MSG_TONET: {
+      qDebug("MSG_TONET");
+      break;
+    }
+    case MSG_GAMEFINISHED: {
+      qDebug("MSG_GAMEFINISHED");
+      emit gameFinished();
+      break;
+    }
+  }
+}
+
+void HWEngine::getPreview() {
+  m_seed = QUuid::createUuid().toByteArray();
+  m_gameConfig.cmdSeed(m_seed);
+  m_gameConfig.setPreview(true);
+
+  m_runQueue->queue(m_gameConfig);
+}
+
+void HWEngine::runQuickGame() {
+  m_gameConfig.cmdSeed(m_seed);
+  m_gameConfig.cmdTheme("Nature");
+  Team team1;
+  team1.name = "team1";
+  Team team2;
+  team2.name = "team2";
+  team2.color = "7654321";
+  m_gameConfig.cmdTeam(team1);
+  m_gameConfig.cmdTeam(team2);
+  m_gameConfig.setPreview(false);
+
+  m_runQueue->queue(m_gameConfig);
+}
+
+int HWEngine::previewHedgehogsCount() const { return m_previewHedgehogsCount; }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/hwengine.h	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,53 @@
+#ifndef HWENGINE_H
+#define HWENGINE_H
+
+#include <QList>
+#include <QObject>
+
+#include "flib.h"
+#include "gameconfig.h"
+
+class QQmlEngine;
+class PreviewImageProvider;
+class RunQueue;
+
+class HWEngine : public QObject {
+  Q_OBJECT
+
+  Q_PROPERTY(int previewHedgehogsCount READ previewHedgehogsCount NOTIFY
+                 previewHedgehogsCountChanged)
+
+ public:
+  explicit HWEngine(QQmlEngine* engine, QObject* parent = nullptr);
+  ~HWEngine();
+
+  static void exposeToQML();
+
+  Q_INVOKABLE void getPreview();
+  Q_INVOKABLE void runQuickGame();
+
+  int previewHedgehogsCount() const;
+
+ signals:
+  void previewIsRendering();
+  void previewImageChanged();
+  void previewHogCountChanged(int count);
+  void gameFinished();
+  void previewHedgehogsCountChanged(int previewHedgehogsCount);
+
+ private:
+  QQmlEngine* m_engine;
+  PreviewImageProvider* m_previewProvider;
+  RunQueue* m_runQueue;
+  GameConfig m_gameConfig;
+  QByteArray m_seed;
+  int m_previewHedgehogsCount;
+
+  static void guiMessagesCallback(void* context, MessageType mt,
+                                  const char* msg, uint32_t len);
+
+ private slots:
+  void engineMessageHandler(MessageType mt, const QByteArray& msg);
+};
+
+#endif  // HWENGINE_H
--- a/qmlfrontend/main.cpp	Tue Nov 06 16:40:54 2018 +0300
+++ b/qmlfrontend/main.cpp	Tue Nov 06 17:00:35 2018 +0100
@@ -1,15 +1,18 @@
 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 
-int main(int argc, char *argv[])
-{
+#include "hwengine.h"
+
+int main(int argc, char* argv[]) {
   QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
   QGuiApplication app(argc, argv);
 
   QQmlApplicationEngine engine;
+
+  HWEngine::exposeToQML();
+
   engine.load(QUrl(QLatin1String("qrc:/main.qml")));
-  if (engine.rootObjects().isEmpty())
-    return -1;
+  if (engine.rootObjects().isEmpty()) return -1;
 
   return app.exec();
 }
--- a/qmlfrontend/main.qml	Tue Nov 06 16:40:54 2018 +0300
+++ b/qmlfrontend/main.qml	Tue Nov 06 17:00:35 2018 +0100
@@ -11,27 +11,8 @@
   SwipeView {
     id: swipeView
     anchors.fill: parent
-    currentIndex: tabBar.currentIndex
 
     Page1 {
     }
-
-    Page {
-      Label {
-        text: qsTr("Second page")
-        anchors.centerIn: parent
-      }
-    }
-  }
-
-  footer: TabBar {
-    id: tabBar
-    currentIndex: swipeView.currentIndex
-    TabButton {
-      text: qsTr("First")
-    }
-    TabButton {
-      text: qsTr("Second")
-    }
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/previewimageprovider.cpp	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,31 @@
+#include "previewimageprovider.h"
+
+PreviewImageProvider::PreviewImageProvider()
+    : QQuickImageProvider(QQuickImageProvider::Pixmap) {}
+
+QPixmap PreviewImageProvider::requestPixmap(const QString &id, QSize *size,
+                                            const QSize &requestedSize) {
+  Q_UNUSED(id);
+  Q_UNUSED(requestedSize);
+
+  if (size) *size = m_px.size();
+
+  return m_px;
+}
+
+void PreviewImageProvider::setPixmap(const QByteArray &px) {
+  QVector<QRgb> colorTable;
+  colorTable.resize(256);
+  for (int i = 0; i < 256; ++i) colorTable[i] = qRgba(255, 255, 0, i);
+
+  const quint8 *buf = (const quint8 *)px.constData();
+  QImage im(buf, 256, 128, QImage::Format_Indexed8);
+  im.setColorTable(colorTable);
+
+  m_px = QPixmap::fromImage(im, Qt::ColorOnly);
+  // QPixmap pxres(px.size());
+  // QPainter p(&pxres);
+
+  // p.fillRect(pxres.rect(), linearGrad);
+  // p.drawPixmap(0, 0, px);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/previewimageprovider.h	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,21 @@
+#ifndef PREVIEWIMAGEPROVIDER_H
+#define PREVIEWIMAGEPROVIDER_H
+
+#include <QPixmap>
+#include <QQuickImageProvider>
+#include <QSize>
+
+class PreviewImageProvider : public QQuickImageProvider {
+ public:
+  PreviewImageProvider();
+
+  QPixmap requestPixmap(const QString &id, QSize *size,
+                        const QSize &requestedSize);
+
+  void setPixmap(const QByteArray &px);
+
+ private:
+  QPixmap m_px;
+};
+
+#endif  // PREVIEWIMAGEPROVIDER_H
--- a/qmlfrontend/qml.qrc	Tue Nov 06 16:40:54 2018 +0300
+++ b/qmlfrontend/qml.qrc	Tue Nov 06 17:00:35 2018 +0100
@@ -1,9 +1,9 @@
-<!DOCTYPE RCC>
-<RCC version="1.0">
+<RCC>
     <qresource prefix="/">
         <file>main.qml</file>
         <file>Page1.qml</file>
         <file>Page1Form.ui.qml</file>
         <file>qtquickcontrols2.conf</file>
+        <file>res/iconTime.png</file>
     </qresource>
 </RCC>
Binary file qmlfrontend/res/iconTime.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/runqueue.cpp	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,47 @@
+#include "runqueue.h"
+
+#include "flib.h"
+
+extern "C" {
+extern RunEngine_t* flibRunEngine;
+extern ipcToEngineRaw_t* flibIpcToEngineRaw;
+extern ipcSetEngineBarrier_t* flibIpcSetEngineBarrier;
+extern ipcRemoveBarrierFromEngineQueue_t* flibIpcRemoveBarrierFromEngineQueue;
+}
+
+RunQueue::RunQueue(QObject* parent)
+    : QObject(parent)
+{
+}
+
+void RunQueue::queue(const GameConfig& config)
+{
+    m_runQueue.prepend(config);
+
+    flibIpcSetEngineBarrier();
+    for (const QByteArray& b : m_runQueue.last().config()) {
+        flibIpcToEngineRaw(b.data(), b.size());
+    }
+
+    if (m_runQueue.size() == 1)
+        nextRun();
+}
+
+void RunQueue::onGameFinished()
+{
+    m_runQueue.pop_front();
+
+    nextRun();
+}
+
+void RunQueue::nextRun()
+{
+    if (!m_runQueue.isEmpty()) {
+        if (m_runQueue[0].isPreview())
+            emit previewIsRendering();
+
+        flibIpcRemoveBarrierFromEngineQueue();
+
+        flibRunEngine(m_runQueue[0].argc(), m_runQueue[0].argv());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/runqueue.h	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,27 @@
+#ifndef RUNQUEUE_H
+#define RUNQUEUE_H
+
+#include <QObject>
+
+#include "gameconfig.h"
+
+class RunQueue : public QObject {
+    Q_OBJECT
+public:
+    explicit RunQueue(QObject* parent = nullptr);
+
+    void queue(const GameConfig& config);
+
+signals:
+    void previewIsRendering();
+
+public slots:
+    void onGameFinished();
+
+private:
+    QList<GameConfig> m_runQueue;
+
+    void nextRun();
+};
+
+#endif // RUNQUEUE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/team.cpp	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,27 @@
+#include "team.h"
+
+Hedgehog::Hedgehog()
+    : name(QObject::tr("unnamed", "default hedgehog name").toUtf8())
+    , hat("NoHat")
+    , hp(100)
+    , level(0)
+{
+}
+
+Team::Team()
+    : name(QObject::tr("unnamed", "default team name").toUtf8())
+    , color("12345678")
+    , m_hedgehogsNumber(4)
+{
+    m_hedgehogs.resize(8);
+}
+
+void Team::resize(int number)
+{
+    m_hedgehogsNumber = number;
+}
+
+QVector<Hedgehog> Team::hedgehogs() const
+{
+    return m_hedgehogs.mid(0, m_hedgehogsNumber);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/team.h	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,31 @@
+#ifndef TEAM_H
+#define TEAM_H
+
+#include <QObject>
+#include <QVector>
+
+struct Hedgehog {
+    Hedgehog();
+
+    QByteArray name;
+    QByteArray hat;
+    quint32 hp;
+    int level;
+};
+
+class Team {
+public:
+    explicit Team();
+
+    void resize(int number);
+    QVector<Hedgehog> hedgehogs() const;
+
+    QByteArray name;
+    QByteArray color;
+
+private:
+    QVector<Hedgehog> m_hedgehogs;
+    int m_hedgehogsNumber;
+};
+
+#endif // TEAM_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-engine/Cargo.toml	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,7 @@
+[package]
+name = "hedgewars-engine"
+version = "0.1.0"
+authors = ["Andrey Korotaev <a.korotaev@hedgewars.org>"]
+
+[dependencies]
+land2d = { path = "../land2d" }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-engine/src/lib.rs	Tue Nov 06 17:00:35 2018 +0100
@@ -0,0 +1,18 @@
+#[repr(C)]
+pub struct Preview {
+    width: u32,
+    height: u32,
+    hedgehogs_number: u8,
+    land: *const u8,
+}
+
+
+#[no_mangle]
+pub extern "C" fn protocol_version() -> u32 {
+    56
+}
+
+#[no_mangle]
+pub extern "C" fn generate_preview () -> Preview  {
+    unimplemented!()
+}