]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
Updating Windows compile guide after the major overhaul of Windows
[xonotic/netradiant.git] / radiant / watchbsp.cpp
1 /*
2 Copyright (c) 2001, Loki software, inc.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
10
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
14
15 Neither the name of Loki software nor the names of its contributors may be used
16 to endorse or promote products derived from this software without specific prior
17 written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 //-----------------------------------------------------------------------------
32 //
33 // DESCRIPTION:
34 // monitoring window for running BSP processes (and possibly various other stuff)
35
36 #include "stdafx.h"
37 #include "watchbsp.h"
38 #include "feedback.h"
39
40 #ifdef _WIN32
41 #include <winsock2.h>
42 #endif
43
44 #if defined (__linux__) || defined (__APPLE__)
45 #include <sys/time.h>
46 #define SOCKET_ERROR -1
47 #endif
48
49 #ifdef __APPLE__
50 #include <unistd.h>
51 #endif
52
53 #include <assert.h>
54
55 // Static functions for the SAX callbacks -------------------------------------------------------
56
57 // utility for saxStartElement below
58 static void abortStream(message_info_t *data)
59 {
60   g_pParentWnd->GetWatchBSP()->Reset();
61   // tell there has been an error
62   if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ())
63     g_BSPFrontendTable.m_pfnEndListen(2);
64   // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
65   data->ignore_depth = -1;
66   data->recurse++;
67 }
68
69 #include "stream_version.h"
70
71 static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs)
72 {
73   if (data->ignore_depth == 0)
74   {
75     if (data->bGeometry)
76       // we have a handler
77     {
78       data->pGeometry->saxStartElement (data, name, attrs);
79     }
80     else
81     {
82       if (strcmp ((char *)name, "q3map_feedback") == 0)
83       {
84         // check the correct version
85         // old q3map don't send a version attribute
86         // the ones we support .. send Q3MAP_STREAM_VERSION
87         if (!attrs[0] || !attrs[1] || (strcmp((char*)attrs[0],"version") != 0))
88         {
89           Sys_FPrintf(SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n"
90                       "Please turn off monitored compiling if you still wish to use this q3map executable\n");
91           abortStream(data);
92           return;
93         }
94         else if (strcmp((char*)attrs[1],Q3MAP_STREAM_VERSION) != 0)
95         {
96           Sys_FPrintf(SYS_ERR,
97             "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n"
98             "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1]);
99           abortStream(data);
100           return;
101         }
102       }
103       // we don't treat locally
104       else if (strcmp ((char *)name, "message") == 0)
105       {
106         data->msg_level = atoi ((char *)attrs[1]);
107       }
108       else if (strcmp ((char *)name, "polyline") == 0)
109       // polyline has a particular status .. right now we only use it for leakfile ..
110       {
111         data->bGeometry = true;
112         data->pGeometry = &g_pointfile;
113         data->pGeometry->saxStartElement( data, name, attrs );
114       }
115       else if (strcmp ((char *)name, "select") == 0)
116       {
117         CSelectMsg *pSelect = new CSelectMsg();
118         data->bGeometry = true;
119         data->pGeometry = pSelect;
120         data->pGeometry->saxStartElement( data, name, attrs );
121       }
122       else if (strcmp ((char *)name, "pointmsg") == 0)
123       {
124         CPointMsg *pPoint = new CPointMsg();
125         data->bGeometry = true;
126         data->pGeometry = pPoint;
127         data->pGeometry->saxStartElement( data, name, attrs );
128       }
129       else if (strcmp ((char *)name, "windingmsg") == 0)
130       {
131         CWindingMsg *pWinding = new CWindingMsg();
132         data->bGeometry = true;
133         data->pGeometry = pWinding;
134         data->pGeometry->saxStartElement( data, name, attrs );
135       }
136       else
137       {
138         Sys_FPrintf (SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name);
139         // we don't recognize this node, jump over it
140         // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
141         data->ignore_depth = data->recurse;
142       }
143     }
144   }
145   data->recurse++;
146 }
147
148 static void saxEndElement(message_info_t *data, const xmlChar *name)  {
149         data->recurse--;
150         // we are out of an ignored chunk
151         if ( data->recurse == data->ignore_depth ) {
152                 data->ignore_depth = 0;
153                 return;
154         }
155         if ( data->bGeometry ) {
156                 data->pGeometry->saxEndElement( data, name );
157                 // we add the object to the debug window
158                 if ( !data->bGeometry ) {
159                         g_DbgDlg.Push( data->pGeometry );
160                 }
161         }
162         if ( data->recurse == data->stop_depth ) {
163 #ifdef _DEBUG
164                 Sys_Printf ("Received error msg .. shutting down..\n");
165 #endif
166                 g_pParentWnd->GetWatchBSP()->Reset();
167                 // tell there has been an error
168                 if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) {
169                         g_BSPFrontendTable.m_pfnEndListen( 2 );
170                 }
171                 return;
172         }
173 }
174
175 static void saxCharacters(message_info_t *data, const xmlChar *ch, int len)
176 {
177   if (data->bGeometry)
178   {
179     data->pGeometry->saxCharacters (data, ch, len);
180   }
181   else
182   {
183     if (data->ignore_depth != 0)
184       return;
185     // output the message using the level
186     char buf[1024];
187     memcpy( buf, ch, len );
188     buf[len] = '\0';
189     Sys_FPrintf (data->msg_level, "%s", buf);
190     // if this message has error level flag, we mark the depth to stop the compilation when we get out
191     // we don't set the msg level if we don't stop on leak
192     if (data->msg_level == 3)
193     {
194       data->stop_depth = data->recurse-1;
195     }
196   }
197 }
198
199 static void saxComment(void *ctx, const xmlChar *msg)
200 {
201   Sys_Printf("XML comment: %s\n", msg);
202 }
203
204 static void saxWarning(void *ctx, const char *msg, ...)
205 {
206   char saxMsgBuffer[4096];
207   va_list args;
208
209   va_start(args, msg);
210   vsprintf (saxMsgBuffer, msg, args);
211   va_end(args);
212   Sys_FPrintf(SYS_WRN, "XML warning: %s\n", saxMsgBuffer);
213 }
214
215 static void saxError(void *ctx, const char *msg, ...)
216 {
217   char saxMsgBuffer[4096];
218   va_list args;
219
220   va_start(args, msg);
221   vsprintf (saxMsgBuffer, msg, args);
222   va_end(args);
223   Sys_FPrintf(SYS_ERR, "XML error: %s\n", saxMsgBuffer);
224 }
225
226 static void saxFatal(void *ctx, const char *msg, ...)
227 {
228   char buffer[4096];
229
230   va_list args;
231
232   va_start(args, msg);
233   vsprintf (buffer, msg, args);
234   va_end(args);
235   Sys_FPrintf(SYS_ERR, "XML fatal error: %s\n", buffer);
236 }
237
238 static xmlSAXHandler saxParser = {
239     0, /* internalSubset */
240     0, /* isStandalone */
241     0, /* hasInternalSubset */
242     0, /* hasExternalSubset */
243     0, /* resolveEntity */
244     0, /* getEntity */
245     0, /* entityDecl */
246     0, /* notationDecl */
247     0, /* attributeDecl */
248     0, /* elementDecl */
249     0, /* unparsedEntityDecl */
250     0, /* setDocumentLocator */
251     0, /* startDocument */
252     0, /* endDocument */
253     (startElementSAXFunc)saxStartElement, /* startElement */
254     (endElementSAXFunc)saxEndElement, /* endElement */
255     0, /* reference */
256     (charactersSAXFunc)saxCharacters, /* characters */
257     0, /* ignorableWhitespace */
258     0, /* processingInstruction */
259     (commentSAXFunc)saxComment, /* comment */
260     (warningSAXFunc)saxWarning, /* warning */
261     (errorSAXFunc)saxError, /* error */
262     (fatalErrorSAXFunc)saxFatal, /* fatalError */
263 };
264
265 // ------------------------------------------------------------------------------------------------
266
267 CWatchBSP::~CWatchBSP()
268 {
269   Reset();
270   if (m_sBSPName)
271   {
272     delete[] m_sBSPName;
273     m_sBSPName = NULL;
274   }
275   Net_Shutdown();
276 }
277
278 void CWatchBSP::Reset()
279 {
280   if (m_pInSocket)
281   {
282     Net_Disconnect(m_pInSocket);
283     m_pInSocket = NULL;
284   }
285   if (m_pListenSocket)
286   {
287     Net_Disconnect(m_pListenSocket);
288     m_pListenSocket = NULL;
289   }
290   if (m_xmlInputBuffer)
291   {
292     xmlFreeParserInputBuffer (m_xmlInputBuffer);
293     m_xmlInputBuffer = NULL;
294   }
295   m_eState = EIdle;
296 }
297
298 bool CWatchBSP::SetupListening()
299 {
300 #ifdef _DEBUG
301   if (m_pListenSocket)
302   {
303     Sys_Printf("ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n");
304     return false;
305   }
306 #endif
307   Sys_Printf("Setting up\n");
308   if( !Net_Setup() )
309     return false;
310
311   m_pListenSocket = Net_ListenSocket(39000);
312   if (m_pListenSocket == NULL)
313     return false;
314
315   Sys_Printf("Listening...\n");
316   return true;
317 }
318
319 void CWatchBSP::DoEBeginStep() {
320         Reset();
321         if ( !SetupListening() ) {
322                 CString msg;
323                 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
324                 Sys_Printf( msg );
325                 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
326                 return;
327         }
328         // set the timer for timeouts and step cancellation
329         g_timer_reset( m_pTimer );
330         g_timer_start( m_pTimer );
331
332         if ( !m_bBSPPlugin ) {
333                 Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
334
335                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) {
336                         CString msg;
337                         msg = "Failed to execute the following command: ";
338                         msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
339                         msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
340                         Sys_Printf( msg );
341                         gtk_MessageBox( g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
342                         return;
343                 }
344                 // re-initialise the debug window
345                 if ( m_iCurrentStep == 0 ) {
346                         g_DbgDlg.Init();
347                 }
348         }
349         m_eState = EBeginStep;
350 }
351
352 void CWatchBSP::RoutineProcessing()
353 {
354   // used for select()
355 #ifdef _WIN32
356     TIMEVAL tout = { 0, 0 };
357 #endif
358 #if defined (__linux__) || defined (__APPLE__)
359                 timeval tout;
360                 tout.tv_sec = 0;
361                 tout.tv_usec = 0;
362 #endif
363
364   switch (m_eState)
365   {
366   case EBeginStep:
367     // timeout: if we don't get an incoming connection fast enough, go back to idle
368     if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout )
369     {
370       gtk_MessageBox(g_pParentWnd->m_pWidget,  "The connection timed out, assuming the BSP process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", MB_OK );
371       Reset();
372       if (m_bBSPPlugin)
373       {
374         // status == 1 : didn't get the connection
375         g_BSPFrontendTable.m_pfnEndListen(1);
376       }
377       return;
378     }
379 #ifdef _DEBUG
380     // some debug checks
381     if (!m_pListenSocket)
382     {
383       Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n");
384       return;
385     }
386 #endif
387     // we are not connected yet, accept any incoming connection
388     m_pInSocket = Net_Accept(m_pListenSocket);
389     if (m_pInSocket)
390     {
391       Sys_Printf("Connected.\n");
392       // prepare the message info struct for diving in
393       memset (&m_message_info, 0, sizeof(message_info_s));
394       // a dumb flag to make sure we init the push parser context when first getting a msg
395       m_bNeedCtxtInit = true;
396       m_eState = EWatching;
397     }
398     break;
399   case EWatching:
400 #ifdef _DEBUG
401     // some debug checks
402     if (!m_pInSocket)
403     {
404       Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n");
405       return;
406     }
407 #endif
408     // select() will identify if the socket needs an update
409     // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
410     fd_set readfds;
411     int ret;
412     FD_ZERO(&readfds);
413     FD_SET(((unsigned int)m_pInSocket->socket), &readfds);
414                 // from select man page:
415                 // n is the highest-numbered descriptor in any of the three sets, plus 1
416                 // (no use on windows)
417     ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
418     if (ret == SOCKET_ERROR)
419     {
420       Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n");
421       Sys_Printf("Terminating the connection.\n");
422       Reset();
423       return;
424     }
425 #ifdef _DEBUG
426     if (ret == -1)
427     {
428       // we are non-blocking?? we should never get timeout errors
429       Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n");
430       Sys_Printf("Terminating the connection.\n");
431       Reset();
432       return;
433     }
434 #endif
435     if (ret == 1)
436     {
437       // the socket has been identified, there's something (message or disconnection)
438       // see if there's anything in input
439       ret = Net_Receive( m_pInSocket, &msg );
440       if (ret > 0)
441       {
442         //        unsigned int size = msg.size; //++timo just a check
443         strcpy (m_xmlBuf, NMSG_ReadString (&msg));
444         if (m_bNeedCtxtInit)
445         {
446           m_xmlParserCtxt = NULL;
447           m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL);
448           if (m_xmlParserCtxt == NULL)
449           {
450             Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf);
451             Reset();
452           }
453           m_bNeedCtxtInit = false;
454         }
455         else
456         {
457           xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0);
458         }
459       }
460       else
461       {
462         // error or connection closed/reset
463         // NOTE: if we get an error down the XML stream we don't reach here
464         Net_Disconnect( m_pInSocket );
465         m_pInSocket = NULL;
466         Sys_Printf("Connection closed.\n");
467         if (m_bBSPPlugin)
468         {
469           Reset();
470           // let the BSP plugin know that the job is done
471           g_BSPFrontendTable.m_pfnEndListen(0);
472           return;
473         }
474         // move to next step or finish
475         m_iCurrentStep++;
476         if (m_iCurrentStep < m_pCmd->len )
477         {
478           DoEBeginStep();
479         }
480         else
481         {
482           // release the GPtrArray and the strings
483           if (m_pCmd != NULL)
484           {
485             for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
486             {
487               delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
488             }
489             g_ptr_array_free( m_pCmd, false );
490           }
491           m_pCmd = NULL;
492           // launch the engine .. OMG
493           if (g_PrefsDlg.m_bRunQuake)
494           {
495             // do we enter sleep mode before?
496             if (g_PrefsDlg.m_bDoSleep)
497             {
498               Sys_Printf("Going into sleep mode..\n");
499               g_pParentWnd->OnSleep();
500             }
501             Sys_Printf("Running engine...\n");
502             Str cmd;
503             // build the command line
504             cmd = g_pGameDescription->mEnginePath.GetBuffer();
505             // this is game dependant
506             if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
507             {
508               // MP
509               cmd += g_pGameDescription->mMultiplayerEngine.GetBuffer();
510             }
511             else
512             {
513               // SP
514               cmd += g_pGameDescription->mEngine.GetBuffer();
515             }
516 #ifdef _WIN32
517             // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
518                         // FIXME: This isn't true anymore, doesn't it?
519             FindReplace( cmd, "/", "\\" );
520 #endif
521             Str cmdline;
522             if ( g_pGameDescription->quake2 )
523             {
524                 cmdline = ". +exec radiant.cfg +map ";
525                 cmdline += m_sBSPName;
526             }
527             else
528             {
529               cmdline = "+set sv_pure 0 ";
530               // TTimo: a check for vm_* but that's all fine
531               //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
532               if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
533               {
534                 cmdline += "+set fs_game ";
535                 cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename");
536                 cmdline += " ";
537               }
538               //!\todo Read the start-map args from a config file.
539               if (g_pGameDescription->mGameFile == "wolf.game")
540               {
541                 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
542                 {
543                   // MP
544                   cmdline += "+devmap ";
545                   cmdline += m_sBSPName;
546                 }
547                 else
548                 {
549                   // SP
550                   cmdline += "+set nextmap \"spdevmap ";
551                   cmdline += m_sBSPName;
552                   cmdline += "\"";
553                 }
554               }
555               else
556               {
557                 cmdline += "+devmap ";
558                 cmdline += m_sBSPName;
559               }
560             }
561
562             Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer());
563
564             // execute now
565             if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false))
566             {
567               CString msg;
568               msg = "Failed to execute the following command: ";
569               msg += cmd; msg += cmdline;
570               Sys_Printf(msg);
571               gtk_MessageBox(g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
572             }
573           }
574           Reset();
575         }
576       }
577     }
578     break;
579   default:
580     break;
581   }
582 }
583
584 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName )
585 {
586   if (m_sBSPName)
587   {
588     delete[] m_sBSPName;
589   }
590   m_sBSPName = sBSPName;
591   if (m_eState != EIdle)
592   {
593     Sys_Printf("WatchBSP got a monitoring request while not idling...\n");
594     // prompt the user, should we cancel the current process and go ahead?
595     if (gtk_MessageBox(g_pParentWnd->m_pWidget,  "I am already monitoring a BSP process.\nDo you want me to override and start a new compilation?",
596       "BSP process monitoring", MB_YESNO ) == IDYES)
597     {
598       // disconnect and set EIdle state
599       Reset();
600     }
601   }
602   m_pCmd = pCmd;
603   m_iCurrentStep = 0;
604   DoEBeginStep();
605 }
606
607 void CWatchBSP::ExternalListen()
608 {
609   m_bBSPPlugin = true;
610   DoEBeginStep ();
611 }
612
613 // the part of the watchbsp interface we export to plugins
614 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
615 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
616 void WINAPI QERApp_Listen()
617 {
618   // open the listening socket
619   g_pParentWnd->GetWatchBSP()->ExternalListen();
620 }