16 arguments limitation

csanyk

Member
I am developing a script, and inside that script I am calling draw_sprite_ext(), which has a 9-arguments long parameter list. I need to pass these args into my script when I call it, and I also need to pass in some additional arguments as well. So I am going to need to be able to pass in >16 arguments; a total of 17 arguments, in fact.

Based on the manual, in GMS1.x it appears that we're limited to 16 arguments, whether we use the older argument0..argument15 variables, or the newer argument[0..15] array. Unless I'm missing something, or they overlooked updating the manual?

But in GMS2.x, they've removed the cap on the argument[] array, allowing us to provide as many args as we need. Which is great, and makes it very easy to do what I need.

I was hoping that I could keep my script compatible with both GMS1.x and 2.x for now, as I'm using it in a Marketplace asset, and I would like to continue to support both versions as long as both are officially supported by YYG.

My question is, what's the best way to go about it?

One way would be to write a GMS1 version of the function, working around the 16-argument limitation, and a GMS2 version of the function, which uses the more straightforward argument[0...z] approach.

But if I did that, then if a project is imported the user would need to convert from the GMS1 version of the function call to the GMS2 version, and I'm not even sure if the GMS2 version would compile in a GMS1 project, meaning I'd need to build a second package...

I would like to avoid inconsistency, and avoid surprising the user, so my ideal solution would be a single script that works the same way in GMS1 and 2.

Yes, I know large parameter lists are a code smell, but I don't think it can be helped in this case, at least not without doing something like passing a data structure or array as an argument. So I could maybe do something like this:

my_script(argument[0], argument[1], argument[2]...) and have argument[0] be itself an array containing the arguments to be fed into the draw_sprite_ext() function. So, something like this:

Code:
//setting up the array that will be used as the first argument passed into my_script
draw_sprite_ext_args[0] = sprite;
draw_sprite_ext_args[1] = subimg;
draw_sprite_ext_args[2] = x;
draw_sprite_ext_args[3] = y;
//etc.
//...

//calling my_script, passing the array of draw_sprite_ext() args into argument[0] and my other arguments into argument[1] through argument[whatever] 
my_script(draw_sprite_ext_args, my_script_arg1, my_script_arg2, my_script_arg2, my_script_arg3, ...)
And so my_script's code would need to look something like this?

Code:
#define my_script
///my_script(draw_sprite_ext_args[], arg1, arg2, arg3, etc)

//declarations block
dse_args = argument[0];

my_sprite = dse_args[0];
my_subimg = dse_args[1];
my_x = dse_args[2];
my_y = dse_args[3];
my_xscale = dse_args[4];
my_yscale = dse_args[5];
my_rot = dse_args[6];
my_colour = dse_args[7];
my_alpha = dse_args[8];

myscript_arg1 = argument[1];
myscript_arg2 = argument[2];
myscript_arg3 = argument[3];
myscript_arg4 = argument[4];
//etc.

//actually do the stuff that my_script does...
Or potentially, rather than passing an array variable as my_script's argument[0], I could pass a ds_map or a ds_list. But then my users would need to be familiar with ds_ structures and know to clean up their ds to avoid memory leaks, etc.

So what's your opinion? What's the best way to implement this? As a user of an extension that provides this function, what would be the most intuitive, elegant way for you to want to call my_script()?
 
H

Harest

Guest
I had a related issue somehow but more about the readability than the number of arguments. It was for a script to draw text. I'm still kinda new to GM but i was told to only draw in the draw event and avoid assigning variables & cie in it.
I was sad to discover you could not use strings as keys for arrays for instance so my current solution is to use a ds_map. Declaring it in the objects where i need to draw text. Declaring only the parameters i need, with a script called in the step event that'll prepare the remaining required parameters for me assigning default parameters* and returning the ready to draw ds_map.

I'll however probably change this for an array with an enum for the keys (ie. instead of declaring ds[? "text"] = ... it'll be arrDraw[drT.text] = ... or something like that). I didn't think of it until i saw it on this forum. Probably way more efficient.

*Since you neither can assign a default value to a function parameter which i was used to (mainly did web dev before).
 

FrostyCat

Redemption Seeker
@Harest: You've been given some damningly bad advice from people who don't have a clue. No wonder GM has the reputation it does.

I'm still kinda new to GM but i was told to only draw in the draw event and avoid assigning variables & cie in it.
Hold it, what kind of BS advice is that? There's nothing wrong with assigning variables in the Draw event, as long as they are part of drawing logic. In particular, caching the value of repeated expressions in local variables (i.e. with var) counts as a best practice.

*Since you neither can assign a default value to a function parameter which i was used to (mainly did web dev before).
This part is also complete BS. You can redirect parameters to inner variables according to argument_count, then work off them.

