Self flood!

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
mongrol
Prole
Posts: 33
Joined: Fri Nov 30, 2012 1:01 am

Self flood!

Post by mongrol »

Hi Folks,
It have to say it. Lua is driving me nuts and the thing that nutses me out the most is "self". I'm using hump's gamestates and classes and seem to be constantly writing "self". Check this out.

Code: Select all

require "entity"
require "map"

game_state = {}

function game_state:enter()
   local playerImage = lg.newImage("images/player.png")
   self.player = Entity(32, 256, playerImage)
   self.map = Map()
   self.map:init()
end

function game_state:keypressed(key)
   if key == 'escape' then
      Gamestate.switch(menu_state)
   elseif key == 'left' then
      self.player.x = self.player.x - self.tile_size
   elseif key == 'right' then
      self.player.x = self.player.x + self.tile_size
   elseif key == 'up' then
      self.player.y = self.player.y - self.tile_size
   elseif key == 'down' then
      self.player.y = self.player.y + self.tile_size
   end
end

function game_state:draw()
   -- draw our tileset
   for i = 0, 19 do
      lg.draw(self.tilemap, self.tiles[i+1], i*self.tile_size, 0)
   end
   -- a square of grass
   for i=0,9 do
      lg.draw(self.tilemap, self.tiles[1], i*self.tile_size, WINDOW_HEIGHT-self.tile_size)
   end
   -- draw player
   self.player:draw()
end
Self this, self that. Am I doing something fundamentally wrong here? Every time I want to access an instance variable I need to stick a self infront of it. Is there a shortcut to get around this?

Yours Existentially,
Mongrol.
User avatar
Zilarrezko
Party member
Posts: 345
Joined: Mon Dec 10, 2012 5:50 am
Location: Oregon

Re: Self flood!

Post by Zilarrezko »

This is going to be a lot, and my paragraphs might be choppy so brace yourself.

self is Lua's way to do Object Orientated Programming with classes. Self is used to grab an instances' variables/attributes what'ever you want to call it. But self is just a table so you can generally by pass that if you can find what table that is and instead of "self.variable", you can do "box.variable" like you always have in Lua. (that paragraph was for in case you didn't know bout OOP, and maybe if you read carfully a way to fix it, maybe but not really.)

However it looks like what's happening here is that you're adding stuff like the map and player data to the game_state table. This is indicated when you call "self.variable". self in this case is the table that is before the colon in your function name, "game_state" is that table. in reality when you call "self.player = Entity(32, 256, playerImage)" in your "game_state:enter()" function, you're saying "game_state.player = Entity(32, 256, playerImage)"

To "shortcut around it" you can just stick the player information in a player table rather than the game_state table. And just keep the gameState methods in the game_state table. (everytime you create a function with game_state before the colon you are adding a function into the game_state table.)

So instead of that game_state:enter() you have there maybe do something like this (only doing part of your code there).

Code: Select all

require "entity"
require "map"

game_state = {}
player = {}

function game_state:enter()
   local playerImage = lg.newImage("images/player.png")
   player.image = Entity(32, 256, playerImage)
   map = Map()
   map:init()
end
This part is a little touchy (what I'm about to put down). In all seriousness, the only reason you probably have player movement in a game_state method is because you were storing the player data in the game_state table. (because of self and all that jazzyness)

So I would make two seperate things to try and get rid of self if you just want to rid yourself of... self... that seemed a little odd sounding to me... anyway let's get this through!

Code: Select all

function game_state:keypressed(key)
   if key == 'escape' then
      Gamestate.switch(menu_state)
   end
end

function player:keypressed(key)
   if key == 'left' then
      player.x = player.x - map.tile_size
   elseif key == 'right' then
      player.x = player.x + map.tile_size
   elseif key == 'up' then
      player.y = player.y - map.tile_size
   elseif key == 'down' then
      player.y = player.y + map.tile_size
   end
end
Don't even need self there, because it's kind of unnecessary to put player and map data in a game state table, and you can just access the player and map data through the good ol' fashioned way of doing stuff :D. (I'm assuming that that Map() function returns data value such as tile_size.)

