This is all I'm going to provide by way of a tutorial. Look at FTEQW for a more compleate sample.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@1050 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2005-05-22 13:48:55 +00:00
parent be270c6961
commit 8f9f5cc125
2 changed files with 227 additions and 0 deletions

50
engine/qclib/readme.txt Normal file
View File

@ -0,0 +1,50 @@
Readme for the FTE QCLib
This library is a library for running QuakeC gamecode. It does not provide any builtins itself.
Features:
* Multiple library instances, enabling server qc, client qc, and menu qc. There is no maximum instance limit other than memory.
* Addons, for running multiple progs in any individual instance.
* Field reassignment, allowing a single engine to support multiple subtly different QC APIs. Also makes additional fields easier.
* Step-by-step debugging. Requires a text editor of some form, however. A printout of the current line is also useful of course.
* 64bit support. All strings, globals, and fields are allocated in a consecutive addressable section of memory. This also allows pointers and secure access (not implemented yet, but should be relativly easy bar builtins, which are your responsability).
* Multiple 'threads'. The library allows a builtin to make a duplicate of the current execution state, or to wipe the current state. This allows sleep commands and fork commands. How handy.
* Integrated QC compiler. FTEQCC comes as part of qclib. By setting up an interface with a specific value, you can cause it to always run, or run only if it detects a source change.
* Support for different sorts of progs. Namly Hexen2's, kkqwsv's bigprogs, and FTE's extended format with extra opcodes and possibly fully 32bit offsets. The use of kkqwsv's progs is not recommended - this might be removed at some point.
Quirks:
* don't use multiple instances of fteqcc at the same time. Compilation will fail.
* 64bit support requires all strings to be allocated by qclib itself, achivable via a method call. Compatability requires a certain ammount of caution.
* a fair number of methods are obsolete.
* An overuse of pointers in the API. There are some macros which you can use to hide some of the dereferences.
* kkqwsv progs are not reliable. Do not try saving the game. Avoid letting your users know of support.
* Builtin structures are different from origional quake. You'll need to convert the arguments to qclib style. This change was required for both multiple instances as well as addon support. It should be straightforward enough.
* Entity fields are accessed via a pointer from the edict_t structure. This was required to place entity fields within the 64bit accessable section. Changing a . to a -> is not a major issue though. However, there are a lot. do a find and replace of ->v. to ->v->
* FTE's entities are numbers not pointers. This fact is not made into a big feature as it's kinda incompatable with standard quake. Please do not use numbers directly to refer to ents but instead use the EDICT_TO_PROGS macro which will give protection. This is consistant with standard quake.
Basic usage:
* refer to test.c for a sample on how to set up the library.
* refer to progslib.h for the things that I've forgotten to mention.
* Call the InitProgs function to get a handle to the instance. It takes a parameter which should be set up with some fields. You'll require ReadFile, FileSize, Abort and printf for basic execution.
* Call the configure function to say how much memory to use, and how many progs/addons to support.
* Load your progs via LoadProgs. Use a crc of 0 to use any. Otherwise progs will be rejected if it doesn't match. Give it a list of progs-specific builtins too. :)
* Before calling the spawn builtin, call the InitEnts method. It's parameter stating how many maximum entities to spawn. Using a really large quantity is not much of an issue, as they are allocated as required.
* Before calling InitEnts, you can tell the VM which fields your engine uses (state all basic ones or none). This will place the entity fields in the same order as your engine expects for entvars_t.
* Obtain pointers to globals, or just use the globals structure directly.
* Call the ExecuteProgram method to start execution.
* Call the FindFunction method to find a function to run in the first place.
* Call the 'globals' method to retrieve a pointer to the globals (you should always use PR_CURRENT here). Set the parameters with the G_INT/G_FLOAT macros and friends. Use OFS_PARM0 - OFS_PARM7 to set params before calling or read inside a builtin. Use OFS_RETURN to read the return value. These macros are hard coded to use a 'pr_globals' symbol, so avoid renaming builtin parameter names.
* Ask me on IRC when it all starts keeling over.
* These are the C files that form qclib: pr_edict.c pr_exec.c pr_multi.c initlib.c qcc_pr_comp.c qcc_pr_lex.c qccmain.c qcc_cmdlib.c comprout.c hash.c qcd_main.c qcdecomp.c

177
engine/qclib/test.c Normal file
View File

