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