spurtg 0.4.2 programming overview --------------------------------- --------------------------------- Details of Current Implementation --------------------------------- How the main program currently works, in the proverbial nutshell: * parse system config * parse show config * when AddEffectModule is encountered, passes to effectLoad: * dlopen module * calls module-wide moduleInit, passing moduleGeneralStruct * adds module to effectManager list * when Effect within a preset is encountered, passes to presetAddEffect: * sets up a new instance of the effect and adds it to the preset, and the preset is added to the presetManager * iterate through presets (presetInitAll()) * iterate through effects within each preset * call effectInstInit for each * initialize key, snd, gfx, and turn off gpm * main loop * check messages (keyboard / preset jump) * call gfxRender for the current preset * iterate through effects within current preset * call effectInstRender for each * shutdown gpm, gfx, snd, key * iterate through presets (presetFiniAll()) * iterate through effects within each preset * call effectInstFini for each ------------------------------ How to write an effects module ------------------------------ How to make your own module (or modify someone else's). To start off, it helps to have a simple effect module to look at while you do this. After a few times, it will sink in, and you'll know how things work. First, make a new directory in modules/effects with the name of the general effect you plan on implementing. In that directory, copy a makefile from any other module directory, and change the name to match. You can also make your own, if you want. Next, create the actual source file, usually named the same as the directory. Now for the tricky part: actually coding the module. Make whatever includes you need to. Then, #include "spurtg.h". Including this will allow you to use some shorthands that make it easier to understand. Next, you'll want to setup the information block for your module. For example: moduleName("Blur") moduleVersion(0,2,0) moduleUrl("http://prj.softpixel.com/spurtg/") moduleAuthor("softpixel, spurtg@softpixel.com") moduleType(MODULE_EFFECTS) moduleDescription( " * blur: surface blurring. two methods: * blur - averages the 8 pixels that surround a given pixel * parameters: none" ) These should all be fairly clear... moduleType currently can only be MODULE_EFFECTS (we intend to create other types of modularity in the future). moduleDescription can be in any format, but it is recommended that you stick with the format as it exists in current modules, for consistency. No semicolon is needed at the end of the lines (these are #defines). Now you'll want to make an effectReg struct called effectReg[]. This is a registry of all the effect-functions that are inside this module. The general layout is something like this: const effectRegStruct effectReg[]= { {"effectString",init_func,render_func,fini_func,0}, {"secondEffectString",init_func2,render_func2,fini_func2,0}, {NULL} } It is very important that you have the last entry be NULL. This tells the module loader that there are no more effect-functions in the module. The 0 at the end of the line will be used in the future to specify exactly what core features the particular effect-function uses, for optimization purposes. After that, you get to make your moduleInit and moduleFini functions. These are to do any setup/cleanup that are required by the effects you are using. This is just general setup, the effect instances have yet to be initialized. Make the init/finis by adding moduleInit { initStuff; return RET_OK; or return RET_ERROR; } and moduleFini { finiStuff; } Now, with that set up, you have a module that has basic io functionality with the module loader. However, you still have no effects. So, let us add some! Each effect listed in your effectReg struct (above) gets its own Init, Fini, and Render functions. They could, in theory, share some as well. Each effect instance init func is created with the following: effectInstInit(init_func) It is expected to return RET_OK assuming everything goes as planned. Otherwise you can return a general error (RET_ERROR), or complain about the parameters passes (RET_INVALID_PARAMETERS). In that function, you initialize any data that is needed by the effect. The init_func is passed a single struct: effectInstanceStruct *ei. That struct contains many of the configuration parameters you might need. It is usually helpful to make a struct to hold your data, and have ei->z point to it. See starfield for an example of this. The effect render func is created by using effectInstRender(render_func) It is also expected to return RET_OK. It is passed two params, effectInstanceStruct *ei and effectDataStruct *ed ei is the same one that was passed to the init func. Therefore, its z pointer is the same, and your data is preserved. The *ed struct contains data that is relevant as of this frame, primarily the sound data (Accessed by ed->snd-> audio data desired [offset]) and the graphical data (Accessed by ed->gfxIn[input number] and ed->gfxOut). It is in good faith that your module reads from gfxIn, and writes only to gfxOut. There is nothing stopping you from doing it, and no doubt, you'll think of something clever, but most effects should follow the rules. It is in the render function that your effect actually puts pixels on the surface. What you do there is up to you. Finally, each effect instance gets cleaned up with the fini function, invoked by effectInstFini It is only passed effectInstanceStruct *ei. It is a good idea to free any memory used by your ei->z struct, and do any other clean up you may need to do. With that in hand, you are set to make your own modules. Have a lot of Fun. ---------------------- Why C rather than C++? ---------------------- * C tends to have anywhere from slightly to quite a bit less overhead than C++. Seeing as this is a 'realtime' application (designed for low-latency and speed), we need minimum overhead. * The added abstraction benefits of C++ did not outweigh the overhead and potential incompatibilities created thereby. Even with C, one can divide code into object files and use similar (albeit less sophisticated) data abstraction by not 'making public' (via a .h) internal data. * Compiler optimization (we assume) works more readily with a simpler programming model. * We, the main coders, are significantly more familiar with C. ------------ Coding Style ------------ some general comments on how we coded this and how we would like it to continue being coded... :^) * use RET_OK and RET_ERROR as return values (defined in spUni.h). * to see if something worked, check if it's equal to RET_OK, rather than not equal to RET_ERROR (as there will be additional error values in the future) * if, in a function, you encounter an error in an external function, report this using eprintf(), then return RET_ERROR. if you encounter something other than RET_OK from an internal function, just return RET_ERROR. * document structures in .h files. document functions in both .c and .h files, and keep them synchronized. * use 8-character hard tabs * curly braces go on their own line, and cause contained lines to be indented one level. for example: for(i=0;i<10;++i) { dprintf("%d\n",i); }