@ -0,0 +1,177 @@
#include "progtype.h"
#include "progslib.h"
#include <stdlib.h>
//the only function that is required externally. :/
void SV_EndRedirect (void) {}
//builtins and builtin management.
void PF_print (progfuncs_t *prinst, struct globalvars_s *gvars)
{
char *s;
s = prinst->VarString(prinst, 0);
printf("%s", s);
}
builtin_t builtins[] = {
PF_print,
PF_print
};
//Called when the qc library has some sort of serious error.
void Sys_Abort(char *s, ...)
{ //quake handles this with a longjmp.
printf("%s", s);
exit(1);
}
//Called when the library has something to say.
//Kinda required for the compiler...
//Not really that useful for the normal vm.
int Sys_Printf(char *s, ...)
{ //look up quake's va function to find out how to deal with variable arguments properly.
printf("%s", s);
}
#include <stdio.h>
//copy file into buffer. note that the buffer will have been sized to fit the file (obtained via FileSize)
unsigned char *Sys_ReadFile (char *fname, void *buffer, int buflen)
{
int len;
FILE *f;
if (!strncmp(fname, "src/", 4))
fname+=4; //skip the src part
f = fopen(fname, "rb");
if (!f)
return NULL;
fseek(f, 0, SEEK_END);
len = ftell(f);
if (buflen < len)
return NULL;
fseek(f, 0, SEEK_SET);
fread(buffer, 1, len, f);
fclose(f);
return buffer;
}
//Finds the size of a file.
int Sys_FileSize (char *fname)
{
int len;
FILE *f;
if (!strncmp(fname, "src/", 4))
fname+=4; //skip the src part
f = fopen(fname, "rb");
if (!f)
return -1;
fseek(f, 0, SEEK_END);
len = ftell(f);
fclose(f);
return len;
}
//Writes a file.
pbool Sys_WriteFile (char *fname, void *data, int len)
{
FILE *f;
f = fopen(fname, "wb");
if (!f)
return 0;
fwrite(data, 1, len, f);
fclose(f);
return 1;
}
int runtest(void)
{
progfuncs_t *pf;
func_t func;
progsnum_t pn;
progparms_t ext;
memset(&ext, 0, sizeof(ext));
ext.progsversion = PROGSTRUCT_VERSION;
ext.ReadFile = Sys_ReadFile;
ext.FileSize= Sys_FileSize;
ext.Abort = Sys_Abort;
ext.printf = printf;
pf = InitProgs(&ext);
pf->Configure(pf, 1024*1024, 1); //memory quantity of 1mb. Maximum progs loadable into the instance of 1
//If you support multiple progs types, you should tell the VM the offsets here, via RegisterFieldVar
pn = pf->LoadProgs(pf, "testprogs.dat", 0, builtins, sizeof(builtins)/sizeof(builtins[0])); //load the progs, don't care about the crc, and use those builtins.
if (pn < 0)
printf("Failed to load progs\n");
else
{
//allocate qc-acessable strings here for 64bit cpus. (allocate via AddString, tempstringbase is a holding area not used by the actual vm)
//you can call functions before InitEnts if you want. it's not really advised for anything except naming additional progs. This sample only allows one max.
pf->InitEnts(pf, 10); //Now we know how many fields required, we can say how many maximum ents we want to allow. 10 in this case. This can be huge without too many problems.
//now it's safe to ED_Alloc.
func = pf->FindFunction(pf, "main", PR_ANY); //find the function 'main' in the first progs that has it.
if (!func)
printf("Couldn't find function\n");
else
pf->ExecuteProgram(pf, func); //call the function
}
CloseProgs(pf);
}
//Run a compiler and nothing else.
//Note that this could be done with an autocompile of PR_COMPILEALWAYS.
int compile(void)
{
progfuncs_t *pf;
func_t func;
progsnum_t pn;
progparms_t ext;
char *testsrcfile = //newstyle progs.src must start with a #.
//it's newstyle to avoid using multiple source files.
"#pragma PROGS_DAT \"testprogs.dat\"\r\n"
"//INTERMEDIATE FILE - EDIT TEST.C INSTEAD\r\n"
"\r\n"
"void(...) print = #1;\r\n"
"void() main =\r\n"
"{\r\n"
" print(\"hello world\\n\");\r\n"
"};\r\n";
//so that the file exists. We could insert it via the callbacks instead
Sys_WriteFile("progs.src", testsrcfile, strlen(testsrcfile));
memset(&ext, 0, sizeof(ext));
ext.progsversion = PROGSTRUCT_VERSION;
ext.ReadFile = Sys_ReadFile;
ext.FileSize= Sys_FileSize;
ext.WriteFile= Sys_WriteFile;
ext.Abort = Sys_Abort;
ext.printf = printf;
pf = InitProgs(&ext);
if (pf->StartCompile(pf, 0, NULL))
{
while(pf->ContinueCompile(pf) == 1)
;
}
CloseProgs(pf);
}
int main(void)
{
compile();
runtest();
return 0;
}