Thread Tools Display Modes
09/14/14, 08:34 AM   #1
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
ZO_Object design question

I have a problem with the behaviour of ZO_Object's two methods. It's confusing at best for me. Currently I'm under the impression that it's simply copied from Programming in Lua.

ZO_Object:Subclass returns setmetatable({}, {__index = BaseClass}). The returned empty table cannot be used as a metatable itself... yet.

ZO_Object:New returns setmetatable({}, Class), and adds Class.__index = Class, so from now on the Class can be used as a metatable. Why does New, method that creates an instance, modify the class? Is there a reason to have a subclass incomplete until an instance is created?

And if you're interested what led me to this question, I want to replace the class (metatable) of an existing object with a subclass (instead of overriding several methods one by one). I never call New on the subclass, so I have to add __index myself due to the current design. It's simple, but I just can't grasp the concept why ZO_Object:Subclass() doesn't return a ready-to-use thing.
  Reply With Quote
09/14/14, 04:08 PM   #2
Sasky
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 231
Originally Posted by merlight View Post
ZO_Object:Subclass returns setmetatable({}, {__index = BaseClass}). The returned empty table cannot be used as a metatable itself... yet.
There is nothing preventing a table from being used as a metatable. It'll look at __index (and other special fields) and just ignore the other values.

What this does actually, is you first look in the table for the key. If it's not there, you look at __index for a function/table to go to. If it goes to another table, it'll first check that for the key then go to its metatable for __index and continue on down the line.

This example will go up the chain and find the field "foo" in A:

Lua Code:
  1. A = ZO_Object:Subclass()
  2. B = A:Subclass()
  3. C = B:Subclass()
  4.  
  5. A.foo = 1
  6.  
  7. d(C.foo) --Prints 1

Originally Posted by merlight View Post
ZO_Object:New returns setmetatable({}, Class), and adds Class.__index = Class, so from now on the Class can be used as a metatable. Why does New, method that creates an instance, modify the class? Is there a reason to have a subclass incomplete until an instance is created?
It's just making sure Class is suitable to use as a metatable.

Originally Posted by merlight View Post
And if you're interested what led me to this question, I want to replace the class (metatable) of an existing object with a subclass (instead of overriding several methods one by one). I never call New on the subclass, so I have to add __index myself due to the current design. It's simple, but I just can't grasp the concept why ZO_Object:Subclass() doesn't return a ready-to-use thing.
[/quote]
:New returns an instance of the object. :Subclass is intended to be a class and not a particular instance that you would then call :New to create instances of. Functionally, though, they're equivalent because Lua doesn't have a different type/method for defining a class like say Java or C.

If your object is foo, you could do something like
Lua Code:
  1. --Get the existing parent class for foo. This should have a __index
  2. local mt = getmetatable(foo)
  3. --Use that for your subclass
  4. local Subclass = setmetatable({}, mt)
  5.  
  6. --Define your override functions on Subclass
  7. function Subclass:foo() d("bar") end
  8. --...
  9.  
  10. --Setup Subclass for parent of the object
  11. setmetatable(foo, {__index = Subclass})

Alternatively, keep in mind the functions are stored on the table referenced by __index. If you define your functions on the particular instance of the object itself, they're put in the table itself rather than the metatable. This means it'll be overridden for that object and not the whole class.
  Reply With Quote
09/14/14, 06:26 PM   #3
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
Originally Posted by Sasky View Post
There is nothing preventing a table from being used as a metatable. It'll look at __index (and other special fields) and just ignore the other values.
Sure, but the :Subclass method doesn't put the __index field there. If you use the result as a metatable directly, it won't work. That's what I don't understand, why doesn't :Subclass create a usable metatable. I'll try to illustrate with concrete example.

Lua Code:
  1. -- defined in EsoUI/Ingame/Stats/Stats.lua
  2.  
  3. ZO_Stats = ZO_Object:Subclass()
  4. -- ZO_Stats.__index == nil   <--- this is my complaint
  5.  
  6. STATS = ZO_Stats:New(control)
  7. -- ZO_Stats.__index == ZO_Stats   <--- why the hell is it set by :New
  8. -- getmetatable(STATS) == ZO_stats
  9.  
  10. -- everything fine, I can call ZO_Stats methods on STATS object
  11. STATS:AddDivider()

Now I want to subclass ZO_Stats, and change the class of STATS object to the subclass:
Lua Code:
  1. MovableStats = ZO_Stats:Subclass()
  2. setmetatable(STATS, MovableStats)
  3.  
  4. -- STATS is now broken, because MovableStats doesn't have __index
  5. STATS:AddDivider() -- error: function expected instead of nil
  6.  
  7. -- I have to either 1) set it myself
  8. MovableStats.__index = MovableStats
  9.  
  10. -- or 2) set metatable in a different way
  11. setmetatable(STATS, {__index = MovableStats})
  12.  
  13. -- or 3) create a dummy object that I won't use at all
  14. dummyStats = ZO_Object:New(MovableStats)

And the 3) point is what doesn't make any sense to me. What was the reasoning behind making :New add __index to the class, and not add it straight in :Subclass?
  Reply With Quote
09/14/14, 09:55 PM   #4
Sasky
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 231
Originally Posted by merlight View Post
And the 3) point is what doesn't make any sense to me. What was the reasoning behind making :New add __index to the class, and not add it straight in :Subclass?
Even though they are stored in the same structure in Lua, it looks like they're trying to differentiate between class (or prototype) and object. If they assume an object is always instantiated from the class then used (and not the prototype/subclass definition used directly), :New() is always called and it works fine.

Lua Code:
  1. PrototypeMovableStats = ZO_Stats:Subclass()
  2. MovableStatsInstance = PrototypeMovableStats:New()

Personally, I'd probably use the alternate method I mentioned above: just define your functions directly on STATS itself. It's the same thing in effect without messing around with metatables (and is slightly more efficient).
  Reply With Quote

ESOUI » Developer Discussions » Lua/XML Help » ZO_Object design question


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off