Index | FAQ | Search | Memberlist | Usergroups | Profile | Log in to check your private messages | Log in | Register

Dynamic registering of extensions

| Inside3d Forums Forum Index -> QuakeC Programming

View previous topic :: View next topic
Author | Message
--- | ---
VorteX Joined: 14 Jan 2005 Posts: 12 |

Posted: Fri Sep 26, 2008 9:47 am Post subject: Dynamic registering of extensions |

QSG extension system is great, but using it adds some dirtyness to code - especially if using builins that adds new subsystems (like new find methods). This idea generally comes to solve situation with unsupported builtins called (yeah, somewhat when you forgot to check for certain extension...)
This example affects three extensions from Darkplaces, but can be usable with all other. One of it's general benefits - you don't need to check for extension in work-code, only in this file. And you always work with extension function, even if it is really a QC stub (many of missing extensions, like str_replace, findchainflags can be replaced with QC alternative, much slower but working).
This code requires a compiler with function pointers support (a 'var' keyword) - FTEQCC, FrikQCC also has function pointers, but it is not tested yet (maybe someone do?)

Code:
/* ======================================== DP_QC_RANDOMVEC ======================================== */
vector() ext_randomvec = #91;
vector() qc_randomvec =
{
vector tVec;
tVec_x = random()*2-1;
tVec_y = random()*2-1;
tVec_z = random()*2-1;
return normalize(tVec)*random();
}
var vector() randomvec = qc_randomvec;
void() DP_QC_RANDOMVEC =
{
randomvec = ext_randomvec;
}
/*
========================================
DP_QC_STRING_CASE_FUNCTIONS
========================================
*/
string(string s) ext_strtolower = #480;
string(string s) ext_strtoupper = #481;
string(string s) qc_strtolower = { return s; }
string(string s) qc_strtoupper = { return s; }
var string(string s) strtolower = qc_strtolower;
var string(string s) strtoupper = qc_strtoupper;
void() DP_QC_STRING_CASE_FUNCTIONS =
{
strtolower = ext_strtolower;
strtoupper = ext_strtoupper;
}
/*
========================================
DP_TE_QUADEFFECTS1
========================================
*/
void(vector org) ext_gunshotquad = #412;
void(vector org) ext_spikequad = #413;
void(vector org) ext_superspikequad = #414;
void(vector org) ext_explosionquad = #415;
var void(vector org) te_gunshotquad = te_gunshot;
var void(vector org) te_spikequad = te_spike;
var void(vector org) te_superspikequad = te_superspike;
var void(vector org) te_explosionquad = te_explosion;
void() DP_TE_QUADEFFECTS1 =
{
te_gunshotquad = ext_gunshotquad;
te_spikequad = ext_spikequad;
te_superspikequad = ext_superspikequad;
te_explosionquad = ext_explosionquad;
}
void RegisterExtension(string tExtName, void() tRegister)
{
if (checkextension(tExtName))
tRegister();
}
void() RegisterExtensions =
{
if (!cvar("pr_checkextension"))
error("Engine does not support QSG Extensions\n");
RegisterExtension("DP_QC_RANDOMVEC", DP_QC_RANDOMVEC);
RegisterExtension("DP_QC_STRING_CASE_FUNCTIONS", DP_QC_STRING_CASE_FUNCTIONS);
RegisterExtension("DP_TE_QUADEFFECTS1", DP_TE_QUADEFFECTS1);
}

To make this all useful, you must call RegisterExtensions() function before use of functions it set. I.e. at very beginning of worldspawn(). You may not use RegisterExtensions() at all, then qc variants will be used for all extensions presented.
And this is code for new FIND* extension, tested on FTEQCC
You can find them in Arcade Quake sourcecodes

