Basic Lua Class Implementation

Get technical support about the C++ source code and about Lua scripts for maps, entities, GUIs, the console, materials, etc. Also covered are the Cafu libraries and APIs, as well as compiling, linking, and the build system.
Post Reply
SoulRider
Posts:95
Joined:2014-04-06, 00:16
Basic Lua Class Implementation

Post by SoulRider » 2014-09-21, 16:12

I have modified a simple class code example for use in Cafu. It is a simple script file called Class.lua:

Code: Select all

--************************************************
-- Class.lua by Andy 'Soul Rider' Wilson
--
--  This file creates a basic class inheritance
--  structure.  Class is intialized with an OnInit()
--  function.
--
--*************************************************
function Class(base, OnInit)
   local c = {}    -- a new class instance
   if not OnInit and type(base) == 'function' then
      OnInit = base
      base = nil
   elseif type(base) == 'table' then
    -- our new class is a shallow copy of the base class!
      for i,v in pairs(base) do
         c[i] = v
      end
      c._base = base
   end
   -- the class will be the metatable for all its objects,
   -- and they will look up their methods in it.
   c.__index = c

   -- expose a constructor which can be called by <classname>(<args>)
   local mt = {}
   mt.__call = function(Class_tbl, ...)
   local obj = {}
   setmetatable(obj,c)
   if class_tbl.OnInit then
       class_tbl.OnInit(obj,...)
   else 
      -- make sure that any stuff from the base class is initialized!
      if base and base.OnInit then
      base.OnInit(obj, ...)
      end
   end
   return obj
   end
   c.OnInit = OnInit
   c.is_a = function(self, klass)
      local m = getmetatable(self)
      while m do 
         if m == klass then return true end
         m = m._base
      end
      return false
   end
   setmetatable(c, mt)
   return c
end 
I have set it to use an OnInit() function, to maintain similarities with current cafu code. Here is an example of my Team.lua code, using the class structure:

Code: Select all

--************************************************
-- Team.lua by Andy 'Soul Rider' Wilson
--
--  This file creates a base team class.
--  It contains basic team functionality and can
--  be extended to create PlayingTeam for example 
--  by using the following code:
--  PlayingTeam = Class(Team)
--*************************************************

Team = Class()

function Team:OnInit(teamName, teamNumber)
    Team.teamName = teamName
    Team.teamNumber = teamNumber
	Team.teamPlayerIDs = {}
end

function Team:GetTeamNumber()
    return Team.teamNumber
end

function Team:AddPlayer(player)
	local id = player.GetID()
	table.insert(Team.teamPlayerIDs, id)
end

function Team:RemovePlayer(player)
	local id = player.GetID()
	table.remove(Team.teamPlayerIDs, id)
end
Note in my code, there is no call to dofile Class.lua in my script, as both these files are loaded from my FileLoader.lua file. You would need to add these to a file loader of your own, or simply add a dofile in the Team.lua file calling Class.lua.

One of my plans is to extend the class to make it so entity scripts can be subclassed, so specific player types could be constructed by sub-classing the HumanPlayer script for example. I haven't tested, but in theory, a call like:

RobotPlayer = Class(PlayerScript)

Could be made to work, as long as PlayerScript was a global, as the script files already define an OnInit() function. The function may need a lua version, but I am sure it could be made to work with a little experimentation.
Attachments
Class.lua
(1.41KiB)Downloaded 461 times
Team.lua
(801Bytes)Downloaded 475 times
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Basic Lua Class Implementation

Post by Carsten » 2014-09-23, 10:07

Hi SoulRider,

thanks for posting this code!

I was wondering if you've checked out the chapter about Object-Oriented Programming in the "Programming in Lua (3rd edition)" book?

Btw., the Script components that you acquire via code like this at the top of custom scripts:

Code: Select all

