]> git.sesse.net Git - pkanalytics/blobdiff - stats.cpp
Begin showing events from the database. (No modification yet.)
[pkanalytics] / stats.cpp
index 1edee1161b2d022de75aa9f690a5aab6f54ede2e..a740c1df9d452edf7a85ac6b3b3913e68c89375f 100644 (file)
--- a/stats.cpp
+++ b/stats.cpp
@@ -5,10 +5,15 @@
 #include <QVideoWidget>
 #include <QShortcut>
 #include <string>
+#include <map>
+#include <vector>
+#include <optional>
 #include <sqlite3.h>
 #include "mainwindow.h"
 #include "ui_mainwindow.h"
 
+using namespace std;
+
 std::string format_timestamp(uint64_t pos)
 {
        int ms = pos % 1000;
@@ -23,6 +28,150 @@ std::string format_timestamp(uint64_t pos)
        return buf;
 }
 
+class EventsModel : public QAbstractTableModel
+{
+public:
+       EventsModel(sqlite3 *db) : db(db) {}
+
+       int rowCount(const QModelIndex &parent) const override
+       {
+               refresh_if_needed();
+               return events.size();
+       }
+       int columnCount(const QModelIndex &column) const override
+       {
+               return 3;
+       }
+       QVariant headerData(int section, Qt::Orientation orientation, int role) const override
+       {
+               if (role != Qt::DisplayRole) {
+                       return QVariant();
+               }
+               if (orientation == Qt::Horizontal) {
+                       if (section == 0) {
+                               return "Time";
+                       } else if (section == 1) {
+                               return "Player";
+                       } else {
+                               return "Type";
+                       }
+               } else {
+                       return "";
+               }
+       }
+
+       QVariant data(const QModelIndex &index, int role) const override
+       {
+               if (role != Qt::DisplayRole) {
+                       return QVariant();
+               }
+               refresh_if_needed();
+               if (index.column() == 0) {
+                       return QString::fromUtf8(format_timestamp(events[index.row()].t));
+               } else if (index.column() == 1) {
+                       optional<int> player_id = events[index.row()].player_id;
+                       if (player_id) {
+                               const Player &p = players[*player_id];
+                               return QString::fromUtf8(p.name + " (" + p.number + ")");
+                       } else {
+                               return QVariant();
+                       }
+               } else if (index.column() == 2) {
+                       return QString::fromUtf8(events[index.row()].type);
+               }
+               return QVariant();
+       }
+
+private:
+       struct Player {
+               int player_id;
+               string number;
+               string name;
+       };
+       mutable map<int, Player> players;
+
+       struct Event {
+               uint64_t t;
+               optional<int> player_id;
+               string type;
+       };
+       mutable vector<Event> events;
+       mutable bool stale = true;
+
+       sqlite3 *db;
+
+       void refresh_if_needed() const;
+};
+
+void EventsModel::refresh_if_needed() const
+{
+       if (!stale) {
+               return;
+       }
+
+       players.clear();
+       events.clear();
+       stale = false;
+
+       // Read the players.
+       sqlite3_stmt *stmt;
+       int ret = sqlite3_prepare_v2(db, "SELECT player, number, name FROM player", -1, &stmt, 0);
+       if (ret != SQLITE_OK) {
+               fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
+               abort();
+       }
+       for ( ;; ) {
+               ret = sqlite3_step(stmt);
+               if (ret == SQLITE_ROW) {
+                       Player p;
+                       p.player_id = sqlite3_column_int(stmt, 0);
+                       p.number = (const char *)sqlite3_column_text(stmt, 1);
+                       p.name = (const char *) sqlite3_column_text(stmt, 2);
+                       players[p.player_id] = move(p);
+               } else if (ret == SQLITE_DONE) {
+                       break;
+               } else {
+                       fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
+                       abort();
+               }
+       }
+       ret = sqlite3_finalize(stmt);
+       if (ret != SQLITE_OK) {
+               fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
+               abort();
+       }
+
+       // Read the events.
+       ret = sqlite3_prepare_v2(db, "SELECT t, player, type FROM event", -1, &stmt, 0);
+       if (ret != SQLITE_OK) {
+               fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
+               abort();
+       }
+       for ( ;; ) {
+               ret = sqlite3_step(stmt);
+               if (ret == SQLITE_ROW) {
+                       Event e;
+                       e.t = sqlite3_column_int(stmt, 0);
+                       e.player_id = sqlite3_column_int(stmt, 1);
+                       e.type = (const char *)sqlite3_column_text(stmt, 2);
+                       events.push_back(move(e));
+               } else if (ret == SQLITE_DONE) {
+                       break;
+               } else {
+                       fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
+                       abort();
+               }
+       }
+       ret = sqlite3_finalize(stmt);
+       if (ret != SQLITE_OK) {
+               fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
+               abort();
+       }
+
+       // TODO what if data changes externally?
+       //emit dataChanged(QModelIndex(
+}
+
 MainWindow::MainWindow()
 {
        player = new QMediaPlayer;
@@ -30,10 +179,10 @@ MainWindow::MainWindow()
        player->setSource(QUrl::fromLocalFile("/home/sesse/dev/stats/ultimate-prores.mkv"));
        player->play();
 
-       Ui::MainWindow *ui = new Ui::MainWindow;
+       ui = new Ui::MainWindow;
        ui->setupUi(this);
 
-       connect(player, &QMediaPlayer::positionChanged, [ui, this](uint64_t pos) {
+       connect(player, &QMediaPlayer::positionChanged, [this](uint64_t pos) {
                ui->timestamp->setText(QString::fromUtf8(format_timestamp(pos)));
                if (buffered_seek) {
                        player->setPosition(*buffered_seek);
@@ -77,6 +226,11 @@ MainWindow::MainWindow()
        });
 }
 
+void MainWindow::setModel(QAbstractItemModel *model)
+{
+       ui->event_view->setModel(model);
+}
+
 void MainWindow::seek(int64_t delta_ms)
 {
        int64_t current_pos = buffered_seek ? *buffered_seek : player->position();
@@ -88,11 +242,35 @@ void MainWindow::seek(int64_t delta_ms)
        }
 }
 
+sqlite3 *open_db(const char *filename)
+{
+       sqlite3 *db;
+       int ret = sqlite3_open(filename, &db);
+       if (ret != SQLITE_OK) {
+               fprintf(stderr, "%s: %s\n", filename, sqlite3_errmsg(db));
+               exit(1);
+       }
+
+       sqlite3_exec(db, R"(
+               CREATE TABLE IF NOT EXISTS player (player INTEGER PRIMARY KEY, number VARCHAR, name VARCHAR);
+       )", nullptr, nullptr, nullptr);  // Ignore errors.
+
+       sqlite3_exec(db, R"(
+               CREATE TABLE IF NOT EXISTS event (t INTEGER, player INTEGER, type VARCHAR, FOREIGN KEY (player) REFERENCES player(player));
+       )", nullptr, nullptr, nullptr);  // Ignore errors.
+
+       sqlite3_exec(db, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr);  // Ignore errors.
+       sqlite3_exec(db, "PRAGMA synchronous=NORMAL", nullptr, nullptr, nullptr);  // Ignore errors.
+       return db;
+}
+
 int main(int argc, char *argv[])
 {
        QApplication app(argc, argv);
+       sqlite3 *db = open_db("ultimate.db");
 
        MainWindow mainWindow;
+       mainWindow.setModel(new EventsModel(db));
        mainWindow.resize(QSize(1280, 720));
        mainWindow.show();