Code:
/* ======================================== DP_QC_FINDCHAIN DP_QC_FINDCHAINFLAGS DP_QC_FINDCHAINFLOAT
DP_QC_FINDFLAGS
DP_QC_FINDFLOAT
========================================
*/
entity(.string fld, string match) ext_findchain = #402;
entity(.float fld, float match) ext_findchainflags = #450;
entity(.entity fld, entity match) ext_findchainentity = #403;
entity(.float fld, float match) ext_findchainfloat = #403;
entity(entity start, .float fld, float match) ext_findflags = #449;
entity(entity start, .entity fld, entity match) ext_findentity = #98;
entity(entity start, .float fld, float match) ext_findfloat = #98;
#define findChainDef(f,m) entity tE;entity tLE;tE=world;tLE=world;for(tE=nextent(tE);tE;tE=nextent(tE)){if(tE.f == m){if (tLE)tLE.chain=tE;tLE=tE;tLE.chain=world;}}return tLE;
#define findChainDefAnd(f,m) entity tE;entity tLE;tE=world;tLE=world;for(tE=nextent(tE);tE;tE=nextent(tE)){if(tE.f & m){if (tLE)tLE.chain=tE;tLE=tE;tLE.chain=world;}}return tLE;
entity(.string tField, string tMatch) qc_findchain = { findChainDef(tField, tMatch) }
entity(.float tField, float tMatch) qc_findchainflags = { findChainDefAnd(tField, tMatch) }
entity(.float tField, float tMatch) qc_findchainfloat = { findChainDef(tField, tMatch) }
entity(.entity tField, entity tMatch) qc_findchainentity = { findChainDef(tField, tMatch) }
#undef findChainDef
#undef findChainDefAnd
entity(entity tE, .float tField, float tMatch) qc_findflags = { for(tE=nextent(tE);tE;tE=nextent(tE)) if (tE.tField & tMatch) return tE; return world; }
entity(entity tE, .float tField, float tMatch) qc_findfloat = { for(tE=nextent(tE);tE;tE=nextent(tE)) if (tE.tField == tMatch) return tE; return world; }
entity(entity tE, .entity tField, entity tMatch) qc_findentity = { for(tE=nextent(tE);tE;tE=nextent(tE)) if (tE.tField == tMatch) return tE; return world; }
var entity(.string fld, string match) findchain = qc_findchain;
var entity(.float fld, float match) findchainflags = qc_findchainflags;
var entity(.entity fld, entity match) findchainentity = qc_findchainentity;
var entity(.float fld, float match) findchainfloat = qc_findchainfloat;
var entity(entity start, .float fld, float match) findflags = qc_findflags;
var entity(entity start, .entity fld, entity match) findentity = qc_findentity;
var entity(entity start, .float fld, float match) findfloat = qc_findfloat;
void() DP_QC_FINDCHAIN =
{
findchain = ext_findchain;
}
void() DP_QC_FINDCHAINFLAGS =
{
findchainflags = ext_findchainflags;
}
void() DP_QC_FINDCHAINFLOAT =
{
findchainfloat = ext_findchainfloat;
findchainentity = ext_findchainentity;
}
void() DP_QC_FINDFLAGS =
{
findflags = ext_findflags;
}
void() DP_QC_FINDFLOAT =
{
findfloat = ext_findfloat;
findentity = ext_findentity;
}

Back to top |

Lardarse Joined: 05 Nov 2005 Posts: 243 Location: Bristol, UK |

Posted: Fri Sep 26, 2008 11:53 am Post subject: |

This isn't an entirely new idea. The bottom half of mathlib.qc uses the var keyword to allow the code to use either the engine builtins or the QC version as appropriate. var (which stops a declaration + initialization in the same statement from being seen as a constant) works in FrikQCC as well, although I have no experience with using it. I don't know if #define does, however.
Do those find* functions actually work, btw?

Back to top |

VorteX Joined: 14 Jan 2005 Posts: 12 |

Posted: Fri Sep 26, 2008 12:21 pm Post subject: |

QC variants of find all working, the defines easily can be removed, they are here because of duplicated code on all find* functions I do not say that this is a new idea, just useful method to get rid of extension-checking in code (one file keeps all checks) that has main logic and easy porting of mod from Darkplaces to other engines. Even FTE lacks a lot of DP extensions (especially new ones).

Back to top |

Spike Joined: 05 Nov 2004 Posts: 944 Location: UK |

Posted: Mon Sep 29, 2008 8:36 am Post subject: |

Extensions are all well and good, but they're extensions. The functionality they provide is not always possible without. For example, your strlwr implementation just returns the original string. In the case of KRIMZON_SV_PARSECLIENTCOMMAND such assumptions could result in security holes.

Quote:
Even FTE lacks a lot of DP extensions (especially new ones).

Even DP lacks a lot of FTE extensions. Especially the fun ones.
In the case of 'DP_QC_STRING_CASE_FUNCTIONS' (which I must say I have never even heard of before), FTE already provides this functionality, although in a more versatile way. Implementing everything twice is not good.
But yes, using the var keyword to wrap builtins is a good way to support builtins that are on one builtin number in one engine and another in a second engine. But if neither version of the extension is supported, you have problems unless you provide a workaround, or a variable to state that such a feature needs to be disabled.
So yeah, using var is good. But take it with a pinch of salt. Don't just do it with every single builtin.
_________________
What's a signature?

