Cause a command to be executed after a delay.
============
*/
-static cmd_input_t *Cbuf_LinkGet(cmd_buf_t *cbuf, cmd_input_t *existing);
+static void Cbuf_ParseText(cmd_state_t *cmd, llist_t *head, cmd_input_t *existing, const char *text, qbool allowpending);
+static void Cbuf_LinkString(cmd_state_t *cmd, llist_t *head, cmd_input_t *existing, const char *text, qbool leavepending, unsigned int cmdsize);
static void Cmd_Defer_f (cmd_state_t *cmd)
{
cmd_input_t *current;
cmd_buf_t *cbuf = cmd->cbuf;
+ unsigned int cmdsize;
if(Cmd_Argc(cmd) == 1)
{
Con_Printf("No commands are pending.\n");
else
{
- llist_t *pos;
- List_For_Each(pos, &cbuf->deferred)
- {
- current = List_Entry(*pos, cmd_input_t, list);
+ List_For_Each_Entry(current, &cbuf->deferred, cmd_input_t, list)
Con_Printf("-> In %9.2f: %s\n", current->delay, current->text);
- }
}
}
else if(Cmd_Argc(cmd) == 2 && !strcasecmp("clear", Cmd_Argv(cmd, 1)))
{
while(!List_Is_Empty(&cbuf->deferred))
+ {
+ cbuf->size -= List_Entry(cbuf->deferred.next, cmd_input_t, list)->length;
List_Move_Tail(cbuf->deferred.next, &cbuf->free);
+ }
}
- else if(Cmd_Argc(cmd) == 3)
+ else if(Cmd_Argc(cmd) == 3 && (cmdsize = strlen(Cmd_Argv(cmd, 2))) )
{
- const char *text = Cmd_Argv(cmd, 2);
- current = Cbuf_LinkGet(cbuf, NULL);
- current->length = strlen(text);
- current->source = cmd;
- current->delay = atof(Cmd_Argv(cmd, 1));
-
- if(current->size < current->length)
- {
- current->text = (char *)Mem_Realloc(cbuf_mempool, current->text, current->length + 1);
- current->size = current->length;
- }
+ Cbuf_Lock(cbuf);
- strlcpy(current->text, text, current->length + 1);
+ Cbuf_LinkString(cmd, &cbuf->deferred, NULL, Cmd_Argv(cmd, 2), false, cmdsize);
+ List_Entry(cbuf->deferred.prev, cmd_input_t, list)->delay = atof(Cmd_Argv(cmd, 1));
- List_Move_Tail(¤t->list, &cbuf->deferred);
+ Cbuf_Unlock(cbuf);
}
else
{
COMMAND BUFFER
+ * The Quake command-line is super basic. It can be entered in the console
+ * or in config files. A semicolon is used to terminate a command and chain
+ * them together. Otherwise, a newline delineates command input.
+ *
+ * In most engines, the Quake command-line is a simple linear text buffer that
+ * is parsed when it executes. In Darkplaces, we use a linked list of command
+ * input and parse the input on the spot.
+ *
+ * This was done because Darkplaces allows multiple command interpreters on the
+ * same thread. Previously, each interpreter maintained its own buffer and this
+ * caused problems related to execution order, and maintaining a single simple
+ * buffer for all interpreters makes it non-trivial to keep track of which
+ * command should execute on which interpreter.
+
=============================================================================
*/
-static cmd_input_t *Cbuf_LinkGet(cmd_buf_t *cbuf, cmd_input_t *existing)
+/*
+============
+Cbuf_NodeGet
+
+Returns an existing buffer node for appending or reuse, or allocates a new one
+============
+*/
+static cmd_input_t *Cbuf_NodeGet(cmd_buf_t *cbuf, cmd_input_t *existing)
{
- cmd_input_t *ret = NULL;
+ cmd_input_t *node;
if(existing && existing->pending)
- ret = existing;
+ node = existing;
else if(!List_Is_Empty(&cbuf->free))
{
- ret = List_Entry(*cbuf->free.next, cmd_input_t, list);
- ret->length = 0;
- ret->pending = false;
+ node = List_Entry(cbuf->free.next, cmd_input_t, list);
+ node->length = node->pending = 0;
+ }
+ else
+ {
+ node = (cmd_input_t *)Mem_Alloc(cbuf_mempool, sizeof(cmd_input_t));
+ node->list.prev = node->list.next = &node->list;
+ node->size = node->length = node->pending = 0;
}
- return ret;
-}
-
-static cmd_input_t *Cmd_AllocInputNode(void)
-{
- cmd_input_t *node = (cmd_input_t *)Mem_Alloc(cbuf_mempool, sizeof(cmd_input_t));
- node->list.prev = node->list.next = &node->list;
- node->size = node->length = node->pending = 0;
return node;
}
-static size_t Cmd_ParseInput (cmd_input_t **output, char **input)
-{
- size_t pos, cmdsize = 0, start = 0;
- qbool command = false, lookahead = false;
- qbool quotes = false, comment = false;
- qbool escaped = false;
-
- /*
- * The Quake command-line is super basic. It can be entered in the console
- * or in config files. A semicolon is used to terminate a command and chain
- * them together. Otherwise, a newline delineates command input.
- *
- * In most engines, the Quake command-line is a simple linear text buffer that
- * is parsed when it executes. In Darkplaces, we use a linked list of command
- * input and parse the input on the spot.
- *
- * This was done because Darkplaces allows multiple command interpreters on the
- * same thread. Previously, each interpreter maintained its own buffer and this
- * caused problems related to execution order, and maintaining a single simple
- * buffer for all interpreters makes it non-trivial to keep track of which
- * command should execute on which interpreter.
- */
-
- // Run until command and lookahead are both true, or until we run out of input.
- for (pos = 0; (*input)[pos]; pos++)
- {
- // Look for newlines and semicolons. Ignore semicolons in quotes.
- switch((*input)[pos])
- {
- case '\r':
- case '\n':
- command = false;
- comment = false;
- break;
- default:
- if(!comment) // Not a newline so far. Still not a valid command yet.
- {
- if(!quotes && (*input)[pos] == ';') // Ignore semicolons in quotes.
- command = false;
- else if (ISCOMMENT((*input), pos)) // Comments
- {
- comment = true;
- command = false;
- }
- else
- {
- command = true;
- if(!lookahead)
- {
- if(!cmdsize)
- start = pos;
- cmdsize++;
- }
+/*
+============
+Cbuf_LinkString
- switch((*input)[pos])
- {
- case '"':
- if (!escaped)
- quotes = !quotes;
- else
- escaped = false;
- break;
- case '\\':
- if (!escaped && quotes)
- escaped = true;
- else if (escaped)
- escaped = false;
- break;
- }
- }
- }
- }
- if(cmdsize && !command)
- lookahead = true;
+Copies a command string into a buffer node
+============
+*/
+static void Cbuf_LinkString(cmd_state_t *cmd, llist_t *head, cmd_input_t *existing, const char *text, qbool leavepending, unsigned int cmdsize)
+{
+ cmd_buf_t *cbuf = cmd->cbuf;
+ cmd_input_t *node = Cbuf_NodeGet(cbuf, existing);
+ unsigned int offset = node->length; // > 0 if(pending)
- if(command && lookahead)
- break;
+ // node will match existing if its text was pending continuation
+ if(node != existing)
+ {
+ node->source = cmd;
+ List_Move_Tail(&node->list, head);
}
- if(cmdsize)
+ node->length += cmdsize;
+ if(node->size < node->length)
{
- size_t offset = 0;
-
- if(!*output)
- *output = Cmd_AllocInputNode();
-
- if((*output)->pending)
- offset = (*output)->length;
-
- (*output)->length += cmdsize;
-
- if((*output)->size < (*output)->length)
- {
- (*output)->text = (char *)Mem_Realloc(cbuf_mempool, (*output)->text, (*output)->length + 1);
- (*output)->size = (*output)->length;
- }
-
- strlcpy(&(*output)->text[offset], &(*input)[start], cmdsize + 1);
- (*output)->pending = !lookahead;
+ node->text = (char *)Mem_Realloc(cbuf_mempool, node->text, node->length + 1);
+ node->size = node->length;
}
+ cbuf->size += cmdsize;
- // Set input to its new position. Can be NULL.
- *input = &(*input)[pos];
-
- return cmdsize;
+ strlcpy(&node->text[offset], text, cmdsize + 1); // always sets the last char to \0
+ //Con_Printf("^5Cbuf_LinkString(): %s `^7%s^5`\n", node->pending ? "append" : "new", &node->text[offset]);
+ node->pending = leavepending;
}
-// Cloudwalk: Not happy with this, but it works.
-static void Cbuf_LinkCreate(cmd_state_t *cmd, llist_t *head, cmd_input_t *existing, const char *text)
+/*
+============
+Cbuf_ParseText
+
+Parses text to isolate command strings for linking into the buffer
+separators: \n \r or unquoted and uncommented ';'
+============
+*/
+static void Cbuf_ParseText(cmd_state_t *cmd, llist_t *head, cmd_input_t *existing, const char *text, qbool allowpending)
{
- char *in = (char *)&text[0];
- cmd_buf_t *cbuf = cmd->cbuf;
- size_t totalsize = 0, newsize = 0;
- cmd_input_t *current = NULL;
+ unsigned int cmdsize = 0, start = 0, pos;
+ qbool quotes = false, comment = false;
- // Slide the pointer down until we reach the end
- while(*in)
+ for (pos = 0; text[pos]; ++pos)
{
- current = Cbuf_LinkGet(cbuf, existing);
- newsize = Cmd_ParseInput(¤t, &in);
-
- // Valid command
- if(newsize)
+ switch(text[pos])
{
- if(current != existing)
- {
- current->source = cmd;
- List_Move_Tail(¤t->list, head);
- }
+ case ';':
+ if (comment || quotes)
+ break;
+ case '\r':
+ case '\n':
+ comment = false;
+ quotes = false; // matches div0-stable
+ if (cmdsize)
+ {
+ Cbuf_LinkString(cmd, head, existing, &text[start], false, cmdsize);
+ cmdsize = 0;
+ }
+ else if (existing && existing->pending) // all I got was this lousy \n
+ existing->pending = false;
+ continue; // don't increment cmdsize
- totalsize += newsize;
+ case '/':
+ if (!quotes && text[pos + 1] == '/' && (pos == 0 || ISWHITESPACE(text[pos - 1])))
+ comment = true;
+ break;
+ case '"':
+ if (!comment && (pos == 0 || text[pos - 1] != '\\'))
+ quotes = !quotes;
+ break;
+ }
+
+ if (!comment)
+ {
+ if (!cmdsize)
+ start = pos;
+ ++cmdsize;
}
- else if (current == existing && !totalsize)
- current->pending = false;
- current = NULL;
}
- cbuf->size += totalsize;
+ if (cmdsize) // the line didn't end yet but we do have a string
+ Cbuf_LinkString(cmd, head, existing, &text[start], allowpending, cmdsize);
}
/*
Con_Print("Cbuf_AddText: overflow\n");
else
{
- Cbuf_LinkCreate(cmd, &llist, (List_Is_Empty(&cbuf->start) ? NULL : List_Entry(*cbuf->start.prev, cmd_input_t, list)), text);
- if(!List_Is_Empty(&llist))
- List_Splice_Tail(&llist, &cbuf->start);
+ // If the string terminates but the (last) line doesn't, the node will be left in the pending state (to be continued).
+ Cbuf_ParseText(cmd, &llist, (List_Is_Empty(&cbuf->start) ? NULL : List_Entry(cbuf->start.prev, cmd_input_t, list)), text, true);
+ List_Splice_Tail(&llist, &cbuf->start);
}
Cbuf_Unlock(cbuf);
}
Cbuf_InsertText
Adds command text immediately after the current command
-FIXME: actually change the command buffer to do less copying
============
*/
void Cbuf_InsertText (cmd_state_t *cmd, const char *text)
Cbuf_Lock(cbuf);
- // we need to memmove the existing text and stuff this in before it...
if (cbuf->size + l >= cbuf->maxsize)
Con_Print("Cbuf_InsertText: overflow\n");
else
{
- Cbuf_LinkCreate(cmd, &llist, List_Entry(*cbuf->start.next, cmd_input_t, list), text);
- if(!List_Is_Empty(&llist))
- List_Splice(&llist, &cbuf->start);
+ // bones_was_here assertion: when prepending to the buffer it never makes sense to leave node(s) in the `pending` state,
+ // it would have been impossible to append to such text later in the old raw text buffer,
+ // and allowing it causes bugs when .cfg files lack \n at EOF (see: https://gitlab.com/xonotic/darkplaces/-/issues/378).
+ Cbuf_ParseText(cmd, &llist, (List_Is_Empty(&cbuf->start) ? NULL : List_Entry(cbuf->start.next, cmd_input_t, list)), text, false);
+ List_Splice(&llist, &cbuf->start);
}
Cbuf_Unlock(cbuf);
*/
static void Cbuf_Execute_Deferred (cmd_buf_t *cbuf)
{
- llist_t *pos;
- cmd_input_t *current;
- double eat;
+ cmd_input_t *current, *n;
+ vec_t eat;
if (host.realtime - cbuf->deferred_oldtime < 0 || host.realtime - cbuf->deferred_oldtime > 1800)
cbuf->deferred_oldtime = host.realtime;
eat = host.realtime - cbuf->deferred_oldtime;
- if (eat < (1.0 / 120.0))
+ if (eat < 1/128)
return;
cbuf->deferred_oldtime = host.realtime;
- List_For_Each(pos, &cbuf->deferred)
+ List_For_Each_Entry_Safe(current, n, &cbuf->deferred, cmd_input_t, list)
{
- current = List_Entry(*pos, cmd_input_t, list);
current->delay -= eat;
if(current->delay <= 0)
{
- cbuf->size += current->length;
- List_Move(pos, &cbuf->start);
- // We must return and come back next frame or the engine will freeze. Fragile... like glass :3
- return;
+ Cbuf_AddText(current->source, current->text); // parse deferred string and append its cmdstring(s)
+ List_Entry(cbuf->start.prev, cmd_input_t, list)->pending = false; // faster than div0-stable's Cbuf_AddText(";\n");
+ List_Move_Tail(¤t->list, &cbuf->free); // make deferred string memory available for reuse
+ cbuf->size -= current->length;
}
}
}
Cbuf_Execute
============
*/
+extern qbool prvm_runawaycheck;
static qbool Cmd_PreprocessString(cmd_state_t *cmd, const char *intext, char *outtext, unsigned maxoutlen, cmd_alias_t *alias );
void Cbuf_Execute (cmd_buf_t *cbuf)
{
cmd_input_t *current;
char preprocessed[MAX_INPUTLINE];
char *firstchar;
+ unsigned int i = 0;
// LadyHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes
cbuf->tokenizebufferpos = 0;
* commands down. This is necessary because commands (exec, alias)
* can insert data at the beginning of the text buffer
*/
- current = List_Entry(*cbuf->start.next, cmd_input_t, list);
+ current = List_Entry(cbuf->start.next, cmd_input_t, list);
// Recycle memory so using WASD doesn't cause a malloc and free
List_Move_Tail(¤t->list, &cbuf->free);
cbuf->wait = false;
break;
}
+
+ if (++i == 1000000 && prvm_runawaycheck)
+ {
+ Con_Printf(CON_WARN "Cbuf_Execute: runaway loop counter hit limit of %d commands, clearing command buffers!\n", i);
+ while (!List_Is_Empty(&cbuf->start))
+ List_Move_Tail(cbuf->start.next, &cbuf->free);
+ while (!List_Is_Empty(&cbuf->deferred))
+ List_Move_Tail(cbuf->deferred.next, &cbuf->free);
+ cbuf->size = 0;
+ }
}
}
"csqc_polygons_defaultmaterial_nocullface 1\n"
"con_chatsound_team_mask 13\n"
"sv_gameplayfix_customstats 1\n"
+"mod_q1bsp_zero_hullsize_cutoff 8.03125\n"
);
break;
// Steel Storm: Burning Retribution csqc misinterprets CSQC_InputEvent if type is a value other than 0 or 1
Cvar_PrintHelp(cvar, cvar->name, true);
count++;
}
- for (int i = 0; i < cvar->aliasindex; i++)
+ for (char **cvar_alias = cvar->aliases; cvar_alias && *cvar_alias; cvar_alias++)
{
- if (matchpattern_with_separator(cvar->aliases[i], partial, true, "", false))
+ if (matchpattern_with_separator(*cvar_alias, partial, true, "", false))
{
Con_Printf ("cvar ");
- Cvar_PrintHelp(cvar, cvar->aliases[i], true);
+ Cvar_PrintHelp(cvar, *cvar_alias, true);
count++;
}
}
cmd_buf_t *cbuf;
cbuf_mempool = Mem_AllocPool("Command buffer", 0, NULL);
cbuf = (cmd_buf_t *)Mem_Alloc(cbuf_mempool, sizeof(cmd_buf_t));
- cbuf->maxsize = 655360;
+ cbuf->maxsize = CMDBUFSIZE;
cbuf->lock = Thread_CreateMutex();
cbuf->wait = false;
host.cbuf = cbuf;
Cmd_Argc
============
*/
-int Cmd_Argc (cmd_state_t *cmd)
+inline int Cmd_Argc (cmd_state_t *cmd)
{
return cmd->argc;
}
Cmd_Argv
============
*/
-const char *Cmd_Argv(cmd_state_t *cmd, int arg)
+inline const char *Cmd_Argv(cmd_state_t *cmd, int arg)
{
if (arg >= cmd->argc )
return cmd->null_string;
Cmd_Args
============
*/
-const char *Cmd_Args (cmd_state_t *cmd)
+inline const char *Cmd_Args (cmd_state_t *cmd)
{
return cmd->args;
}
}
}
-
func = (cmd_function_t *)Mem_Alloc(cmd->mempool, sizeof(cmd_function_t));
func->flags = flags;
func->name = cmd_name;
func->qcfunc = true; //[515]: csqc
func->next = cmd->userdefined->qc_functions;
+ // bones_was_here: if this QC command overrides an engine command, store its pointer
+ // to avoid doing this search at invocation if QC declines to handle this command.
+ for (cmd_function_t *f = cmd->engine_functions; f; f = f->next)
+ {
+ if (!strcmp(cmd_name, f->name))
+ {
+ Con_DPrintf("Adding QC override of engine command %s\n", cmd_name);
+ func->overridden = f;
+ break;
+ }
+ }
+
// insert it at the right alphanumeric position
for (prev = NULL, current = cmd->userdefined->qc_functions; current && strcmp(current->name, func->name) < 0; prev = current, current = current->next)
;
if(((func->flags & CF_CLIENT) && CL_VM_ConsoleCommand(text)) ||
((func->flags & CF_SERVER) && SV_VM_ConsoleCommand(text)))
return true;
+
+ if (func->overridden) // If this QC command overrides an engine command,
+ func = func->overridden; // fall back to that command.
}
if (func->flags & CF_SERVER_FROM_CLIENT)
{