Example:
Code:
///draw_some_text(x, y, [text])
{
  var xx, yy, text;
  xx = argument[0];
  yy = argument[1];
  switch (argument_count) {
    case 2:
      text = "Default Text";
    break;
    case 3:
      text = argument[2];
    break;
    default:
      show_error("Wrong number of arguments to draw_some_text().", true);
    break;
  }
  draw_text(xx, yy, text);
}
 
H

Harest

Guest
To be fair, the docs gave him some credit as it's stated for the draw event :
[...] The draw event is a very intensive event, in that it's one of the events that takes up most time and resources... to that end it is never a good idea to do anything in the draw event other than drawing. So save your large code or complex actions for the Step event or Alarms or whatever other event fits, but leave the draw event clear for the drawing as that's what it does best. [...]
About the default parameters, this kind of solution doesn't seem really flexible. Hence the choice of a structure that has parameters in it in a way i can easily tell which is what. If i want to only pass the text, x and y parameters and let the script set a default value for the rest, i can. Here is what i currently have :
Code:
// --- Called in the step event
///scr_prepareDrawText(dsMap dsmToComplete); return dsm;
// Script used to fill the missing parameters before a draw_text()
// arg0 : ds_map : Contains the info to draw
// return : the ds_map with eventual undefined parameters set to a default value
var dsm = argument0;

if (!ds_map_exists(dsm, "alpha"))   dsm[? "alpha"] = 1;
if (!ds_map_exists(dsm, "font"))    dsm[? "font"] = fnt_pixel14;
if (!ds_map_exists(dsm, "colour"))  dsm[? "colour"] = c_white;
if (!ds_map_exists(dsm, "halign"))  dsm[? "halign"] = fa_left;
if (!ds_map_exists(dsm, "valign"))  dsm[? "valign"] = fa_top;

return dsm;

// --- Called in the draw event
///scr_drawText(dsMap dsm);
// Script used to draw_text()
// arg0 : ds_map : Contains all the prepared info to draw
var dsm = argument0;

draw_set_font(dsm[? "font"]);
draw_set_alpha(dsm[? "alpha"]);
draw_set_colour(dsm[? "colour"]);
draw_set_halign(dsm[? "halign"]);
draw_set_valign(dsm[? "valign"]);

draw_text(dsm[? "x"], dsm[? "y"], dsm[? "text"]);

return true;
So i could in fact go back to what i had before with only one script in the draw event without any incidence ? Isn't at all better to do any checks & assigning in a step event instead ?

Sorry for the off-topic btw :oops:.
Edit @FrostyCat (Below) : Yep like i said above i intend to switch to an array (with enum keys to keep some readability).
 
Last edited by a moderator:

FrostyCat

Redemption Seeker
With the argument cap gone, you don't even have to use maps. A loop can collect the arguments.
Code:
///scr_drawText(x, y, text, [params])
{
  if ((argument_count < 3) || (argument_count & 1 == 0)) {
    show_error("Wrong number of arguments to scr_drawText()", true);
  }
  var font, alpha, colour, halign, valign;
  font = fnt_pixel14;
  alpha = 1;
  colour = c_white;
  halign = fa_left;
  valign = fa_top;
  for (var i = 3; i < argument_count; i += 2) {
    switch (argument[i]) {
      case "font": font = argument[i+1]; break;
      case "alpha": alpha = argument[i+1]; break;
      case "colour": colour = argument[i+1]; break;
      case "halign": halign = argument[i+1]; break;
      case "valign": valign = argument[i+1]; break;
    }
  }
  draw_set_font(font);
  draw_set_alpha(alpha);
  draw_set_colour(colour);
  draw_set_halign(halign);
  draw_set_valign(valign);
  draw_text(argument[0], argument[1], argument[2]);
}
Your map-based method is better for reusable styles, while mine is better for styles that vary between uses.
 

Yal

šŸ§ *penguin noises*
GMC Elder
Another solution would be to use arrays, since they can be passed as arguments now... not sure if that predates the infinite-arguments thing, though, but I used it as a 'workaround' before. The manual definitely needs some love <__<

A pet peeve I have is that the code examples in the manual often show exactly the same thing as the argument explanation, rather than some common practical application, which ruins the point of actually having them since you need to look up its quirks in some unofficial source.
 

csanyk

Member
Well, guess what, I found a bug!

If you have a script with >16 arguments, GMS1.4.1763 it's fine, but if you have an extension with a function that has >16 arguments, GMS1.4.1763 thinks that when you call the function, it has the wrong number of arguments.

As a workaround, if you create the function and leave the Variable Length Arguments option checked, the compiler doesn't bother checking whether the function call has the right number of arguments, and lets it by.

Filing a bug report...
 
Last edited:
Top