]> git.sesse.net Git - pkanalytics/blob - events.cpp
Add some defensive events.
[pkanalytics] / events.cpp
1 #include <algorithm>
2 #include <string>
3 #include <map>
4 #include <vector>
5 #include <optional>
6 #include <sqlite3.h>
7 #include "events.h"
8
9 using namespace std;
10
11 string format_timestamp(uint64_t pos);
12
13 EventsModel::EventsModel(sqlite3 *db) : db(db)
14 {
15         load_data();
16 }
17
18 QVariant EventsModel::headerData(int section, Qt::Orientation orientation, int role) const
19 {
20         if (role != Qt::DisplayRole) {
21                 return QVariant();
22         }
23         if (orientation == Qt::Horizontal) {
24                 if (section == 0) {
25                         return "Time";
26                 } else if (section == 1) {
27                         return "Player";
28                 } else {
29                         return "Type";
30                 }
31         } else {
32                 return "";
33         }
34 }
35
36 QVariant EventsModel::data(const QModelIndex &index, int role) const
37 {
38         if (role != Qt::DisplayRole) {
39                 return QVariant();
40         }
41         if (index.column() == 0) {
42                 return QString::fromUtf8(format_timestamp(events[index.row()].t));
43         } else if (index.column() == 1) {
44                 optional<int> player_id = events[index.row()].player_id;
45                 if (player_id) {
46                         auto p_it = players.find(*player_id);
47                         const Player &p = p_it->second;
48                         return QString::fromUtf8(p.name + " (" + p.number + ")");
49                 } else {
50                         return QVariant();
51                 }
52         } else if (index.column() == 2) {
53                 return QString::fromUtf8(events[index.row()].type);
54         }
55         return QVariant();
56 }
57
58 void EventsModel::load_data()
59 {
60         players.clear();
61         events.clear();
62
63         // Read the players.
64         sqlite3_stmt *stmt;
65         int ret = sqlite3_prepare_v2(db, "SELECT player, number, name FROM player", -1, &stmt, 0);
66         if (ret != SQLITE_OK) {
67                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
68                 abort();
69         }
70         for ( ;; ) {
71                 ret = sqlite3_step(stmt);
72                 if (ret == SQLITE_ROW) {
73                         Player p;
74                         p.player_id = sqlite3_column_int(stmt, 0);
75                         p.number = (const char *)sqlite3_column_text(stmt, 1);
76                         p.name = (const char *) sqlite3_column_text(stmt, 2);
77                         players[p.player_id] = std::move(p);
78                 } else if (ret == SQLITE_DONE) {
79                         break;
80                 } else {
81                         fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
82                         abort();
83                 }
84         }
85         ret = sqlite3_finalize(stmt);
86         if (ret != SQLITE_OK) {
87                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
88                 abort();
89         }
90
91         // Read the events.
92         ret = sqlite3_prepare_v2(db, "SELECT event, t, player, type FROM event ORDER BY t", -1, &stmt, 0);
93         if (ret != SQLITE_OK) {
94                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
95                 abort();
96         }
97         for ( ;; ) {
98                 ret = sqlite3_step(stmt);
99                 if (ret == SQLITE_ROW) {
100                         Event e;
101                         e.event_id = sqlite3_column_int(stmt, 0);
102                         e.t = sqlite3_column_int(stmt, 1);
103                         e.player_id = sqlite3_column_int(stmt, 2);
104                         e.type = (const char *)sqlite3_column_text(stmt, 3);
105                         events.push_back(std::move(e));
106                 } else if (ret == SQLITE_DONE) {
107                         break;
108                 } else {
109                         fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
110                         abort();
111                 }
112         }
113         ret = sqlite3_finalize(stmt);
114         if (ret != SQLITE_OK) {
115                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
116                 abort();
117         }
118 }
119
120 unsigned EventsModel::insert_event(uint64_t t, optional<int> player_id, const string &type)
121 {
122         auto it = lower_bound(events.begin(), events.end(), t,
123                 [](const Event &e, uint64_t t) { return e.t < t; });
124         unsigned pos = distance(events.begin(), it);
125         beginInsertRows(QModelIndex(), pos, pos);
126
127         Event e;
128         e.t = t;
129         e.player_id = player_id;
130         e.type = type;
131         events.insert(events.begin() + pos, e);
132
133         endInsertRows();
134
135         // Insert the new row into the database.
136         sqlite3_stmt *stmt;
137         int ret = sqlite3_prepare_v2(db, "INSERT INTO event (t, player, type) VALUES (?, ?, ?)", -1, &stmt, 0);
138         if (ret != SQLITE_OK) {
139                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
140                 abort();
141         }
142
143         sqlite3_bind_int64(stmt, 1, t);
144         if (player_id) {
145                 sqlite3_bind_int64(stmt, 2, *player_id);
146         } else {
147                 sqlite3_bind_null(stmt, 2);
148         }
149         sqlite3_bind_text(stmt, 3, type.data(), type.size(), SQLITE_STATIC);
150
151         ret = sqlite3_step(stmt);
152         if (ret == SQLITE_ROW) {
153                 fprintf(stderr, "INSERT step: %s\n", sqlite3_errmsg(db));
154                 abort();
155         }
156
157         ret = sqlite3_finalize(stmt);
158         if (ret != SQLITE_OK) {
159                 fprintf(stderr, "INSERT finalize: %s\n", sqlite3_errmsg(db));
160                 abort();
161         }
162
163         events[pos].event_id = sqlite3_last_insert_rowid(db);
164         return pos;
165 }
166
167 void EventsModel::delete_event(unsigned pos)
168 {
169         int event_id = events[pos].event_id;
170
171         beginRemoveRows(QModelIndex(), pos, pos);
172         events.erase(events.begin() + pos);
173         endRemoveRows();
174
175         // Delete the row from the database.
176         sqlite3_stmt *stmt;
177         int ret = sqlite3_prepare_v2(db, "DELETE FROM event WHERE event=?", -1, &stmt, 0);
178         if (ret != SQLITE_OK) {
179                 fprintf(stderr, "DELETE prepare: %s\n", sqlite3_errmsg(db));
180                 abort();
181         }
182
183         sqlite3_bind_int64(stmt, 1, event_id);
184
185         ret = sqlite3_step(stmt);
186         if (ret == SQLITE_ROW) {
187                 fprintf(stderr, "DELETE step: %s\n", sqlite3_errmsg(db));
188                 abort();
189         }
190
191         ret = sqlite3_finalize(stmt);
192         if (ret != SQLITE_OK) {
193                 fprintf(stderr, "DELETE finalize: %s\n", sqlite3_errmsg(db));
194                 abort();
195         }
196 }
197
198 void EventsModel::set_event_type(unsigned pos, const string &type)
199 {
200         events[pos].type = type;
201         emit dataChanged(createIndex(pos, 0), createIndex(pos, 2));
202
203         sqlite3_stmt *stmt;
204         int ret = sqlite3_prepare_v2(db, "UPDATE event SET type=? WHERE event=?", -1, &stmt, 0);
205         if (ret != SQLITE_OK) {
206                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
207                 abort();
208         }
209
210         sqlite3_bind_text(stmt, 1, type.data(), type.size(), SQLITE_STATIC);
211         sqlite3_bind_int64(stmt, 2, events[pos].event_id);
212
213         ret = sqlite3_step(stmt);
214         if (ret == SQLITE_ROW) {
215                 fprintf(stderr, "UPDATE step: %s\n", sqlite3_errmsg(db));
216                 abort();
217         }
218
219         ret = sqlite3_finalize(stmt);
220         if (ret != SQLITE_OK) {
221                 fprintf(stderr, "UPDATE finalize: %s\n", sqlite3_errmsg(db));
222                 abort();
223         }
224 }
225
226 EventsModel::Status EventsModel::get_status_at(uint64_t t)
227 {
228         Status s;
229         s.our_score = 0;
230         s.their_score = 0;
231         s.offense = true;
232         uint64_t last_gained_possession = 0;
233         unsigned num_touches = 0;
234         for (const Event &e : events) {
235                 if (e.t > t) {
236                         break;
237                 }
238                 if (e.type == "goal") {
239                         ++s.our_score;
240                         s.offense = false;
241                         num_touches = 0;
242                 }
243                 if (e.type == "their_goal") {
244                         ++s.their_score;
245                         s.offense = true;
246                         num_touches = 0;
247                 }
248                 if (e.type == "catch") {
249                         if (num_touches == 0) {  // Pick up.
250                                 last_gained_possession = e.t;
251                         }
252                         ++num_touches;
253                 }
254                 if (e.type == "interception") {
255                         num_touches = 1;
256                         s.offense = true;
257                         last_gained_possession = e.t;
258                 }
259         }
260         s.num_passes = (num_touches == 0) ? 0 : num_touches - 1;
261         s.possession_sec = (s.offense && last_gained_possession != 0) ? (t - last_gained_possession) / 1000 : 0;
262         return s;
263 }
264
265 set<int> EventsModel::get_team_at(uint64_t t)
266 {
267         set<int> team;
268         for (const Event &e : events) {
269                 if (e.t > t) {
270                         break;
271                 }
272                 if (e.type == "in") {
273                         team.insert(*e.player_id);
274                 }
275                 if (e.type == "out") {
276                         team.erase(*e.player_id);
277                 }
278         }
279         return team;
280 }