make timedemo and -benchmark use srand(0) to get more predictable results
[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 int old_vsync = 0;
24
25 void CL_FinishTimeDemo (void);
26
27 /*
28 ==============================================================================
29
30 DEMO CODE
31
32 When a demo is playing back, all outgoing network messages are skipped, and
33 incoming messages are read from the demo file.
34
35 Whenever cl.time gets past the last received message, another message is
36 read from the demo file.
37 ==============================================================================
38 */
39
40 /*
41 =====================
42 CL_NextDemo
43
44 Called to play the next demo in the demo loop
45 =====================
46 */
47 void CL_NextDemo (void)
48 {
49         char    str[MAX_INPUTLINE];
50
51         if (cls.demonum == -1)
52                 return;         // don't play demos
53
54         if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
55         {
56                 cls.demonum = 0;
57                 if (!cls.demos[cls.demonum][0])
58                 {
59                         Con_Print("No demos listed with startdemos\n");
60                         cls.demonum = -1;
61                         return;
62                 }
63         }
64
65         sprintf (str,"playdemo %s\n", cls.demos[cls.demonum]);
66         Cbuf_InsertText (str);
67         cls.demonum++;
68 }
69
70 /*
71 ==============
72 CL_StopPlayback
73
74 Called when a demo file runs out, or the user starts a game
75 ==============
76 */
77 // LordHavoc: now called only by CL_Disconnect
78 void CL_StopPlayback (void)
79 {
80         if (!cls.demoplayback)
81                 return;
82
83         FS_Close (cls.demofile);
84         cls.demoplayback = false;
85         cls.demofile = NULL;
86
87         if (cls.timedemo)
88                 CL_FinishTimeDemo ();
89
90         if (COM_CheckParm("-demo"))
91                 Host_Quit_f();
92
93 }
94
95 /*
96 ====================
97 CL_WriteDemoMessage
98
99 Dumps the current net message, prefixed by the length and view angles
100 ====================
101 */
102 void CL_WriteDemoMessage (sizebuf_t *message)
103 {
104         int             len;
105         int             i;
106         float   f;
107
108         if (cls.demopaused) // LordHavoc: pausedemo
109                 return;
110
111         len = LittleLong (message->cursize);
112         FS_Write (cls.demofile, &len, 4);
113         for (i=0 ; i<3 ; i++)
114         {
115                 f = LittleFloat (cl.viewangles[i]);
116                 FS_Write (cls.demofile, &f, 4);
117         }
118         FS_Write (cls.demofile, message->data, message->cursize);
119 }
120
121 /*
122 ====================
123 CL_ReadDemoMessage
124
125 Handles playback of demos
126 ====================
127 */
128 void CL_ReadDemoMessage(void)
129 {
130         int r, i;
131         float f;
132
133         if (!cls.demoplayback)
134                 return;
135
136         // LordHavoc: pausedemo
137         if (cls.demopaused)
138                 return;
139
140         while (1)
141         {
142                 // decide if it is time to grab the next message
143                 // always grab until fully connected
144                 if (cls.signon == SIGNONS)
145                 {
146                         if (cls.timedemo)
147                         {
148                                 if (host_framecount == cls.td_lastframe)
149                                 {
150                                         // already read this frame's message
151                                         return;
152                                 }
153                                 if (cls.td_lastframe == -1)
154                                 {
155                                         // we start counting on the second frame
156                                         // (after parsing connection stuff)
157                                         cls.td_startframe = host_framecount + 1;
158                                 }
159                                 cls.td_lastframe = host_framecount;
160                                 cls.td_onesecondframes++;
161                                 // if this is the first official frame we can now grab the real
162                                 // td_starttime so the bogus time on the first frame doesn't
163                                 // count against the final report
164                                 if (host_framecount == cls.td_startframe)
165                                 {
166                                         cls.td_starttime = realtime;
167                                         cls.td_onesecondnexttime = realtime + 1;
168                                         cls.td_onesecondframes = 0;
169                                         cls.td_onesecondminframes = 0;
170                                         cls.td_onesecondmaxframes = 0;
171                                         cls.td_onesecondavgframes = 0;
172                                         cls.td_onesecondavgcount = 0;
173                                 }
174                                 if (realtime >= cls.td_onesecondnexttime)
175                                 {
176                                         if (cls.td_onesecondavgcount == 0)
177                                         {
178                                                 cls.td_onesecondminframes = cls.td_onesecondframes;
179                                                 cls.td_onesecondmaxframes = cls.td_onesecondframes;
180                                         }
181                                         cls.td_onesecondminframes = min(cls.td_onesecondminframes, cls.td_onesecondframes);
182                                         cls.td_onesecondmaxframes = max(cls.td_onesecondmaxframes, cls.td_onesecondframes);
183                                         cls.td_onesecondavgframes += cls.td_onesecondframes;
184                                         cls.td_onesecondavgcount++;
185                                         cls.td_onesecondframes = 0;
186                                         cls.td_onesecondnexttime++;
187                                 }
188                         }
189                         else if (cl.time <= cl.mtime[0])
190                         {
191                                 // don't need another message yet
192                                 return;
193                         }
194                 }
195
196                 // get the next message
197                 FS_Read(cls.demofile, &net_message.cursize, 4);
198                 net_message.cursize = LittleLong(net_message.cursize);
199                 if (net_message.cursize > net_message.maxsize)
200                         Host_Error("Demo message (%i) > net_message.maxsize (%i)", net_message.cursize, net_message.maxsize);
201                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
202                 for (i = 0;i < 3;i++)
203                 {
204                         r = (int)FS_Read(cls.demofile, &f, 4);
205                         cl.mviewangles[0][i] = LittleFloat(f);
206                 }
207
208                 if (FS_Read(cls.demofile, net_message.data, net_message.cursize) == net_message.cursize)
209                 {
210                         MSG_BeginReading();
211                         CL_ParseServerMessage();
212
213                         // In case the demo contains a "svc_disconnect" message
214                         if (!cls.demoplayback)
215                                 return;
216                 }
217                 else
218                 {
219                         CL_Disconnect();
220                         return;
221                 }
222         }
223 }
224
225
226 /*
227 ====================
228 CL_Stop_f
229
230 stop recording a demo
231 ====================
232 */
233 void CL_Stop_f (void)
234 {
235         sizebuf_t buf;
236         unsigned char bufdata[64];
237
238         if (!cls.demorecording)
239         {
240                 Con_Print("Not recording a demo.\n");
241                 return;
242         }
243
244 // write a disconnect message to the demo file
245         // LordHavoc: don't replace the net_message when doing this
246         buf.data = bufdata;
247         buf.maxsize = sizeof(bufdata);
248         SZ_Clear(&buf);
249         MSG_WriteByte(&buf, svc_disconnect);
250         CL_WriteDemoMessage(&buf);
251
252 // finish up
253         FS_Close (cls.demofile);
254         cls.demofile = NULL;
255         cls.demorecording = false;
256         Con_Print("Completed demo\n");
257 }
258
259 /*
260 ====================
261 CL_Record_f
262
263 record <demoname> <map> [cd track]
264 ====================
265 */
266 void CL_Record_f (void)
267 {
268         int c, track;
269         char name[MAX_OSPATH];
270
271         c = Cmd_Argc();
272         if (c != 2 && c != 3 && c != 4)
273         {
274                 Con_Print("record <demoname> [<map> [cd track]]\n");
275                 return;
276         }
277
278         if (strstr(Cmd_Argv(1), ".."))
279         {
280                 Con_Print("Relative pathnames are not allowed.\n");
281                 return;
282         }
283
284         if (c == 2 && cls.state == ca_connected)
285         {
286                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
287                 return;
288         }
289
290         if (cls.state == ca_connected)
291                 CL_Disconnect();
292
293         // write the forced cd track number, or -1
294         if (c == 4)
295         {
296                 track = atoi(Cmd_Argv(3));
297                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
298         }
299         else
300                 track = -1;
301
302         // get the demo name
303         strlcpy (name, Cmd_Argv(1), sizeof (name));
304         FS_DefaultExtension (name, ".dem", sizeof (name));
305
306         // start the map up
307         if (c > 2)
308                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
309
310         // open the demo file
311         Con_Printf("recording to %s.\n", name);
312         cls.demofile = FS_Open (name, "wb", false, false);
313         if (!cls.demofile)
314         {
315                 Con_Print("ERROR: couldn't open.\n");
316                 return;
317         }
318
319         cls.forcetrack = track;
320         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
321
322         cls.demorecording = true;
323 }
324
325
326 /*
327 ====================
328 CL_PlayDemo_f
329
330 play [demoname]
331 ====================
332 */
333 void CL_PlayDemo_f (void)
334 {
335         char    name[MAX_QPATH];
336         int c;
337         qboolean neg = false;
338
339         if (Cmd_Argc() != 2)
340         {
341                 Con_Print("play <demoname> : plays a demo\n");
342                 return;
343         }
344
345         // disconnect from server
346         CL_Disconnect ();
347         Host_ShutdownServer ();
348
349         // update networking ports (this is mainly just needed at startup)
350         NetConn_UpdateSockets();
351
352         // open the demo file
353         strlcpy (name, Cmd_Argv(1), sizeof (name));
354         FS_DefaultExtension (name, ".dem", sizeof (name));
355         cls.protocol = PROTOCOL_QUAKE;
356
357         Con_Printf("Playing demo from %s.\n", name);
358         cls.demofile = FS_Open (name, "rb", false, false);
359         if (!cls.demofile)
360         {
361                 Con_Print("ERROR: couldn't open.\n");
362                 cls.demonum = -1;               // stop demo loop
363                 return;
364         }
365
366         strlcpy(cls.demoname, name, sizeof(cls.demoname));
367         cls.demoplayback = true;
368         cls.state = ca_connected;
369         cls.forcetrack = 0;
370
371         while ((c = FS_Getc (cls.demofile)) != '\n')
372                 if (c == '-')
373                         neg = true;
374                 else
375                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
376
377         if (neg)
378                 cls.forcetrack = -cls.forcetrack;
379 }
380
381 /*
382 ====================
383 CL_FinishTimeDemo
384
385 ====================
386 */
387 void CL_FinishTimeDemo (void)
388 {
389         int frames;
390         double time, totalfpsavg;
391         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
392
393         cls.timedemo = false;
394
395 // the first frame didn't count
396         frames = (host_framecount - cls.td_startframe) - 1;
397         time = realtime - cls.td_starttime;
398         totalfpsavg = time > 0 ? frames / time : 0;
399         fpsmin = cls.td_onesecondminframes;
400         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgframes / cls.td_onesecondavgcount : 0;
401         fpsmax = cls.td_onesecondmaxframes;
402         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
403         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);
404         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);
405         if (COM_CheckParm("-benchmark"))
406                 Host_Quit_f();
407 }
408
409 /*
410 ====================
411 CL_TimeDemo_f
412
413 timedemo [demoname]
414 ====================
415 */
416 void CL_TimeDemo_f (void)
417 {
418         if (Cmd_Argc() != 2)
419         {
420                 Con_Print("timedemo <demoname> : gets demo speeds\n");
421                 return;
422         }
423
424         srand(0); // predictable random sequence for benchmarking
425
426         CL_PlayDemo_f ();
427
428 // cls.td_starttime will be grabbed at the second frame of the demo, so
429 // all the loading time doesn't get counted
430
431         // instantly hide console and deactivate it
432         key_dest = key_game;
433         key_consoleactive = 0;
434         scr_con_current = 0;
435
436         cls.timedemo = true;
437         // get first message this frame
438         cls.td_lastframe = -1;
439 }
440