08-19-2012, 03:36 PM
Tutorial: Classes in the Las Venturas Playground PreCompiler
The primary new feature introduced in the LVP PreCompiler is the ability to write your code using classes. While they are, especially in their design, comparable to classes in other programming languages, a number of additional limitations exist in Pawn which mean that they're slightly different to work with.
The primary purpose of a class is to group code driving towards a similar goal together. This could be described as encapsulation[1]. Examples of well known features that can be rewritten as a class include the Pirate Ship, display of a count down and the spawn selection screens. By writing features as a class, there will be a single file which contains all the code and variables, making it much easier to maintain them and disable them when necessary.
Basic terminology
Let's start off with defining terminology for classes. This is slightly different from the rest of the code, mostly to emphasize on the way how classes encapsulate (own) data.
![[Image: terminology.jpg]](http://crew.sa-mp.nl/russell/terminology.jpg)
![[Image: terminology-2.jpg]](http://crew.sa-mp.nl/russell/terminology-2.jpg)
In the code above, 150 instances of the Vehicle class will exist, each of which have their own m_modelId property and can be identified (if necessary) with the vehicleId variable, which will be available in every* method.
Instances
Say you have a feature such as a player's bank account. Each function or method you write to implement it will take the player's Id as an argument, considering you need to know that in order to find the right information. Storing that information will be done in arrays, and you have to repeat the index every time you want to access it. Of course you could use an enum, but then you'll end up with accessors such as aPlayers[playerid][P_CashMoney]. This will quickly become unmaintainable, and worse, if San Andreas: Multiplayer ever decides to update the version of Pawn they support, enumerations (the feature used to implement that) have been removed!
![[Image: meme.jpg]](http://crew.sa-mp.nl/russell/meme.jpg)
The LVP PreCompiler solves this by introducing a notion of class instances. You can compare them to a form of secret arrays, except that you don't have to worry about them too much: all the work is being done for you on the background. This allows you to maintain player information much closer to where the actual code is located, and, together with some other features of the PreCompiler, will allow you to write features completely defined and used (except for one #include statement) in a single file. Remove the file, and your feature is gone. That's what I call isolation! Let's look at the following simple code example:
![[Image: bank-account.jpg]](http://crew.sa-mp.nl/russell/bank-account.jpg)
As you see, you define the player's Id once and the rest just magically works! Especially when more of your code is being written as classes, this significantly brings down the complexity of maintaining proper array sizes. Let's look at the declaration more closely:
![[Image: instance-declaration.jpg]](http://crew.sa-mp.nl/russell/instance-declaration.jpg)
As you can see, the declaration contains both the number of instances to be created, as the name it should be given. The instance name is a magic variable available in all methods, which means that you're welcome to use the "playerId" variable in the balance() method if you want. You don't have to worry about not using it either, as the PreCompiler will take care of any warnings that might be introduced by not doing so.
One thing you cannot do, is pass an argument named "playerId" to a method in a class that has "playerId" as its instance name (unless the method is declared as static, more on that later). This will mean that two arguments named "playerId" will be passed to the method, which will result in a compiler error.
---- calling instance methods.
There are a few things you have to keep in mind when using instances:
The LVP PreCompiler also contains a number of debugging features, one of which will enable overflow checking for method calls. This will, however, make class calls slightly slower than normal calls, and therefore is disabled by default. More on this will be available in another article about the PreCompiler's debugging features.
Static methods
When a class has instances, every call to a method in that class must include the instance you'd like to address. There are many programming paradigms which don't need this. For example, you could have a method doing calculations which is agnostic of whatever instance is being used. In that case, you can declare the method as being "static", which in the LVP PreCompiler is a word for saying "without instances".
Usage is pretty simple: just add the "static" keyword before the method's name. The instance's name variable will not be available, and access to any non-static properties won't be easily possible either. As an example of doing this:
![[Image: static-balance.jpg]](http://crew.sa-mp.nl/russell/static-balance.jpg)
If all methods in your class have to be declared as "static", then you probably should be using an array for properties instead, and not use instances at all.
Method visibility
The public keyword in classes means a different thing than it does in normal Pawn code, where it means that a function is being exported for use outside of your script itself. Think about it: the San Andreas: Multiplayer server is not written in Pawn, yet it is able to call the OnPlayerConnect function defined in your script. There is no way to export methods inside of a class.
Each method in your class has a certain visibility. By default, in Pawn, everything in your code is public, meaning that all other code in the script is able to call it. If you have a very large script existing of tens of thousands of lines of code, this may not always be necessary: identifying a user by their account Id is something you only want to do after they entered the right password, not through a hidden command introduced by a new developer.
For this purpose, there are two visibility keywords available:
Let's visualize this in a simple code example:
![[Image: visibility.jpg]](http://crew.sa-mp.nl/russell/visibility.jpg)
The errors are created when you are compiling your gamemode. It's best practice to mark a method as private unless other code really needs to access it. Also, mind that all properties defined in a class are always private! You can write an (inline) method to retrieve its value, but code outside of the class cannot access the property directly.
Inline methods
Direct variable access in Pawn is a lot quicker than returning it through a function, which introduced a serious issue when we were designing the syntax of the LVP PreCompiler: there cannot be any run-time performance cost for the usage of classes. To solve this issue, a notion of inline methods was introduced.
Inline methods are comparable to the better known #define statement in Pawn. Calling an inline method will be translated to inserting the method's code directly, meaning that there is no performance cost for using them. To give an example:
![[Image: inline-methods.jpg]](http://crew.sa-mp.nl/russell/inline-methods.jpg)
There are a few things you have to keep in mind with inline methods. While these are limitations of the PreCompiler and may be fixed one day, they are not a sufficient priority right now to get fixed immediately. Play on the safe side, and write your code assuming that they will never be fixed.
Despite these constraints, they are a powerful tool in writing your classes. Don't make everything in-line, though: performance problems very rarely are related to function calls. If they are, you should consider optimizing code so that it's called much less often, for example by caching the value in a variable elsewhere.
Code commentary: documentation!
The source code of Las Venturas Playground has online documentation available. While we are not yet ready to start offering this service to other servers, documenting your classes is strongly encouraged. While the purpose and workings of a class may be obvious to the person who write it, to other people, especially when they just joined your server's team, it may be a complete mystery.
The documentation format is roughly comparable to JavaDoc. We encourage you to write documentation blocks above classes and methods, and shorter documentation (a single line of description) above properties. When doing so, remember that the why is more important than the what. People can read your code to see how exactly it works, but the purpose and idea about your class or method is much more interesting.
![[Image: documentation.jpg]](http://crew.sa-mp.nl/russell/documentation.jpg)
Final words
These are the basics of using classes in Pawn. Of course, there is much, much more to know, and the general idea behind object oriented programming is not one I'm going to explain to you. The syntax of the LVP PreCompiler has stabilized over the past few years, so you can go forth and use it for any code you want without having to worry about backwards incompatibilities in new updates.
If you have any question, comment or feature request for classes in the Las Venturas Playground PreCompiler, feel free to post them in this topic. I intend to keep this post updated as new features are being developed and added to the tool. The PreCompiler is currently available for Windows and Linux, and can be used as a drop-in replacement for the default Pawn compiler which is included in the Windows server package.
TODO
- Calling class methods.
- Constants.
The primary new feature introduced in the LVP PreCompiler is the ability to write your code using classes. While they are, especially in their design, comparable to classes in other programming languages, a number of additional limitations exist in Pawn which mean that they're slightly different to work with.
The primary purpose of a class is to group code driving towards a similar goal together. This could be described as encapsulation[1]. Examples of well known features that can be rewritten as a class include the Pirate Ship, display of a count down and the spawn selection screens. By writing features as a class, there will be a single file which contains all the code and variables, making it much easier to maintain them and disable them when necessary.
Basic terminology
Let's start off with defining terminology for classes. This is slightly different from the rest of the code, mostly to emphasize on the way how classes encapsulate (own) data.
- class - A structure containing code and variables which work towards the same goal.
- member - A variable (property) or function (method) defined in a class.
- property - A member variable defined in a class, only accessible to other members of that class.
- method - A member function defined in a class. Could be limited to the class, but could also be public.
![[Image: terminology.jpg]](http://crew.sa-mp.nl/russell/terminology.jpg)
- instance - A copy of the methods and properties defined in a class.
- visibility - A keyword ("public" or "private") to describe which code can access the member.
![[Image: terminology-2.jpg]](http://crew.sa-mp.nl/russell/terminology-2.jpg)
In the code above, 150 instances of the Vehicle class will exist, each of which have their own m_modelId property and can be identified (if necessary) with the vehicleId variable, which will be available in every* method.
Instances
Say you have a feature such as a player's bank account. Each function or method you write to implement it will take the player's Id as an argument, considering you need to know that in order to find the right information. Storing that information will be done in arrays, and you have to repeat the index every time you want to access it. Of course you could use an enum, but then you'll end up with accessors such as aPlayers[playerid][P_CashMoney]. This will quickly become unmaintainable, and worse, if San Andreas: Multiplayer ever decides to update the version of Pawn they support, enumerations (the feature used to implement that) have been removed!
![[Image: meme.jpg]](http://crew.sa-mp.nl/russell/meme.jpg)
The LVP PreCompiler solves this by introducing a notion of class instances. You can compare them to a form of secret arrays, except that you don't have to worry about them too much: all the work is being done for you on the background. This allows you to maintain player information much closer to where the actual code is located, and, together with some other features of the PreCompiler, will allow you to write features completely defined and used (except for one #include statement) in a single file. Remove the file, and your feature is gone. That's what I call isolation! Let's look at the following simple code example:
![[Image: bank-account.jpg]](http://crew.sa-mp.nl/russell/bank-account.jpg)
As you see, you define the player's Id once and the rest just magically works! Especially when more of your code is being written as classes, this significantly brings down the complexity of maintaining proper array sizes. Let's look at the declaration more closely:
![[Image: instance-declaration.jpg]](http://crew.sa-mp.nl/russell/instance-declaration.jpg)
As you can see, the declaration contains both the number of instances to be created, as the name it should be given. The instance name is a magic variable available in all methods, which means that you're welcome to use the "playerId" variable in the balance() method if you want. You don't have to worry about not using it either, as the PreCompiler will take care of any warnings that might be introduced by not doing so.
One thing you cannot do, is pass an argument named "playerId" to a method in a class that has "playerId" as its instance name (unless the method is declared as static, more on that later). This will mean that two arguments named "playerId" will be passed to the method, which will result in a compiler error.
---- calling instance methods.
There are a few things you have to keep in mind when using instances:
- Instances have to be declared at compile time. Because Pawn is a static language, we cannot easily get access to new memory after your script has been compiled.
- No overflow checking will be done by default. If you call a method for the 9000th instance, while there only are 250, your script will crash.
The LVP PreCompiler also contains a number of debugging features, one of which will enable overflow checking for method calls. This will, however, make class calls slightly slower than normal calls, and therefore is disabled by default. More on this will be available in another article about the PreCompiler's debugging features.
Static methods
When a class has instances, every call to a method in that class must include the instance you'd like to address. There are many programming paradigms which don't need this. For example, you could have a method doing calculations which is agnostic of whatever instance is being used. In that case, you can declare the method as being "static", which in the LVP PreCompiler is a word for saying "without instances".
Usage is pretty simple: just add the "static" keyword before the method's name. The instance's name variable will not be available, and access to any non-static properties won't be easily possible either. As an example of doing this:
![[Image: static-balance.jpg]](http://crew.sa-mp.nl/russell/static-balance.jpg)
If all methods in your class have to be declared as "static", then you probably should be using an array for properties instead, and not use instances at all.
Method visibility
The public keyword in classes means a different thing than it does in normal Pawn code, where it means that a function is being exported for use outside of your script itself. Think about it: the San Andreas: Multiplayer server is not written in Pawn, yet it is able to call the OnPlayerConnect function defined in your script. There is no way to export methods inside of a class.
Each method in your class has a certain visibility. By default, in Pawn, everything in your code is public, meaning that all other code in the script is able to call it. If you have a very large script existing of tens of thousands of lines of code, this may not always be necessary: identifying a user by their account Id is something you only want to do after they entered the right password, not through a hidden command introduced by a new developer.
For this purpose, there are two visibility keywords available:
- public - All code in the entire gamemode is able to make a call to this method.
- private - Only code in the current class is able to make a call to this method.
Let's visualize this in a simple code example:
![[Image: visibility.jpg]](http://crew.sa-mp.nl/russell/visibility.jpg)
The errors are created when you are compiling your gamemode. It's best practice to mark a method as private unless other code really needs to access it. Also, mind that all properties defined in a class are always private! You can write an (inline) method to retrieve its value, but code outside of the class cannot access the property directly.
Inline methods
Direct variable access in Pawn is a lot quicker than returning it through a function, which introduced a serious issue when we were designing the syntax of the LVP PreCompiler: there cannot be any run-time performance cost for the usage of classes. To solve this issue, a notion of inline methods was introduced.
Inline methods are comparable to the better known #define statement in Pawn. Calling an inline method will be translated to inserting the method's code directly, meaning that there is no performance cost for using them. To give an example:
![[Image: inline-methods.jpg]](http://crew.sa-mp.nl/russell/inline-methods.jpg)
There are a few things you have to keep in mind with inline methods. While these are limitations of the PreCompiler and may be fixed one day, they are not a sufficient priority right now to get fixed immediately. Play on the safe side, and write your code assuming that they will never be fixed.
- Inline methods can only have a single statement. The ending semi-colon will be stripped away. You can use the tenary operator or (to limited uses) the comma operator to achieve the same goal.
- Inline methods must be defined on a single line. The opening curly bracket needs to be on the same line as the method's prototype (as in the image), and there may only be a single line of code in the method itself.
- Use additional paranthesis where possible. - Arguments cannot be validated in the same way as normal calls can, so always use extra parenthesis wherever you use an argument or return a value.
- Do not use function calls as arguments in your call. - Similar to what #define-statements do, if you use a function call as an argument when calling an inline method, it means that the function will be called once for every time you use the argument.
Despite these constraints, they are a powerful tool in writing your classes. Don't make everything in-line, though: performance problems very rarely are related to function calls. If they are, you should consider optimizing code so that it's called much less often, for example by caching the value in a variable elsewhere.
Code commentary: documentation!
The source code of Las Venturas Playground has online documentation available. While we are not yet ready to start offering this service to other servers, documenting your classes is strongly encouraged. While the purpose and workings of a class may be obvious to the person who write it, to other people, especially when they just joined your server's team, it may be a complete mystery.
The documentation format is roughly comparable to JavaDoc. We encourage you to write documentation blocks above classes and methods, and shorter documentation (a single line of description) above properties. When doing so, remember that the why is more important than the what. People can read your code to see how exactly it works, but the purpose and idea about your class or method is much more interesting.
![[Image: documentation.jpg]](http://crew.sa-mp.nl/russell/documentation.jpg)
Final words
These are the basics of using classes in Pawn. Of course, there is much, much more to know, and the general idea behind object oriented programming is not one I'm going to explain to you. The syntax of the LVP PreCompiler has stabilized over the past few years, so you can go forth and use it for any code you want without having to worry about backwards incompatibilities in new updates.
If you have any question, comment or feature request for classes in the Las Venturas Playground PreCompiler, feel free to post them in this topic. I intend to keep this post updated as new features are being developed and added to the tool. The PreCompiler is currently available for Windows and Linux, and can be used as a drop-in replacement for the default Pawn compiler which is included in the Windows server package.
TODO
- Calling class methods.
- Constants.