]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - net_main.c
water moves around again
[xonotic/darkplaces.git] / net_main.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 // net_main.c
21
22 #include "quakedef.h"
23 #include "net_master.h"
24
25 qsocket_t *net_activeSockets = NULL;
26 mempool_t *net_mempool;
27
28 qboolean        ipxAvailable = false;
29 qboolean        tcpipAvailable = false;
30
31 int                     net_hostport;
32 int                     DEFAULTnet_hostport = 26000;
33
34 char            my_ipx_address[NET_NAMELEN];
35 char            my_tcpip_address[NET_NAMELEN];
36
37 static qboolean listening = false;
38
39 qboolean        slistInProgress = false;
40 qboolean        slistSilent = false;
41 qboolean        slistLocal = true;
42 static double   slistStartTime;
43 static int              slistLastShown;
44
45 static void Slist_Send(void);
46 static void Slist_Poll(void);
47 PollProcedure   slistSendProcedure = {NULL, 0.0, Slist_Send};
48 PollProcedure   slistPollProcedure = {NULL, 0.0, Slist_Poll};
49
50 static void InetSlist_Send(void);
51 static void InetSlist_Poll(void);
52 PollProcedure   inetSlistSendProcedure = {NULL, 0.0, InetSlist_Send};
53 PollProcedure   inetSlistPollProcedure = {NULL, 0.0, InetSlist_Poll};
54
55
56 sizebuf_t               net_message;
57 int                             net_activeconnections = 0;
58
59 int messagesSent = 0;
60 int messagesReceived = 0;
61 int unreliableMessagesSent = 0;
62 int unreliableMessagesReceived = 0;
63
64 cvar_t  net_messagetimeout = {0, "net_messagetimeout","300"};
65 cvar_t  hostname = {CVAR_SAVE, "hostname", "UNNAMED"};
66
67 qboolean        configRestored = false;
68
69 // these two macros are to make the code more readable
70 #define sfunc   net_drivers[sock->driver]
71 #define dfunc   net_drivers[net_driverlevel]
72
73 int     net_driverlevel;
74
75
76 double                  net_time;
77
78 double SetNetTime(void)
79 {
80         net_time = Sys_DoubleTime();
81         return net_time;
82 }
83
84
85 /*
86 ===================
87 NET_NewQSocket
88
89 Called by drivers when a new communications endpoint is required
90 The sequence and buffer fields will be filled in properly
91 ===================
92 */
93 qsocket_t *NET_NewQSocket (void)
94 {
95         qsocket_t       *sock;
96
97         if (net_activeconnections >= svs.maxclients)
98                 return NULL;
99
100         sock = Mem_Alloc(net_mempool, sizeof(qsocket_t));
101
102         // add it to active list
103         sock->next = net_activeSockets;
104         net_activeSockets = sock;
105
106         sock->disconnected = false;
107         sock->connecttime = net_time;
108         strcpy (sock->address,"UNSET ADDRESS");
109         sock->driver = net_driverlevel;
110         sock->socket = 0;
111         sock->driverdata = NULL;
112         sock->canSend = true;
113         sock->sendNext = false;
114         sock->lastMessageTime = net_time;
115         sock->ackSequence = 0;
116         sock->sendSequence = 0;
117         sock->unreliableSendSequence = 0;
118         sock->sendMessageLength = 0;
119         sock->receiveSequence = 0;
120         sock->unreliableReceiveSequence = 0;
121         sock->receiveMessageLength = 0;
122
123         return sock;
124 }
125
126
127 void NET_FreeQSocket(qsocket_t *sock)
128 {
129         qsocket_t       *s;
130
131         // remove it from active list
132         if (sock == net_activeSockets)
133                 net_activeSockets = net_activeSockets->next;
134         else
135         {
136                 for (s = net_activeSockets; s; s = s->next)
137                         if (s->next == sock)
138                         {
139                                 s->next = sock->next;
140                                 break;
141                         }
142                 if (!s)
143                         Sys_Error ("NET_FreeQSocket: not active\n");
144         }
145
146         Mem_Free(sock);
147 }
148
149
150 static void NET_Listen_f (void)
151 {
152         if (Cmd_Argc () != 2)
153         {
154                 Con_Printf ("\"listen\" is \"%u\"\n", listening ? 1 : 0);
155                 return;
156         }
157
158         listening = atoi(Cmd_Argv(1)) ? true : false;
159
160         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
161         {
162                 if (net_drivers[net_driverlevel].initialized == false)
163                         continue;
164                 dfunc.Listen (listening);
165         }
166 }
167
168
169 static void MaxPlayers_f (void)
170 {
171         int n;
172
173         if (Cmd_Argc () != 2)
174         {
175                 Con_Printf ("\"maxplayers\" is \"%u\"\n", svs.maxclients);
176                 return;
177         }
178
179         if (sv.active)
180         {
181                 Con_Printf ("maxplayers can not be changed while a server is running.\n");
182                 return;
183         }
184
185         n = atoi(Cmd_Argv(1));
186         n = bound(1, n, MAX_SCOREBOARD);
187         if (svs.maxclients != n)
188                 Con_Printf ("\"maxplayers\" set to \"%u\"\n", n);
189
190         if ((n == 1) && listening)
191                 Cbuf_AddText ("listen 0\n");
192
193         if ((n > 1) && (!listening))
194                 Cbuf_AddText ("listen 1\n");
195
196         SV_SetMaxClients(n);
197 }
198
199
200 static void NET_Port_f (void)
201 {
202         int     n;
203
204         if (Cmd_Argc () != 2)
205         {
206                 Con_Printf ("\"port\" is \"%u\"\n", net_hostport);
207                 return;
208         }
209
210         n = atoi(Cmd_Argv(1));
211         if (n < 1 || n > 65534)
212         {
213                 Con_Printf ("Bad value, must be between 1 and 65534\n");
214                 return;
215         }
216
217         DEFAULTnet_hostport = n;
218         net_hostport = n;
219
220         if (listening)
221         {
222                 // force a change to the new port
223                 Cbuf_AddText ("listen 0\n");
224                 Cbuf_AddText ("listen 1\n");
225         }
226 }
227
228
229 static void NET_Heartbeat_f (void)
230 {
231         NET_Heartbeat (2);
232 }
233
234
235 static void PrintSlistHeader(void)
236 {
237         Con_Printf("Server          Map             Users\n");
238         Con_Printf("--------------- --------------- -----\n");
239         slistLastShown = 0;
240 }
241
242
243 static void PrintSlist(void)
244 {
245         int n;
246
247         for (n = slistLastShown; n < hostCacheCount; n++)
248         {
249                 if (hostcache[n].maxusers)
250                         Con_Printf("%-15.15s %-15.15s %2u/%2u\n", hostcache[n].name, hostcache[n].map, hostcache[n].users, hostcache[n].maxusers);
251                 else
252                         Con_Printf("%-15.15s %-15.15s\n", hostcache[n].name, hostcache[n].map);
253         }
254         slistLastShown = n;
255 }
256
257
258 static void PrintSlistTrailer(void)
259 {
260         if (hostCacheCount)
261                 Con_Printf("== end list ==\n\n");
262         else
263         {
264                 if (gamemode == GAME_TRANSFUSION)
265                         Con_Printf("No Transfusion servers found.\n\n");
266                 else
267                         Con_Printf("No Quake servers found.\n\n");
268         }
269 }
270
271
272 void NET_SlistCommon (PollProcedure *sendProcedure, PollProcedure *pollProcedure)
273 {
274         if (slistInProgress)
275                 return;
276
277         if (! slistSilent)
278         {
279                 if (gamemode == GAME_TRANSFUSION)
280                         Con_Printf("Looking for Transfusion servers...\n");
281                 else
282                         Con_Printf("Looking for Quake servers...\n");
283                 PrintSlistHeader();
284         }
285
286         slistInProgress = true;
287         slistStartTime = Sys_DoubleTime();
288
289         SchedulePollProcedure(sendProcedure, 0.0);
290         SchedulePollProcedure(pollProcedure, 0.1);
291
292         hostCacheCount = 0;
293 }
294
295
296 void NET_Slist_f (void)
297 {
298         NET_SlistCommon (&slistSendProcedure, &slistPollProcedure);
299 }
300
301
302 void NET_InetSlist_f (void)
303 {
304         NET_SlistCommon (&inetSlistSendProcedure, &inetSlistPollProcedure);
305 }
306
307
308 static void Slist_Send(void)
309 {
310         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
311         {
312                 if (!slistLocal && net_driverlevel == 0)
313                         continue;
314                 if (net_drivers[net_driverlevel].initialized == false)
315                         continue;
316                 dfunc.SearchForHosts (true);
317         }
318
319         if ((Sys_DoubleTime() - slistStartTime) < 0.5)
320                 SchedulePollProcedure(&slistSendProcedure, 0.75);
321 }
322
323
324 static void Slist_Poll(void)
325 {
326         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
327         {
328                 if (!slistLocal && net_driverlevel == 0)
329                         continue;
330                 if (net_drivers[net_driverlevel].initialized == false)
331                         continue;
332                 dfunc.SearchForHosts (false);
333         }
334
335         if (! slistSilent)
336                 PrintSlist();
337
338         if ((Sys_DoubleTime() - slistStartTime) < 1.5)
339         {
340                 SchedulePollProcedure(&slistPollProcedure, 0.1);
341                 return;
342         }
343
344         if (! slistSilent)
345                 PrintSlistTrailer();
346         slistInProgress = false;
347         slistSilent = false;
348         slistLocal = true;
349 }
350
351
352 static void InetSlist_Send(void)
353 {
354         const char* host;
355
356         if (!slistInProgress)
357                 return;
358
359         while ((host = Master_BuildGetServers ()) != NULL)
360         {
361                 for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
362                 {
363                         if (!slistLocal && net_driverlevel == 0)
364                                 continue;
365                         if (net_drivers[net_driverlevel].initialized == false)
366                                 continue;
367                         dfunc.SearchForInetHosts (host);
368                 }
369         }
370
371         if ((Sys_DoubleTime() - slistStartTime) < 3.5)
372                 SchedulePollProcedure(&inetSlistSendProcedure, 1.0);
373 }
374
375
376 static void InetSlist_Poll(void)
377 {
378         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
379         {
380                 if (!slistLocal && net_driverlevel == 0)
381                         continue;
382                 if (net_drivers[net_driverlevel].initialized == false)
383                         continue;
384                 // We stop as soon as we have one answer (FIXME: bad...)
385                 if (dfunc.SearchForInetHosts (NULL))
386                         slistInProgress = false;
387         }
388
389         if (! slistSilent)
390                 PrintSlist();
391
392         if (slistInProgress && (Sys_DoubleTime() - slistStartTime) < 4.0)
393         {
394                 SchedulePollProcedure(&inetSlistPollProcedure, 0.1);
395                 return;
396         }
397
398         if (! slistSilent)
399                 PrintSlistTrailer();
400         slistInProgress = false;
401         slistSilent = false;
402         slistLocal = true;
403 }
404
405
406 /*
407 ===================
408 NET_Connect
409 ===================
410 */
411
412 int hostCacheCount = 0;
413 hostcache_t hostcache[HOSTCACHESIZE];
414
415 qsocket_t *NET_Connect (char *host)
416 {
417         qsocket_t               *ret;
418         int                             n;
419         int                             numdrivers = net_numdrivers;
420
421         SetNetTime();
422
423         if (host && *host == 0)
424                 host = NULL;
425
426         if (host)
427         {
428                 if (Q_strcasecmp (host, "local") == 0)
429                 {
430                         numdrivers = 1;
431                         goto JustDoIt;
432                 }
433
434                 if (hostCacheCount)
435                 {
436                         for (n = 0; n < hostCacheCount; n++)
437                                 if (Q_strcasecmp (host, hostcache[n].name) == 0)
438                                 {
439                                         host = hostcache[n].cname;
440                                         break;
441                                 }
442                         if (n < hostCacheCount)
443                                 goto JustDoIt;
444                 }
445         }
446
447         slistSilent = host ? true : false;
448         NET_Slist_f ();
449
450         while(slistInProgress)
451                 NET_Poll();
452
453         if (host == NULL)
454         {
455                 if (hostCacheCount != 1)
456                         return NULL;
457                 host = hostcache[0].cname;
458                 Con_Printf("Connecting to...\n%s @ %s\n\n", hostcache[0].name, host);
459         }
460
461         if (hostCacheCount)
462                 for (n = 0; n < hostCacheCount; n++)
463                         if (Q_strcasecmp (host, hostcache[n].name) == 0)
464                         {
465                                 host = hostcache[n].cname;
466                                 break;
467                         }
468
469 JustDoIt:
470         for (net_driverlevel=0 ; net_driverlevel<numdrivers; net_driverlevel++)
471         {
472                 if (net_drivers[net_driverlevel].initialized == false)
473                         continue;
474                 ret = dfunc.Connect (host);
475                 if (ret)
476                         return ret;
477         }
478
479         if (host)
480         {
481                 Con_Printf("\n");
482                 PrintSlistHeader();
483                 PrintSlist();
484                 PrintSlistTrailer();
485         }
486         
487         return NULL;
488 }
489
490
491 /*
492 ===================
493 NET_CheckNewConnections
494 ===================
495 */
496
497 qsocket_t *NET_CheckNewConnections (void)
498 {
499         qsocket_t       *ret;
500
501         SetNetTime();
502
503         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
504         {
505                 if (net_drivers[net_driverlevel].initialized == false)
506                         continue;
507                 if (net_driverlevel && listening == false)
508                         continue;
509                 ret = dfunc.CheckNewConnections ();
510                 if (ret)
511                         return ret;
512         }
513
514         return NULL;
515 }
516
517 /*
518 ===================
519 NET_Close
520 ===================
521 */
522 void NET_Close (qsocket_t *sock)
523 {
524         if (!sock)
525                 return;
526
527         if (sock->disconnected)
528                 return;
529
530         SetNetTime();
531
532         // call the driver_Close function
533         sfunc.Close (sock);
534
535         NET_FreeQSocket(sock);
536 }
537
538
539 /*
540 =================
541 NET_GetMessage
542
543 If there is a complete message, return it in net_message
544
545 returns 0 if no data is waiting
546 returns 1 if a message was received
547 returns -1 if connection is invalid
548 =================
549 */
550
551 extern void PrintStats(qsocket_t *s);
552
553 int     NET_GetMessage (qsocket_t *sock)
554 {
555         int ret;
556
557         if (!sock)
558                 return -1;
559
560         if (sock->disconnected)
561         {
562                 Con_Printf("NET_GetMessage: disconnected socket\n");
563                 return -1;
564         }
565
566         SetNetTime();
567
568         ret = sfunc.QGetMessage(sock);
569
570         // see if this connection has timed out
571         if (ret == 0 && sock->driver)
572         {
573                 if (net_time - sock->lastMessageTime > net_messagetimeout.value)
574                 {
575                         NET_Close(sock);
576                         return -1;
577                 }
578         }
579
580
581         if (ret > 0)
582         {
583                 if (sock->driver)
584                 {
585                         sock->lastMessageTime = net_time;
586                         if (ret == 1)
587                                 messagesReceived++;
588                         else if (ret == 2)
589                                 unreliableMessagesReceived++;
590                 }
591         }
592
593         return ret;
594 }
595
596
597 /*
598 ==================
599 NET_SendMessage
600
601 Try to send a complete length+message unit over the reliable stream.
602 returns 0 if the message cannot be delivered reliably, but the connection
603                 is still considered valid
604 returns 1 if the message was sent properly
605 returns -1 if the connection died
606 ==================
607 */
608 int NET_SendMessage (qsocket_t *sock, sizebuf_t *data)
609 {
610         int             r;
611         
612         if (!sock)
613                 return -1;
614
615         if (sock->disconnected)
616         {
617                 Con_Printf("NET_SendMessage: disconnected socket\n");
618                 return -1;
619         }
620
621         SetNetTime();
622         r = sfunc.QSendMessage(sock, data);
623         if (r == 1 && sock->driver)
624                 messagesSent++;
625
626         return r;
627 }
628
629
630 int NET_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data)
631 {
632         int             r;
633         
634         if (!sock)
635                 return -1;
636
637         if (sock->disconnected)
638         {
639                 Con_Printf("NET_SendMessage: disconnected socket\n");
640                 return -1;
641         }
642
643         SetNetTime();
644         r = sfunc.SendUnreliableMessage(sock, data);
645         if (r == 1 && sock->driver)
646                 unreliableMessagesSent++;
647
648         return r;
649 }
650
651
652 /*
653 ==================
654 NET_CanSendMessage
655
656 Returns true or false if the given qsocket can currently accept a
657 message to be transmitted.
658 ==================
659 */
660 qboolean NET_CanSendMessage (qsocket_t *sock)
661 {
662         int             r;
663         
664         if (!sock)
665                 return false;
666
667         if (sock->disconnected)
668                 return false;
669
670         SetNetTime();
671
672         r = sfunc.CanSendMessage(sock);
673         
674         return r;
675 }
676
677
678 /*
679 ====================
680 NET_Heartbeat
681
682 Send an heartbeat to the master server(s)
683 ====================
684 */
685 void NET_Heartbeat (int priority)
686 {
687         const char* host;
688
689         if (! Master_AllowHeartbeat (priority))
690                 return;
691
692         while ((host = Master_BuildHeartbeat ()) != NULL)
693         {
694                 for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
695                 {
696                         if (net_drivers[net_driverlevel].initialized == false)
697                                 continue;
698                         if (net_driverlevel && listening == false)
699                                 continue;
700                         dfunc.Heartbeat (host);
701                 }
702         }
703 }
704
705
706 int NET_SendToAll(sizebuf_t *data, int blocktime)
707 {
708         double          start;
709         int                     i;
710         int                     count = 0;
711         qboolean        state1 [MAX_SCOREBOARD];
712         qboolean        state2 [MAX_SCOREBOARD];
713
714         for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
715         {
716                 if (!host_client->netconnection)
717                         continue;
718                 if (host_client->active)
719                 {
720                         if (host_client->netconnection->driver == 0)
721                         {
722                                 NET_SendMessage(host_client->netconnection, data);
723                                 state1[i] = true;
724                                 state2[i] = true;
725                                 continue;
726                         }
727                         count++;
728                         state1[i] = false;
729                         state2[i] = false;
730                 }
731                 else
732                 {
733                         state1[i] = true;
734                         state2[i] = true;
735                 }
736         }
737
738         start = Sys_DoubleTime();
739         while (count)
740         {
741                 count = 0;
742                 for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
743                 {
744                         if (! state1[i])
745                         {
746                                 if (NET_CanSendMessage (host_client->netconnection))
747                                 {
748                                         state1[i] = true;
749                                         NET_SendMessage(host_client->netconnection, data);
750                                 }
751                                 else
752                                 {
753                                         NET_GetMessage (host_client->netconnection);
754                                 }
755                                 count++;
756                                 continue;
757                         }
758
759                         if (! state2[i])
760                         {
761                                 if (NET_CanSendMessage (host_client->netconnection))
762                                 {
763                                         state2[i] = true;
764                                 }
765                                 else
766                                 {
767                                         NET_GetMessage (host_client->netconnection);
768                                 }
769                                 count++;
770                                 continue;
771                         }
772                 }
773                 if ((Sys_DoubleTime() - start) > blocktime)
774                         break;
775         }
776         return count;
777 }
778
779
780 //=============================================================================
781
782 /*
783 ====================
784 NET_Init
785 ====================
786 */
787
788 void NET_Init (void)
789 {
790         int                     i;
791         int                     controlSocket;
792
793         i = COM_CheckParm ("-port");
794         if (!i)
795                 i = COM_CheckParm ("-udpport");
796         if (!i)
797                 i = COM_CheckParm ("-ipxport");
798
799         if (i)
800         {
801                 if (i < com_argc-1)
802                         DEFAULTnet_hostport = atoi (com_argv[i+1]);
803                 else
804                         Sys_Error ("NET_Init: you must specify a number after -port");
805         }
806         net_hostport = DEFAULTnet_hostport;
807
808         if (COM_CheckParm("-listen") || cls.state == ca_dedicated || gamemode == GAME_TRANSFUSION)
809                 listening = true;
810
811         SetNetTime();
812
813         net_mempool = Mem_AllocPool("qsocket");
814
815         // allocate space for network message buffer
816         SZ_Alloc (&net_message, NET_MAXMESSAGE, "net_message");
817
818         Cvar_RegisterVariable (&net_messagetimeout);
819         Cvar_RegisterVariable (&hostname);
820
821         Cmd_AddCommand ("net_slist", NET_Slist_f);
822         Cmd_AddCommand ("net_inetslist", NET_InetSlist_f);
823         Cmd_AddCommand ("listen", NET_Listen_f);
824         Cmd_AddCommand ("maxplayers", MaxPlayers_f);
825         Cmd_AddCommand ("port", NET_Port_f);
826         Cmd_AddCommand ("heartbeat", NET_Heartbeat_f);
827
828         // initialize all the drivers
829         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers ; net_driverlevel++)
830                 {
831                 controlSocket = net_drivers[net_driverlevel].Init();
832                 if (controlSocket == -1)
833                         continue;
834                 net_drivers[net_driverlevel].initialized = true;
835                 net_drivers[net_driverlevel].controlSock = controlSocket;
836                 if (listening)
837                         net_drivers[net_driverlevel].Listen (true);
838                 }
839
840         if (*my_ipx_address)
841                 Con_DPrintf("IPX address %s\n", my_ipx_address);
842         if (*my_tcpip_address)
843                 Con_DPrintf("TCP/IP address %s\n", my_tcpip_address);
844
845         Master_Init ();
846 }
847
848 /*
849 ====================
850 NET_Shutdown
851 ====================
852 */
853
854 void NET_Shutdown (void)
855 {
856         SetNetTime();
857
858         while (net_activeSockets)
859                 NET_Close(net_activeSockets);
860
861 //
862 // shutdown the drivers
863 //
864         for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++)
865         {
866                 if (net_drivers[net_driverlevel].initialized == true)
867                 {
868                         net_drivers[net_driverlevel].Shutdown ();
869                         net_drivers[net_driverlevel].initialized = false;
870                 }
871         }
872
873         Mem_FreePool(&net_mempool);
874 }
875
876
877 static PollProcedure *pollProcedureList = NULL;
878
879 void NET_Poll(void)
880 {
881         PollProcedure *pp;
882
883         if (!configRestored)
884                 configRestored = true;
885
886         SetNetTime();
887
888         for (pp = pollProcedureList; pp; pp = pp->next)
889         {
890                 if (pp->nextTime > net_time)
891                         break;
892                 pollProcedureList = pp->next;
893                 pp->procedure(pp->arg);
894         }
895 }
896
897
898 void SchedulePollProcedure(PollProcedure *proc, double timeOffset)
899 {
900         PollProcedure *pp, *prev;
901
902         proc->nextTime = Sys_DoubleTime() + timeOffset;
903         for (pp = pollProcedureList, prev = NULL; pp; pp = pp->next)
904         {
905                 if (pp->nextTime >= proc->nextTime)
906                         break;
907                 prev = pp;
908         }
909
910         if (prev == NULL)
911         {
912                 proc->next = pollProcedureList;
913                 pollProcedureList = proc;
914                 return;
915         }
916
917         proc->next = pp;
918         prev->next = proc;
919 }
920