Difference between revisions of "User:Toy Wylie/Phoenix/Preprocessor"

From Second Life Wiki
Jump to navigation Jump to search
(first migration steps)
 
(converted)
Line 47: Line 47:
Save the script. You will see the "Debugging with the Phoenix LSL Preprocessor" message in your chat console. Now change the first line of the LSL script to:
Save the script. You will see the "Debugging with the Phoenix LSL Preprocessor" message in your chat console. Now change the first line of the LSL script to:


%%
<lsl>#undef DEBUG</lsl>
#undef DEBUG
%%


Save the script again. You will see that the debugging message is gone. This is a very handy way to enable and disable all debugging code in one single line. To understand what really happens, have a look at the "Postprocessed" tab. You can clearly see how the debug line will simply disappear, a lonely ";" the only trace of it having been there.
Save the script again. You will see that the debugging message is gone. This is a very handy way to enable and disable all debugging code in one single line. To understand what really happens, have a look at the "Postprocessed" tab. You can clearly see how the debug line will simply disappear, a lonely ";" the only trace of it having been there.
Line 61: Line 59:
The Preprocessor also adds a set of new commands to the LSL editor which has been sorely missing until now: the <tt>switch/case</tt> construct known from many other languages. You can enable support for this construct in the script editor's "Advanced" menu. <tt>switch/case</tt> is a handy replacement for <tt>if(...) else if()</tt> chains. Additionally, switch/case supports "fallthrough" from one case to another, so you can chain up several cases with different conditions. A <tt>break</tt> statement is used to prevent fallthrough. The <tt>default</tt> case is used if none of the cases match the <tt>switch()</tt> condition.
The Preprocessor also adds a set of new commands to the LSL editor which has been sorely missing until now: the <tt>switch/case</tt> construct known from many other languages. You can enable support for this construct in the script editor's "Advanced" menu. <tt>switch/case</tt> is a handy replacement for <tt>if(...) else if()</tt> chains. Additionally, switch/case supports "fallthrough" from one case to another, so you can chain up several cases with different conditions. A <tt>break</tt> statement is used to prevent fallthrough. The <tt>default</tt> case is used if none of the cases match the <tt>switch()</tt> condition.
   
   
Example:
Example:
%%
 
default
<lsl>default
{
{
state_entry()
    state_entry()
{
    {
integer i;
        integer i;
switch(i)
        switch(i)
{
        {
case 1:
            case 1:
{
            {
llOwnerSay("1");
                llOwnerSay("1");
// fallthrough to case 2
                // fallthrough to case 2
}
            }
case 2:
            case 2:
{
            {
llOwnerSay("1 or 2");
                llOwnerSay("1 or 2");
// no fallthrough
                // no fallthrough
break;
                break;
}
            }
case 3:
            case 3:
{
            {
llOwnerSay("3");
                llOwnerSay("3");
// fallthrough to default
                // fallthrough to default
}
            }
default:
            default:
{
            {
llOwnerSay("3 or default");
                llOwnerSay("3 or default");
}
            }
}
        }
}
    }
}
}</lsl>
%%


===7. Lazy Lists addition===
===7. Lazy Lists addition===
Line 99: Line 96:
Assigning values to list indexes is always a cumbersome thing to do in LSL (<tt>llListReplaceList()</tt> needed). Lazy Lists can help you a little by providing a way to just say:
Assigning values to list indexes is always a cumbersome thing to do in LSL (<tt>llListReplaceList()</tt> needed). Lazy Lists can help you a little by providing a way to just say:


%%
<lsl>myList[index]=value;</lsl>
myList[index]=value;
%%


Sadly it is not really possible to get list elements the same way because the return data can be of different types.
Sadly it is not really possible to get list elements the same way because the return data can be of different types.
Line 109: Line 104:
The Preprocessor understands the following commands:
The Preprocessor understands the following commands:


%%
<lsl>#define
#define
#undef
#undef
#ifdef
#ifdef
#ifndef
#ifndef
#if
#if
#elif
#elif
#else
#else
#endif
#endif
#warning
#warning
#error
#error
#include</lsl>
#include
%%


There are a few more, but these are not really useful within LSL.
There are a few more, but these are not really useful within LSL.
Line 127: Line 120:
Additionally, you can use the following macros in your scripts to help with debugging and giving you other useful information:
Additionally, you can use the following macros in your scripts to help with debugging and giving you other useful information:


