"Constructor methods", assigning a creating struct's id to a newly created struct

Bart

WiseBart
Hi all.

I'm trying to do a seemingly simple thing using the new structs in GM2.3:
GML:
/// Convenient macro definition
#macro log show_debug_message

/// Function definitions in script asset
function ins(creator) constructor {
    self.creator = creator;
    log(self);
}

/// Struct definition
struct = {
    _method: function() constructor {
        log(self);
    },
    instance: method({creator:self}, ins),         // It seems like the reference to the struct parameter is lost when the method is called as a constructor using the new keyword
    instance_the_other_way: function() {
        var new_ins = new ins(self);               // Call the global function
    }
}
new_instance = new struct.instance();              // creator = undefined, this is the one I'm trying to get to work
inst2 = new ins();                                 // creator = undefined
inst3 = new ins(struct);                           // creator = struct, but obviously we passed this manually
Where struct is some struct and instance is a method of that struct that is tagged with the constructor keyword (let's say a "constructor method").

The important thing is that the instance needs a reference to the struct but that seems to be hard to do using the current syntax.
Is there a way to get a reference to the struct of such "constructor method"?
self cannot be used since it becomes a reference to the new struct when using the new keyword.

Obviously the easiest to fix this would be to do it like this:
GML:
new_instance = struct.instance_the_other_way();
i.e. make a "normal" method return a new instance and manually set its creator.

I'm wondering how everyone else is doing this.
Are there any things that I'm missing here?
 
Last edited:

rytan451

Member
I don't see where you're defining creator when you're doing new struct.instance(). It looks like you're setting bound_struct instead.
 

Bart

WiseBart
Ah yes, you're right. That bound_struct is really supposed to be creator. I changed it in the original post.
But the result is the same in this case: creator is still undefined.
And it's because of that method being called as a constructor (using new). The self then no longer refers to that anonymous struct ({creator:self}, with self in that context being a reference to struct) but rather to the new struct that we're creating.

What I'm really trying to figure out is if there's a way to get a reference back to that {creator:self} struct that the instance method is bound to in case it's called as a constructor using the new keyword, like this: new_instance = new struct.instance();.
 

Alice

Darts addict
Forum Staff
Moderator
I don't think method(...) syntax should ever be used with constructors - constructor's "self" should be the newly created instance, while method(...) changes the meaning of "self" within the method altogether. While it's a fascinating to consider whether method(context, constructor) will use "context" as self or the newly created instance, it still doesn't help.

If there are no easily determined and limited cases of associating child with creator,I guess I'd just use the logic similar to instance_the_other_way(), or maybe have add_child(child) method instead (which doesn't assign the creator on creation, but rather associates the creator after the struct is created). It can work even with anonymous structs:
GML:
// method itself
add_child(child) {
    child.creator = self;
    return child;
}

// later
var world_greeter = creator.add_child({ text: "Hello, world!" }); // because of add_child(...) call, we get a struct with { text: "Hello world!", creator: <struct>}
But for real-life cases, I assume that there are very specific cases of structs created as children of larger parent object - for example, you could use it for such specific purposes like body parts of a boss, node in a tree, nested UI components etc. In such case, I'd just create whichever constructors are needed for the child structs, with creator/parent as one of constructor parameters.
Maybe I could use inheritance to properly handle parent/child relationship in one place, though only among related items (so e.g. two different UI components could inherit from the same base component constructor, but not a UI component and a boss part). If the parent/child code would get repetitive among different types of items, I suppose I would create a function like bind_to_creator(self,creator) and re-use it in base constructors.
(note: I haven't played around much with inheritance in GM:S 2.3+)

Then, once you have these very specific cases, you probably don't want the boss to use a generic function to create a child that might or might not be a boss component. It's cleaner to narrow things down to specific components that are clearly boss-related (and not e.g. UI related). Then, methods like:
- create_barrier = function(angle, distance) { return new BossBarrier(self, angle, distance); }
- create_turret = function(angle, distance) { return new BossTurret(angle, distance); }
follow naturally.

Alternatively, you might abandon the creator-side creation methods altogether, and rely on constructors instead. It especially makes sense in the context of the UI, with dozens of possible UI element types available:
GML:
with (new Ui_RootPanel()) {
    with (new Ui_SideMenu(self)) {  // self is the creator here
        new Ui_Button(self, "New game", new_game);  // new_game is a script called whenever a buttonis created
        new Ui_Button(self, "Quit", quit);
    }
new Ui_Image(spr_CuteCat);
}
Hope this gives some ideas about approaching the issue of associating createe with the creator.
 

Juju

Member
All method(scope, constructor) is doing is wrapping the outside of the constructor in another layer of scoping. The constructor code always generates a totally new struct regardless of outside scope. Setting the scope of a constructor using method() conveys zero information to the new struct that a constructor creates (beyond setting the keyword other).

Also I believe new Ui_Image(spr_CuteCat); as a statement throws an error, Alice, though I very much wish it didn't!
 

Bart

WiseBart
Those are some interesting considerations, @Alice.
In my specific situation the newly created structs aren't meant to be children of the creating struct, though.
Rather they're "instances" that modify a certain index (or range of indices) in an array that resides in the creating struct.

It looks like @Juju's answer is the one I could use to get that syntax I'm looking for.
I remember reading about the use of other in the context of structs some time ago but for some reason that totally slipped my mind.

Thank you for the helpful answers, everyone!

Seems like I'll need to think this through a bit more first.
 
Top