Greetings,
At the end of defs.qc there is a bunch of built in functions, can someone explain me how they work? I mean in a generic way, not what each of them does, like where they come from, how the engine handle them, things like that.
Thanks
Built in functions
Re: Built in functions
All the actual compiled QuakeC code is compiled into one array of instructions. There's also a bunch of indexes for things like global variables, fields and functions.
Each entry in the function list contains, among other things, the index into the instruction list of the first instruction of that function.
Builtins however have a negative index, and instead point to an index in an engine-side list of C function pointers. The engine checks the sign and if it's negative it looks in the builtin list and calls the C function rather than interpreting any instructions from the progs. The # number in the declaration/definition of a builtin is its actual position in the builtins list.
So the engine has to define every single builtin function that gets made available to the QC. They're generally for something that QC can't do on its own, or which would be much slower to do in QC. To QC they behave exactly like QC functions - they take parameters, they return a value and they can affect other globals or entities while running.
I don't know if that fully explains it. I have further notes on QuakeC here but they're incomplete and aimed for developing a QC interpreter. There's also a somewhat human-readable dump of the stock progs.dat, which I used when making those notes.
(While posting this I noticed slight errors in my notes, but they still demonstrate the general structure of a compiled progs.dat)
Each entry in the function list contains, among other things, the index into the instruction list of the first instruction of that function.
Builtins however have a negative index, and instead point to an index in an engine-side list of C function pointers. The engine checks the sign and if it's negative it looks in the builtin list and calls the C function rather than interpreting any instructions from the progs. The # number in the declaration/definition of a builtin is its actual position in the builtins list.
So the engine has to define every single builtin function that gets made available to the QC. They're generally for something that QC can't do on its own, or which would be much slower to do in QC. To QC they behave exactly like QC functions - they take parameters, they return a value and they can affect other globals or entities while running.
I don't know if that fully explains it. I have further notes on QuakeC here but they're incomplete and aimed for developing a QC interpreter. There's also a somewhat human-readable dump of the stock progs.dat, which I used when making those notes.
(While posting this I noticed slight errors in my notes, but they still demonstrate the general structure of a compiled progs.dat)
That's actually a pretty useful point - the only thing that matters when declaring a particular builtin is the number. (Also the parameters have to match so that they make sense to the function and don't cause an error, but they don't have to be exact).
You can change the name of a builtin and write your own wrapper for it - Frikbot does this to make it so easy to add to any mod - it does stuff like:
This means that the rest of any mod can keep calling sprintf but the builtin only gets called for real players.
You can also have multiple names for the same builtin, a common example being:
You'll then get multiple functions in the function list that point to the same builtin.
All the # part does is say that instead of having a body, this function is performed by the built in function pointer #73 or whichever number it is.
They're actually defined the same as a normal function (at least in the original QuakeC syntax) except where the body goes, you put # followed by a number:
It's replacing the body "{ .. }" with builtin number "#...".
So:
They can be in any order. (Just have to be defined before use.)
They can have any name.
There can be duplicates with different names.
They can have any parameters so long as the builtin code knows what to do with them. (But for most of the stock quake builtins the parameters in defs.qc are pretty much the only right ones.)
Also you don't even have to define builtins you don't use - they can just be left out entirely. (This is how extensions work - the engine has the builtins but there's no problem for mods to not define them or call them. You can also define a builtin that isn't present in a particular engine, and everything will be fine so long as you don't actually call that function.)
You can change the name of a builtin and write your own wrapper for it - Frikbot does this to make it so easy to add to any mod - it does stuff like:
Code: Select all
void(entity e, string s) sprint_real = #24;
void(entity e, string s) sprint =
{
if (!e.is_bot)
sprint_real(e, s);
};
You can also have multiple names for the same builtin, a common example being:
Code: Select all
void(entity e, string s) centerprint = #73;
void(entity e, string s1, string s2) centerprint2 = #73;
void(entity e, string s1, string s2, string s3) centerprint3 = #73;
// etc.
All the # part does is say that instead of having a body, this function is performed by the built in function pointer #73 or whichever number it is.
They're actually defined the same as a normal function (at least in the original QuakeC syntax) except where the body goes, you put # followed by a number:
Code: Select all
*return_value* ( *parameter_list* ) *name* = { *code* }; // qc
*return_value* ( *parameter_list* ) *name* = #*number* ; // builtin
So:
They can be in any order. (Just have to be defined before use.)
They can have any name.
There can be duplicates with different names.
They can have any parameters so long as the builtin code knows what to do with them. (But for most of the stock quake builtins the parameters in defs.qc are pretty much the only right ones.)
Also you don't even have to define builtins you don't use - they can just be left out entirely. (This is how extensions work - the engine has the builtins but there's no problem for mods to not define them or call them. You can also define a builtin that isn't present in a particular engine, and everything will be fine so long as you don't actually call that function.)