Describing the Quake Command System
Re: Describing the Quake Command System
Random link to reddit, some small C json parser was posted earlier and I thought of this thread: http://www.reddit.com/r/programming/com ... rser_in_c/
Improve Quaddicted, send me a pull request: https://github.com/SpiritQuaddicted/Quaddicted-reviews
Re: Describing the Quake Command System
I don't like certain things about the Quake console. I'm writing a console from nothingness and will be building "objects", so I can have multiple consoles if I want with a very simplistic interface.
Here are some things that the Quake console can't handle well:
1) Resizes affect the actual data in the console buffer. Now, to some extent this is unavoidable using a wrapping buffer of fixed column width. I'm addressing this by having 3 types of line breaking. A soft line break functions like a newline, except if you resize the console, you can convert all the soft line breaks into spaces and print it back into the console buffer. No loss of data. The third kind of line break is an implied line break.
2) Word wrapping essentially loses trailing spaces in the data. By introducing line breaks into the data, I can preserve those. Leading spaces simply aren't rendered.
The line breaking method itself is a bit of puzzle to write and keep in simplified form. I wanted the function concise, clear as it can be and to fit on my screen.
More to do ...
Here are some things that the Quake console can't handle well:
1) Resizes affect the actual data in the console buffer. Now, to some extent this is unavoidable using a wrapping buffer of fixed column width. I'm addressing this by having 3 types of line breaking. A soft line break functions like a newline, except if you resize the console, you can convert all the soft line breaks into spaces and print it back into the console buffer. No loss of data. The third kind of line break is an implied line break.
2) Word wrapping essentially loses trailing spaces in the data. By introducing line breaks into the data, I can preserve those. Leading spaces simply aren't rendered.
The line breaking method itself is a bit of puzzle to write and keep in simplified form. I wanted the function concise, clear as it can be and to fit on my screen.
Code: Select all
// Note: Message_Print goes to the Console Log but not the Host_Log
void _Console_Print (fbool hostPrint, const char* in_text)
{
const char *text = in_text;
if (hostPrint)
Host_Log (text);
if (console.ready == False || console.console_text == NULL || text == NULL)
return;
while (*text)
{
int cursor, maxsize, stringlength = strlen(text);
// Grab as much text as possible and throw it into the buffer
wordlen = 0;
maxsize = console.columns - console.cursor_column + 1;
for (cursor = console.cursor_column; cursor < console.columns && text[cursor] && text[cursor] != '\n'; cursor ++)
{
if (text[cursor + 1] <= ' ') // If the next character is a space, we assured to have breakable text that fits of X length
wordlen = cursor - console.cursor_column + 1;
}
if (*text == '\n') // HARD LINE BREAK: hit newline char
{
Console_Row_Advance (HARD_LINE_FEED);
text ++;
}
else if (wordlen == maxsize) // NO LINE BREAK: simple row advance
{
memcpy (CURRENT_CELL, text, wordlen); console.cursor_column += wordlen; text += wordlen;
Console_Row_Advance (NO_LINE_FEED);
}
else if (wordlen > 0) // SOFT line break: word wrap
{
// Transfer wordlen to the console, put a soft carriage return after it and advance to next row
memcpy (CURRENT_CELL, text, wordlen); console.cursor_column += wordlen; text += wordlen;
Console_Row_Advance (SOFT_LINE_FEED);
}
else if (String_WordLength(text) <= console.columns) // Text too big for this line but fits on next
{
Console_Row_Advance (SOFT_LINE_FEED);
// Copy nothing! Let this run through again now that we advanced row.
}
else if (wordlen == 0) // NO LINE BREAK: Tough luck: Unbreakable text.
{
wordlen = maxsize;
memcpy (CURRENT_CELL, text, wordlen); console.cursor_column += wordlen; text += wordlen;
Console_Row_Advance (NO_LINE_FEED);
}
}
}
The night is young. How else can I annoy the world before sunsrise? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Re: Describing the Quake Command System
Lossless resizeable console achieved. So more more work than I thought.
Had to think in 1980s terms of carriage returns and line feeds (i.e. move cursor to end of row and advance separately).
Ended up turning console into an object in an unintentional way, had to write a number of macros and some of them favored a pointer to the console and by the time I was done turned all functions into pointers to the console as convenience. For simplicity, resizing the console I had to copy the original console to feed text to the new one and kill the old one. So I wasn't specifically after turning the console into an object.
Dealing with white spaces issues and newlines in various situations was a pain.
Had to think in 1980s terms of carriage returns and line feeds (i.e. move cursor to end of row and advance separately).
Ended up turning console into an object in an unintentional way, had to write a number of macros and some of them favored a pointer to the console and by the time I was done turned all functions into pointers to the console as convenience. For simplicity, resizing the console I had to copy the original console to feed text to the new one and kill the old one. So I wasn't specifically after turning the console into an object.
Dealing with white spaces issues and newlines in various situations was a pain.
The night is young. How else can I annoy the world before sunsrise? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Re: Describing the Quake Command System
I've had that in the back of my mind with this.Spike wrote:what about variable-width fonts?
The night is young. How else can I annoy the world before sunsrise? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Re: Describing the Quake Command System
Here's the code that I have that works:
The "wordlen" variable ... in theory, in the look that determines the wordlen, a look up each character's width in a table for variable font could be used per character while building that. On resize, I take the old console buffer and print each line into the new one.
I also have macros like this:
Code: Select all
void Console_PrintText (console_t* printcon, const char* in_text)
{
const char *text = in_text;
int stringlength = strlen(in_text);
#ifdef _DEBUG
const char* stuckcheck = text;
int stuckcheckcount = 0;
#endif
const char *output_row = ROW_START_CELLTEXT(printcon, console.cursor_row);
const char *output_cursor = CURSOR_CELL(printcon);
const char in_term = in_text[stringlength - 1];
if (console.ready == False || console.console_text == NULL || text == NULL)
return;
while (*text)
{
int cursor, maxsize, wordlen = 0, hitTerminator, leadingspaces = 0;
#ifdef _DEBUG
if (text == stuckcheck)
{
if (++stuckcheckcount > 3)
stuckcheckcount = stuckcheckcount;
}
else
{
stuckcheck = text;
stuckcheckcount = 0;
}
#endif
if (CURSOR_AT_END_OF_LINE (printcon))
{
// An end of line \n means we need to eat the line feed before advancing.
if (*text == '\n' && printcon->cursor_column == printcon->display_columns) // HARD LINE BREAK: Print and advance to end of line
{
Text_Copy_Cursor_Advance (printcon, &text, 1); // We can do this no matter what. Even if past display_columns due
}
else Console_Row_Advance (printcon);
}
// Grab as much text as possible and throw it into the buffer
maxsize = console.display_columns - console.cursor_column;
for (cursor = 0; cursor < maxsize && text[cursor] && text[cursor] != '\n'; cursor ++)
{
if (text[cursor] > ' ' && text[cursor + 1] <= ' ') // If the next character is a space, we assured to have breakable text that fits of X length
{
wordlen = cursor + 1;
}
else if (text[cursor] <= ' ' && wordlen == 0)
leadingspaces ++;
}
if (wordlen == 0)
wordlen = leadingspaces;
hitTerminator = (cursor == maxsize) ? False : True;
if (*text == '\n') // HARD LINE BREAK: Print and advance to end of line
{
Text_Copy_Cursor_Advance (printcon, &text, 1); // We can do this no matter what. Even if past display_columns due
Console_Row_End_of_Line (printcon); // to 1 character of column padding.
}
else if (wordlen == maxsize) // NO LINE BREAK: simple row advance
{
Text_Copy_Cursor_Advance (printcon, &text, wordlen);
// Do not advance line, next iteration will
}
else if (wordlen > 0 && hitTerminator) // HARD line break on NEXT iteration so don't advance
{
Text_Copy_Cursor_Advance (printcon, &text, wordlen);
}
else if (wordlen > 0 && hitTerminator == False) // SOFT line break: word wrap
{
Text_Copy_Cursor_Advance (printcon, &text, wordlen);
}
else if (String_WordLength(text) <= printcon->display_columns && leadingspaces == 0) // Text too big for this line but fits on next (We can always break spaces)
{
Console_Row_End_of_Line (printcon);
}
else if (wordlen == 0) // NO LINE BREAK: Tough luck: Unbreakable text.
{
wordlen = maxsize;
Text_Copy_Cursor_Advance (printcon, &text, wordlen);
}
// End of text segment
}
// End of text
I also have macros like this:
Code: Select all
#define ROW_START_CELLNUM(z, rown) ((rown) * z->columns)
#define ROW_START_CELLTEXT(z, rown) &z->console_text[ROW_START_CELLNUM(z, rown)]
#define CELL_TEXT(z, rown, coln) &z->console_text[ROW_START_CELLNUM(z, rown) + coln]
#define ROWS_POPULATED(z) (z->rows_filled + 1) // Include incompletely filled row
#define ROW_RANGE_SIZE(z) (ROWS_POPULATED(z) < z->rows ? ROWS_POPULATED(z) : z->rows)
#define ROW_RANGE_END(z) z->cursor_row
#define ROW_RANGE_BEGIN(z) Row_Wrap (z, z->cursor_row - ROW_RANGE_SIZE (z) + 1) // 0-79 ... 79 - 80 + 1 = 0
#define BACKSCROLL_MAX(z) (ROWS_POPULATED(z) - 2) // -1 to keep one displayed, -1 more for backscroll bar
#define DISPLAY_RANGE_SIZE(z) z->display_rows
#define DISPLAY_RANGE_END(z) Row_Wrap (z, z->cursor_row - z->backscroll_rows)
#define DISPLAY_RANGE_BEGIN(z) Row_Wrap (z, DISPLAY_RANGE_END (z) - DISPLAY_RANGE_SIZE (z) + 1) // 0-79 ... 79 - 80 + 1 = 0
#define DISPLAY_RANGE_EMPTIES(z) max (z->display_rows - ROWS_POPULATED(z), 0)
#define COLUMN_MEM_LENGTH(z) z->columns
#define ERASE_ROW(z, row) memset(ROW_START_CELLTEXT(z, z->cursor_row), 0, COLUMN_MEM_LENGTH(z))
#define CURSOR_CELL(z) CELL_TEXT(z, z->cursor_row, z->cursor_column)
#define CURSOR_AT_END_OF_LINE(z) (z->cursor_column >= z->display_columns)
The night is young. How else can I annoy the world before sunsrise? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Re: Describing the Quake Command System
what about colours?
FTE's console printing is a bit different. Con_Print expands the string into colours+unicode chars first of all (using a generic reusable function to do so), then generates a list of buffers separated by the \ns. If it gets a \r then it arranges for the next char received to clear the entire line. Dynamically allocated, the screen width is not a concern. The console buffer is just a linked list of \n separated tokens, each char has its own flags for colouring.
The lines are wrapped at render time, where annother generic function splits the line into sub-lines that fit the width of the console.
Yay for centerprints reusing these generic functions too.
Advantages:
Console resizing just works. No weird single characters when resizing the window, etc.
Font code handles char width. No console resizing on font changes.
Doesn't waste memory simply because the user is running widescreen.
Can change the number of lines stored in the buffer with a cvar, without needing to memcpy everything or restart the client.
Colours are directly stored.
Disadvantages:
Moving up/down by one line has a tendancy to move up/down according to its \n chars rather than screen estate.
Doubly linked lists... And tracking pointers...
Can be a little slower...
Quake2 does Con_Printf("Loading %s...\r", model->name); etc in quite a few places. Its a nice trick. the \r does nothing until the next char is printed, which overwrites the first char of the line, so print the line, refresh the screen, load the model, print the next model and you don't spam the console needlessly.
Interestingly the same works for Quake. Beware of mods that spam lines with \r in them for some sort of radar!
\r is also commonly used in team messages in quakeworld.
It can also be used to put words into other people's mouths... say "\rIdiot: I'm a <INSERT INSULT HERE>"
You probably want to block that.
Fun stuff really.
FTE's console printing is a bit different. Con_Print expands the string into colours+unicode chars first of all (using a generic reusable function to do so), then generates a list of buffers separated by the \ns. If it gets a \r then it arranges for the next char received to clear the entire line. Dynamically allocated, the screen width is not a concern. The console buffer is just a linked list of \n separated tokens, each char has its own flags for colouring.
The lines are wrapped at render time, where annother generic function splits the line into sub-lines that fit the width of the console.
Yay for centerprints reusing these generic functions too.
Advantages:
Console resizing just works. No weird single characters when resizing the window, etc.
Font code handles char width. No console resizing on font changes.
Doesn't waste memory simply because the user is running widescreen.
Can change the number of lines stored in the buffer with a cvar, without needing to memcpy everything or restart the client.
Colours are directly stored.
Disadvantages:
Moving up/down by one line has a tendancy to move up/down according to its \n chars rather than screen estate.
Doubly linked lists... And tracking pointers...
Can be a little slower...
Quake2 does Con_Printf("Loading %s...\r", model->name); etc in quite a few places. Its a nice trick. the \r does nothing until the next char is printed, which overwrites the first char of the line, so print the line, refresh the screen, load the model, print the next model and you don't spam the console needlessly.
Interestingly the same works for Quake. Beware of mods that spam lines with \r in them for some sort of radar!
\r is also commonly used in team messages in quakeworld.
It can also be used to put words into other people's mouths... say "\rIdiot: I'm a <INSERT INSULT HERE>"
You probably want to block that.
Fun stuff really.
Re: Describing the Quake Command System
I'm not that much of a fan of the whole "colors" thing. I look at the console more like a tool, I guess.Spike wrote:what about colours?
But that just my opinion.
I'm more in the simpler is better camp. But I understand the appeal of the colors thing.
I almost went towards the a linked list of newlines method. But then I thought about wrapping and wanted to just keep it simple where I can easily calculate a character for each row and col coordinate.FTE's console printing is a bit different. Con_Print expands the string into colours+unicode chars first of all (using a generic reusable function to do so), then generates a list of buffers separated by the \ns. If it gets a \r then it arranges for the next char received to clear the entire line. Dynamically allocated, the screen width is not a concern. The console buffer is just a linked list of \n separated tokens
But clearly there are advantages to the linked newlines method. I just didn't want to have to calculate space to kill after the buffer is full and each print.
The night is young. How else can I annoy the world before sunsrise? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Re: Describing the Quake Command System
colours can be nice for certain things, like using different colours for easier chat.
That said, quake is not an instant messenger.
ezquake's implementation of colours scares me. I really hope its more sane nowadays.
Tbh, mostly I'm paranoid about resizing. Though I assume that if you flag each line as having an explicit or implicit newline, you can always reform the line to resize.
Main issue with variable-width-fonts and fixed-width-consoles is that you need to allocate enough memory for the user to make a line that contains a whole lot of, eg, i chars, thereby resulting in weird wrapping if you didn't give it enough memory for the line.
Different problems, different solutions.
Strictly speaking, the console buffer is a ring buffer. You presumably already allocate memory inside it, just with line granularity instead of byte granularity. A little more complex, but would work fine for variable-width fonts.
That said, quake is not an instant messenger.
ezquake's implementation of colours scares me. I really hope its more sane nowadays.
Tbh, mostly I'm paranoid about resizing. Though I assume that if you flag each line as having an explicit or implicit newline, you can always reform the line to resize.
Main issue with variable-width-fonts and fixed-width-consoles is that you need to allocate enough memory for the user to make a line that contains a whole lot of, eg, i chars, thereby resulting in weird wrapping if you didn't give it enough memory for the line.
Different problems, different solutions.
Strictly speaking, the console buffer is a ring buffer. You presumably already allocate memory inside it, just with line granularity instead of byte granularity. A little more complex, but would work fine for variable-width fonts.
Re: Describing the Quake Command System
Colours would actually be neat for chat if they used the player skin colours (top half of text - shirt, bottom half = pants) - an extra visual cue as to who is doing to the talking, and being able to match a chat string to a player model on-screen at a glance. Might be some issues with ease of visibility though.
Personally though I don't like the idea of over-sexing the console. Basic display and input are good enough for me.
Personally though I don't like the idea of over-sexing the console. Basic display and input are good enough for me.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
We knew the words, we knew the score, we knew what we were fighting for
Re: Describing the Quake Command System
Code: Select all
{"@clip", Cmd_AtClip, "Sends command results to clipboard."},
Largely killed any need for this personally when I made the entities inspector for FitzQuake Mark V in release #1 (June 29?) where you can visually see what's up with entities because they have a caption over them (that changes when you change weapon, i.e. pressing 1 shows edict #, pressing 2 might show origin x, y, z over the entity, but pressing 7 might show nextthink or pressing 3 might show model name + model index).
Historically, it sucked that there wasn't any easy way to check out entities since the edicts command can't be contained in even a unreasonably large console buffer.
Still redirecting output of a command to the clipboard can be convenient.
The night is young. How else can I annoy the world before sunsrise? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..