I have been working with Lua lately and I have discovered that it is capable of doing some very interesting things. I have created a proof of concept that allows one Lua script to load another Lua script and use the loaded script to modify itself by passing itself as an argument to the creation function of the loaded script. The basic example here is from a simple card game.
In this specific example, I have a Card "object" which is really just a LuaTable. At the top of the Player.lua script I have:
local card = require "Card"
Then I create a card "object" by calling:
function Player:addSpecificCardToDeck(cardSuit, cardType, cardId)
table.insert(self.deck, card:new(cardSuit, cardType, cardId))
end
The Card.lua script loaded by require
contains code that loads another script during its creation. Here is the Card.lua script. Please ignore cardSuit in this example as that code is incomplete and I have omitted it.
Card.lua
Card = {} -- the table representing the class, which will double as the metatable for the instances
Card.__index = Card -- failed table lookups on the instances should fallback to the class table, to get methods
local cardList = require "Cardlist"
-- syntax equivalent to "MyClass.new = function..."
function Card:new(cardSuit, cardType, cardId)
local self = setmetatable({}, Card)
self.id = cardId
self.type = cardType
--this is where another script is loaded
print(self.id .. " cardID")
--getAbilityScriptsForId returns the appropriate string
self.testScript = require (cardList:getAbilityScriptsForId(2)) --2 is the proof of concept script being called
test = self.testScript:new(self)
test:increaseCardId()
print(self.id.. " new cardID") --the cardID will have increased by 1
return self
end
return Card
Here is the sample script being loaded:
SampleScript02.lua
SampleScript02 = {} -- the table representing the class, which will double as the metatable for the instances
SampleScript02.__index = SampleScript02 -- failed table lookups on the instances should fallback to the class table, to get methods
-- syntax equivalent to "MyClass.new = function..."
function SampleScript02:new(inputCard)
self.card = inputCard
return self
end
function SampleScript02:increaseCardId()
self.card.id = self.card.id + 1
end
return SampleScript02
As stated in the comments, the self.id of the script that loads the sample script will be incremented by 1. In theory, a script could pass itself into any potential script that modifies it. When a table is passed as the argument to a function in Lua, changes made to it inside that function apply to the original table. In contrast, when values such as integer or boolean are passed into a function, a copy is made instead.
What I am looking for is whether I am following the best practices for Lua, and also how to avoid any potential pitfalls that the language presents. Am I using require
properly? I am still learning the language, so any tips on syntax are also much appreciated.
-
1\$\begingroup\$ I won't post this as an answer because it's just a link but you could use pcall as a way to catch errors that may occur when loading the external files \$\endgroup\$Dan– Dan2014年08月21日 20:37:39 +00:00Commented Aug 21, 2014 at 20:37
1 Answer 1
On the first line, Card = {}
should be local Card = {}
, otherwise Card
becomes a global, breaking encapsulation. You might want to consider using strict.lua or another method of "locking" the global table to prevent mistakes like this.
Having Card
double as the metatable for card instances is pretty unusual. It will work, but now Card and each of its instances are polluted with an __index
property, and this could be avoided simply by writing setmetatable({}, { __index = Card })
instead of setmetatable({}, Card)
, which is probably what people would expect to see anyway.
You declare the function as Card:new
; it has an implicit self
argument because of the colon. But you never use that argument; instead, you shadow it with a local variable named self
. You probably wanted to do something like local instance = setmetatable({}, self)
, although as mentioned it would probably be better not to have Card
double as the metatable for instances, so instead you could use local instance = setmetatable({}, { __index = self })
.
local Card = {}
function Card:new(cardSuit, cardType, cardId)
local instance = setmetatable({}, { __index = self })
instance.id = cardId
instance.type = cardType
-- ...
return instance
end
return Card
The same things apply to your second block of code.
-
\$\begingroup\$ Thanks for the feedback. To clarify, when I do
local self = setmetatable
, is conflicting with the original self in any way? \$\endgroup\$bazola– bazola2014年08月21日 19:48:26 +00:00Commented Aug 21, 2014 at 19:48 -
1\$\begingroup\$
local self = selfmetatable
will causeself
to be come shadowed in the scope in whichlocal self
was declared, however you will be fine outside of the method. I would recommend against it though because semanticallyself
is known to refer to the current object (in this case, theCard
table) in which this function is being invoked on \$\endgroup\$Dan– Dan2014年08月21日 20:35:36 +00:00Commented Aug 21, 2014 at 20:35 -
\$\begingroup\$ Thing is, when I change it to instance, I have to change all the method calls elsewhere from self:whatever() to instance:whatever() and reference instance.property in those functions. Is this still the preferred way to do it? Thanks for your response. \$\endgroup\$bazola– bazola2014年08月21日 20:40:26 +00:00Commented Aug 21, 2014 at 20:40
-
\$\begingroup\$ Ah I see your point now. I would say that you really should keep using
instance
as self should always refer to the owner of the current scope - it's just semantics, though. If it'slocal
it's only going to affect your code. \$\endgroup\$Dan– Dan2014年08月21日 20:41:43 +00:00Commented Aug 21, 2014 at 20:41