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