more db stuff
[carveJwlIkooP6JGAAIwe30JlM.git] / highscores.h
1 #ifndef HIGHSCORES_H
2 #define HIGHSCORES_H
3
4 #include "vg/vg_store.h"
5 #include "vg/vg_stdint.h"
6
7 /*
8 * Designed to be used across client and server,
9 * for the client its only storing the local users records, for server its
10 * storing many.
11 */
12
13 typedef struct highscore highscore;
14 typedef struct highscore_record highscore_record;
15 typedef struct highscore_track_table highscore_track_table;
16 typedef struct highscore_database highscore_database;
17 typedef struct highscore_playerinfo highscore_playerinfo;
18
19 #pragma pack(push,1)
20
21 struct highscore_playerinfo
22 {
23 char nickname[16];
24 u64 playerid;
25
26 union
27 {
28 aatree_pool_node aapn;
29 aatree_node aa_playerid;
30 };
31 };
32
33 struct highscore_record
34 {
35 u16 trackid, points, time, reserved0;
36 u64 playerid;
37 u32 datetime;
38 u32 reserved1;
39
40 union
41 {
42 struct
43 {
44 aatree_node points,
45 time,
46 playerid,
47 datetime;
48 }
49 aa;
50
51 aatree_pool_node pool;
52 };
53 };
54
55 struct highscore_track_table
56 {
57 aatree_ptr root_points,
58 root_time,
59 root_playerid,
60 root_datetime;
61
62 u32 reserved[12];
63 };
64
65 struct highscore_database
66 {
67 highscore_track_table tracks[ 128 ];
68
69 aatree_ptr pool_head, playerinfo_head;
70 u32 entry_capacity,
71 playerinfo_capacity, playerinfo_root;
72
73 u32 reserved[59];
74 };
75
76 #pragma pack(pop)
77
78 static struct highscore_system
79 {
80 highscore_database dbheader;
81 aatree aainfo,
82 aainfo_points,
83 aainfo_time,
84 aainfo_playerid,
85 aainfo_datetime,
86 aainfo_playerinfo_playerid,
87 aainfo_playerinfo;
88
89 void *data, *playerinfo_data;
90 }
91 highscore_system;
92
93 static int highscore_cmp_points( void *a, void *b )
94 {
95 highscore_record *pa = a, *pb = b;
96 return (int)pa->points - (int)pb->points;
97 }
98
99 static int highscore_cmp_datetime( void *a, void *b )
100 {
101 highscore_record *pa = a, *pb = b;
102
103 if( pa->datetime == pb->datetime ) return 0;
104 return pa->datetime < pb->datetime? 1: -1;
105 }
106
107 static int highscore_cmp_time( void *a, void *b )
108 {
109 highscore_record *pa = a, *pb = b;
110 return (int)pb->time - (int)pa->time;
111 }
112
113 static int highscore_cmp_playerid( void *a, void *b )
114 {
115 highscore_record *pa = a, *pb = b;
116 if( pa->playerid == pb->playerid ) return 0;
117 return pa->playerid < pb->playerid? -1: 1;
118 }
119
120 static int highscore_cmp_playerinfo_playerid( void *a, void *b )
121 {
122 highscore_playerinfo *pa = a, *pb = b;
123 if( pa->playerid == pb->playerid ) return 0;
124 return pa->playerid < pb->playerid? -1: 1;
125 }
126
127 static void *highscore_malloc( u32 count, u32 size )
128 {
129 size_t requested_mem = size * count;
130 void *data = malloc( requested_mem );
131
132 requested_mem /= 1024;
133 requested_mem /= 1024;
134
135 if( !data )
136 {
137 vg_error( "Could not allocated %dmb of memory\n", requested_mem );
138 return NULL;
139 }
140 else
141 vg_success( "Allocated %dmb for %u records\n", requested_mem, count );
142
143 return data;
144 }
145
146 static void highscores_free(void)
147 {
148 free( highscore_system.data );
149 free( highscore_system.playerinfo_data );
150 }
151
152 static int highscores_init( u32 pool_size, u32 playerinfo_pool_size )
153 {
154 struct highscore_system *sys = &highscore_system;
155
156 sys->data = highscore_malloc( pool_size, sizeof(highscore_record) );
157 if( !sys->data ) return 0;
158
159 sys->playerinfo_data =
160 highscore_malloc( playerinfo_pool_size, sizeof(highscore_playerinfo));
161 if( !sys->playerinfo_data )
162 {
163 free( sys->data );
164 return 0;
165 }
166
167 /* This is ugly.. too bad! */
168 sys->aainfo.base = highscore_system.data;
169 sys->aainfo.stride = sizeof(highscore_record);
170 sys->aainfo.offset = offsetof(highscore_record,pool);
171 sys->aainfo.p_cmp = NULL;
172
173 sys->aainfo_datetime.base = highscore_system.data;
174 sys->aainfo_datetime.stride = sizeof(highscore_record);
175 sys->aainfo_datetime.offset = offsetof(highscore_record,aa.datetime);
176 sys->aainfo_datetime.p_cmp = highscore_cmp_datetime;
177
178 sys->aainfo_points.base = highscore_system.data;
179 sys->aainfo_points.stride = sizeof(highscore_record);
180 sys->aainfo_points.offset = offsetof(highscore_record,aa.points);
181 sys->aainfo_points.p_cmp = highscore_cmp_points;
182
183 sys->aainfo_time.base = highscore_system.data;
184 sys->aainfo_time.stride = sizeof(highscore_record);
185 sys->aainfo_time.offset = offsetof(highscore_record,aa.time);
186 sys->aainfo_time.p_cmp = highscore_cmp_time;
187
188 sys->aainfo_playerid.base = highscore_system.data;
189 sys->aainfo_playerid.stride = sizeof(highscore_record);
190 sys->aainfo_playerid.offset = offsetof(highscore_record,aa.playerid);
191 sys->aainfo_playerid.p_cmp = highscore_cmp_playerid;
192
193 sys->aainfo_playerinfo_playerid.base = highscore_system.playerinfo_data;
194 sys->aainfo_playerinfo_playerid.stride = sizeof(highscore_playerinfo);
195 sys->aainfo_playerinfo_playerid.offset =
196 offsetof(highscore_playerinfo,aa_playerid);
197 sys->aainfo_playerinfo_playerid.p_cmp = highscore_cmp_playerinfo_playerid;
198
199 sys->aainfo_playerinfo.base = highscore_system.playerinfo_data;
200 sys->aainfo_playerinfo.stride = sizeof(highscore_playerinfo);
201 sys->aainfo_playerinfo.offset = offsetof(highscore_playerinfo,aapn);
202 sys->aainfo_playerinfo.p_cmp = NULL;
203
204 FILE *fp = fopen( ".aadb", "rb" );
205 if( fp )
206 {
207 vg_info( "Loading existing database\n" );
208
209 u64 count = fread( &sys->dbheader, sizeof(highscore_database), 1, fp );
210
211 if( count != 1 )
212 {
213 vg_error( "Unexpected EOF reading database header\n" );
214
215 highscores_free();
216 return 0;
217 }
218
219 count = fread( sys->data, sizeof(highscore_record), pool_size, fp );
220 if( count != pool_size )
221 {
222 vg_error( "Unexpected EOF reading database contents;"
223 " %lu records of %u were read\n", count, pool_size );
224
225 highscores_free();
226 return 0;
227 }
228
229 count = fread( sys->playerinfo_data, sizeof(highscore_playerinfo),
230 playerinfo_pool_size, fp );
231 if( count != playerinfo_pool_size )
232 {
233 vg_error( "Unexpected EOF reading playerinfo contents;"
234 " %lu records of %u were read\n", count,
235 playerinfo_pool_size );
236
237 highscores_free();
238 return 0;
239 }
240
241 fclose( fp );
242 }
243 else
244 {
245 vg_log( "No existing database found (.aadb)\n" );
246 vg_info( "Initializing database nodes\n" );
247 memset( &sys->dbheader, 0, sizeof(highscore_database) );
248
249 sys->dbheader.pool_head = aatree_init_pool( &sys->aainfo, pool_size );
250 sys->dbheader.entry_capacity = pool_size;
251
252 for( int i=0; i<vg_list_size(sys->dbheader.tracks); i++ )
253 {
254 highscore_track_table *table = &sys->dbheader.tracks[i];
255 table->root_points = AATREE_PTR_NIL;
256 table->root_playerid = AATREE_PTR_NIL;
257 table->root_time = AATREE_PTR_NIL;
258 table->root_datetime = AATREE_PTR_NIL;
259 }
260
261 /* Initialize secondary db */
262 sys->dbheader.playerinfo_head = aatree_init_pool(
263 &sys->aainfo_playerinfo,
264 playerinfo_pool_size );
265 sys->dbheader.playerinfo_capacity = playerinfo_pool_size;
266 sys->dbheader.playerinfo_root = AATREE_PTR_NIL;
267 }
268
269 return 1;
270 }
271
272 static int highscores_serialize_all(void)
273 {
274 struct highscore_system *sys = &highscore_system;
275 vg_info( "Serializing database\n" );
276
277 FILE *fp = fopen( ".aadb", "wb" );
278
279 if( !fp )
280 {
281 vg_error( "Could not open .aadb\n" );
282 return 0;
283 }
284
285 fwrite( &sys->dbheader, sizeof(highscore_database), 1, fp );
286 fwrite( sys->data, sizeof(highscore_record),
287 sys->dbheader.entry_capacity, fp );
288 fwrite( sys->playerinfo_data, sizeof(highscore_playerinfo),
289 sys->dbheader.playerinfo_capacity, fp );
290
291 fclose( fp );
292 return 1;
293 }
294
295 static aatree_ptr highscores_push_record( highscore_record *record )
296 {
297 struct highscore_system *sys = &highscore_system;
298
299 /* TODO: Verify steam ID */
300 vg_log( "Inserting record into database for track %hu\n",record->trackid );
301
302 if( record->trackid >= vg_list_size(sys->dbheader.tracks) )
303 {
304 vg_error( "TrackID out of range (%hu>=%d)\n", record->trackid,
305 vg_list_size(sys->dbheader.tracks) );
306
307 return AATREE_PTR_NIL;
308 }
309
310 /* Search for existing record on this track */
311 highscore_track_table *table = &sys->dbheader.tracks[record->trackid];
312 aatree_ptr existing = aatree_find( &sys->aainfo_playerid,
313 table->root_playerid,
314 record );
315
316 if( existing != AATREE_PTR_NIL )
317 {
318 highscore_record *crecord = aatree_get_data( &sys->aainfo_playerid,
319 existing );
320
321 if( crecord->time < record->time ||
322 (crecord->time == record->time && crecord->points > record->points))
323 {
324 vg_log( "Not overwriting better score\n" );
325 return existing;
326 }
327
328 vg_log( "Freeing existing record for player %lu\n", record->playerid );
329 table->root_playerid = aatree_del( &sys->aainfo_playerid, existing );
330 table->root_datetime = aatree_del( &sys->aainfo_datetime, existing );
331 table->root_points = aatree_del( &sys->aainfo_points, existing );
332 table->root_time = aatree_del( &sys->aainfo_time, existing );
333
334 aatree_pool_free( &sys->aainfo, existing, &sys->dbheader.pool_head );
335 }
336
337 aatree_ptr index =
338 aatree_pool_alloc( &sys->aainfo, &sys->dbheader.pool_head );
339
340 if( index == AATREE_PTR_NIL )
341 {
342 vg_error( "Database records are over capacity!\n" );
343 return index;
344 }
345
346 highscore_record *dst = aatree_get_data( &sys->aainfo, index );
347 memset( dst, 0, sizeof(highscore_record) );
348
349 dst->trackid = record->trackid;
350 dst->datetime = record->datetime;
351 dst->playerid = record->playerid;
352 dst->points = record->points;
353 dst->time = record->time;
354
355 table->root_time =
356 aatree_insert( &sys->aainfo_time, table->root_time, index );
357 table->root_datetime =
358 aatree_insert( &sys->aainfo_datetime, table->root_datetime, index );
359 table->root_playerid =
360 aatree_insert( &sys->aainfo_playerid, table->root_playerid, index );
361 table->root_points =
362 aatree_insert( &sys->aainfo_points, table->root_points, index );
363
364 return index;
365 }
366
367 static aatree_ptr highscore_set_user_nickname( u64 steamid, char nick[16] )
368 {
369 vg_log( "Updating %lu's nickname\n", steamid );
370
371 struct highscore_system *sys = &highscore_system;
372
373 highscore_playerinfo temp;
374 temp.playerid = steamid;
375
376 aatree_ptr record = aatree_find( &sys->aainfo_playerinfo_playerid,
377 sys->dbheader.playerinfo_root,
378 &temp );
379 highscore_playerinfo *info;
380
381 if( record != AATREE_PTR_NIL )
382 {
383 info = aatree_get_data( &sys->aainfo_playerinfo, record );
384 }
385 else
386 {
387 record = aatree_pool_alloc( &sys->aainfo_playerinfo,
388 &sys->dbheader.playerinfo_head );
389
390 if( record == AATREE_PTR_NIL )
391 {
392 vg_error( "Player info database is over capacity!\n" );
393 return AATREE_PTR_NIL;
394 }
395
396 info = aatree_get_data( &sys->aainfo_playerinfo, record );
397 memset( info, 0, sizeof(highscore_playerinfo) );
398
399 info->playerid = steamid;
400 sys->dbheader.playerinfo_root = aatree_insert(
401 &sys->aainfo_playerinfo_playerid,
402 sys->dbheader.playerinfo_root,
403 record );
404 }
405
406 for( int i=0; i<16; i++ )
407 info->nickname[i] = nick[i];
408
409 return AATREE_PTR_NIL;
410 }
411
412 static void _highscore_showtime( void *data )
413 {
414 highscore_record *record = data;
415 printf( "%hu", record->time );
416 }
417
418 static void _highscore_showname( void *data )
419 {
420 char namebuf[17];
421 namebuf[16] = '\0';
422
423 highscore_playerinfo *info = data;
424 for( int i=0; i<16; i++ )
425 namebuf[i] = info->nickname[i];
426
427 printf( " %lu %s", info->playerid, namebuf );
428 }
429
430 static void highscores_print_track( u32 trackid, u32 count )
431 {
432 struct highscore_system *sys = &highscore_system;
433
434 highscore_track_table *table = &sys->dbheader.tracks[ trackid ];
435 aatree_ptr it = aatree_kth( &sys->aainfo_time, table->root_time, 0 );
436
437 vg_info( "Highscores: top %u fastest records for track %u\n", count, trackid );
438 vg_info( "================================================\n" );
439 vg_info( "%3s| %16s | %5s | %5s | %s\n", "#", "Player", "Time", "Score",
440 "TrackID" );
441 vg_info( "================================================\n" );
442 int i=0;
443 while( it != AATREE_PTR_NIL && i < 10 )
444 {
445 highscore_record *record = aatree_get_data( &sys->aainfo_time, it );
446
447 highscore_playerinfo temp;
448 temp.playerid = record->playerid;
449
450 aatree_ptr info_ptr = aatree_find( &sys->aainfo_playerinfo_playerid,
451 sys->dbheader.playerinfo_root,
452 &temp );
453
454 char namebuf[17];
455 if( info_ptr == AATREE_PTR_NIL )
456 snprintf( namebuf, 16, "[%lu]", record->playerid );
457 else
458 {
459 highscore_playerinfo *inf = aatree_get_data(
460 &sys->aainfo_playerinfo_playerid, info_ptr );
461
462 for( int i=0; i<16; i++ )
463 namebuf[i] = inf->nickname[i];
464 namebuf[16] = '\0';
465 }
466
467 vg_info( "%3d| %16s %5hu %5hu %3hu\n",
468 i+1, namebuf, record->time, record->points,
469 record->trackid );
470
471 i++;
472 it = aatree_next( &sys->aainfo_time, it );
473 }
474
475 vg_info( "================================================\n" );
476 }
477
478 #endif /* HIGHSCORES_H */