-- Retrieve the ComponentScriptT instance that is responsible for this script.
local PlayerScript   = ...
these Script component instances are already part of the components class hierarchy, which not only exists in C++, but is modeled in Lua as well:
  • The reference documentation at http://docs.cafu.de/lua/hierarchy.html truly reflects the Lua class hierarchy.
  • See file Libs/UniScriptState.dia (requires the program Dia to view) for a very brief overview of how it is done (the "table" object at the left with member __userdata is what you're dealing with in Lua).
  • Besides the above mentioned "PiL3" book, another resource that I found very useful was the Game Programming Gems 6 book, chapter 4.2 "Binding C/C++ Objects to Lua" (about which I've posted to the Lua mailing-list here and here).
In short, you can of course use the code that you've posted (it seems there is a typo, as Class_tbl occurs once with C, once with c), but note that you could also directly extend the hierarchy of the ComponentBaseT classes quite plainly via the __index metamethod.

This in turn would address the biggest issue that I see with the code that you posted: It seems like you cannot call a method of a "grandfather" base class. For example, Team:get("xy") is not possible with this code, is it?
Best regards,
Carsten
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Basic Lua Class Implementation

Post by SoulRider » 2014-09-23, 21:56

I haven't tested Grandfather inheritance, I don't have enough code yet. It is an interesting point, and I will look into when I can.

I am afraid the limit of my current knowledge prevents me from understanding too much of what I am doing, but I am reading the links and getting better :)
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Basic Lua Class Implementation

Post by SoulRider » 2014-09-27, 11:58

I have checked the example I modified for this code, and it does support grandfather inheritance. Here is a the example they use, modified to fit the class changes made to fit in with the Cafu engine:
Simple inheritance is supported. For example, here a base class Animal is defined, and several specific kinds of animals are declared. All classes made using class have a is_a method, which you can use to find out the actual class at runtime:

Code: Select all

Animal = Class()

function Animal:OnInit(name)
    self.name = name
end

function Animal:__tostring()
  return self.name..': '..self:Speak()
end

Dog = Class(Animal)

function Dog:Speak()
  return 'bark'
end

Cat = Class(Animal)

function Cat:OnInit(name, breed)
    Animal.OnInit(name)  -- must OnInit base!
    self.breed = breed
end

function Cat:Speak()
  return 'meow'
end

Lion = Class(Cat)

function Lion:Speak()
  return 'roar'
end

fido = Dog('Fido')
felix = Cat('Felix','Tabby')
leo = Lion('Leo','African')

Code: Select all

D:\Downloads\func>lua -i animal.lua
> = fido,felix,leo
Fido: bark      Felix: meow     Leo: roar
> = leo:is_a(Animal)
true
> = leo:is_a(Dog)
false
> = leo:is_a(Cat)
true
All Animal does is define __tostring, which Lua will use whenever a string representation is needed of the object. In turn, this relies on speak, which is not defined. So it's what C++ people would call an abstract base class; the specific derived classes like Dog define speak. Please note that if derived classes have their own initialization functions, they must explicitly call OnInit for their base class.
I have had to make some changes to the Class.lua file I have posted, to make everything work correctly with inheritance as posted in the edited example above. I think it is working pretty well, but need to test further.

Code: Select all

--************************************************
-- Class.lua by Andy 'Soul Rider' Wilson
--
--  This file creates a basic class inheritance
--  structure.  Class is intialized with an OnInit()
--  function.
--
--*************************************************
function Class(base)
    local c = {}    -- a new class instance
    if not c.OnInit and type(base) == 'function' then
        OnInit = base
        base = nil
    elseif type(base) == 'table' then
    -- our new class is a shallow copy of the base class!
        for i,v in pairs(base) do
            c[i] = v
        end
        c._base = base
    end
    -- the class will be the metatable for all its objects,
    -- and they will look up their methods in it.
    c.__index = c

    -- expose a constructor which can be called by <classname>(<args>)
    local mt = {}
    mt.__call = function(Class_tbl, ...)
    local obj = {}
    setmetatable(obj,c)
    if Class_tbl.OnInit then
        Class_tbl.OnInit(obj,...)
    else 
        -- make sure that any stuff from the base class is initialized!
        if base and base.OnInit then
        base.OnInit(obj, ...)
        end
    end
    return obj
    end
    if not c.OnInit then
        c.OnInit = OnInit
    end
    c.is_a = function(self, klass)
        local m = getmetatable(self)
        while m do 
            if m == klass then return true end
            m = m._base
        end
        return false
    end
    setmetatable(c, mt)
    return c
end
For me, the is_a is very important. But from what I see with the _index method also being in the Class I have created, grandfather inheritance is supported in much the same way was the components are in Cafu. This means that this class can be used for standard non entities, but it's naming convention etc will be similar to the component class implementation.

Could you write a simple example of how I would implement a subclass of a component? I can then make sure there are enough similarities between the way the classes are used to not confuse people.

My plan with the code I write is to make it so easy to understand that someone without any programming experience can look at it and straight away start modding it. I would say one of the great successes of NS2 was the ease of understanding the code, so much so, that complete novices like myself were able to dive right in and create some very interesting mods. I want to ensure my code is as user friendly as possible. It may not always be the most efficient code, but it will be very simple to understand, follow and modify.
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Basic Lua Class Implementation

Post by Carsten » 2014-09-29, 15:50

Hi SoulRider,

hm... I'm not sure if I understand all of what you wrote, or what to think of it.

What problem are you trying to solve with the "class" code?

Generally, you can of course use any Lua code that you like in the component scripts. For example, you could use the class system that you posted above, create instances of such classes as members (table elements) of the Script component objects (tables), and use these class instances to implement the desired behavior.

But I cannot quite see what purpose you would want to use the "class" code for, and what you can accomplish with it that cannot already be achieved in a simpler manner. I may be overlooking something here, but please help me first understanding what the problem is, as this will make it a lot simpler to discuss the solution. ;-)
Best regards,
Carsten
SoulRider
Posts:95
Joined:2014-04-06, 00:16

Re: Basic Lua Class Implementation

Post by SoulRider » 2014-09-29, 21:47

The Class code is for non-component code. Ie Game logic... There are plenty of things which could require a class hierarchy, such as Team, subclassed for PlayingTeams etc. These and other things which are not game entities, but could still be used in a hierarchical system.

There may be other ways of doing things, but as I have previously mentioned, my understanding of code is limited, and I only know the way I know. Obviously I am learning more as I go along, but I am building things from my limited knowledge perspective.
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Basic Lua Class Implementation

Post by Carsten » 2014-09-30, 14:57

Ok, thanks for these details!

As mentioned above, one of the great features of Lua is that you can freely and flexibly use any code you like, and especially remain independent and "de-coupled" from the Lua API that the Cafu Engine exposes to the scripts. :D :up:
Best regards,
Carsten
Post Reply

Who is online

Users browsing this forum: No registered users and 6 guests