<tt>""__FILE__""</tt> - the full path to the script as it would appear in the include cache. The top script only uses its name.
<tt>__FILE__</tt> - the full path to the script as it would appear in the include cache. The top script only uses its name.<br>
<tt>""__LINE__""</tt> - the line of the current script where it is expanded; this starts at line 0
<tt>__LINE__</tt> - the line of the current script where it is expanded; this starts at line 0<br>
<tt>""__SHORTFILE__""</tt> - the name of the current script without full file path
<tt>__SHORTFILE__</tt> - the name of the current script without full file path<br>
<tt>""__AGENTID__""</tt> - a string-encapsulated version of the agent's key who compiles the script
<tt>__AGENTID__</tt> - a string-encapsulated version of the agent's key who compiles the script<br>
<tt>""__AGENTKEY__""</tt> - same as above, legacy version
<tt>__AGENTKEY__</tt> - same as above, legacy version<br>
<tt>""__AGENTIDRAW__""</tt> - a nonstring-encapsulated version of the agent's key who compiles the script
<tt>__AGENTIDRAW__</tt> - a nonstring-encapsulated version of the agent's key who compiles the script<br>
<tt>""__AGENTNAME__""</tt> - a string-encapsulated version of the agent's full name who compiles the script
<tt>__AGENTNAME__</tt> - a string-encapsulated version of the agent's full name who compiles the script<br>
<tt>""__ASSETID__""</tt> - a string-encapsulated version of the assetid of the current script; may return "NOT IN WORLD" or a nonstring-encapsulated null key in rare circumstances  
<tt>__ASSETID__</tt> - a string-encapsulated version of the assetid of the current script; may return "NOT IN WORLD" or a nonstring-encapsulated null key in rare circumstances  
 
Thanks to Zwagoth Klaar for this list.
Thanks to Zwagoth Klaar for this list.
 
===8.1 <tt>#define</tt>===
===8.1 <tt>#define</tt>===
   
   
Line 144: Line 137:
The following examples will give you an overview of what </tt>#define</tt> does. Have a look at the "Postprocessed" tab to see what the Preprocessor creates from the source.
The following examples will give you an overview of what </tt>#define</tt> does. Have a look at the "Postprocessed" tab to see what the Preprocessor creates from the source.
   
   
Example 1:
Example 1:
%%
 
#define CHANNEL 12345
<lsl>#define CHANNEL 12345
llOwnerSay((string) CHANNEL); // CHANNEL will be replaced with the literal 12345 stated in the #define above
llOwnerSay((string) CHANNEL); // CHANNEL will be replaced with the literal 12345 stated in the #define above</lsl>
%%


<tt>#define</tt> can also take parameters to apply to the replacement code:
<tt>#define</tt> can also take parameters to apply to the replacement code:
   
   
Example 2:
Example 2:
%%
 
#define OS(b,c) llOwnerSay(b+c)
<lsl>#define OS(b,c) llOwnerSay(b+c)
  OS("Test","123"); // will expand to: llOwnerSay("Test"+"123")
  OS("Test","123"); // will expand to: llOwnerSay("Test"+"123")</lsl>
%%
 
Example 3: Making strings out of parameters
Example 3: Making strings out of parameters
%%
 
#define OS(a) llOwnerSay(#a)
<lsl>#define OS(a) llOwnerSay(#a)
OS(1234); // will expand to: llOwnerSay("1234");
OS(1234); // will expand to: llOwnerSay("1234");</lsl>
%%
 
Example 4: Using <tt>""##""</tt> to concatenate parameters
Example 4: Using <tt>""##""</tt> to concatenate parameters
%%
 
#define OS(a,b) llOwnerSay((string) a##b)
<lsl>#define OS(a,b) llOwnerSay((string) a##b)
OS(1234,5678); // will expand to: llOwnerSay((string) 12345678);
OS(1234,5678); // will expand to: llOwnerSay((string) 12345678);</lsl>
%%


