]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_demo.c
major overhaul for thread-safety - many global variables and static
[xonotic/darkplaces.git] / cl_demo.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 extern cvar_t cl_capturevideo;
24 int old_vsync = 0;
25
26 static void CL_FinishTimeDemo (void);
27
28 /*
29 ==============================================================================
30
31 DEMO CODE
32
33 When a demo is playing back, all outgoing network messages are skipped, and
34 incoming messages are read from the demo file.
35
36 Whenever cl.time gets past the last received message, another message is
37 read from the demo file.
38 ==============================================================================
39 */
40
41 /*
42 =====================
43 CL_NextDemo
44
45 Called to play the next demo in the demo loop
46 =====================
47 */
48 void CL_NextDemo (void)
49 {
50         char    str[MAX_INPUTLINE];
51
52         if (cls.demonum == -1)
53                 return;         // don't play demos
54
55         if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
56         {
57                 cls.demonum = 0;
58                 if (!cls.demos[cls.demonum][0])
59                 {
60                         Con_Print("No demos listed with startdemos\n");
61                         cls.demonum = -1;
62                         return;
63                 }
64         }
65
66         dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
67         Cbuf_InsertText (str);
68         cls.demonum++;
69 }
70
71 /*
72 ==============
73 CL_StopPlayback
74
75 Called when a demo file runs out, or the user starts a game
76 ==============
77 */
78 // LordHavoc: now called only by CL_Disconnect
79 void CL_StopPlayback (void)
80 {
81         if (!cls.demoplayback)
82                 return;
83
84         FS_Close (cls.demofile);
85         cls.demoplayback = false;
86         cls.demofile = NULL;
87
88         if (cls.timedemo)
89                 CL_FinishTimeDemo ();
90
91         if (COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
92                 Host_Quit_f();
93
94 }
95
96 /*
97 ====================
98 CL_WriteDemoMessage
99
100 Dumps the current net message, prefixed by the length and view angles
101 ====================
102 */
103 void CL_WriteDemoMessage (sizebuf_t *message)
104 {
105         int             len;
106         int             i;
107         float   f;
108
109         if (cls.demopaused) // LordHavoc: pausedemo
110                 return;
111
112         len = LittleLong (message->cursize);
113         FS_Write (cls.demofile, &len, 4);
114         for (i=0 ; i<3 ; i++)
115         {
116                 f = LittleFloat (cl.viewangles[i]);
117                 FS_Write (cls.demofile, &f, 4);
118         }
119         FS_Write (cls.demofile, message->data, message->cursize);
120 }
121
122 /*
123 ====================
124 CL_CutDemo
125
126 Dumps the current demo to a buffer, and resets the demo to its starting point.
127 Used to insert csprogs.dat files as a download to the beginning of a demo file.
128 ====================
129 */
130 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
131 {
132         *buf = NULL;
133         *filesize = 0;
134
135         FS_Close(cls.demofile);
136         *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
137
138         // restart the demo recording
139         cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
140         if(!cls.demofile)
141                 Sys_Error("failed to reopen the demo file");
142         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
143 }
144
145 /*
146 ====================
147 CL_PasteDemo
148
149 Adds the cut stuff back to the demo. Also frees the buffer.
150 Used to insert csprogs.dat files as a download to the beginning of a demo file.
151 ====================
152 */
153 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
154 {
155         fs_offset_t startoffset = 0;
156
157         if(!*buf)
158                 return;
159
160         // skip cdtrack
161         while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
162                 ++startoffset;
163         if(startoffset < *filesize)
164                 ++startoffset;
165
166         FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
167
168         Mem_Free(*buf);
169         *buf = NULL;
170         *filesize = 0;
171 }
172
173 /*
174 ====================
175 CL_ReadDemoMessage
176
177 Handles playback of demos
178 ====================
179 */
180 void CL_ReadDemoMessage(void)
181 {
182         int i;
183         float f;
184
185         if (!cls.demoplayback)
186                 return;
187
188         // LordHavoc: pausedemo
189         if (cls.demopaused)
190                 return;
191
192         for (;;)
193         {
194                 // decide if it is time to grab the next message
195                 // always grab until fully connected
196                 if (cls.signon == SIGNONS)
197                 {
198                         if (cls.timedemo)
199                         {
200                                 cls.td_frames++;
201                                 cls.td_onesecondframes++;
202                                 // if this is the first official frame we can now grab the real
203                                 // td_starttime so the bogus time on the first frame doesn't
204                                 // count against the final report
205                                 if (cls.td_frames == 0)
206                                 {
207                                         cls.td_starttime = realtime;
208                                         cls.td_onesecondnexttime = cl.time + 1;
209                                         cls.td_onesecondrealtime = realtime;
210                                         cls.td_onesecondframes = 0;
211                                         cls.td_onesecondminfps = 0;
212                                         cls.td_onesecondmaxfps = 0;
213                                         cls.td_onesecondavgfps = 0;
214                                         cls.td_onesecondavgcount = 0;
215                                 }
216                                 if (cl.time >= cls.td_onesecondnexttime)
217                                 {
218                                         double fps = cls.td_onesecondframes / (realtime - cls.td_onesecondrealtime);
219                                         if (cls.td_onesecondavgcount == 0)
220                                         {
221                                                 cls.td_onesecondminfps = fps;
222                                                 cls.td_onesecondmaxfps = fps;
223                                         }
224                                         cls.td_onesecondrealtime = realtime;
225                                         cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
226                                         cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
227                                         cls.td_onesecondavgfps += fps;
228                                         cls.td_onesecondavgcount++;
229                                         cls.td_onesecondframes = 0;
230                                         cls.td_onesecondnexttime++;
231                                 }
232                         }
233                         else if (cl.time <= cl.mtime[0])
234                         {
235                                 // don't need another message yet
236                                 return;
237                         }
238                 }
239
240                 // get the next message
241                 FS_Read(cls.demofile, &cl_message.cursize, 4);
242                 cl_message.cursize = LittleLong(cl_message.cursize);
243                 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
244                 {
245                         // skip over demo packet
246                         FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
247                         continue;
248                 }
249                 if (cl_message.cursize > cl_message.maxsize)
250                 {
251                         Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
252                         cl_message.cursize = 0;
253                         CL_Disconnect();
254                         return;
255                 }
256                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
257                 for (i = 0;i < 3;i++)
258                 {
259                         FS_Read(cls.demofile, &f, 4);
260                         cl.mviewangles[0][i] = LittleFloat(f);
261                 }
262
263                 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
264                 {
265                         MSG_BeginReading(&cl_message);
266                         CL_ParseServerMessage();
267
268                         if (cls.signon != SIGNONS)
269                                 Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect!
270
271                         // In case the demo contains a "svc_disconnect" message
272                         if (!cls.demoplayback)
273                                 return;
274
275                         if (cls.timedemo)
276                                 return;
277                 }
278                 else
279                 {
280                         CL_Disconnect();
281                         return;
282                 }
283         }
284 }
285
286
287 /*
288 ====================
289 CL_Stop_f
290
291 stop recording a demo
292 ====================
293 */
294 void CL_Stop_f (void)
295 {
296         sizebuf_t buf;
297         unsigned char bufdata[64];
298
299         if (!cls.demorecording)
300         {
301                 Con_Print("Not recording a demo.\n");
302                 return;
303         }
304
305 // write a disconnect message to the demo file
306         // LordHavoc: don't replace the cl_message when doing this
307         buf.data = bufdata;
308         buf.maxsize = sizeof(bufdata);
309         SZ_Clear(&buf);
310         MSG_WriteByte(&buf, svc_disconnect);
311         CL_WriteDemoMessage(&buf);
312
313 // finish up
314         if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
315         {
316                 FS_RemoveOnClose(cls.demofile);
317                 Con_Print("Completed and deleted demo\n");
318         }
319         else
320                 Con_Print("Completed demo\n");
321         FS_Close (cls.demofile);
322         cls.demofile = NULL;
323         cls.demorecording = false;
324 }
325
326 /*
327 ====================
328 CL_Record_f
329
330 record <demoname> <map> [cd track]
331 ====================
332 */
333 void CL_Record_f (void)
334 {
335         int c, track;
336         char name[MAX_OSPATH];
337         char vabuf[1024];
338
339         c = Cmd_Argc();
340         if (c != 2 && c != 3 && c != 4)
341         {
342                 Con_Print("record <demoname> [<map> [cd track]]\n");
343                 return;
344         }
345
346         if (strstr(Cmd_Argv(1), ".."))
347         {
348                 Con_Print("Relative pathnames are not allowed.\n");
349                 return;
350         }
351
352         if (c == 2 && cls.state == ca_connected)
353         {
354                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
355                 return;
356         }
357
358         if (cls.state == ca_connected)
359                 CL_Disconnect();
360
361         // write the forced cd track number, or -1
362         if (c == 4)
363         {
364                 track = atoi(Cmd_Argv(3));
365                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
366         }
367         else
368                 track = -1;
369
370         // get the demo name
371         strlcpy (name, Cmd_Argv(1), sizeof (name));
372         FS_DefaultExtension (name, ".dem", sizeof (name));
373
374         // start the map up
375         if (c > 2)
376                 Cmd_ExecuteString ( va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(2)), src_command, false);
377
378         // open the demo file
379         Con_Printf("recording to %s.\n", name);
380         cls.demofile = FS_OpenRealFile(name, "wb", false);
381         if (!cls.demofile)
382         {
383                 Con_Print("ERROR: couldn't open.\n");
384                 return;
385         }
386         strlcpy(cls.demoname, name, sizeof(cls.demoname));
387
388         cls.forcetrack = track;
389         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
390
391         cls.demorecording = true;
392         cls.demo_lastcsprogssize = -1;
393         cls.demo_lastcsprogscrc = -1;
394 }
395
396
397 /*
398 ====================
399 CL_PlayDemo_f
400
401 play [demoname]
402 ====================
403 */
404 void CL_PlayDemo_f (void)
405 {
406         char    name[MAX_QPATH];
407         int c;
408         qboolean neg = false;
409
410         if (Cmd_Argc() != 2)
411         {
412                 Con_Print("play <demoname> : plays a demo\n");
413                 return;
414         }
415
416         // disconnect from server
417         CL_Disconnect ();
418         Host_ShutdownServer ();
419
420         // update networking ports (this is mainly just needed at startup)
421         NetConn_UpdateSockets();
422
423         // open the demo file
424         strlcpy (name, Cmd_Argv(1), sizeof (name));
425         FS_DefaultExtension (name, ".dem", sizeof (name));
426         cls.protocol = PROTOCOL_QUAKE;
427
428         Con_Printf("Playing demo %s.\n", name);
429         cls.demofile = FS_OpenVirtualFile(name, false);
430         if (!cls.demofile)
431         {
432                 Con_Print("ERROR: couldn't open.\n");
433                 cls.demonum = -1;               // stop demo loop
434                 return;
435         }
436         strlcpy(cls.demoname, name, sizeof(cls.demoname));
437
438         cls.demoplayback = true;
439         cls.state = ca_connected;
440         cls.forcetrack = 0;
441
442         while ((c = FS_Getc (cls.demofile)) != '\n')
443                 if (c == '-')
444                         neg = true;
445                 else
446                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
447
448         if (neg)
449                 cls.forcetrack = -cls.forcetrack;
450 }
451
452 /*
453 ====================
454 CL_FinishTimeDemo
455
456 ====================
457 */
458 static void CL_FinishTimeDemo (void)
459 {
460         int frames;
461         int i;
462         double time, totalfpsavg;
463         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
464         static int benchmark_runs = 0;
465         char vabuf[1024];
466
467         cls.timedemo = false;
468
469         frames = cls.td_frames;
470         time = realtime - cls.td_starttime;
471         totalfpsavg = time > 0 ? frames / time : 0;
472         fpsmin = cls.td_onesecondminfps;
473         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
474         fpsmax = cls.td_onesecondmaxfps;
475         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
476         Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
477         Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | run %d | result %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), buildstring, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
478         if (COM_CheckParm("-benchmark"))
479         {
480                 ++benchmark_runs;
481                 i = COM_CheckParm("-benchmarkruns");
482                 if(i && i + 1 < com_argc)
483                 {
484                         if(atoi(com_argv[i + 1]) > benchmark_runs)
485                         {
486                                 // restart the benchmark
487                                 Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
488                                 // cannot execute here
489                         }
490                         else
491                                 Host_Quit_f();
492                 }
493                 else
494                         Host_Quit_f();
495         }
496 }
497
498 /*
499 ====================
500 CL_TimeDemo_f
501
502 timedemo [demoname]
503 ====================
504 */
505 void CL_TimeDemo_f (void)
506 {
507         if (Cmd_Argc() != 2)
508         {
509                 Con_Print("timedemo <demoname> : gets demo speeds\n");
510                 return;
511         }
512
513         srand(0); // predictable random sequence for benchmarking
514
515         CL_PlayDemo_f ();
516
517 // cls.td_starttime will be grabbed at the second frame of the demo, so
518 // all the loading time doesn't get counted
519
520         // instantly hide console and deactivate it
521         key_dest = key_game;
522         key_consoleactive = 0;
523         scr_con_current = 0;
524
525         cls.timedemo = true;
526         cls.td_frames = -2;             // skip the first frame
527         cls.demonum = -1;               // stop demo loop
528         cls.demonum = -1;               // stop demo loop
529 }
530