reworked min/avg/max fps report in timedemo, now works again
[divverent/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 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         sprintf (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_ReadDemoMessage
125
126 Handles playback of demos
127 ====================
128 */
129 void CL_ReadDemoMessage(void)
130 {
131         int r, i;
132         float f;
133
134         if (!cls.demoplayback)
135                 return;
136
137         // LordHavoc: pausedemo
138         if (cls.demopaused)
139                 return;
140
141         for (;;)
142         {
143                 // decide if it is time to grab the next message
144                 // always grab until fully connected
145                 if (cls.signon == SIGNONS)
146                 {
147                         if (cls.timedemo)
148                         {
149                                 cls.td_frames++;
150                                 cls.td_onesecondframes++;
151                                 // if this is the first official frame we can now grab the real
152                                 // td_starttime so the bogus time on the first frame doesn't
153                                 // count against the final report
154                                 if (cls.td_frames == 0)
155                                 {
156                                         cls.td_starttime = realtime;
157                                         cls.td_onesecondnexttime = cl.time + 1;
158                                         cls.td_onesecondrealtime = realtime;
159                                         cls.td_onesecondframes = 0;
160                                         cls.td_onesecondminfps = 0;
161                                         cls.td_onesecondmaxfps = 0;
162                                         cls.td_onesecondavgfps = 0;
163                                         cls.td_onesecondavgcount = 0;
164                                 }
165                                 if (cl.time >= cls.td_onesecondnexttime)
166                                 {
167                                         double fps = cls.td_onesecondframes / (realtime - cls.td_onesecondrealtime);
168                                         if (cls.td_onesecondavgcount == 0)
169                                         {
170                                                 cls.td_onesecondminfps = fps;
171                                                 cls.td_onesecondmaxfps = fps;
172                                         }
173                                         cls.td_onesecondrealtime = realtime;
174                                         cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
175                                         cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
176                                         cls.td_onesecondavgfps += fps;
177                                         cls.td_onesecondavgcount++;
178                                         cls.td_onesecondframes = 0;
179                                         cls.td_onesecondnexttime++;
180                                 }
181                         }
182                         else if (cl.time <= cl.mtime[0])
183                         {
184                                 // don't need another message yet
185                                 return;
186                         }
187                 }
188
189                 // get the next message
190                 FS_Read(cls.demofile, &net_message.cursize, 4);
191                 net_message.cursize = LittleLong(net_message.cursize);
192                 if(net_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
193                 {
194                         // skip over demo packet
195                         FS_Seek(cls.demofile, 12 + (net_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
196                         continue;
197                 }
198                 if (net_message.cursize > net_message.maxsize)
199                         Host_Error("Demo message (%i) > net_message.maxsize (%i)", net_message.cursize, net_message.maxsize);
200                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
201                 for (i = 0;i < 3;i++)
202                 {
203                         r = (int)FS_Read(cls.demofile, &f, 4);
204                         cl.mviewangles[0][i] = LittleFloat(f);
205                 }
206
207                 if (FS_Read(cls.demofile, net_message.data, net_message.cursize) == net_message.cursize)
208                 {
209                         MSG_BeginReading();
210                         CL_ParseServerMessage();
211
212                         // In case the demo contains a "svc_disconnect" message
213                         if (!cls.demoplayback)
214                                 return;
215
216                         if (cls.timedemo)
217                                 return;
218                 }
219                 else
220                 {
221                         CL_Disconnect();
222                         return;
223                 }
224         }
225 }
226
227
228 /*
229 ====================
230 CL_Stop_f
231
232 stop recording a demo
233 ====================
234 */
235 void CL_Stop_f (void)
236 {
237         sizebuf_t buf;
238         unsigned char bufdata[64];
239
240         if (!cls.demorecording)
241         {
242                 Con_Print("Not recording a demo.\n");
243                 return;
244         }
245
246 // write a disconnect message to the demo file
247         // LordHavoc: don't replace the net_message when doing this
248         buf.data = bufdata;
249         buf.maxsize = sizeof(bufdata);
250         SZ_Clear(&buf);
251         MSG_WriteByte(&buf, svc_disconnect);
252         CL_WriteDemoMessage(&buf);
253
254 // finish up
255         FS_Close (cls.demofile);
256         cls.demofile = NULL;
257         cls.demorecording = false;
258         Con_Print("Completed demo\n");
259 }
260
261 /*
262 ====================
263 CL_Record_f
264
265 record <demoname> <map> [cd track]
266 ====================
267 */
268 void CL_Record_f (void)
269 {
270         int c, track;
271         char name[MAX_OSPATH];
272
273         c = Cmd_Argc();
274         if (c != 2 && c != 3 && c != 4)
275         {
276                 Con_Print("record <demoname> [<map> [cd track]]\n");
277                 return;
278         }
279
280         if (strstr(Cmd_Argv(1), ".."))
281         {
282                 Con_Print("Relative pathnames are not allowed.\n");
283                 return;
284         }
285
286         if (c == 2 && cls.state == ca_connected)
287         {
288                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
289                 return;
290         }
291
292         if (cls.state == ca_connected)
293                 CL_Disconnect();
294
295         // write the forced cd track number, or -1
296         if (c == 4)
297         {
298                 track = atoi(Cmd_Argv(3));
299                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
300         }
301         else
302                 track = -1;
303
304         // get the demo name
305         strlcpy (name, Cmd_Argv(1), sizeof (name));
306         FS_DefaultExtension (name, ".dem", sizeof (name));
307
308         // start the map up
309         if (c > 2)
310                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
311
312         // open the demo file
313         Con_Printf("recording to %s.\n", name);
314         cls.demofile = FS_Open (name, "wb", false, false);
315         if (!cls.demofile)
316         {
317                 Con_Print("ERROR: couldn't open.\n");
318                 return;
319         }
320
321         cls.forcetrack = track;
322         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
323
324         cls.demorecording = true;
325 }
326
327
328 /*
329 ====================
330 CL_PlayDemo_f
331
332 play [demoname]
333 ====================
334 */
335 void CL_PlayDemo_f (void)
336 {
337         char    name[MAX_QPATH];
338         int c;
339         qboolean neg = false;
340
341         if (Cmd_Argc() != 2)
342         {
343                 Con_Print("play <demoname> : plays a demo\n");
344                 return;
345         }
346
347         // disconnect from server
348         CL_Disconnect ();
349         Host_ShutdownServer ();
350
351         // update networking ports (this is mainly just needed at startup)
352         NetConn_UpdateSockets();
353
354         // open the demo file
355         strlcpy (name, Cmd_Argv(1), sizeof (name));
356         FS_DefaultExtension (name, ".dem", sizeof (name));
357         cls.protocol = PROTOCOL_QUAKE;
358
359         Con_Printf("Playing demo %s.\n", name);
360         cls.demofile = FS_Open (name, "rb", false, false);
361         if (!cls.demofile)
362         {
363                 Con_Print("ERROR: couldn't open.\n");
364                 cls.demonum = -1;               // stop demo loop
365                 return;
366         }
367
368         strlcpy(cls.demoname, name, sizeof(cls.demoname));
369         cls.demoplayback = true;
370         cls.state = ca_connected;
371         cls.forcetrack = 0;
372
373         while ((c = FS_Getc (cls.demofile)) != '\n')
374                 if (c == '-')
375                         neg = true;
376                 else
377                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
378
379         if (neg)
380                 cls.forcetrack = -cls.forcetrack;
381 }
382
383 /*
384 ====================
385 CL_FinishTimeDemo
386
387 ====================
388 */
389 void CL_FinishTimeDemo (void)
390 {
391         int frames;
392         double time, totalfpsavg;
393         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
394
395         cls.timedemo = false;
396
397         frames = cls.td_frames;
398         time = realtime - cls.td_starttime;
399         totalfpsavg = time > 0 ? frames / time : 0;
400         fpsmin = cls.td_onesecondminfps;
401         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
402         fpsmax = cls.td_onesecondmaxfps;
403         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
404         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);
405         Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | 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, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
406         if (COM_CheckParm("-benchmark"))
407                 Host_Quit_f();
408 }
409
410 /*
411 ====================
412 CL_TimeDemo_f
413
414 timedemo [demoname]
415 ====================
416 */
417 void CL_TimeDemo_f (void)
418 {
419         if (Cmd_Argc() != 2)
420         {
421                 Con_Print("timedemo <demoname> : gets demo speeds\n");
422                 return;
423         }
424
425         srand(0); // predictable random sequence for benchmarking
426
427         CL_PlayDemo_f ();
428
429 // cls.td_starttime will be grabbed at the second frame of the demo, so
430 // all the loading time doesn't get counted
431
432         // instantly hide console and deactivate it
433         key_dest = key_game;
434         key_consoleactive = 0;
435         scr_con_current = 0;
436
437         cls.timedemo = true;
438         cls.td_frames = -2;             // skip the first frame
439         cls.demonum = -1;               // stop demo loop
440         cls.demonum = -1;               // stop demo loop
441 }
442