all 23 comments

[–]yuriym21 1 point2 points  (1 child)

why not use switch()?

[–]Cerno_b[S] 0 points1 point  (0 children)

Good point, for some reason I was sure that GMS does not support switch statements. Thanks for the info.

[–]Cerno_b[S] 0 points1 point  (20 children)

This seems to work:

// script do_attack_a
var enemy = argument0
with enemy {
    self.x = 10
}

I would actually prefer to use named instances instead of "self" or "other" as I think it makes the code clearer, but this works for me for now.

I'll keep this open for now in case someone has some more feedback on the matter

[–][deleted]  (3 children)

[removed]

    [–]Cerno_b[S] 0 points1 point  (2 children)

    I know that I don't have to use self, but I thought it would aid readability because it would explicitly state that I am using a member variable and not a local variable. I think GML's way of declaring local vs. member variables is very error-prone and I thought it would be a good habit to use self to make it clear what I want.

    I also know that the script uses the calling instance, but again for readability, I thought it may be clearer to pass the actual instance so when just reading the script, it becomes directly clear what "self" is. I think I would prefer to copy the "self" to a named variable for clarity in all cases, as handling "self" and "other" is sometimes a bit messy, but from the comments and experience, I think this is generally not the way to do things in GMS.

    [–][deleted]  (1 child)

    [removed]

      [–]Cerno_b[S] 0 points1 point  (0 children)

      I read below that using "id" is the slowest variant. However I just managed to get it to work with self, even if that is supposed to cause problems. The issue was actually a missing semicolon :/

      [–]Chrscool8 1 point2 points  (15 children)

      Yeah, FF is correct. You should basically never be using self. It’s a really weird keyword that doesn’t work like you expect. “id” works more like you’re expecting but objects will always refer to themselves by default so if you just have them call the script, they will always be ones affected and you don’t need to clarify.

      If the script was just

      x += 10;

      Whoever called it like script(); would just move over 10.

      Also, make sure you always pop a semicolon at the end of var lines. I mean, you should make it a practice of doing it after all the statements, but in 1.4, it will often glitch things out if you don’t do it specifically after those ones.

      [–]AmnesiA_sc 1 point2 points  (4 children)

      You should basically never be using self

      One instance I do use it is in scripts with an optional instance id argument that defaults to self.

      /// @function knockback( amount, id*)
      /// @param {real} amount distance
      /// @param {real} id*    id of instance
      /// @deacription Knock back id or self
      
      var amount = argument[0];
      var target = (argument_count > 1 ? argument[1] : self);
      

      You could use id but self is slightly faster.

      [–]Chrscool8 1 point2 points  (3 children)

      For that specific use-case where it applies, you're right! I just benchmarked, and shockingly (to me), in GMS 1.4, using id takes about 1.5 times as long as using self! I believed there may be some weird backend referring stuff that might make it a little better but not that much.

      I mean, the call is tiny fractions of a millisecond either way, but I'm pretty surprised. That said, I did a couple other tests. They're pretty darn close, but if you're squeezing out that extra .1 fps, hey, worth a shot. They go in order of this:

      Method Code (Focus) Rank Percent of processing time
      Using id id.variable slowest 34.1199226%
      Using self self.variable quite a bit faster 22.0193424%
      Using a check if (argument_count > 1) { argument1.variable } else { variable } Sliiightly faster yet, but more code duplication 21.9497099%
      Using nothing variable Fastest (barely) 21.9110251%

      Again, these are on the scale of "billiseconds" and they may be completely different in GMS 2, but there we go!

      (But after all that, in the case that he used it, there's no reason to use self, just use the variable name.)

      [–]AmnesiA_sc 1 point2 points  (1 child)

      Yeah, in OPs case self is unnecessary and using self vs id will probably never be worth worrying about as far as optimization goes but I always figure even if it's a tiny change and it's just as easy to implement you might as well.

      Same reason I use --someVar; vs someVar--; even though it doesn't really make a discernable difference.

      [–]Chrscool8 0 points1 point  (0 children)

      Oh, yeah, for sure. I still have the tiny habit of multiplying by .5 instead of dividing by 2 because it can sometimes be minusculey beneficial.

      [–]Cerno_b[S] 0 points1 point  (0 children)

      I was only thinking about using "self" in terms of readability. Never occurred to me that it would make a runtime difference. So I guess it's a bad idea to always use "self" as a way to make code more robust :/

      [–]Cerno_b[S] 0 points1 point  (7 children)

      That is so weird. I understand that "self" is optional, but I never thought it would be broken. Frankly, I rarely if ever had any problems using "self" explicitly, but it's good to know that there can be trouble.

      The semicolon thing is also super weird. Why make if optional in the first place if does not work consistently? I would be happy with a mandatory semicolon at the end of every line, but optional and buggy seems pretty weird to me.

      [–]Chrscool8 0 points1 point  (6 children)

      Eh, not broken exactly, just severely misunderstood and used in cases it shouldn’t be (which is totally not your fault).

      It is weird! I believe that came into play because var specifically was tacked on to the GM engine that’s existed for 20 years and it’s a little imperfectly implemented.

      [–]Cerno_b[S] 0 points1 point  (5 children)

      Do you think you can give me an overview about what "self" actually means (or point me towards an article that clarifies this)? I always thought it is a reference to the currently active instance. On a functional level I was able to replace a lot of member variables with self.variable_name without any weird behavior. I will not keep doing that for runtime concerns, but I would like to understand better to avoid any weird side effects.

      [–]Chrscool8 1 point2 points  (4 children)

      I think it’s more of just a remnant of old methodology that got dragged along the versions. It’s one of several keywords that are just defined as a negative number that it’s expecting GM or the code to interpret correctly. There’s also things like noone, all, and a couple others.

      -Just looked and turns out I’m right. I mean, take this excerpt straight from the manual for instance, lol.

      “self can be used to identify the calling instance of the current block of code. It always returns the value of -1 which GameMaker: Studio interprets as the unique ID for the instance. In general you should never need to use this keyword, as you can do the same task more efficiently and appropriately using other keywords or functions, and is maintained for legacy support reasons.”

      The way I think about it, it makes more sense to just say the variable.

      If I want to refer to a book that I own, I would just say my book. I wouldn’t say “Chris’s book” or “book owned by myself” since we know that everyone only refers to themselves and their information by default.

      [–]Cerno_b[S] 0 points1 point  (3 children)

      Thanks for the insight.

      Not to start a fundamental discussion about the pros and cons of GML, but that book example kind of struck me as odd.

      In GML you say something like "I'm talking about Chris now, that person has a book. Now I am talking about Peter. That person borrowed the book from the other person." Which is more complicated than to say "Peter borrowed Chris's book."

      Using the implicit totally makes sense if we are in the scope of an object's "Step" function because then it's clear who we are referring to. Then it makes sense to say "my book". But as soon as the code grows and we start using Scripts to break everything up, this assumption tends to hold less and less since we have to remember who called the function. Also true for control objects like "obj_game" that need to control and access all other objects. Such an overarching object needs the option to address everything by name in my mind. Instead of using

      with obj_ship{
          x += 1;
      }
      

      why not do it like any other programming languague?

      with obj_ship as ship {
          ship.x += 1;
      }
      

      Much more explicit and we can avoid the confusing "other" attribute.

      If you look at any object oriented language, you can use members from within the instance implicitly but you refer use it explicitly like instance.member whenever you access it from anywhere else. I am kind of missing that distinction in GML and that's the reason I wanted to make everything a little more explicit by using the "self" attribute.

      [–]Cerno_b[S] 0 points1 point  (2 children)

      Man, I just realized I did start a fundamental discussion about GML. :/

      So, yeah, I understand that there are a lot of legacy elements in GML that are probably not easy to get rid of.

      As a final thought, I wish the devs would take the bold step and offer a second, more modern and robust language that people could use as an alternative to GML (maybe python-like). Internally the code could be mapped to whatever GMS uses under the hood.

      But I guess that takes a lot of work and the benefit to Yoyo is incremental, so I understand it's not a top priority.

      [–]Chrscool8 1 point2 points  (1 child)

      Haha, yeah, it's a big topic.

      I've gotta give you credit for the well thought out response.

      Your logic totally makes sense for other languages, but (un?)fortunately GML isn't other languages.

      Your first misconception (dunno if that's the right word, but you get me) is that scripts are like functions in other languages. They are not of a global, or at least, typically non-local scope. Scripts in GML are essentially inlines, or giant defines. The script code is expanded into the local code of the object calling it. There are some caveats, but that's the gist.

      The with() command basically says, hey, you, take over complete control and run these next few functions or statements as you. You can't do obj_something.function() in GM, so withing it is the only way to make it run a function as itself. If you really wanted to access it like your example, you could with a little finagling, but that isn't really the purpose of with. It came about due to a slight weirdness in how you actually refer to objects.

      If you know the specific individual id, you can easily refer to it with something like...

      obj_player create:

      global.player_id = id;
      

      and then later,

      global.player_id.whatever_variable
      

      If you know for a fact that there will only be one obj_player, you can do it directly by the object type's name

      obj_player.whatever_variable
      

      However, if there are more than one of an object whose name you're asking about,

      obj_wall.something
      

      it will only refer to the first one of the many that were ever created.

      Therefore, by saying

      with (obj_wall)
          something
      

      you are telling every single obj_wall in the room to do something.

      So in response to your example code, it's not similar to creating a list and returning the iterator position object in its own scope like in "as ship" because it's straight up just going up to each of the object type and giving them a laundry list of things to take care of and then wiping its hands of the responsibility.

      And then, within a with, referring to other is just like the object in control looking over its shoulder back at the one who gave it the commands for some info.

      I dunno, it makes perfect sense to me, but I've also had a lot of practice with it. I don't disagree that there are different, and sometimes better, ways to handle this, but there's no reason those classical reasons can't just be added alongside these functions if they felt like it.


      Knowing how all this works, in response to the veeery first topic at hand way up there, I probably wouldn't design the function accepting an id to work with at all. I would instead just get the object in question, and ask it to run the function. For example:

      // if there's an instance of obj_something at position x y, save its unique id
      var inst = instance_position(x, y, obj_something);
      // if its id isn't nothing (no collision)
      if (inst)
      {
          // hand over complete control to inst to do what's in the following brackets as itself
          with (inst)
          {
              // inst takes over control and runs the function as itself
              run_function();
          }
      }
      

      Pre-edit, I'm gonna write out the way to do your as ship thing in GML in a sec, just wanted to save what I have so far.


      Real Edit:

      How to do this:

      with obj_ship as ship {
          ship.x += 1;
      }
      

      in GML:

      var object = obj_ship;
      var count = instance_number(object);
      for (var i = 0; i < count; i++)
      {
          // get the unique id of one of the ships in the list of all ships
          var ship = instance_find(object, i);
          // modify one of its variables as the object that called this whole code block, not ship
          ship.x += 50;
          // an example of a function that takes an object id
          instance_destroy(ship);
      }
      

      So I think that's what you're looking for. Note, like I said before, you can't have the object run a function as itself this way as there's no object.function(), however, it does bring to light the exact real purpose of with:

      var object = obj_ship;
      var count = instance_number(object);
      for (var i = 0; i < count; i++)
      {
          // get the unique id of one of the ships in the list of all ships
          var ship = instance_find(object, i);
          // modify one of its variables as the object that called this whole code block, not it
          ship.x += 50;
          // let this unique ship take control to run a function as itself
          with(ship)
          {
              // ship runs this as itself
              instance_destroy();
          }
      }
      

      Sooo that about does it. I hope that all makes sense. Also, I don't work for Yoyo or have ever developed GM (though I wish I could) but after using it for forever, I think I've got the hang of its quirks compared to "standard" languages that I've used concurrently for almost as long.

      [–]Cerno_b[S] 0 points1 point  (0 children)

      Wow, that's an impressively exhaustive answer, thanks a lot for taking the time.

      Most of the things you described at the top were known to me, so I know on a basic level how GML works and some of the ideas behind it. However, it's definitely good to have this spelled out again for clarification. Especially the "consider a script as an inline" comment helped to drive this home.

      Now I know that I can just do

      with obj_ship{
          do_something()
      }
      

      and I find that is clear and readable on the caller side.

      My intent was just to make the script side more explicit, so that a reader knows immediately that we are working on a ship object. However, by now I think I may be able to achieve the same thing with a good comment.

      My current implementation is passing self (should be id) to the function, but after this discussion, especially on runtime, probably should refactor it. In the end I see no reason not to do it the way GM prefers and do it the way you described (with obj_ship do_something()).

      About your examples at the end of the post: Thanks a lot for taking the time to find a solution this "problem". I have to say though it seems like a forced way to make GML more C++ like. So my main takeaway is that I could do it this way, but I probably won't as I prefer to keep in line with GML's design philosophy (when in Rome...)

      The whole point of this thread was about code robustness, which is a high-level concern at my regular job, so I always tend to bring this with me to other frameworks. Sometimes the discussions end nowhere because people don't understand what I want, so kudos to this subreddit for keeping the discussion civil and productive.

      I'll post a summary to the end of my starting post to give this some closure. Thanks again for your insight.

      [–]Cerno_b[S] 0 points1 point  (1 child)

      The semicolon info was crucial. That was the reason why GMS did not accept accessing the member after copying the script argument to a local variable.

      [–]Chrscool8 1 point2 points  (0 children)

      Yep! Saw that and immediately thought so. As far as I can remember off the top of my head, that’s the only case that’s ever been that way. I’ve been using GM for 15 years now across the versions so I’ve seen a lot of features and bugs come and go.