Squirrel pitfalls
NoAI Framework

NoAI Main Page

Development

Development milestones
Suggested API changes

AI Programming

API Documentation
Introduction
info.nut file
main.nut file
Basics
Using the road pathfinder
Using the rail pathfinder
Saving / Loading data
Things you need to know
Squirrel
Home page
Common mistakes
Lists
Coping with OTTD errors
Trams
AI Libraries
Forum
Forum FAQ
AI Behavior
ShortNames in use

AIs

TestAI
WrightAI
Convoy
SimpleAI
Comparison of AIs
See forum
See BaNaNaS
All articles in NoAI category

Contents

require()

To include files inside your main.nut, you can use require(). This function works relative from the current file, so it should be easy to use. But, there are several things you have to keep in mind:

Classes

Make sure you initialize all your non-static class variables in the constructor. Just giving them a value when you declare them is not enough if you create multiple instances!! If you fail to initialize a variable in the constructor it might end up with a weird value, such as copying the value from another instance of the class. The box below shows a correct way to initialize variables. The important thing is that all variables get a value in the constructor.

class Developer
{
    name = null;
    age  = null;
    constructor() {
        name = "Unknown Developer";
        age  = 25;
    }
    function SetName(name);
    // ...
}

Function declarations

Also note that function declarations in a class happens outside the block belonging to the class.

class Util{
    function Util::GetMsg(){
        return "Test";
    }	
}

will make the AI crash when loading, while

class Util{
    	
}
function Util::GetMsg(){
   return "Test";
}

will work as expected.

Squirrel lacks a way to mark methods as static or private

In other object oriented languages you can limit how functions are called when you declare them. In squirrel the only difference between an instance function and a static function are the way you call them. In other languages you can also choose to make methods public or private. All methods in squirrel are public.

There are two ways to call functions. You may call them from the instance or you may call them staticly. When you call a function from the instance you either preface the call with the the keyword this or you don't prefix it at all.

The AIFoo.Valuate() methods always call your methods statically. So any custom valuators should be self contained.

instance
 function MyNewAI::Start()
 {
   this.MainLoop();
   /* MainLoop(); would also work. this. is usually optional. */
   
   /* Note that this sample code won't actually get called, because MainLoop() does not return ;-) */
   local otherClass = MyOtherClass();
   otherClass.SomeFunction(); // Calls the function with the instance method on another class.
 }
 
 function MyNewAI::MainLoop()
 {
   while(true) { // Loop forever.
     this.Sleep(10);
   }
 }
 

When you call the method from the instance, the method has all the variables and other information avialalble to this instance of the class. So the line this.Sleep(10) will work correctly, because AIController has the sleep method for your class. Remember your main class is an extension of AIController and so has all it's abilities as well as any you give it.

In order to call a static instance you need to preface the method with the class name.

static
 function MyNewAI::Start()
 {
   MyNewAI.MainLoop();
   MyOtherClass.SomeFunction(); // Calls the otherclass function staticly.
 }
 
 function MyNewAI::MainLoop()
 {
   while(true) { // Loop forever.
     AIController.Sleep(10); // "this" no longer exists in static context.
   }
 }
 

Remember that your functions can be called in either way. It's up to the caller how to use the function.

Assignment statements that take long time

Assume that good_pairs is a member variable of your main class. Assume that there is a method FindGoodPairs that finds new pairs and that this method takes some time to return.

You may think that this is a good way to update good_pairs:

 this.good_pairs = FindGoodPairs();

Now lets assume FindGoodPairs look some thing like this:

 function FindGoodPairs() {
   local result = [];
   // .. time consuming code that finds good pairs
   return result;
 }

Now if Save() is called from the point when FindGoodPairs() is called, but before it returns, good_pairs will not hold the old result from your previous call to FindGoodPairs, instead it will hold some partial result from the FindGoodPairs method.

I can't tell exactly what will happen for different ways of writing FindGoodPairs, only that I've found bugs in scripts related to this thing.

A way to get around this is to change the code that calls FindGoodPairs to this:

 local temp = FindGoodPairs();
 this.good_pairs = temp;