The last one is pretty easy to fix so, let's just replace every self with the correct and corresponding table. And remove the ones that don't matter.

Code: Select all

function game_state:draw()
   -- draw our tileset
   for i = 0, 19 do
      lg.draw(map.tilemap, map.tiles[i+1], i*map.tile_size, 0)
   end
   -- a square of grass
   for i=0,9 do
      lg.draw(map.tilemap, map.tiles[1], i*map.tile_size, WINDOW_HEIGHT-map.tile_size)
   end
   -- draw player
   player:draw()
end
That works in my head. Now you don't even need to use the function name "game_state:draw()" you could do more like "gameDraw()" Because if what I think is right about that game state library is that all it does is handle game states and the switching of them. Like what you did with "Gamestate.switch(menu_state)". So I'm not really sure why there is a game_state table at all. But you can program how you want to, within the boundaries of things working correctly of course (a joke).

I'm getting the vibe that you don't fully understand OOP and self so it's a sensitive subject that if It's not explained very carefully to how you could comprehend it, then you are more confused then when you were before the explanation and it gets worse and worse (I have a whole forum post about me trying to get other to teach me OOP and it took me some months.)

If you need anything clarified just ask. Because I would after this big blocky mess of a post I just made. :crazy:
User avatar
MadByte
Party member
Posts: 533
Joined: Fri May 03, 2013 6:42 pm
Location: Braunschweig, Germany

Re: Self flood!

Post by MadByte »

And if you want to keep your player table inside your gamestate table you can simply do that:

Code: Select all

function game_state:enter()
  local player = self.player
  Player.x = bla bla
end
mongrol
Prole
Posts: 33
Joined: Fri Nov 30, 2012 1:01 am

Re: Self flood!

Post by mongrol »

Zilarrezko wrote:This is going to be a lot, and my paragraphs might be choppy so brace yourself.
It is a lot. Thanks very much. :)
To "shortcut around it" you can just stick the player information in a player table rather than the game_state table. And just keep the gameState methods in the game_state table. (everytime you create a function with game_state before the colon you are adding a function into the game_state table.)

So instead of that game_state:enter() you have there maybe do something like this (only doing part of your code there).

Code: Select all

require "entity"
require "map"

game_state = {}
player = {}

function game_state:enter()
   local playerImage = lg.newImage("images/player.png")
   player.image = Entity(32, 256, playerImage)
   map = Map()
   map:init()
end
I do fully understand OOP and have came from a C++ background. The above way of doing it creates globals which I want to avoid. I want to encapsulate all data related to the game state inside that state table, so I have to use self all the time, since the game_state is an instantiation of the "State()" class from Hump.

The player table, which is a local of the game_state table, is an instantiation of the Entity() class which I intend to be my top level container for my game objects. I agree the movement should be where it is and I'll probably subclass Entity() to create moveable entity for monsters/players/bullets etc.

Still, the problems remains of having self everywhere since I'm using locals extensively. Maybe this is just a side affect of Lua making variables globals by default.
User avatar
Zilarrezko
Party member
Posts: 345
Joined: Mon Dec 10, 2012 5:50 am
Location: Oregon

Re: Self flood!

Post by Zilarrezko »

To be honest, you have the same amount of globals in the code you put down versus mine. The only difference is where you put the player and map data (In which case it sounds like you want to put it into the game_state table). I put mine in a table outside of game_state, and your's was inside of game_state. (Does that make sense?) To be honest you only ever have 1 local variable in that entire code you put!

