quakeforge/libs/net/net_main.c
Bill Currie 12c84046f3 [cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.

As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.

The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).

While not used yet (partly due to working out the design), cvars can
have a validation function.

Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.

nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-24 19:15:22 +09:00

931 lines
19 KiB
C

/*
net_main.c
@description@
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include "QF/cbuf.h"
#include "QF/cmd.h"
#include "QF/cvar.h"
#include "QF/msg.h"
#include "QF/qargs.h"
#include "QF/quakeio.h"
#include "QF/sizebuf.h"
#include "QF/sys.h"
#include "netmain.h"
#include "net_vcr.h"
#include "../nq/include/server.h"
int net_is_dedicated = 0;
qsocket_t *net_activeSockets = NULL;
qsocket_t *net_freeSockets = NULL;
int net_numsockets = 0;
qboolean tcpipAvailable = false;
int net_hostport;
int DEFAULTnet_hostport = 26000;
char my_tcpip_address[NET_NAMELEN];
static qboolean listening = false;
qboolean slistInProgress = false;
qboolean slistSilent = false;
qboolean slistLocal = true;
static double slistStartTime;
static int slistLastShown;
static void Slist_Send (void *);
static void Slist_Poll (void *);
PollProcedure slistSendProcedure = { NULL, 0.0, Slist_Send };
PollProcedure slistPollProcedure = { NULL, 0.0, Slist_Poll };
static sizebuf_t _net_message_message;
static qmsg_t _net_message = { 0, 0, &_net_message_message };
qmsg_t *net_message = &_net_message;
unsigned net_activeconnections = 0;
int messagesSent = 0;
int messagesReceived = 0;
int unreliableMessagesSent = 0;
int unreliableMessagesReceived = 0;
float net_messagetimeout;
static cvar_t net_messagetimeout_cvar = {
.name = "net_messagetimeout",
.description =
"None",
.default_value = "300",
.flags = CVAR_NONE,
.value = { .type = &cexpr_float, .value = &net_messagetimeout },
};
char *hostname;
static cvar_t hostname_cvar = {
.name = "hostname",
.description =
"None",
.default_value = "UNNAMED",
.flags = CVAR_NONE,
.value = { .type = 0, .value = &hostname },
};
QFile *vcrFile;
qboolean recording = false;
// these two macros are to make the code more readable
#define sfunc net_drivers[sock->driver]
#define dfunc net_drivers[net_driverlevel]
int net_driverlevel;
double net_time;
static int hostCacheCount = 0;
static hostcache_t hostcache[HOSTCACHESIZE];
static cbuf_t *net_cbuf;
double
SetNetTime (void)
{
net_time = Sys_DoubleTime ();
return net_time;
}
qsocket_t *
NET_NewQSocket (void)
{
qsocket_t *sock;
if (net_freeSockets == NULL)
return NULL;
if (net_activeconnections >= svs.maxclients)
return NULL;
// get one from free list
sock = net_freeSockets;
net_freeSockets = sock->next;
// add it to active list
sock->next = net_activeSockets;
net_activeSockets = sock;
sock->disconnected = false;
sock->connecttime = net_time;
strcpy (sock->address, "UNSET ADDRESS");
sock->driver = net_driverlevel;
sock->socket = 0;
sock->driverdata = NULL;
sock->canSend = true;
sock->sendNext = false;
sock->lastMessageTime = net_time;
sock->ackSequence = 0;
sock->sendSequence = 0;
sock->unreliableSendSequence = 0;
sock->sendMessageLength = 0;
sock->receiveSequence = 0;
sock->unreliableReceiveSequence = 0;
sock->receiveMessageLength = 0;
return sock;
}
void
NET_FreeQSocket (qsocket_t *sock)
{
qsocket_t *s;
// remove it from active list
if (sock == net_activeSockets)
net_activeSockets = net_activeSockets->next;
else {
for (s = net_activeSockets; s; s = s->next)
if (s->next == sock) {
s->next = sock->next;
break;
}
if (!s)
Sys_Error ("NET_FreeQSocket: not active");
}
// add it to free list
sock->next = net_freeSockets;
net_freeSockets = sock;
sock->disconnected = true;
}
static void
NET_Listen_f (void)
{
if (Cmd_Argc () != 2) {
Sys_Printf ("\"listen\" is \"%u\"\n", listening ? 1 : 0);
return;
}
listening = atoi (Cmd_Argv (1)) ? true : false;
for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
net_driverlevel++) {
if (net_drivers[net_driverlevel].initialized == false)
continue;
dfunc.Listen (listening);
}
}
static void
MaxPlayers_f (void)
{
unsigned n;
if (Cmd_Argc () != 2) {
Sys_Printf ("\"maxplayers\" is \"%u\"\n", svs.maxclients);
return;
}
if (sv.active) {
Sys_Printf
("maxplayers can not be changed while a server is running.\n");
return;
}
n = atoi (Cmd_Argv (1));
if (n < 1)
n = 1;
if (n > svs.maxclientslimit) {
n = svs.maxclientslimit;
Sys_Printf ("\"maxplayers\" set to \"%u\"\n", n);
}
if ((n == 1) && listening)
Cbuf_AddText (net_cbuf, "listen 0\n");
if ((n > 1) && (!listening))
Cbuf_AddText (net_cbuf, "listen 1\n");
svs.maxclients = n;
if (n == 1)
Cvar_Set ("deathmatch", "0");
else
Cvar_Set ("deathmatch", "1");
}
static void
NET_Port_f (void)
{
int n;
if (Cmd_Argc () != 2) {
Sys_Printf ("\"port\" is \"%u\"\n", net_hostport);
return;
}
n = atoi (Cmd_Argv (1));
if (n < 1 || n > 65534) {
Sys_Printf ("Bad value, must be between 1 and 65534\n");
return;
}
DEFAULTnet_hostport = n;
net_hostport = n;
if (listening) {
// force a change to the new port
Cbuf_AddText (net_cbuf, "listen 0\n");
Cbuf_AddText (net_cbuf, "listen 1\n");
}
}
static void
PrintSlistHeader (void)
{
Sys_Printf ("Server Map Users\n");
Sys_Printf ("--------------- --------------- -----\n");
slistLastShown = 0;
}
void
NET_AddCachedHost (const char *name, const char *map, const char *cname,
int users, int maxusers, int driver, int ldriver,
const netadr_t *addr)
{
if (hostCacheCount == HOSTCACHESIZE) {
return;
}
for (int i = 0; i < hostCacheCount; i++) {
// addr will be 0 for loopback, and there can be only one loopback
// server.
if (!addr || !memcmp (addr, &hostcache[i].addr, sizeof (netadr_t))) {
return;
}
}
const int namesize = sizeof (hostcache[0].name) - 1;
const int mapsize = sizeof (hostcache[0].map) - 1;
const int cnamesize = sizeof (hostcache[0].cname) - 1;
hostcache_t *host = &hostcache[hostCacheCount++];
strncpy (host->name, name, namesize);
strncpy (host->map, map, mapsize);
strncpy (host->cname, cname, cnamesize);
host->name[namesize] = 0;
host->map[mapsize] = 0;
host->cname[cnamesize] = 0;
host->users = users;
host->maxusers = maxusers;
host->driver = driver;
host->ldriver = ldriver;
if (addr) {
host->addr = *addr;
} else {
memset (&host->addr, 0, sizeof (host->addr));
}
// check for and resolve name conflicts
for (int i = 0; i < hostCacheCount - 1; i++) {
if (strcasecmp (host->name, hostcache[i].name) == 0) {
int len = strlen (host->name);
if (len < namesize && host->name[len - 1] > '8') {
host->name[len] = '0';
host->name[len + 1] = 0;
} else {
host->name[len - 1]++;
}
i = -1; // restart loop
}
}
}
static void
PrintSlist (void)
{
int n;
for (n = slistLastShown; n < hostCacheCount; n++) {
if (hostcache[n].maxusers)
Sys_Printf ("%-15.15s %-15.15s %2u/%2u\n", hostcache[n].name,
hostcache[n].map, hostcache[n].users,
hostcache[n].maxusers);
else
Sys_Printf ("%-15.15s %-15.15s\n", hostcache[n].name,
hostcache[n].map);
}
slistLastShown = n;
}
static void
PrintSlistTrailer (void)
{
if (hostCacheCount)
Sys_Printf ("== end list ==\n\n");
else
Sys_Printf ("No Quake servers found.\n\n");
}
static void
NET_Slist_f (void)
{
if (slistInProgress)
return;
if (!slistSilent) {
Sys_Printf ("Looking for Quake servers...\n");
PrintSlistHeader ();
}
slistInProgress = true;
slistStartTime = Sys_DoubleTime ();
SchedulePollProcedure (&slistSendProcedure, 0.0);
SchedulePollProcedure (&slistPollProcedure, 0.1);
hostCacheCount = 0;
}
static void
Slist_Send (void *unused)
{
for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
net_driverlevel++) {
if (!slistLocal && net_driverlevel == 0)
continue;
if (net_drivers[net_driverlevel].initialized == false)
continue;
dfunc.SearchForHosts (true);
}
if ((Sys_DoubleTime () - slistStartTime) < 0.5)
SchedulePollProcedure (&slistSendProcedure, 0.75);
}
static void
Slist_Poll (void *unused)
{
for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
net_driverlevel++) {
if (!slistLocal && net_driverlevel == 0)
continue;
if (net_drivers[net_driverlevel].initialized == false)
continue;
dfunc.SearchForHosts (false);
}
if (!slistSilent)
PrintSlist ();
if ((Sys_DoubleTime () - slistStartTime) < 1.5) {
SchedulePollProcedure (&slistPollProcedure, 0.1);
return;
}
if (!slistSilent)
PrintSlistTrailer ();
slistInProgress = false;
slistSilent = false;
slistLocal = true;
}
qsocket_t *
NET_Connect (const char *host)
{
qsocket_t *ret;
int n;
int numdrivers = net_numdrivers;
SetNetTime ();
if (host && *host == 0)
host = NULL;
if (host) {
if (strcasecmp (host, "local") == 0) {
numdrivers = 1;
goto JustDoIt;
}
if (hostCacheCount) {
for (n = 0; n < hostCacheCount; n++)
if (strcasecmp (host, hostcache[n].name) == 0) {
host = hostcache[n].cname;
break;
}
if (n < hostCacheCount)
goto JustDoIt;
}
}
slistSilent = host ? true : false;
NET_Slist_f ();
while (slistInProgress)
NET_Poll ();
if (host == NULL) {
if (hostCacheCount != 1)
return NULL;
host = hostcache[0].cname;
Sys_Printf ("Connecting to...\n%s @ %s\n\n", hostcache[0].name, host);
}
if (hostCacheCount)
for (n = 0; n < hostCacheCount; n++)
if (strcasecmp (host, hostcache[n].name) == 0) {
host = hostcache[n].cname;
break;
}
JustDoIt:
for (net_driverlevel = 0; net_driverlevel < numdrivers; net_driverlevel++) {
if (net_drivers[net_driverlevel].initialized == false)
continue;
ret = dfunc.Connect (host);
if (ret)
return ret;
}
if (host) {
Sys_Printf ("\n");
PrintSlistHeader ();
PrintSlist ();
PrintSlistTrailer ();
}
return NULL;
}
struct {
double time;
int op;
intptr_t session;
} vcrConnect;
qsocket_t *
NET_CheckNewConnections (void)
{
qsocket_t *ret;
SetNetTime ();
for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
net_driverlevel++) {
if (net_drivers[net_driverlevel].initialized == false)
continue;
if (net_driverlevel && listening == false)
continue;
ret = dfunc.CheckNewConnections ();
if (ret) {
if (recording) {
vcrConnect.time = host_time;
vcrConnect.op = VCR_OP_CONNECT;
vcrConnect.session = (intptr_t) ret;
Qwrite (vcrFile, &vcrConnect, sizeof (vcrConnect));
Qwrite (vcrFile, ret->address, NET_NAMELEN);
}
return ret;
}
}
if (recording) {
vcrConnect.time = host_time;
vcrConnect.op = VCR_OP_CONNECT;
vcrConnect.session = 0;
Qwrite (vcrFile, &vcrConnect, sizeof (vcrConnect));
}
return NULL;
}
void
NET_Close (qsocket_t *sock)
{
if (!sock)
return;
if (sock->disconnected)
return;
SetNetTime ();
// call the driver_Close function
sfunc.Close (sock);
Sys_MaskPrintf (SYS_net, "closing socket\n");
NET_FreeQSocket (sock);
}
struct {
double time;
int op;
intptr_t session;
int ret;
int len;
} vcrGetMessage;
int
NET_GetMessage (qsocket_t *sock)
{
int ret;
if (!sock)
return -1;
if (sock->disconnected) {
Sys_Printf ("NET_GetMessage: disconnected socket\n");
return -1;
}
SetNetTime ();
ret = sfunc.QGetMessage (sock);
// see if this connection has timed out
if (ret == 0 && sock->driver) {
if (net_time - sock->lastMessageTime > net_messagetimeout) {
Sys_MaskPrintf (SYS_net, "socket timed out\n");
NET_Close (sock);
return -1;
}
}
if (ret > 0) {
if (sock->driver) {
sock->lastMessageTime = net_time;
if (ret == 1)
messagesReceived++;
else if (ret == 2)
unreliableMessagesReceived++;
}
if (recording) {
vcrGetMessage.time = host_time;
vcrGetMessage.op = VCR_OP_GETMESSAGE;
vcrGetMessage.session = (intptr_t) sock;
vcrGetMessage.ret = ret;
vcrGetMessage.len = _net_message_message.cursize;
Qwrite (vcrFile, &vcrGetMessage, 24);
Qwrite (vcrFile, _net_message_message.data,
_net_message_message.cursize);
}
} else {
if (recording) {
vcrGetMessage.time = host_time;
vcrGetMessage.op = VCR_OP_GETMESSAGE;
vcrGetMessage.session = (intptr_t) sock;
vcrGetMessage.ret = ret;
Qwrite (vcrFile, &vcrGetMessage, 20);
}
}
return ret;
}
struct {
double time;
int op;
intptr_t session;
int r;
} vcrSendMessage;
int
NET_SendMessage (qsocket_t *sock, sizebuf_t *data)
{
int r;
if (!sock)
return -1;
if (sock->disconnected) {
Sys_Printf ("NET_SendMessage: disconnected socket\n");
return -1;
}
SetNetTime ();
r = sfunc.QSendMessage (sock, data);
if (r == 1 && sock->driver)
messagesSent++;
if (recording) {
vcrSendMessage.time = host_time;
vcrSendMessage.op = VCR_OP_SENDMESSAGE;
vcrSendMessage.session = (intptr_t) sock;
vcrSendMessage.r = r;
Qwrite (vcrFile, &vcrSendMessage, 20);
}
return r;
}
int
NET_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data)
{
int r;
if (!sock)
return -1;
if (sock->disconnected) {
Sys_Printf ("NET_SendMessage: disconnected socket\n");
return -1;
}
SetNetTime ();
r = sfunc.SendUnreliableMessage (sock, data);
if (r == 1 && sock->driver)
unreliableMessagesSent++;
if (recording) {
vcrSendMessage.time = host_time;
vcrSendMessage.op = VCR_OP_SENDMESSAGE;
vcrSendMessage.session = (intptr_t) sock;
vcrSendMessage.r = r;
Qwrite (vcrFile, &vcrSendMessage, 20);
}
return r;
}
qboolean
NET_CanSendMessage (qsocket_t *sock)
{
int r;
if (!sock)
return false;
if (sock->disconnected)
return false;
SetNetTime ();
r = sfunc.CanSendMessage (sock);
if (recording) {
vcrSendMessage.time = host_time;
vcrSendMessage.op = VCR_OP_CANSENDMESSAGE;
vcrSendMessage.session = (intptr_t) sock;
vcrSendMessage.r = r;
Qwrite (vcrFile, &vcrSendMessage, 20);
}
return r;
}
int
NET_SendToAll (sizebuf_t *data, double blocktime)
{
double start;
unsigned i;
int count = 0;
qboolean state1[MAX_SCOREBOARD]; /* can we send */
qboolean state2[MAX_SCOREBOARD]; /* did we send */
for (i = 0, host_client = svs.clients; i < svs.maxclients;
i++, host_client++) {
if (host_client->netconnection && host_client->active) {
if (host_client->netconnection->driver == 0) {
NET_SendMessage (host_client->netconnection, data);
state1[i] = true;
state2[i] = true;
continue;
}
count++;
state1[i] = false;
state2[i] = false;
} else {
state1[i] = true;
state2[i] = true;
}
}
start = Sys_DoubleTime ();
while (count) {
count = 0;
for (i = 0, host_client = svs.clients; i < svs.maxclients;
i++, host_client++) {
if (!state1[i]) {
if (NET_CanSendMessage (host_client->netconnection)) {
state1[i] = true;
NET_SendMessage (host_client->netconnection, data);
} else {
NET_GetMessage (host_client->netconnection);
}
count++;
continue;
}
if (!state2[i]) {
if (NET_CanSendMessage (host_client->netconnection)) {
state2[i] = true;
} else {
NET_GetMessage (host_client->netconnection);
}
count++;
continue;
}
}
if ((Sys_DoubleTime () - start) > blocktime)
break;
}
return count;
}
//=============================================================================
static void
NET_shutdown (void *data)
{
qsocket_t *sock;
SetNetTime ();
for (sock = net_activeSockets; sock; sock = sock->next)
NET_Close (sock);
//
// shutdown the drivers
//
for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
net_driverlevel++) {
if (net_drivers[net_driverlevel].initialized == true) {
net_drivers[net_driverlevel].Shutdown ();
net_drivers[net_driverlevel].initialized = false;
}
}
if (vcrFile) {
Sys_Printf ("Closing vcrfile.\n");
Qclose (vcrFile);
}
}
void
NET_Init (cbuf_t *cbuf)
{
int i;
int controlSocket;
qsocket_t *s;
net_cbuf = cbuf;
Sys_RegisterShutdown (NET_shutdown, 0);
if (COM_CheckParm ("-playback")) {
net_numdrivers = 1;
net_drivers[0].Init = VCR_Init;
}
if (COM_CheckParm ("-record"))
recording = true;
i = COM_CheckParm ("-port");
if (!i)
i = COM_CheckParm ("-udpport");
if (!i)
i = COM_CheckParm ("-ipxport");
if (i) {
if (i < com_argc - 1)
DEFAULTnet_hostport = atoi (com_argv[i + 1]);
else
Sys_Error ("NET_Init: you must specify a number after -port");
}
net_hostport = DEFAULTnet_hostport;
if (COM_CheckParm ("-listen") || net_is_dedicated)
listening = true;
net_numsockets = svs.maxclientslimit;
if (!net_is_dedicated)
net_numsockets++;
SetNetTime ();
for (i = 0; i < net_numsockets; i++) {
s = (qsocket_t *) Hunk_AllocName (0, sizeof (qsocket_t), "qsocket");
s->next = net_freeSockets;
net_freeSockets = s;
s->disconnected = true;
}
// allocate space for network message buffer
SZ_Alloc (&_net_message_message, NET_MAXMESSAGE);
Cvar_Register (&net_messagetimeout_cvar, 0, 0);
Cvar_Register (&hostname_cvar, 0, 0);
Cmd_AddCommand ("slist", NET_Slist_f, "No Description");
Cmd_AddCommand ("listen", NET_Listen_f, "No Description");
Cmd_AddCommand ("maxplayers", MaxPlayers_f, "No Description");
Cmd_AddCommand ("port", NET_Port_f, "No Description");
// initialize all the drivers
for (net_driverlevel = 0; net_driverlevel < net_numdrivers;
net_driverlevel++) {
controlSocket = net_drivers[net_driverlevel].Init ();
if (controlSocket == -1)
continue;
net_drivers[net_driverlevel].initialized = true;
net_drivers[net_driverlevel].controlSock = controlSocket;
if (listening)
net_drivers[net_driverlevel].Listen (true);
}
if (*my_tcpip_address)
Sys_MaskPrintf (SYS_net, "TCP/IP address %s\n", my_tcpip_address);
}
static PollProcedure *pollProcedureList = NULL;
void
NET_Poll (void)
{
PollProcedure *pp;
SetNetTime ();
for (pp = pollProcedureList; pp; pp = pp->next) {
if (pp->nextTime > net_time)
break;
pollProcedureList = pp->next;
pp->procedure (pp->arg);
}
}
void
SchedulePollProcedure (PollProcedure *proc, double timeOffset)
{
PollProcedure *pp, *prev;
proc->nextTime = Sys_DoubleTime () + timeOffset;
for (pp = pollProcedureList, prev = NULL; pp; pp = pp->next) {
if (pp->nextTime >= proc->nextTime)
break;
prev = pp;
}
if (prev == NULL) {
proc->next = pollProcedureList;
pollProcedureList = proc;
return;
}
proc->next = pp;
prev->next = proc;
}