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.
|