===8.2 <tt>#undef</tt>===
===8.2 <tt>#undef</tt>===
Line 176: Line 167:
This command is a part of conditional preprocessing in association with <tt>#else</tt> and <tt>#endif</tt>. <tt>#ifdef</tt> checks if a macro has been previously <tt>#define</tt>d. It doesn't matter if the macro has actually a value assigned to it. It just needs to be <tt>#define</tt>d. If it has, all of the code after <tt>#ifdef</tt> up to <tt>#endif</tt> or <tt>#else</tt> gets replaced into the postprocessed code. <tt>#ifndef</tt> does the exact opposite. If a macro does not exist, the code goes into the Preprocessor.
This command is a part of conditional preprocessing in association with <tt>#else</tt> and <tt>#endif</tt>. <tt>#ifdef</tt> checks if a macro has been previously <tt>#define</tt>d. It doesn't matter if the macro has actually a value assigned to it. It just needs to be <tt>#define</tt>d. If it has, all of the code after <tt>#ifdef</tt> up to <tt>#endif</tt> or <tt>#else</tt> gets replaced into the postprocessed code. <tt>#ifndef</tt> does the exact opposite. If a macro does not exist, the code goes into the Preprocessor.
   
   
Example:
Example:
%%
 
#define OWNER_ONLY
<lsl>#define OWNER_ONLY
...
...
#ifdef OWNER_ONLY
#ifdef OWNER_ONLY
key var=llGetOwner();
key var=llGetOwner();
#else
#else
key var=llDetectedKey(0);
key var=llDetectedKey(0);
#endif
#endif</lsl>
%%


===8.4 <tt>#if</tt> and <tt>#elif</tt>===
===8.4 <tt>#if</tt> and <tt>#elif</tt>===
Line 191: Line 181:
These are also conditional preprocessing commands. They take a general condition and pass on the code to the Preprocessor if the condition evaluates to <tt>TRUE</tt>. They can also be used in conjunction with <tt>#else</tt>. <tt>#elif</tt> is the equivalent to <tt>else if</tt>.
These are also conditional preprocessing commands. They take a general condition and pass on the code to the Preprocessor if the condition evaluates to <tt>TRUE</tt>. They can also be used in conjunction with <tt>#else</tt>. <tt>#elif</tt> is the equivalent to <tt>else if</tt>.
   
   
Example:
Example:
%%
 
#define DEBUGLEVEL 2
<lsl>#define DEBUGLEVEL 2
 
#if DEBUGLEVEL==1
#if DEBUGLEVEL==1
llOwnerSay("Point reached");
llOwnerSay("Point reached");
#elif DEBUGLEVEL==2
#elif DEBUGLEVEL==2
llOwnerSay("Lots of more data here");
llOwnerSay("Lots of more data here");
#else
#else
llOwnerSay("Unknown debug level: "+(string) DEBUGLEVEL);
llOwnerSay("Unknown debug level: "+(string) DEBUGLEVEL);
#endif
#endif</lsl>
%%


===8.5 <tt>#warning</tt> and <tt>#error</tt>===
===8.5 <tt>#warning</tt> and <tt>#error</tt>===
   
   
These two commands show a string in the compiler window to warn you about certain problems or to halt compilation immediately due to a fatal error. Right now, both <tt>#warning</tt> and <tt>#error</tt> cause the compiler to stop. It is unclear yet if <tt>#warning</tt> will allow the compiler to continue to completion in the future.
These two commands show a string in the compiler window to warn you about certain problems or to halt compilation immediately due to a fatal error. Right now, both <tt>#warning</tt> and <tt>#error</tt> cause the compiler to stop. It is unclear yet if <tt>#warning</tt> will allow the compiler to continue to completion in the future.
 
'''NOTE:''' If one of these commands is hit by the Preprocessor, the script is '''NOT''' saved!
'''NOTE:''' If one of these commands is hit by the Preprocessor, the script is '''NOT''' saved!
 
Example 1:
Example 1:
%%
 
#warning This include file is obsolete!
<lsl>#warning This include file is obsolete!</lsl>
%%
 
Example 2:
Example 2:
%%
 
#error This include file does not work anymore. Please update.
<lsl>#error This include file does not work anymore. Please update.</lsl>
%%


