Extending VSXu - Writing a module

The aim of this document is to show you how quickly you as a programmer can realize your own module and then make use of it together with all the other modules of VSXu, saving lots of development time.

A module in vsxu is a class implementing the vsx_module interface. It lives in a dynamically linked library (DLL or SO). This listing with comments will flesh out a full library so you know every aspect of what you will encounter in terms of integration with vsxu_engine. To actually compile+link a module, you'll need some more things, like a CMakeLists.txt file, and some other config files for the build process, but you can just copy those from another module.

And that's the thing - you will most likely just clone an existing module project directory that does almost what you want, or at least within the same realm.

After reading this guide - you'll know how to modify the existing code to make it your own, personally developmed module. You don't really need to do anything in the Cmake stuff, so start with hacking the actual class.


First, some includes.

001 #include <vsxfst.h> // file system and string functions
002 #include <vsx_param.h> // parameter definitions
003 #include <vsx_module.h> // module base class + definition
004 #include <vsx_math_3d.h> // vsx_vector and matrix maths

We call our class vsx_module_template

006 class vsx_module_template : public vsx_module {

We define some pointers to parameter holders for inputs. Various types - float/float3/float4. The memory pointed to by these is never owned by us, it's taken care of by the engine. A consequence of this is that to set values, we have to call methods to this object. But the advantage of this is that it saves the module developer from a lot of issues, especially when the engine wants to (sometimes) override the values.

007   // in
008   vsx_module_param_float3* position;
009   vsx_module_param_float3* size;
010   vsx_module_param_float* angle;
011   vsx_module_param_float3* rotation_axis;
012   vsx_module_param_float4* color_rgb;
013   vsx_module_param_int* border;

The same is done for our only output parameter - of type render.

014   // out
015   vsx_module_param_render* render_result;

This is just a placeholder for variables internal to the class.

016   // internal

Now time for our first public method. This one is mandatory and it basically tells artiste stuff about us. Where in the module hierarchy to put us, what our name is, what parameters we have, what constraints and controllers those should have and some other flags to both the engine and artiste.

module_info is run multiple times, and is the only method run on each module during initialization of the engine - which means (among other things) that you can not assume OpenGL to be initialized here for instance.

018 public:
020   void module_info(vsx_module_info* info)
021   {

First comes the path to the module in the hierarchy. Using ; as separator. Another valid path would be for instance - render;basic;simple_module

022     info->identifier = "templates;simple";

Next comes the textual description displayed by Luna, and in the module chooser. These lines shoudn't be too wide - try out for yourself how much you can cram in.

023     info->description = 
024       "Template module showing all\n"
025       "possible data types and tricks\n"
026       "you can do in modules.\n"
027     ;

Now for the parameter specification. The syntax is pretty much covered in this following big code comment:

028     /*
029      * Parameter specifications
030      *   Used to tell artiste what parameters we want in the GUI and how 
031      *   those should behave.
032      * 
033      * Syntax:
034      * 
035      * [name]:[type][?[flag][=value]]
036      * 
037      * - Name and type are required.
038      * - Flag can be many things, controllers or options on how the GUI should
039      *   treat it.
040      * 
041      * To prevent connections to a parameter, add the non-connection flag:
042      * nc=1
043      * 
044      * This will make it impossible to connect the parameter to any other 
045      * module. That is, only the human operating VSXu can set it. 
046      * Use this when you do operations that involve large chunks of memory
047      * or heavy one-time precalculations.
048      * 
049      * Setting "Default Controller" (which controller is displayed when double-
050      *  clicking the param):
051      *   default_controller=[value]
052      * 
053      * Different values (controllers) for different parameters are available:
054      *  
055      *   controller_knob        float
056      *   controller_slider      float, float3, float4
057      *   controller_edit        string/resource  
058      *   controller_resource    resource (to use it with strings, make it 
059      *                          default)
060      * 
061      * Example:
062      *   To make a knob default (when double-clicking) go like this:
063      *   angle:float?default_controller=controller_knob
064      * 
065      */
066     info->in_param_spec = 
067       "spatial:complex{"
068         "position:float3,"
069         "angle:float,"
070         "rotation_axis:float3,"
071         "size:float3,"
072         "border:enum?no|yes
073       "},"
074       "color:float4"
075     ;

The out-param specification shares the same syntax language as the input specification, but this module has only one output parameter.

077     info->out_param_spec = "render_out:render";

Here we tell artiste what graphic icon to display for the module. These are pre-defined so you have a bunch to choose from. Here are the possible choices: bitmap, mesh, output, parameters, particlesystem, render, system, texture, texture_surface. This module however, is a plain and simple renderer.

078     info->component_class = "render";
079   }

And that's the end of the module_info method. Next comes declare_params where we ask the engine to create data holders for us and return them.

When they've been returend, we can call methods to set their values. To see which data types are available here, check out the vsx_param.h file.

The engine supplies us with 2 module_param_lists - a parameter holder class - which it expects us to fill with bindings of data type and string name.

083   void declare_params(
084     vsx_module_param_list& in_parameters, 
085     vsx_module_param_list& out_parameters
086   )
087   {

First we set loading done is a flag telling the engine that we're done loading. Since this module is not dependant on external file system or anything else that is time consuming and might not be completed in one frame, we can set it in the declare_params method. If you need to use this flag, be aware that the engine will consider the whole state runnable if loading_done is true - so if you're not really done, you'll end up displaying garbage or even crash the engine if you don't set this at the right time.

088     loading_done = true;

Now it's time to ask the engine to create our first parameter. The variable is declared at the top of the class - line 8 - and the string we pass in here - "position" is referred to in the module_info method's in_param_spec. As we just get a vsx_param_abs back, we have to typecast it to vsx_module_param_float3:

089     position = (vsx_module_param_float3*)in_parameters.create(
091       "position"
092     );

Now we can set default values. As this is written, it's actually not needed as the engine will initialize float tuples to 0.0f. But it's still good practice during development to list all the starting values as it'll make it easier for you to know what's going on.

093     position->set(0.0f, 0);
094     position->set(0.0f, 1);
095     position->set(0.0f, 2);

Another float3 parameter - size:

097     size = (vsx_module_param_float3*)in_parameters.create(
099       "size"
100     );

This will only be 2-dimensional size, so we ignore the 3rd parameter.

101     size->set(1.0f,0);
102     size->set(0.3f,1);

Now a single float parameter - angle

103     angle = (vsx_module_param_float*)in_parameters.create(
105       "angle"
106     );

...and its initialization.

107     angle->set(0.0f);

Now for a different type of parameter - an integer parameter. If you remember this was declared as an enum in the in_param_spec:
What this means is that it can get 2 different values: 0 or 1. They're just set in order - so in this case 0 means no, 1 means yes.
If it looked like this: border:enum?yes|no 0 would be yes and 1 would be no. An enum can take on multiple options - not just 2 as in this case.

109     border = (vsx_module_param_int*)in_parameters.create(
111       "border"
112     );

We don't care to initialize this... The engine will set it to 0 for us.

You probably know enough now to understand what's going on in this following code block:

114     rotation_axis = (vsx_module_param_float3*)in_parameters.create(
116       "rotation_axis"
117     );
118     rotation_axis->set(1.0f, 0);
119     rotation_axis->set(1.0f, 1);
120     rotation_axis->set(0.0f, 2);
121     color_rgb = (vsx_module_param_float4*)in_parameters.create(
123       "color"
124     );
125     color_rgb->set(1.0,0);
126     color_rgb->set(1.0,1);
127     color_rgb->set(1.0,2);
128     color_rgb->set(1.0,3);

The final parameter we need initialized is the render_result - our only out parameter. Note that unlike the previous declarations, this is called from the out_parameters parameter list.
You might wonder why the render data type is only an int? This is just used to hint the engine that we did render successfully or not.

130     render_result = (vsx_module_param_render*)out_parameters.create(
132       "render_out"
133     );
134     render_result->set(0);
135   }

In this module - which is focused solely on output - we don't have a run() method. It's however important to understand the difference between run() and output().

run() is called once per frame. output() on the other hand is called once per connection per parameter to our out parameters.

Thus as you can see in the output function header:
void output(vsx_module_param_abs* param)
...the output method is passed the pointer to the parameter's connection in question so that you can filter on what to do, if you want to support many different kinds of outputs.

Most often though, a module only has one or a few similar outputs and in that case it might be enough just to have a frame counter or a flapping integer or similar.

137   // this is run once per frame/iteration of the whole engine
138   void run()
139   {
140     // procrastinate
141   }

This is now the output function. We'll not dive too much into it, it's basically doing general OpenGL stuff to draw a rect, using the parameters we've declared before.

Only thing worth noting is the calls to get the values of parameters:
index is optional (defaults to 0), and only useful for tuples - like float3, float4 etc.

143   // this is run for each connection to this in-param.
144   void output(vsx_module_param_abs* param) 
145   {
146     glMatrixMode(GL_MODELVIEW);
147     glPushMatrix();
148     // translation
149     glTranslatef(position->get(0),position->get(1),position->get(2));
150     // rotation
151     glRotatef(
152       (float)angle->get()*360, 
153       rotation_axis->get(0), 
154       rotation_axis->get(1), 
155       rotation_axis->get(2)
156     );
157     // scaling
158     glScalef(size->get(0), size->get(1), size->get(2));
159     // color
160     glColor4f(
161       color_rgb->get(0),
162       color_rgb->get(1),
163       color_rgb->get(2),
164       color_rgb->get(3)
165     );
167     glBegin(GL_QUADS);
168       glTexCoord2f(0.0f,0.0f);
169       glVertex3f(-1.0f, -1.0f, 0.0f);
170       glTexCoord2f(0.0f,1.0f);
171       glVertex3f(-1.0f,  1.0f, 0.0f);
172       glTexCoord2f(1.0f,1.0f);
173       glVertex3f( 1.0f,  1.0f, 0.0f);
174       glTexCoord2f(1.0f,0.0f);
175       glVertex3f( 1.0f, -1.0f, 0.0f);
176     glEnd();
178     if (border->get()) 
179     {
180       glEnable(GL_LINE_SMOOTH);
181       glLineWidth(1.5);
182       glEnable(GL_BLEND);
183       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
185       glBegin(GL_LINE_STRIP);
186         glColor3f(0, 0, 0);
187         glVertex3f(-2, -0.4f, 0);
188         glVertex3f(-2, -0.2f, 0);
189         glVertex3f( 2, -0.2f, 0);
190         glVertex3f( 2, -0.4f, 0);
191         glVertex3f(-2, -0.4f, 0);
192       glEnd();
193     }
195     glPopMatrix();
196     render_result->set(1);
197     loading_done = true;
198   }
199 };

