Creating Classes
The Lua table is a tremendously versatile construct which can be used to create true classes for object oriented programming. The Mira Pro Script module makes extensive use of classes created in this way. The present topic describes how to create and use classes in Lua by using a simple class as an example. The sample class is provided with the Mira distribution in the following files:
[Mira]\Scripts\Classes\CMyClass.lua (class definition)
[Mira]\Scripts\CMyClass Test.lua (sample implementation)
where [Mira] refers to the folder containing your Mira installation. You can easily access these files using the Script Manager. To load them from the Script Manager into the Script Editor, click on each file to select it and then click [Open].
A class is a construct that relates data and functions that operate on those data. A class is a template that defines membership but it is not an actual object. A class consists of class members of two types: values (class data, or "properties") and functions (class "methods"). To work with a class you must create an object in memory based on the class template. That is, you must create an instance of the class. Creating a class is called construction and uses a constructor. The constructor creates an object of the specified class type, giving access to the members of the class. For example, your script must call the CMyClass constructor method to create an object of type CMyClass before you can use any of its properties or methods . When you are finished using a class, the object must be destroyed using a method called a destructor. The constructor and destructor are methods (functions) that are defined for each class.
To create a class, we create a Lua table and define data (variable) and methods (class functions) as members of the table. The difference between a table and a class is based upon how an instance of the table is created. An empty Lua table is instantiated using a syntax like t = {}. We can use the same syntax to define a class. To define properties, we simply enclose them in the {} and assign them values. For example, here is the definition of CMyClass from the file Classes\CMyClass.lua. Note that a comma is used at the end of each table member:
|
|
|
|
|
-- variable containing a number |
|
-- another variable with a number |
|
-- a string |
|
-- a pointer or reference to some other object |
|
|
This is all we have to do to create a class containing 4 initialized variables. This is a table and not really a class yet because the table has not been instantiated as a class (see below). Inside the table we define 4 variables just to show how it is done. Remember that Lua creates a variable when it is assigned a value, which is why the class members have initial values. Note that each initialized member is separated by a comma but all of them could be placed on a single line if you wish. Additional class members can be defined later, simply by using the dot operator with an instance of the class. For example, suppose a CMyClass type object named C has been constructed. To add a new data member to the CMyClass defined above, just use a statement like this in your script:
|
-- makes newMember a number |
|
-- makes newMember a string |
and so on, like shown in the class definition itself.
To add a function (or "method") to a class, we define the function outside the class using the class name and the colon operator, like this:
|
|
|
|
|
|
There are a number of items to notice in the definition of the class function:
Use the words function and end to start and finish the function definition.
The method name is specified in the format ClassName:FunctionName() using a single colon between the names. This is different from C++ which uses 2 colons in the definition of a class member.
Inside the function, class members are accessed using the dot operator and the self (or this) operator.
The function can be declared on any number of lines, so that the following is perfectly equivalent to the above representation:
function CMyClass:SetX( x ) self.x = x end
This function returns nothing, but it could return one or more values using a return statement, like return self.x. Look at the CMyClass.lua file to see other examples of function declarations.
Two particular functions that we should note in the CMyClass.lua file are the new and delete methods. Every class defines these two methods which are used to construct and destruct (that is, create and destroy) an instance of the class. The particular way this new method is declared is what makes a Lua class from a table. In fact, without executing the statements inside this new method, the table would not become a class. The reasons behind this are described in the book Programming in Lua. If you create your own classes, you must use new and delete methods patterned after the ones provided for this class; these examples show the base functionality of the new and delete methods required for any Lua class.
|
|
|
-- new object (a table) |
|
|
|
|
|
-- return the new class object |
|
|
What this new method does is to create a table object named o, which is a local variable so that the name does not conflict with any other o in the script. Next, the Lua setmetatable function is called to associate the object o with the special object pointer self, which basically creates a private copy of the table to protect its data. The next statement essentially synchronizes the private copy and public copy of the table's members. Finally, the function returns the new object o to the calling code. After this statement, an object of type CMyClass exists in memory, with its own private members. A reference to the table (the address where it is stored) is returned in the value of o, which your calling statement can assign to any name it wishes. For example, you would create a new instance of the class CMyClass using a statement like this, as shown in the CMyClass Test.lua script:
After executing this statement, the script can refer to class members using the variable C.
After you are finished using a class, you should inform Lua to clean up the memory it uses. For this purposes the script calls the class delete method. The delete method involves at least the following code:
|
|
|
|
|
|
The important function of the delete method is that is sets self to nil, which informs Lua that self is now undefined and is to be deleted from memory during Lua's next cycle of "garbage collection". Lua marks any item for garbage collection when it is assigned the value nil. If you have other objects defined as class members that must be destroyed separately, then this is the place to do it. For example, CMyClass (or any other class) might contain another class that would involve calling its own delete method from within the CMyClass delete method.
Data members are accessed using a variable containing a reference to an instance of a class. Using the above example of C = CMyClass:new(), we have the following cases:
To access class data, use the dot operator, as in C.x.
To access a class method, use the colon operator, as in C:GetX()
The difference in syntax between accessing class data and methods is that the dot operator accesses private data held in a specific instance C of the class CMyClass; however, a class function is not really a private member of a class, so the colon operator specifically passes it the self pointer so that the method knows which instance of CMyClass to use. When executing, the value of self inside the function is taken from a hidden value of self that is passed by the colon operator. Therefore, the following calls are equivalent:
|
-- uses the colon syntax |
|
-- uses the dot syntax with self |
The included sample CMyClass Test.lua provides a short script that uses the concepts described above. These principles are also described in Using Classes in your Scripts.
The first thing done by the script is to include the file containing the class definition by using the Include function. Immediately following, the script creates an instance of CMyClass using the new method; the statement C = CMyClass:new() creates the new object of the class CMyClass and returns a reference to it, which is stored in the variable C. We could improve this script by testing if C == nil, to catch the case that something went wrong in the creation of the object.
Next, the script queries the user for two values that define the first and last limits for a loop. The first value is stored in the class member C.x using the method C:SetX(). You could also set x directly using C.x but it is often a better programming practice to hide values from direct manipulation. Inside the loop, the script calls the class method CMyClass:SumSquare() to compute a running sum of squares. The Printf function formats the result and lists it in the default text window named Script Messages.
The script ends by listing the final value of the class member x and then deleting the class instance (object C) by calling C:delete(). This example shows the normal progression of operations involved in using a class: include the definition, create an instance of the class, use the class, and then delete the class.
Using the class architecture, we can create multiple instances of a given class. Each new() method returns a reference to a distinct object in memory. We can then freely work with these instances as independent objects without fear of collision. This is why the Mira implementation of Lua as a scripting language is based primarily on a class architecture.
As an example of multiple instances, suppose that we create two instances of the class CMyClass, like this:
|
-- include the class definition from its file |
|
-- create an instance |
|
-- create another instance |
These statements give the script two distinct objects, C1 and C2. Then we could fetch their x values and save them into an array like this:
or
Each of these examples returns the distinct value of member x stored from each of the different instances of the same type of object. Notice that we have declared the array with the name x. This x is independent of the variable x in each of the CMyClass objects. There is no confusion of the x's, as far as Lua is concerned. One final point: It it generally a better programming practice to use the GetX() accessor method rather than to access the variable directly but either method works.
In the next example, we assign the class objects themselves to an array named C:
and we might then access their members like this:
Obviously, the variations are virtually limitless. So then, why do we show an example of class usage involving an array like this? Using the array to hold multiple instances makes it easy to create and destroy multiple instances using a for loop containing the new method (the class constructor). Here, we create 10 instances of the class CMyClass and save it for future use:
|
-- create a table (array) |
|
-- create 10 objects |
|
-- call the class constructor |
|
-- end of loop |
We implement the delete method (class destructor) in a similar way using an additional for loop. Note that the class delete method needs to be called to garbage collect the class object. Lua does automatic "garbage collection", or memory recovery, on its own objects such as the table C. However, we created the class objects so our script must deallocate the memory when the script is finished with the objects. In general, objects created outside of Lua's built-in objects and types must be explicitly deleted by your script.
Overview of the Mira Pro Script Module
Mira Pro x64 Script User's Guide, Copyright Ⓒ 2023 Mirametrics,
Inc. All Rights Reserved.