» Home

  » Developer Guide


Brass SSE Developer Guide - Tutorials

 

Tutorial 3 - Global Variables and Event Handlers

In tutorial 2 you saw how to display text in different fonts, and use variables instead of constant values. The problem is, the code we wrote in the last tutorial isn't particularly efficient. Let's look at improving it.

 

Before We Continue

The first 2 tutorials used screenshots to show you what your code should look like. This is very useful when you start writing plugins as it's not always clear what you should be doing and how it should look. From now on we'll assume you understand the basic use of SSEdit, so instead of using screenshots in the tutorials the actual code will be included on the page instead. This will enable you to copy and paste any sample code directly from the tutorial into your plugin, which is a bit hard to do with a screenshot :)

 

Improving Our Code

Back in tutorial 1 we talked about event handlers, and learned that the Render handler is called every time your plugin needs to update its display. In tutorial 2 we wrote some code to display a message in a custom font on screen.

To use that custom font we had to declare a font variable then call createfont to initialize it with our desired settings, and we did this right inside the Render handler.

If our Render handler is going to be called over and over, and we're going to be using the same font variable to draw with, it seems pretty inefficient to recreate the variable every single time Render is called. It would make much more sense to create the font variable once and just reuse it each time. Doing this will make our plugin more efficient as we won't be executing repeated, unneccessary code.

So how do we reuse a variable? If you've written code in another language your first instinct might be to use some kind of loop. Here's some pseudocode you might be thinking about:

function Render()
{
    declare MyFont Font;
    createfont(MyFont, "Arial", 10);
    selectfont(MyFont);

    begin_loop
        drawtext("This code is more efficient!", 0, 0);
    end_loop
}

Resist the temptation! Do not EVER write code like this! The reason is simple. Brass calls your Render handler once and once only when your plugin needs to update its display. When the code above is called, it will enter an infinite loop of constantly updating the plugin display. The plugin display will flicker, your CPU usage will increase and Brass will eventually terminate your plugin. Forcefully.

For advanced developers: Brass runs different parts of your plugin in separate threads, which reduces the risk of an infinite loop killing your plugin (or any other plugins currently running). Still, infinite loops are a bad idea and should be avoided. Performing constant updates and creating proper loops is covered in a later tutorial.

The Brass SSE system has been designed to be very efficient; it will only call your function handlers once when it needs to. Second-guessing the handler system like the code above won't achieve what you want.

There's a much simpler way to reuse a variable...

 

The Init Handler, and Global Variables

Time to introduce another handler! When Brass loads your plugin you have a number of opportunities to do some one-time initialization. The Init handler is the perfect place to solve our efficiency question.

When Brass loads a plugin the Init handler is called. The purpose of this handler is to setup any variables, perform any preparation and generally get ready to do whatever the plugin wants to do.

Adding an Init handler is just like adding a Render handler. When you click on the Init handler entry in the Handler Wizardbar , look at the status bar at the bottom of the screen. You'll see a brief explanation of the handler as a quick reminder.

 

Select the Init handler in the Handler Wizardbar and click "Add Handler"

Scroll down to the bottom of your code and you'll see SSEdit has added an Init handler skeleton for you, just like it did for Render.

I like to keep my handler source code in the order they are executed, so I generally move the Init handler above the Render handler. Use copy and paste, or select the code and drag it to the top of your source file.

That's the handler, now what about the variables...

 

All About Scope

If you've written code in any language you'll be familiar with the idea of scope. Don't skip this section though, because Shiny has some very important differences to other languages.

"Scope" is the term used to refer to the lifetime of a variable - the time in a program where it is created and available for use. In the code we've written so far we've declared a couple of variables - MyText, PosX, PosY and MyFont. Because we declared these variables inside the Render handler, they have a "local scope" of the Render handler only. In other words, once the Render handler has finished executing these variables are not valid for use anywhere else. The next time the Render handler runs the variables are created all over again, from scratch, ready for use. If we tried to use them in another handler the Shiny compiler would generate an error.

Shiny code has 2 scopes - local and global. A variable with global scope is declared at the top of your source code and is accessible absolutely everywhere. A variable with local scope is declared in the function that needs it, and is only accessible by code within that function.

Take a look at this code:

function Handler1()
{
    declare MyVar string;
}

function Handler2()
{
    MyVar = "This will fail!";
}

As it says, this code will not compile because MyVar is not "in scope" when we use it in Handler2. The compiler will generate an "Assignment to uninitialized type" error for the code in Handler2().

If you wanted to use the variable name "MyVar" in both functions but you want the variables to be different, you would write the code like this:

function Handler1()
{
    declare MyVar string;
    MyVar = "I'm only valid in Handler1";
}

function Handler2()
{
    declare MyVar string;
    MyVar = "Now I'm valid in Handler2";
}

With this additional declaration in Handler2 you have created 2 entirely separate variables with the same name - one is valid in Handler1, the other is valid in Handler2.

However, what if you wanted to use the same MyVar variable in both functions? To do this you declare a global variable by adding the global keyword to your variable declaration.

Type 1: The Visual Basic "Dim" style:

declare global MyString string;

 

Type 2: The C/C++ declaration style:

string global MyString;

 

Easy! There is one last rule you must remember - you must declare global variables before you use them. This code is wrong:

function Handler1()
{
    MyGlobalVar = "I'm valid everywhere!";
}

