We have detected that cookies are not enabled on your browser. Please enable cookies to ensure the proper experience.
Results 1 to 8 of 8
  1. #1

    A rather tricksie lua / class / object-oriented problem... help!

    Been stuck on this all week. I'm at my wit's end... okay, in my case, half-wit's.


    I'm trying to modify the class() routine. Specifically, I want to be able to have Constructor detect, under certain conditions, that a different sort of object is required, construct that object, and pass the result back to class(), which would take any necessary action to ensure the metatable is set up properly.

    So, for example, the constructor would look something like:

    Code:
    :
    :
    if <I need something else> then
       return ( <create the something else>)
    else
       <do normal initialization>
       return (self);
    end;
    :
    :
    Then, in the class() routine (I'm using that same one all the plugins seem to use), I replace:
    Code:
    :
    :
    		if ( ( instance.Constructor ~= nil ) and ( type( instance.Constructor ) == 'function' ) ) then
    			instance:Constructor( ... );
    		end
    :
    :
    with
    Code:
    :
    :
    		if ( ( instance.Constructor ~= nil ) and ( type( instance.Constructor ) == 'function' ) ) then
    			local newSelf = instance:Constructor( ... );
    			if (newSelf ~= nil) and (newSelf ~= instance) then instance = newSelf; end;
    		end
    :
    :

    The result of this is that, if Constructor() returns a (non-self) value, then the mt.__call function says "aha, I've been overridden - return the new object which Constructor() has provided as the newly-created instance.


    So far, so good. This, in itself, works.

    But there's a problem. Suppose, I'm writing a child function of this variable object. Then I'll have something like:

    Code:
    :
    self = <My Variable Object>.Constructor.(self);
    :
    :
    :
    return (self)
    :
    The parent may or not return a different 'self' value (depending on whether the I-need-a-different-version case is met or not). I need to make sure the class() constructor picks up on this, so I'll catch that (possibly new) self value and pass it back as the result of the child's constructor.

    After all, the parent's Constructor() routine is not called by the mt.__call in the class() package. The /child/ is called that way, and the child in turn calls the parent's constructor. So the child needs to keep track of whether self has changed, and pass that back to the class() package to handle. The child Constructor doesn't create a variant, but the parent's might, so the child needs to inform class().

    Still no problem.


    BUT


    The child class will have its own methods and overrides. That is, after all, why it was created in the first place.

    And now we have a problem.

    Those methods, of the child object, are associated with 'self' when the object definition is created by class. They are the members of 'c' to which that class routine sets the metatable's __index. This is in the line:

    Code:
    :
    		setmetatable( instance, { __index = c } );
    :
    But when I pass back that 'self' parameter, if it has changed, I no longer have the __index value associated with the child class - now I have the one associated with the parent's variation. Meaning, if 'self' changes, then all logically subsequent overrides and extensions are lost as well - I'm returning a self which is a parent object, not a child object.


    Alright, a straight-forward solutions presents itself. I need to get those additional members of the child's table into this new self, adding or overriding whatever additional fields the child defined as part of its class definition.

    So, I do this:


    Code:
    :
    :
    if ( ( instance.Constructor ~= nil ) and ( type( instance.Constructor ) == 'function' ) ) then
    	local newSelf = instance:Constructor( ... );
    	if (newSelf ~= nil) and (newSelf ~= instance) then
    		local newMT = getmetatable(newSelf);
    		local oldMT = getmetatable(instance);
    		for i,v in pairs (oldMT.__index) do
    			newMT.__index[i] = v;
    		end;
    		setmetatable (newSelf, newMT);
    		instance = newSelf;
    	end;
    end;
    :
    :
    In other words, I take the original class definition produced when the child object was created (that setmetatable( instance, { __index = c } ) line), and copy or overwrite its fields into the new class definition. Essentially, I'm creating a combined class definition through merging them rather than inheritance, but following the same principles: call the child's method, then the parent's, then any remoter ancestors.

    Again, so far so good... almost.


    Here's the catch. Simple calls to this works. But if I **NEST** these calls, I get bleed-over.

    Specifically, suppose I have two such objects (with variable identities), one which creates the other.

    So:
    * Create object 1
    * Object 1 says; Aha, I need the alternate version, and creates <AlternateVersion>.
    * This alternate version, within its own constructor, creates another such object <AlternateMember>.
    * <AlternateMember> sets up fine.
    * But then when I get back to those class() routines for <AlternateVersion> ... its metatable returns <AlternateMember>'s metatable!!!


    That is to say, when - while debugging - I get to my modified class() code that's creating <AlternateVersion>, and I say newMT = getmetatable(newSelf), the values I get back in newMT.__index are *not* the proper newSelf values, but, rather, is the merged metatable.__index which was produced for the <AlternateMember>.

    This /looks/ like some sort of re-used variable issue, but that doesn't make sense. It's a different variable, in different scope, and, anyway, all wrapped up within the class definition produced by class() which should be unique for each class anyway. <AlternateVersion> and <AlternateMember> are two entirely different variable objects, and, because they have different class definitions, ought to be running completely different mt.__call routines!



    I've tried numerous different variants on this (including doing all this processing in a stand-alone function to ensure volitile local variables)... and I just can't seem to make it work.

    So I think I need a luru's help!



    Thanks!

  2. #2

    Followup

    Been doing some more experimenting, with ideas that struck me after I took a break.

    No solution, but perhaps a better diagnosis of the problem?

    In this segment:
    Code:
    :
    :
    if ( ( instance.Constructor ~= nil ) and ( type( instance.Constructor ) == 'function' ) ) then
    	local newSelf = instance:Constructor( ... );
    	if (newSelf ~= nil) and (newSelf ~= instance) then
    		local newMT = getmetatable(newSelf);
    		local oldMT = getmetatable(instance);
    		for i,v in pairs (oldMT.__index) do
    			newMT.__index[i] = v;
    		end;
    		setmetatable (newSelf, newMT);
    		instance = newSelf;
    	end;
    end;
    :
    :
    I did a tostring() call on the various parameters to try to figure out what was going on.

    On the seperate (nested) pass-throughs, instance, oldMT, oldMT.__index, newSelf and newMT all had differing values in the different pass-throughs. In other words, they were correctly holding different tables and values for the different creations.

    BUT newMT.__index was the *SAME TABLE* both times!! So, indifferent iterations, different getmetatable(newMT)s had identical [__index] values.

    ?!!!

    could this be that __index is giving me the index of the newMT variable, not of the table it holds? That makes no sense. Besides, oldMT.__index was different in the different nested instances.


    So, yeah, I'm baffled.

    SOMEHOW there's a variable in there that's being treated as a non-volitile global and winding up as the [__index] field of every new metatable coming out of that routine.... but I can't for the life of me figure out how!

  3. #3
    I'm not even going to begin to decipher all of that gobbledygook. (Normal people just use the class implementation as-is, and we're happy to have it. )

    However, just as a general debugging technique, you can use Equendil's HereBeDragons utility to display all of the relevant tables at any given instant. You can create instances of HereBeDragons at various places in the code, and see if the tables are being modified as you expect.

    Garan also has a nifty DebugWindow utility that is included in all of his plugins:
    For a graphical representation, just download any of my plugins that include my debugger (the latest ones have some internal improvements so ItemTracker or AltInventory would be best) and create an instance of the DebugWindow from debugwindow.lua. This dialog allows for interactively running lua commands, watching variable/expression values and a tree for displaying objects.
    Good luck.
    Thurallor, Warden of Landroval

  4. #4

    Bah

    This approach isn't going to work. No way, no how.

    I've finally realized that I'm trying to manipulate the 'global' class definitions in response to the values of a specific instance. Not only is that difficult, it's also bad practice. (I think this also explains the shared __index value.)

    I'm trying something new now, with better hopes of success. If it works, I'll get not only multiple inheritance but also (in effect) unions (though not nearly as tidy an implementation as C supports).



    Thanks for the reply, Thur. I've seen Garan's debug pane - a very clever idea!

    I hadn't thought to use HBD that way - so far, I've just used it to check what Turbine structures are visible to the lua environment.

    Oddly, my own plugin doesn't show up in HBD's list. I must have something configured wrong. I haven't worried about it before - but, as you point out - HBD could be a useful debugging tool if I can straighten that out. I'll have to get on that. Thanks!


    I haven't had a chance to check out your colorpicker yet [that you mentioned in the other thread] but I'm looking forward to seeing your excel information when I get back to that point!

  5. #5
    Quote Originally Posted by Eddenwulf View Post
    I'm trying something new now, with better hopes of success. If it works, I'll get not only multiple inheritance but also (in effect) unions (though not nearly as tidy an implementation as C supports).
    If you do it, then it may be worth sharing as a library over on LOTROInterface. Maybe someone else would like to use all those high-falutin' object oriented paradigms, but isn't hardcore enough to modify the classes implementation to bend it to his/her will.
    Thurallor, Warden of Landroval

  6. #6
    Testing it this evening. So far, so good... though it's not yet as powerful as I'd like.


    But I've implemented overrides - so an instance can (after being created) start behaving as if it were a different sort of object. This was my first desideratum -- I can now have single class that acts as a child of Turbine.UI.Control when its parent is non-nil, but acts as a child of Turbine.UI.Window when its parent is nil. (In fact, it can change this behavior on the fly.)


    I've also got multiple inheritance working - well, all still in beta of course - which is letting me do cool things like describe "behaviors" and append them to any object. I'm testing it now by having created 'move' and 'resize' behaviors and sticking it on to different sorts of buttons. This lets me make 'flavors' of buttons without having to write a different version for each one. (E.g. if I have code for Button1, Button2 and Button3, I can make a class that's a draggable version of any of them simply by writing something like:
    Code:
    DragButton1 = class (DragBehavior, Button1Class);
    DragButton2 = class (DragBehavior, Button2Class);
    DragButton3 = class (DragBehavior, Button3Class);
    But this is all still in the early stages, and it has that 'house of cards' feeling. I'll keep using my new Class definition as I work on my plugin, and thus hopefully be checking/debugging class() at the same time...

  7. #7
    That sounds really cool.

    So what plugin are you working on, or is it a secret?
    Thurallor, Warden of Landroval

  8. #8
    Well, it /will/ be really cool, if it works. Been beating my head against it for 5 days or so... keep having what I think is a solution and then finding more problems.

    Learning a whole lot about the nuances of lua though - and all this finally forced me to read up on metatables and __index and __call and all that. I only started teaching myself lua when I started this plugin, so there's still some slope to this learning curve.


    And - lol - no, not a secret. But also it keep changing on me. I started this out as just a quick experiment to see if I could find an easy way to block the 'noisy emotes' you get at concerts and the like, with all the clap and dance and cheer and other irritating scroll. Quickly discovered that, with the strict limits put on plugins, there was no way that could be done. So now I'm trying to make a chat window to run in parallel that does that filtering. Only I keep thinking of cool new things to add to it...

    ... or, it seems, getting distracted by trying to rewrite the class() code!

 

 

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  

This form's session has expired. You need to reload the page.

Reload