Self is of course just simplifying objected orientated programming like I said. Of course you already know this with... well "this" statements in C++ (I hope it's "this" in C++ otherwise I just embarrassed myself.). It's kinda useless in this occasion (at least as far as I see in the code [for now at least this might just be a prototype of yours]) because you aren't creating any childs or objects. So in this case you could just access the same variables just instead of "self" you have "game_state".

Since you know OOP a lot of that post was unnecessary there, so sorry about that haha. But really there isn't any way "around" it so to say. At least that I know of. I'm sorry man, I'm not too much of help :| .
mongrol
Prole
Posts: 33
Joined: Fri Nov 30, 2012 1:01 am

Re: Self flood!

Post by mongrol »

Here's a workaround using closures. I'll give this a go later and report back. While there's some trickery involved around the self table, it's no more trickeryry than other ways of implementing higher level paradigms in Lua.

http://lua-users.org/wiki/ObjectOrienta ... reApproach
mongrol
Prole
Posts: 33
Joined: Fri Nov 30, 2012 1:01 am

Re: Self flood!

Post by mongrol »

I've been using the closure approach now and find it much superior and more readable, typeble and more agreeable to my C++ background. Here's my refactored code. Admittedly it's evolved a bit further than a straight translation from above. I've dumped the hump.gamestate lib as it's Class based and written my own game state code.

Code: Select all

require "entity"
--require "map"

GameState = {}

function GameState.new()
   local self = {}
   local playerImage = lg.newImage("images/player.png")
   local player = Entity.new (32, 256, playerImage)
   local tile_size = 32

   function self.enter()
      --   self.map = Map()
      --   self.map:init()
   end

   function self.exit()
   end

   function self.update(dt)
   end

   function self.keypressed(key, code)
      if key == 'escape' then
	 local menuState = MenuState.new()
	 switchState(menuState)
      elseif key == 'left' then
	 player.move(-1, 0)
      elseif key == 'right' then
	 player.move(1,0)
      elseif key == 'up' then
	 player.move(0,-1)
      elseif key == 'down' then
	 player.move(0,1)
      end
   end

   function self.draw()
      -- draw our tileset
      for i = 0, 19 do
	 --      lg.draw(self.tilemap, self.tiles[i+1], i*self.tile_size, 0)
      end
      -- a square of grass
      for i=0,9 do
	 --      lg.draw(self.tilemap, self.tiles[1], i*self.tile_size, WINDOW_HEIGHT-self.tile_size)
      end
      -- draw player
      player.draw()
   end

   return self
end
and my entity class (which will be further devolved as the game progresses)

Code: Select all

Entity = {}

function Entity.new (x, y, image)
   local self = {}
   local tileSize = 32

   local x = x
   local y = y
   local image = image

   function self.move(xmod, ymod)
      x = x + (xmod*tileSize)
      y = y + (ymod*tileSize)
   end

   function self.draw()
      love.graphics.draw(image, x, y)
   end
   
   return self
end
Note the distinct lack of using "self." to access class variables locally. I also get strict privacy with this method as you simply can't access instance variables from outside. It forces you to use getters and setters or rethink you're code.

Nice. I like it.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Self flood!

Post by kikito »

Notice that creating functions and closures is cheap in Lua, but isn't free. The closures-based approach can be used in things that are small in number, or not created very often; the game-states are a perfect example of this.

Once created, closure-based objects perform similarly to the self-based objects, but the initial construction is slower and takes more memory (after all you are duplicating and storing lots of functions).

If you use the closures-based approach on entities that get created frequently (i.e. bullets instead of game states), the memory and time it takes to create each entity can pile up.

Plus, LuaJIT has some optimizations for the metatable-based case which can't be applied in the closure-based one. Reference: answers to this email.

I also believe that you are not using locals to their full potential. This code:

Code: Select all

function game_state:keypressed(key)
   if key == 'escape' then
      Gamestate.switch(menu_state)
   elseif key == 'left' then
      self.player.x = self.player.x - self.tile_size
   elseif key == 'right' then
      self.player.x = self.player.x + self.tile_size
   elseif key == 'up' then
      self.player.y = self.player.y - self.tile_size
   elseif key == 'down' then
      self.player.y = self.player.y + self.tile_size
   end
end
Can lose almost all references to self by simply using two locals:

Code: Select all

function game_state:keypressed(key)
  local player, tile_size = self.player, self.tile_size 
  if key == 'escape' then
      Gamestate.switch(menu_state)
   elseif key == 'left' then
      player.x = player.x - tile_size
   elseif key == 'right' then
      player.x = player.x + tile_size
   elseif key == 'up' then
      player.y = player.y - tile_size
   elseif key == 'down' then
      player.y = player.y + tile_size
   end
end
Moreover, I think you are cramping too much information in a single table. That's what your code is telling you - you have to repeat "self" a lot because you are relying on it too much.

It's not the state responsibility to decide how the player moves around; That's for the player to decide. The state should just do "game-statey" things (like changing to another state). For the rest, it should just "pass messages around". It just "inform" self.player that "he has received the order to move". How that message is interpreted is up to the Player class to decide.

Hopefully the player already has the tile size inside of it, since it's critical for its movement.

Code: Select all

self.player = Player:new(x,y, self.tile_size)
Then the keypressed function could be something like this:

Code: Select all

function game_state:keypressed(key)
  if key == 'escape' then
      Gamestate.switch(menu_state)
   elseif key == 'left' or key == 'right' or key == 'up' or key == 'down' then
     self.player:move(key)
   end
end
I wrote a bit about this in my blog.
When I write def I mean function.
User avatar
Xgoff
Party member
Posts: 211
Joined: Fri Nov 19, 2010 4:20 am

Re: Self flood!

Post by Xgoff »

mongrol wrote:Still, the problems remains of having self everywhere since I'm using locals extensively. Maybe this is just a side affect of Lua making variables globals by default.
no, it's because lua doesn't actually have methods.

the purpose : serves is to make a function definition or regular table index + function call look like a method; this is pretty much the only concession to OO apart from __index that lua makes. this goes along with lua's general "mechanisms instead of policy" design, since it allows you to implement OO as you see fit (if at all), instead of being constrained to a specific design (of course, this results in a proliferation of incompatible class libraries, but many of these are just fairly simple wrappers over basic __index usage, anyway).

speaking of globals, lua doesn't really have those either. at least, not as some special type of variable. instead, each lua function has an environment which is simply an object which is accesed when you lookup or assign to a "global". sure, by default all functions start off with the table known as _G as their environment, hence giving the illusion of globals. why did i bring this up? well, technically it's another way you can get rid of self inside the methods:

Code: Select all

-- assumes 5.2 since _ENV is more elegant for this than setfenv
local obj = { }
function obj.foo (_ENV) return name .. ": " .. x .. ", " .. y end

local objmt = { __index = obj }
function Obj (name, x, y)
  return setmetatable({ name = name or "?", x = x or 0, y = y or 0 }, objmt)
end

local o = Obj("Foo", 10, 20)
print(o:foo()) -- note how this makes the implicitly passed object the new environment for that call
would i recommend this at all? hell no! environment variables (ie your object's members) will get shadowed if there happens to be a local with the same name in scope, and you'd no longer be able to access the old environment or its functions unless you had them localized outside the methods. but still, if one were desperate...

as has been mentioned, the other way is closures, with the associated gc and memory overhead (also, luajit currently does not compile function instantiation, so creating closures like this is going to hurt). also, you lose the reusability of functions since they are more or less permanently bound to a specific object

ultimately, however, lua programmers expect to use lua idioms (ie : syntax and self) for lua code ;)
mongrol
Prole
Posts: 33
Joined: Fri Nov 30, 2012 1:01 am

Re: Self flood!

Post by mongrol »

Thanks Kikito and xGoff. I'll revert back to the standard way of doing it, and actually not use any libs either in order to get better experience of the Lua way. I love refactoring. No really! :)
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot], Semrush [Bot] and 2 guests