function Handler2()
{
    declare global MyGlobalVar string;
}

Why is this wrong? When the compiler reads each line of your source code, it will find the line of code in Handler1 where you assign MyGlobalVar a value before it finds the global declaration in Handler2!

That's why you should place all your global variable declarations at the very top of your code, outside of any functions:

// My global variable declarations
declare
global MyGlobalVar string;

function Handler1()
{
    MyGlobalVar = "Now I'm in Handler1";
}

function Handler2()
{
    MyGlobalVar = "Now I'm in Handler2";
}

This is very important! If you get compiler errors mentioning uninitialized or incompatible types and you're sure you haven't mistyped a variable name, make sure you've declared your variables before using them.

It's so important it's worth repeating - make sure you declare your variables before you use them, and put global declarations at the top of your source code not inside any functions.

 

Did you skip the scope section?

You can code in other languages and you already knew about scope, right? No problem, but read this carefully.

The Shiny language does NOT break down scope inside functions. The easiest way to explain this is with some code. Here's some sample C code that uses different scopes:

int a = 99;
while(somecondition == true)
{
    int a = 0;
    cout << a; // Here "0" will be displayed
}
cout << a; // Here "99" will be displayed

In C (and a lot of other languages), this code is perfectly legal. The second "int a = 0" declares a variable that only has a scope of the while() loop. Although there are 2 "int a" declarations, they refer to completely separate variables.

Shiny does NOT work like this. When you declare a variable it exists everywhere for the life of its scope. If you tried this code in Shiny:

int a = 99;
while(somecondition == true)
{
    int a = 0;
}

You would receive a compiler error due to the redeclaration of variable "a". These errors look similar to this:

Redeclaration of a on line 4 (prev decl on line 1)

 

Where this might catch you out is in this situation:

while(somecondition == true)
{
    int a = 99;
}
print a; // a is valid here!

 

As the comment says, "a" is valid at the print statement! Why does Shiny work like this?

There's a simple reason - it's easier for people to learn. Shiny and the SSE system was designed so that anyone - even people who haven't programmed anything before - can pick up and write a plugin with ease. Scope is one of the most confusing things for new programmers to get to grips with, and the bugs it can cause when a variable in a different scope is being used instead of the one you wanted can be very difficult to track down.

For this reason, Shiny prevents you from redeclaring a global variable as a local one. Take the following code as an example:

// My global variable declarations
declare
global MyVar string;

function Handler1()
{
    declare MyVar string;
}

Technically there is nothing wrong with this code because one variable is at global scope and the other is at function local scope, but the Shiny compiler will generate a "Redeclaration of MyVar" error. The reason for this is easy to see here:

// My global variable declarations
declare
global MyVar string;

function Handler1()
{
    // Imagine 200 lines of code here

    declare MyVar string;

    // Imagine another 200 lines of code here

}

For the first 200 lines of code all uses of MyVar will refer to the global variable, but MyVar in the second 200 lines of code will refer to the function local scope.

It's a very easy way to get yourself into trouble and cause hard-to-find bugs, which is why Shiny prevents it.

 

Phew, that's Scope done

Let's get back to the code! If you remember back to the start of this tutorial, the reason we started talking about scope is because we want to create our font variable in the Init handler, but use it repeatedly in the Render handler.

How we do this should be obvious by now. We need to move our font declaration to the very top of our code and move the createfont statement to the Init handler.

Remove the declare MyFont font; line from the Render handler

Add the following code to the first line of your plugin:

declare global MyFont font;

 

Move the createfont(MyFont, "Arial", 12); line from Render to the Init handler

Move the setfontcol(MyFont, RGB(0, 0, 255)); line from Render to the Init handler

 

Note that we leave the selectfont() statement in the Render handler. Every time we draw our plugin's output we want to make sure we use the font we created. Because selectfont() is a drawing command we must only call it in Render and not at any other time.

 

All done! Your code should now look exactly like this:

// The font used for displaying text
declare global MyFont font;

// ******************************************
// Called once when the plugin is started.
// ******************************************

function Init()
{
    createfont(MyFont, "Arial", 12);
    setfontcol(MyFont, RGB(0, 0, 255));
}

// **********************************************
// The plugin must draw all output here ONLY.
// **********************************************
function Render()
{
    declare MyText string;
    declare PosX int;
    declare PosY int;

    MyText = "A plugin with variables!";
    PosX = 10;
    PosY = 50;

    selectfont(MyFont);

    drawtext(MyText, PosX, PosY);
}

 

That's all the code finished, as usual let's compile and run the plugin.

Click the "Compile" icon ( )on the toolbar, or select "Compile" from the Build menu

Click the "Execute" icon ( ) on the toolbar, or select "Run in Brass" from the Build menu

 

If everything went OK you should see absolutely no change to your plugin display! That was the entire aim, after all - make the code more efficient without changing the output.

 

A Quick Recap

Well we were going to do some input handling, but understanding scope and the Init handler was more important. What you have now is code that uses the basic principles that all plugins will use. And if you're still finding things easy then great - it really doesn't get any harder than this.

In this tutorial you saw:

  • How to declare global variables
  • Using the one-time setup Init handler
  • The principles behind scope

In the next tutorial we really are going to see how to respond to user input, as well as how to do some drawing. We're aiming for a nice little game over the next couple of tutorials.