2 Copyright (C) 1996-1997 Id Software, Inc.
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.
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.
13 See the GNU General Public License for more details.
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.
23 extern cvar_t cl_capturevideo;
24 extern cvar_t cl_capturevideo_demo_stop;
27 static void CL_FinishTimeDemo (void);
30 ==============================================================================
34 When a demo is playing back, all outgoing network messages are skipped, and
35 incoming messages are read from the demo file.
37 Whenever cl.time gets past the last received message, another message is
38 read from the demo file.
39 ==============================================================================
46 Called to play the next demo in the demo loop
49 void CL_NextDemo (void)
51 char str[MAX_INPUTLINE];
53 if (cls.demonum == -1)
54 return; // don't play demos
56 if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
59 if (!cls.demos[cls.demonum][0])
61 Con_Print("No demos listed with startdemos\n");
67 dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
68 Cbuf_InsertText (str);
76 Called when a demo file runs out, or the user starts a game
79 // LordHavoc: now called only by CL_Disconnect
80 void CL_StopPlayback (void)
82 if (cl_capturevideo_demo_stop.integer)
83 Cvar_Set("cl_capturevideo", "0");
85 if (!cls.demoplayback)
88 FS_Close (cls.demofile);
89 cls.demoplayback = false;
95 if (!cls.demostarting) // only quit if not starting another demo
96 if (COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
105 Dumps the current net message, prefixed by the length and view angles
106 #====================
108 void CL_WriteDemoMessage (sizebuf_t *message)
114 if (cls.demopaused) // LordHavoc: pausedemo
117 len = LittleLong (message->cursize);
118 FS_Write (cls.demofile, &len, 4);
119 for (i=0 ; i<3 ; i++)
121 f = LittleFloat (cl.viewangles[i]);
122 FS_Write (cls.demofile, &f, 4);
124 FS_Write (cls.demofile, message->data, message->cursize);
131 Dumps the current demo to a buffer, and resets the demo to its starting point.
132 Used to insert csprogs.dat files as a download to the beginning of a demo file.
135 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
140 FS_Close(cls.demofile);
141 *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
143 // restart the demo recording
144 cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
146 Sys_Error("failed to reopen the demo file");
147 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
154 Adds the cut stuff back to the demo. Also frees the buffer.
155 Used to insert csprogs.dat files as a download to the beginning of a demo file.
158 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
160 fs_offset_t startoffset = 0;
166 while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
168 if(startoffset < *filesize)
171 FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
182 Handles playback of demos
185 void CL_ReadDemoMessage(void)
190 if (!cls.demoplayback)
193 // LordHavoc: pausedemo
199 // decide if it is time to grab the next message
200 // always grab until fully connected
201 if (cls.signon == SIGNONS)
206 cls.td_onesecondframes++;
207 // if this is the first official frame we can now grab the real
208 // td_starttime so the bogus time on the first frame doesn't
209 // count against the final report
210 if (cls.td_frames == 0)
212 cls.td_starttime = realtime;
213 cls.td_onesecondnexttime = cl.time + 1;
214 cls.td_onesecondrealtime = realtime;
215 cls.td_onesecondframes = 0;
216 cls.td_onesecondminfps = 0;
217 cls.td_onesecondmaxfps = 0;
218 cls.td_onesecondavgfps = 0;
219 cls.td_onesecondavgcount = 0;
221 if (cl.time >= cls.td_onesecondnexttime)
223 double fps = cls.td_onesecondframes / (realtime - cls.td_onesecondrealtime);
224 if (cls.td_onesecondavgcount == 0)
226 cls.td_onesecondminfps = fps;
227 cls.td_onesecondmaxfps = fps;
229 cls.td_onesecondrealtime = realtime;
230 cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
231 cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
232 cls.td_onesecondavgfps += fps;
233 cls.td_onesecondavgcount++;
234 cls.td_onesecondframes = 0;
235 cls.td_onesecondnexttime++;
238 else if (cl.time <= cl.mtime[0])
240 // don't need another message yet
245 // get the next message
246 FS_Read(cls.demofile, &cl_message.cursize, 4);
247 cl_message.cursize = LittleLong(cl_message.cursize);
248 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
250 // skip over demo packet
251 FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
254 if (cl_message.cursize > cl_message.maxsize)
256 Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
257 cl_message.cursize = 0;
261 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
262 for (i = 0;i < 3;i++)
264 FS_Read(cls.demofile, &f, 4);
265 cl.mviewangles[0][i] = LittleFloat(f);
268 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
270 MSG_BeginReading(&cl_message);
271 CL_ParseServerMessage();
273 if (cls.signon != SIGNONS)
274 Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect!
276 // In case the demo contains a "svc_disconnect" message
277 if (!cls.demoplayback)
296 stop recording a demo
299 void CL_Stop_f (void)
302 unsigned char bufdata[64];
304 if (!cls.demorecording)
306 Con_Print("Not recording a demo.\n");
310 // write a disconnect message to the demo file
311 // LordHavoc: don't replace the cl_message when doing this
313 buf.maxsize = sizeof(bufdata);
315 MSG_WriteByte(&buf, svc_disconnect);
316 CL_WriteDemoMessage(&buf);
319 if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
321 FS_RemoveOnClose(cls.demofile);
322 Con_Print("Completed and deleted demo\n");
325 Con_Print("Completed demo\n");
326 FS_Close (cls.demofile);
328 cls.demorecording = false;
335 record <demoname> <map> [cd track]
338 void CL_Record_f (void)
341 char name[MAX_OSPATH];
345 if (c != 2 && c != 3 && c != 4)
347 Con_Print("record <demoname> [<map> [cd track]]\n");
351 if (strstr(Cmd_Argv(1), ".."))
353 Con_Print("Relative pathnames are not allowed.\n");
357 if (c == 2 && cls.state == ca_connected)
359 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
363 if (cls.state == ca_connected)
366 // write the forced cd track number, or -1
369 track = atoi(Cmd_Argv(3));
370 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
376 strlcpy (name, Cmd_Argv(1), sizeof (name));
377 FS_DefaultExtension (name, ".dem", sizeof (name));
381 Cmd_ExecuteString ( va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(2)), src_command, false);
383 // open the demo file
384 Con_Printf("recording to %s.\n", name);
385 cls.demofile = FS_OpenRealFile(name, "wb", false);
388 Con_Print("ERROR: couldn't open.\n");
391 strlcpy(cls.demoname, name, sizeof(cls.demoname));
393 cls.forcetrack = track;
394 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
396 cls.demorecording = true;
397 cls.demo_lastcsprogssize = -1;
398 cls.demo_lastcsprogscrc = -1;
409 void CL_PlayDemo_f (void)
411 char name[MAX_QPATH];
413 qboolean neg = false;
418 Con_Print("play <demoname> : plays a demo\n");
422 // open the demo file
423 strlcpy (name, Cmd_Argv(1), sizeof (name));
424 FS_DefaultExtension (name, ".dem", sizeof (name));
425 f = FS_OpenVirtualFile(name, false);
428 Con_Printf("ERROR: couldn't open %s.\n", name);
429 cls.demonum = -1; // stop demo loop
433 cls.demostarting = true;
435 // disconnect from server
437 Host_ShutdownServer ();
439 // update networking ports (this is mainly just needed at startup)
440 NetConn_UpdateSockets();
442 cls.protocol = PROTOCOL_QUAKE;
444 Con_Printf("Playing demo %s.\n", name);
446 strlcpy(cls.demoname, name, sizeof(cls.demoname));
448 cls.demoplayback = true;
449 cls.state = ca_connected;
452 while ((c = FS_Getc (cls.demofile)) != '\n')
456 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
459 cls.forcetrack = -cls.forcetrack;
461 cls.demostarting = false;
467 double time, totalfpsavg;
468 double fpsmin, fpsavg, fpsmax;
471 static size_t doublecmp_offset;
472 static int doublecmp_withoffset(const void *a_, const void *b_)
474 const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
475 const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
489 static void CL_FinishTimeDemo (void)
493 double time, totalfpsavg;
494 double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
495 static int benchmark_runs = 0;
498 cls.timedemo = false;
500 frames = cls.td_frames;
501 time = realtime - cls.td_starttime;
502 totalfpsavg = time > 0 ? frames / time : 0;
503 fpsmin = cls.td_onesecondminfps;
504 fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
505 fpsmax = cls.td_onesecondmaxfps;
506 // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
507 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);
508 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);
509 if (COM_CheckParm("-benchmark"))
512 i = COM_CheckParm("-benchmarkruns");
513 if(i && i + 1 < com_argc)
515 static benchmarkhistory_t *history = NULL;
517 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(com_argv[i + 1]));
519 history[benchmark_runs - 1].frames = frames;
520 history[benchmark_runs - 1].time = time;
521 history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
522 history[benchmark_runs - 1].fpsmin = fpsmin;
523 history[benchmark_runs - 1].fpsavg = fpsavg;
524 history[benchmark_runs - 1].fpsmax = fpsmax;
526 if(atoi(com_argv[i + 1]) > benchmark_runs)
528 // restart the benchmark
529 Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
530 // cannot execute here
535 int first = COM_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
536 if(benchmark_runs > first)
539 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
542 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
545 doublecmp_offset = (char *)&history->f - (char *)history; \
546 qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
547 if((first + benchmark_runs) & 1) \
548 f = history[(first + benchmark_runs - 1) / 2].f; \
550 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
558 Con_Printf("MIN: %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);
566 Con_Printf("MED: %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);
574 Con_Printf("MAX: %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);
593 void CL_TimeDemo_f (void)
597 Con_Print("timedemo <demoname> : gets demo speeds\n");
601 srand(0); // predictable random sequence for benchmarking
605 // cls.td_starttime will be grabbed at the second frame of the demo, so
606 // all the loading time doesn't get counted
608 // instantly hide console and deactivate it
610 key_consoleactive = 0;
614 cls.td_frames = -2; // skip the first frame
615 cls.demonum = -1; // stop demo loop