Inside3D!
     

Dynamic registering of extensions

 
Post new topic   Reply to topic    Inside3d Forums Forum Index -> QuakeC Programming
View previous topic :: View next topic  
Author Message
VorteX



Joined: 14 Jan 2005
Posts: 12

PostPosted: Fri Sep 26, 2008 9:47 am    Post subject: Dynamic registering of extensions Reply with quote

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
View user's profile Send private message
Lardarse



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

PostPosted: Fri Sep 26, 2008 11:53 am    Post subject: Reply with quote

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
View user's profile Send private message
VorteX



Joined: 14 Jan 2005
Posts: 12

PostPosted: Fri Sep 26, 2008 12:21 pm    Post subject: Reply with quote

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
View user's profile Send private message
Spike



Joined: 05 Nov 2004
Posts: 944
Location: UK

PostPosted: Mon Sep 29, 2008 8:36 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
VorteX



Joined: 14 Jan 2005
Posts: 12

PostPosted: Fri Oct 03, 2008 8:28 am    Post subject: Reply with quote

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 Smile

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
View user's profile Send private message
Spike



Joined: 05 Nov 2004
Posts: 944
Location: UK

PostPosted: Fri Oct 03, 2008 9:07 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Display posts from previous:   
Post new topic   Reply to topic    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