Back to top |

VorteX Joined: 14 Jan 2005 Posts: 12 |

Posted: Fri Oct 03, 2008 8:28 am Post subject: |

Spike wrotes:

Quote:
your strlwr implementation just returns the original string

Yeah, though it's easy to write a working QC alternative for qc_strtolower or redirect it to FTE_STRINGS extension.

Code:
/* ======================================== FTE_STRINGS override DP's strtolower/strtoupper by FTE alternatives if they are not binded yet ========================================
*/
float CONV_SAME = 0;
float CONV_CASE_LOWER = 1;
float CONV_CASE_UPPER = 2;
float CONV_WHITE = 1;
float CONV_RED = 2;
float CONV_REDSPECIAL = 3;
float CONV_WHITESPECIAL = 4;
float CONV_ALTERNATE_RW = 5;
float CONV_ALTERNATE_WR = 6;
float(string str, string sub) ext_strstr = #221;
float(string str, string sub, float startpos) ext_strstrofs = #221;
float(string str, float ofs) ext_str2chr = #222;
string(float c) ext_chr2str = #223;
string(float ccase, float calpha, float cnum, string s) ext_strconv = #223;
string(float chars, string s) ext_strpad = #225;
string(string info, string key, string value) ext_infoadd = #226;
string(string info, string key) ext_infoget = #227;
float(string s1, string s2, float len) ext_strncmp = #228;
float(string s1, string s2) ext_strcasecmp = #229;
float(string s1, string s2, float len) ext_strncasecmp = #230;
void ext_fte_strings_strtolower(string s) = { return ext_strconv(CONV_CASE_LOWER, CONV_SAME, CONV_SAME, s); }
void ext_fte_strings_strtoupper(string s) = { return ext_strconv(CONV_CASE_UPPER, CONV_SAME, CONV_SAME, s); }
void() FTE_STRINGS =
{
// override DP's strtolower/strtoupper by FTE alternatives if they are not binded yet
if (strtolower == qc_strtolower)
strtolower= ext_fte_strings_strtolower;
if (strtoupper == qc_strtoupper)
strtoupper= ext_fte_strings_strtoupper;
}

Quote:
Even DP lacks a lot of FTE extensions. Especially the fun ones.

TRUE. Under 'Even FTE lacks a lot of DP extensions (especially new ones).' i mean that FTEQW is very friendly to DP in the plane of extensions, although it has some missing ones, but it's easy to replicate them using this way without hammering you

Quote:
In the case of 'DP_QC_STRING_CASE_FUNCTIONS' (which I must say I have never even heard of before), FTE already provides this functionality, although in a more versatile way. Implementing everything twice is not good.

Yeah, thats 'dynamic registered of extensions' is for. If engine has no builtin we want, but has a pretty same one, we just create a sub function that does transformation job (like strconv -> strtoupper/strtolower).

Back to top |

Spike Joined: 05 Nov 2004 Posts: 944 Location: UK |

Posted: Fri Oct 03, 2008 9:07 am Post subject: |

alternatively, you can change the case of a string using just frik_file. Although the quirks generated by doing so boggles the mind. But again, this is another extension. Its not possible to do it without extensions. If your engine doesn't support any similar extensions, what do you do? Disable the mod, or disable the feature, in the latter case, have you really gained anything other than a line or two in the function it was used?
If you can create a library of extensions that can be mimiced with other extensions (for example incorporating the mathlib topic) then its obviously useful for other modders. But if you're doing it for only your own mod, then you're not really at any advantage, you just have a huge chunk of code that could have been done with an extra condition.
Yes its handy to know how the var keyword is useful, but hey, its simpler not to. :)
From a coding standards simplicity, you really ought to make sure that the builtin is fully emulated if implemented with the same name, alternatively you should give it a different name if it is not, eg: strtolower_try. As then you'll remember that its not always implemented as it should be on your default platform. This will mean you write your code a little more robustly.
Note: using the var keyword, you can leave functions as null if you couldn't provide an alternative. This gives you a per-function check should you be able to live without an extension (ie: disabling file access features if fopen was not found in either frik_file or 'tomaz_file' variants). Which of course saves creating a seperate var for each one, and doesn't name tomaz_file's fopen as frik_file (for example).
(side note: I added support for strtolower/strtoupper in fte some time this week, its not committed yet)
_________________
What's a signature?

Back to top |

Display posts from previous:

| Inside3d Forums Forum Index -> QuakeC Programming | All times are GMT
Page 1 of 1

Jump to:

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

Powered by phpBB © 2004 phpBB Group