===8.6 <tt>#include</tt>===
===8.6 <tt>#include</tt>===
Line 223: Line 211:
This is probably the most powerful feature of the Phoenix LSL Preprocessor. It includes whole source code files from your harddisk or from the same folder tree in your inventory into the script you are working on. A small example of this feature can be seen above in Section 4. <tt>#include</tt> takes a file name relative to the include path set up in your Preferences. You can also include files inside of subfolders. If you are compiling your script from your inventory, the Preprocessor will search the inventory path you are working in, descending into any subfolders, to find the referenced include file. Unused functions and global variable declarations are removed by the Optimizer.
This is probably the most powerful feature of the Phoenix LSL Preprocessor. It includes whole source code files from your harddisk or from the same folder tree in your inventory into the script you are working on. A small example of this feature can be seen above in Section 4. <tt>#include</tt> takes a file name relative to the include path set up in your Preferences. You can also include files inside of subfolders. If you are compiling your script from your inventory, the Preprocessor will search the inventory path you are working in, descending into any subfolders, to find the referenced include file. Unused functions and global variable declarations are removed by the Optimizer.
   
   
Examples:
Examples:
%%
 
#include "command_ids.lsl"
<lsl>#include "command_ids.lsl"
#include "general_functions.lsl"
#include "general_functions.lsl"
#include "hud/layout.lsl"
#include "hud/layout.lsl"</lsl>
%%


'''NOTE:''' Be careful about including files from within <tt>#include</tt>s. You might <tt>#include</tt> a file twice, if you are not really keeping track, and this will lead to problems. This issue is usually addressed by using so-called "Include guards". You basically have a conditional compile that sets a <tt>#define</tt> macro and checks if it's already there. If it's not, <tt>#include</tt> the contents of the file. If it was set, ignore the contents.
'''NOTE:''' Be careful about including files from within <tt>#include</tt>s. You might <tt>#include</tt> a file twice, if you are not really keeping track, and this will lead to problems. This issue is usually addressed by using so-called "Include guards". You basically have a conditional compile that sets a <tt>#define</tt> macro and checks if it's already there. If it's not, <tt>#include</tt> the contents of the file. If it was set, ignore the contents.
   
   
Example:
Example:
%%
 
#ifndef SCRIPT_NAME_LSL
<lsl>#ifndef SCRIPT_NAME_LSL
#define SCRIPT_NAME_LSL
#define SCRIPT_NAME_LSL
your_script_starts_here()
your_script_starts_here()
{
{
}
}
#endif //SCRIPT_NAME_LSL
#endif //SCRIPT_NAME_LSL</lsl>
%%


These <tt>#ifndef</tt>, <tt>#define</tt> and <tt>#endif</tt> commands make sure that the <tt>#include</tt> happens only once regardless of how often the file is actually referenced with <tt>#include</tt>.
These <tt>#ifndef</tt>, <tt>#define</tt> and <tt>#endif</tt> commands make sure that the <tt>#include</tt> happens only once regardless of how often the file is actually referenced with <tt>#include</tt>.

Revision as of 16:46, 12 September 2010

Phoenix LSL Preprocessor

an overview by Toy Wylie, rev. 0.8

1. Overview

Developing scripts in LSL can be tedious, especially if you are creating scripts that use the same things over and over again, and you keep copying pieces of your older scripts to new creations. And even within one project, you often have to copy parts of your scripts into sub-scripts (example: link message numbers as identifiers for commands). Changing any of these numbers or updating and fixing bugs in older variants of the code might result in different versions of the same functions being used or in bugs found in older versions not being fixed in newer creations. The Phoenix LSL Preprocessor is a tool to help you circumvent a lot of these problems.

Adding and removing debugging statements is another thing with which the Preprocessor can be helpful. Usually you have debugging functions in the script to see if all is working fine, and you take them out before release. But this in itself creates new places for mistakes as well as the opportunity for some debugging output to remain in the script on release day because it was overlooked during final screening. Using the Phoenix LSL Preprocessor gives you a very simple way of making sure that no debug output is left in your final release.

2. Setup

To enable the Phoenix LSL Preprocessor, open your preferences panel (CTRL-P), click on "Phoenix", "Page 2", and then on "Inventory". Mark the checkbox "Enable LSL Preprocessor Filesystem Includes" and click on "Set". This will open a file requester, prompting you to point at a directory on your local computer. This is the place where you store all your LSL include files. Next, open any LSL script, click on "Advanced", and select "Enable Preprocessor". Then close the script.

3. How it Works

After setting up, you will see two tabs in your LSL editor: "Script" and "Postprocessed". The first is your active scripting window; the second shows the output of the Preprocessor, essentially the content that gets stored in the script. The postprocessed script contains the entire source code of what you have written in a comment block at the beginning, and the postprocessed output following this comment block. This ensures backwards compatibility with older versions of Phoenix and regular viewers that do not support preprocessing at all.