Finally we have our interface with the engine. It's our responsibility to provide typecasted objects of all our module classes, so there is support for that. Also since the engine don't know how to delete these, we have to take care of this too. If you're familiar with design patterns, this is the "factory" pattern.

MOD_CM is short for MODule Create Module
MOD_DM is short for MODule Destroy Module
MOD_NM is short for MODule Num Modules

Note! Don't forget to increment the return value of MOD_NM if you add more modules!

203 //******************************************************************************
204 //*** F A C T O R Y ************************************************************
205 //******************************************************************************
207 #ifndef _WIN32
208 #define __declspec(a)
209 #endif
211 extern "C" {
212 __declspec(dllexport) vsx_module* create_new_module(unsigned long module);
213 __declspec(dllexport) void destroy_module(vsx_module* m,unsigned long module);
214 __declspec(dllexport) unsigned long get_num_modules();
215 }
218 vsx_module* MOD_CM(unsigned long module) {
219   switch (module) {
220     case 0: return (vsx_module*)new vsx_module_template;
221   } // switch
222   return 0;
223 }
226 void MOD_DM(vsx_module* m,unsigned long module) {
227   switch(module) {
228     case 0: delete (vsx_module_template*)m; break;
229   }
230 }
233 unsigned long MOD_NM() {
234   return 1;
235 }  

VSXu Music Visualizer VSXu.com music/audio visualizer, visual programming language