NOTE: Since the LSL/Mono compiler gets the script after preprocessing, the line numbers in error messages refer to the postprocessed script. So if you are getting compiler errors, be sure to look for the error in the "Postprocessed" tab rather than in your original script.

4. A Short Example

Create a file in your include folder with the name debug.lsl. Copy the following snippet into this file:

<lsl>#ifdef DEBUG debug(string text) {

   llOwnerSay(text);

}

  1. else
  2. define debug(dummy)
  3. endif</lsl>

Now create a new LSL script and copy the following code into the script editor:

<lsl>#define DEBUG

  1. include "debug.lsl"

default {

   state_entry()
   {
       debug("Debugging with the Phoenix LSL Preprocessor.");
   }

}</lsl>

Save the script. You will see the "Debugging with the Phoenix LSL Preprocessor" message in your chat console. Now change the first line of the LSL script to:

<lsl>#undef DEBUG</lsl>

Save the script again. You will see that the debugging message is gone. This is a very handy way to enable and disable all debugging code in one single line. To understand what really happens, have a look at the "Postprocessed" tab. You can clearly see how the debug line will simply disappear, a lonely ";" the only trace of it having been there.

5. The Optimizer

Including files has one disadvantage though. You will get the complete contents of the file, if you need it or not. But the Phoenix LSL Preprocessor uses an optimizing technique, which only keeps the things you really used in your code and removes all global functions and variables you didn't reference in your script. This makes sure that your scripts don't get burdened with a lot of unused code. You can enable or disable this functionality in the script editor's "Advanced" menu.

6. switch/case addition

The Preprocessor also adds a set of new commands to the LSL editor which has been sorely missing until now: the switch/case construct known from many other languages. You can enable support for this construct in the script editor's "Advanced" menu. switch/case is a handy replacement for if(...) else if() chains. Additionally, switch/case supports "fallthrough" from one case to another, so you can chain up several cases with different conditions. A break statement is used to prevent fallthrough. The default case is used if none of the cases match the switch() condition.

Example:

<lsl>default {

   state_entry()
   {
       integer i;
       switch(i)
       {
           case 1:
           {
               llOwnerSay("1");
               // fallthrough to case 2
           }
           case 2:
           {
               llOwnerSay("1 or 2");
               // no fallthrough
               break;
           }
           case 3:
           {
               llOwnerSay("3");
               // fallthrough to default
           }
           default:
           {
               llOwnerSay("3 or default");
           }
       }
   }

}</lsl>

7. Lazy Lists addition

Assigning values to list indexes is always a cumbersome thing to do in LSL (llListReplaceList() needed). Lazy Lists can help you a little by providing a way to just say:

<lsl>myList[index]=value;</lsl>

Sadly it is not really possible to get list elements the same way because the return data can be of different types.

8. Preprocessor Commands and Macros

The Preprocessor understands the following commands:

<lsl>#define

  1. undef
  2. ifdef
  3. ifndef
  4. if
  5. elif
  6. else
  7. endif
  8. warning
  9. error
  10. include</lsl>

There are a few more, but these are not really useful within LSL.

Additionally, you can use the following macros in your scripts to help with debugging and giving you other useful information:

__FILE__ - the full path to the script as it would appear in the include cache. The top script only uses its name.
__LINE__ - the line of the current script where it is expanded; this starts at line 0
__SHORTFILE__ - the name of the current script without full file path
__AGENTID__ - a string-encapsulated version of the agent's key who compiles the script
__AGENTKEY__ - same as above, legacy version
__AGENTIDRAW__ - a nonstring-encapsulated version of the agent's key who compiles the script
__AGENTNAME__ - a string-encapsulated version of the agent's full name who compiles the script
__ASSETID__ - a string-encapsulated version of the assetid of the current script; may return "NOT IN WORLD" or a nonstring-encapsulated null key in rare circumstances

Thanks to Zwagoth Klaar for this list.

8.1 #define

#define creates a case-sensitive macro which will be replaced while saving and compiling a script. This can be applied as simple constant numbers, strings, and even functions. What happens is a literal text replacement in the source code. Because of this, be careful with ";" inside your macros. These usually don't cause any harm; but within a one-line conditional, things can break in unexpected ways if it creates a ";;" in the end of a line, for example.

The following examples will give you an overview of what #define does. Have a look at the "Postprocessed" tab to see what the Preprocessor creates from the source.

Example 1:

<lsl>#define CHANNEL 12345 llOwnerSay((string) CHANNEL); // CHANNEL will be replaced with the literal 12345 stated in the #define above</lsl>

#define can also take parameters to apply to the replacement code:

Example 2:

<lsl>#define OS(b,c) llOwnerSay(b+c)

OS("Test","123"); // will expand to: llOwnerSay("Test"+"123")</lsl>

Example 3: Making strings out of parameters

<lsl>#define OS(a) llOwnerSay(#a) OS(1234); // will expand to: llOwnerSay("1234");</lsl>

Example 4: Using ""##"" to concatenate parameters

<lsl>#define OS(a,b) llOwnerSay((string) a##b) OS(1234,5678); // will expand to: llOwnerSay((string) 12345678);</lsl>

8.2 #undef

Removes a macro previously set up with #define. If the macro was not made in the first place, nothing happens. This is a useful way to enable or disable parts of the source code for debugging. See Section 4 above for an example on how to use it.

8.3 #ifdef and #ifndef, #else and #endif

This command is a part of conditional preprocessing in association with #else and #endif. #ifdef checks if a macro has been previously #defined. It doesn't matter if the macro has actually a value assigned to it. It just needs to be #defined. If it has, all of the code after #ifdef up to #endif or #else gets replaced into the postprocessed code. #ifndef does the exact opposite. If a macro does not exist, the code goes into the Preprocessor.

Example:

<lsl>#define OWNER_ONLY ...

  1. ifdef OWNER_ONLY

key var=llGetOwner();

  1. else

key var=llDetectedKey(0);

  1. endif</lsl>

8.4 #if and #elif

These are also conditional preprocessing commands. They take a general condition and pass on the code to the Preprocessor if the condition evaluates to TRUE. They can also be used in conjunction with #else. #elif is the equivalent to else if.

Example:

<lsl>#define DEBUGLEVEL 2

  1. if DEBUGLEVEL==1

llOwnerSay("Point reached");

  1. elif DEBUGLEVEL==2

llOwnerSay("Lots of more data here");

  1. else

llOwnerSay("Unknown debug level: "+(string) DEBUGLEVEL);

  1. endif</lsl>

8.5 #warning and #error

These two commands show a string in the compiler window to warn you about certain problems or to halt compilation immediately due to a fatal error. Right now, both #warning and #error cause the compiler to stop. It is unclear yet if #warning will allow the compiler to continue to completion in the future.

NOTE: If one of these commands is hit by the Preprocessor, the script is NOT saved!

Example 1:

<lsl>#warning This include file is obsolete!</lsl>

Example 2:

<lsl>#error This include file does not work anymore. Please update.</lsl>

8.6 #include

This is probably the most powerful feature of the Phoenix LSL Preprocessor. It includes whole source code files from your harddisk or from the same folder tree in your inventory into the script you are working on. A small example of this feature can be seen above in Section 4. #include takes a file name relative to the include path set up in your Preferences. You can also include files inside of subfolders. If you are compiling your script from your inventory, the Preprocessor will search the inventory path you are working in, descending into any subfolders, to find the referenced include file. Unused functions and global variable declarations are removed by the Optimizer.

Examples:

<lsl>#include "command_ids.lsl"

  1. include "general_functions.lsl"
  2. include "hud/layout.lsl"</lsl>

NOTE: Be careful about including files from within #includes. You might #include a file twice, if you are not really keeping track, and this will lead to problems. This issue is usually addressed by using so-called "Include guards". You basically have a conditional compile that sets a #define macro and checks if it's already there. If it's not, #include the contents of the file. If it was set, ignore the contents.

Example:

<lsl>#ifndef SCRIPT_NAME_LSL

  1. define SCRIPT_NAME_LSL

your_script_starts_here() { }

  1. endif //SCRIPT_NAME_LSL</lsl>

These #ifndef, #define and #endif commands make sure that the #include happens only once regardless of how often the file is actually referenced with #include.

9. Known Issues

  • Including a file from your hard drive containing a #endif as last line (without linebreak) will not produce a warning about not having a line break but rather a statement error
  • This can be fixed by adding a line break to the end of the file
  • "Enable Text Compress" will break your script! (At least in its current form. It may be fixed, or removed, in the future.)