Jump to content


Uninitialised "things"


  • This topic is locked This topic is locked
49 replies to this topic

#1

  • Guests

Posted 08 April 2011 - 03:08 PM

So, here's today's topic. We've been putting in code to force you to pass the correct number of arguments because aside from being a bad habit to get into, it's also a performance and "bug waiting to happen" issue. So in 8.1 you will now get an error if you call a script with the incorrect number of arguments.

   myScript()
Would be wrong if you use argument0 through argument15 in your code. You must now provide the correct number. Currently, it would initialise the variable to 0 (or false) if you hadn't passed it in. We consider this bad. It's an undocumented thing which should be avoided. if you mean 0, you should pass 0 in.

Now... this also got us looking at other issues. We have problems with folk creating arrays and touching element (say) 15, but then reading element 5. Again, this would be initialised to 0. This is again bad... but it would mean you have to "initialise the whole array.

So I'm currently looking to see if it's possible to throw an error in this case.


Now here's the question. What do you think should happen in these cases? The argument one is a no-brainer to me. It's dangerous, and it's a source of many a bug if you don't know what your doing. It's also a performance issue, as we need to clear 16 arguments every time we enter a function in case it accesses them. Now however, if you don't use args, we can detect that... and not clear anything, making script->script calling, faster (well.. a tiny bit).

The array access is different. We currently set the LAST element first to "size" the array, and know that our code will fill it all in first, but many folk I suspect will depend on the fact it'll be 0. What I'd prefer is for you to use some kind of DIM() command that would WIPE it if you needed 0, but if you didn't, you would get an error if you accessed an uninitialised element.

What do you think?


EDIT: Just been pointed out that printf() style calls would no longer work. So we'll make it so that argument[x] types are not effected by calling limits. Basically we'll assume you know what your doing if you access arguments via arrays.

#2 Nocturne

Nocturne

    Nocturne Games

  • Administrators
  • 16814 posts
  • Version:GM:Studio

Posted 08 April 2011 - 03:17 PM

The argument business sounds good to me... any performance boost is a good thing, and if it enforces good programming habits then I´m all for it! As for the arrays, what does DIM() mean? At the moment it´s quite useful to do

array[15]=0;

and know that the whole array is initialised to 0... are you suggesting that we do this but in some way choose the initial value? Or havit so that that code throws an error and we have to use

var i;
i=0;
repeat(15)
{
array[i]=0;
i+=1;
}

to create the whole array? Sorry if I´m being dumb, but I only know how to program in GM...

Edited by Nocturne, 08 April 2011 - 03:17 PM.

  • 0

#3

  • Guests

Posted 08 April 2011 - 03:24 PM

At the moment yes. You would have to "clear" the array yourself. However, if we added a DIM command, then it could initialise an array to the size you wanted.


   Dim(myarray,16);

This would create/resize an array to 16 initialised elements. meaning you don't need to manually do it. We've seen a lot of bugs where someone simply hasn't filled in a variable, then read from it and it's worked "by chance" really.

I guess we could also make it optional that this could be expanded, allowing for "bounds" checking. Theres no way to protect going outside the array at the moment as it just expands.....?

#4 Nocturne

Nocturne

    Nocturne Games

  • Administrators
  • 16814 posts
  • Version:GM:Studio

Posted 08 April 2011 - 03:26 PM

Thanks for the info, Mike... seems like a sensible move and easy to adjust my coding style to!
  • 0

#5 GameGeisha

GameGeisha

    GameGeisha

  • GMC Member
  • 2747 posts
  • Version:GM:Studio

Posted 08 April 2011 - 03:58 PM

Dim(myarray,16);
This would create/resize an array to 16 initialised elements. meaning you don't need to manually do it. We've seen a lot of bugs where someone simply hasn't filled in a variable, then read from it and it's worked "by chance" really.

I like this. Would it be possible to expand this to initialize all the values to a user-specified value (e.g. noone), or even generate common sequences (like range() from Python). That would definitely cut down on the time spent writing FOR loops for the purpose.

Also, will this be a special syntax, or just a new function? If it really is a function, that would mean passing arrays as arguments would be permitted. This function can be really powerful and would reduce the number of arguments per script (e.g. if min() implements this, it can take just one argument).

GameGeisha
  • 0

#6 paul23

paul23

    GMC Member

  • Global Moderators
  • 3365 posts
  • Version:GM8

Posted 08 April 2011 - 04:07 PM

Well I'm always in favour of getting things done explicitelly and do as little as possible implicetelly.. So I shared your opinion that initializing elements 0-x when setting array[x+1] is bad.

That said: did you also consider the value of optional arguments? - Optional arguments allow for much more flexibility & faster programming (no longer do you have to create a seperate script for each different number of arguments).. From what I read optional arguments will be impossible in gm 8.1?
  • 0

#7 BlueMoonProductions

BlueMoonProductions

    BlueMoonProductions

  • GMC Member
  • 241 posts
  • Version:GM:Studio

Posted 08 April 2011 - 04:21 PM

I'm not sure wether that's a good idea. For example, in my game, I use a sound system: FMOD. There's a script FMODSound_Play(sound), which calls an external function and plays it.
I edited the script from:
external_call(argument0,and,some,other,stuff)
.. to ..
if (argument1 == false)
 {
  if (global.setting_sfx == true)
   {
    external_call(argument0,and,some,other,stuff)
   }
 }else{
  if (global.setting_music == true)
   {
    external_call(argument0,and,some,other,stuff)
   }
 }
The second argument I added tells wether the played sound is music or SFX. I edited at the time I already used this script a lot, so argument1 would be set to false > it is SFX.

So I use it in 2 ways: FMODSound_Play(sound) and FMODSound_Play(sound,type).

So, I hope you'll at least consider an option to allow a "random" number of arguments? Because there are way more possibilities

Eg: script(numberofarguments, argument1[, argument2[, argument3[, ..]]]):
n = argument0
var i;
for ( i = 1; i <= n; i += 1)
 {
  do_something_with( argument[i])
 }

I think the whole thing is a bad idea, but at least consider an option for a random number of arguments.

Edited by BlueMoonProductions, 08 April 2011 - 04:21 PM.

  • 0

#8 LSnK

LSnK

    NaN

  • GMC Member
  • 1188 posts

Posted 08 April 2011 - 04:59 PM

So we'll make it so that argument[x] types are not effected by calling limits. Basically we'll assume you know what your doing if you access arguments via arrays.

Does that also mean the array would be zeroed? If not I don't like the outcome: no variadic scripts, or variadic scripts with no means of determining the argument count. That would be a very painful change. Maybe something best left to a major revision than a point release.

A function to initialise arrays would be welcome, as would error-reporting for accessing [technically] uninitialised elements.

It'd also be nice to have a means of getting the size of arrays. It's simpler and less error-prone than storing it yourself.

Edited by LSnK, 08 April 2011 - 05:00 PM.

  • 0

#9 JAk HAk

JAk HAk

    sepius fidelis

  • New Member
  • 713 posts
  • Version:GM:HTML5

Posted 08 April 2011 - 06:35 PM

I'd prefer it if scripts defaulted to no arguments and every one you added had to be given a name. Currently, to avoid confusion, I end up making scripts like this:

var id, amount, direction;
id = argument0;
amount = argument1;
direction = argument2;

...

That way, I can refer to variables by a human name, rather than the easily confusable argumentx.


For arrays, I tend to initialize them either sequentially or in a for loop. Considering arrays in GM are basically unparameterized Vectors, this has always worked fine, but it sounds to me like you're wanting to make them more like traditional arrays. As long as the functionality to dynamically resize the array is left in (or Vectors are introduced) I'm fine with what you do.
  • 0

#10

  • Guests

Posted 08 April 2011 - 07:56 PM

GameGeisha: Its would just be a new function... nothing special other than it'll clear the array quicker since it's native.

paul23: Named arguments is definitely on the list of things we WANT to add, but it's just not possible with 8.1. Well... anything is obviously possible, but it's not worth the effort in 8.1, probably the next version.

BlueMoonProductions: The current idea, is that if you use argument[x], then it'll be as normal. So no checking. But most folk will use argument0, argument1 etc. All you'd have to do is rewrite your function to use argument[0] and argument[1] instead.

The idea being that is you use array access, then you know the issues and accept the risk, where as most newbies will use the normal syntax and get better protection.

I also think adding a new function/variable argc to give you the number of arguments, would be a damn fine idea.... This would in fact make your function far better!


var i;
for ( i = 0; i <= argc; i += 1)
{
  do_something_with( argument[i] )
}

Anything outside the argc range could then be deemed invalid - it "could" throw an error I guess, where as just now you simply don't know how many args you have, so they HAVE to be 0. Even your first function would be better, as you could now almost have default arguments.... you'd have to code them yourself, but you COULD do it.

JAk HAk: They would clear them in the same way, and yes... currently they would still be expandable - although I think an option to be able to LOCK them into a standard array (complete with bounds checking) would be highly beneficial to beginners.

#11 GameGeisha

GameGeisha

    GameGeisha

  • GMC Member
  • 2747 posts
  • Version:GM:Studio

Posted 08 April 2011 - 08:43 PM

GameGeisha: Its would just be a new function... nothing special other than it'll clear the array quicker since it's native.

I understand that it's a new function, but what about the syntax?

Consider this example:
foo[0] = "Foo";
foo[1] = "Bar";
foo[2] = "Foobar";
show_message(bar(foo)); //show "Bar"
/* bar(array_here) */
return argument0[1];
If the example code is run in GM8.0, you will always get "Foo", not "Bar". Changing bar to "return argument0[2]" will still return "Foo", not "Foobar". Clearly passing entire arrays by reference is not permitted in GM8.0 --- will this be permitted in GM8.1 now that we have dim()?

GameGeisha

PS: In debug mode, will all the contents of an array be displayed (i.e. through "Show local variables"), or will only the first entry be visible like now? Also, having bounds for arrays now should allow for returning arrays and passing them by reference, wouldn't it?
  • 0

#12 mcoot

mcoot

    GMC Member

  • New Member
  • 387 posts

Posted 08 April 2011 - 08:44 PM

I agree that the argumentn variables shouldn't initialise to 0. As long as optional arguments are still possible with the argument[n] array, then that's fine with me.

As for the arrays, that sounds fine to me, just so long as we have this dim() function so we don't have to initialise it manually.
  • 0

#13 Smarty

Smarty

    GMC Member

  • Retired Staff
  • 7223 posts
  • Version:GM:Studio

Posted 08 April 2011 - 09:08 PM

At the moment yes. You would have to "clear" the array yourself. However, if we added a DIM command, then it could initialise an array to the size you wanted.

   Dim(myarray,16);

This would create/resize an array to 16 initialised elements. meaning you don't need to manually do it. We've seen a lot of bugs where someone simply hasn't filled in a variable, then read from it and it's worked "by chance" really.

If you're going that way, would you mind adding a parameter that specifies a value / string to initialize it with?

With regard to the arguments: I was wondering if you were going to preserve the little known fact that to scripts, arguments are actually local variables rather than constants - you can't just read them, you can store new values in them too. I use it as a shorthand for not having to initialize private variables in scripts, e.g.:

if (argument1==0) argument1=1;
return argument0/argument1;

Edit: I also wonder how you intend to make this interact with the (horrible) "Treat initialized variables as value 0" setting for a game. If arrays throw an error when not DIMmed, that basically goes against that setting. But if they do not throw an error, you're back with arrays without bounds checks.

Edited by Smarty, 08 April 2011 - 09:11 PM.

  • 0

#14 Medusar

Medusar

    GMC Member

  • GMC Member
  • 1228 posts
  • Version:GM:Studio

Posted 08 April 2011 - 09:12 PM

Always wondered why having the "treat uninitialized variables as 0" option turned off still produced this behaviour for unset script arguments and array values... has been a slight annoyance. Not as big however as the lack of argument_count.

As for the dim() function, I suggest that it should follow GM's current syntax of variable_local/global_set/get(), that is, passing the variable by name. Not that it's the way I like to see this kind of thing, but at least it's consistent.
Unless, of course, you're going to properly allow passing arrays by reference... but I don't expect that for now.

Anyway... I don't see your current way of handling script arguments as the proper way to do it... Yes it's better than it is done currently, but at some point you're going to want to have named arguments and default values for them (instead of forcing the user to use an "unsafe" method and filling every uninitialised "thing" with 0). If you're going to adapt the script system anyway, why not do it properly straight away? I know it's probably a lot of extra work but if you're not careful you end up supporting two old versions and the "proper" version all at the same time... It's not what I'd want.
That's my opinion anyway.
  • 0

#15 paul23

paul23

    GMC Member

  • Global Moderators
  • 3365 posts
  • Version:GM8

Posted 08 April 2011 - 09:17 PM

paul23: Named arguments is definitely on the list of things we WANT to add, but it's just not possible with 8.1. Well... anything is obviously possible, but it's not worth the effort in 8.1, probably the next version.


I think I didn't make myself clear/you didn't understand my post:

I meant optional arguments, in the sense that you give them and "initial" value:
void foo(int i, bool b = true); //"b" is optional and when not given that argument is initialized at "true"

But I didn't read your post very well either, I just realize you can still keep the "old" behaviour (of considering the value 0 being an not included argument) by treating arguments as arrays.

ow and not entirelly on topic: is there any indication that the (arbitrary) limit of 16 maximum number of arguments will be lifted some day?

Edited by paul23, 08 April 2011 - 09:19 PM.

  • 0

#16 JonathanPzone

JonathanPzone

    GMC Member

  • GMC Member
  • 1199 posts
  • Version:GM:Studio

Posted 08 April 2011 - 10:11 PM

Yea.

I never realized it behaved this way anyway. :mellow:
  • 0

#17 Maarten Baert

Maarten Baert

    GMC Member

  • GMC Member
  • 714 posts
  • Version:GM8.1

Posted 08 April 2011 - 11:08 PM

I like the 'Dim' approach, I think it's far better than resizing arrays automatically. This would also mean we can finally delete huge global arrays to save some memory, instead of just forgetting them.

argc sounds good too, however I think the name is confusing. Since the arguments are called argumentN or argument[N], the number of arguments should be called argument_count or similar.

I'd also prefer 'dim' instead of 'Dim', but that's just a detail.

While we're talking about varargs, could you please fix external_define? Currently it requires entering the type of all external arguments (ty_real/ty_string), even though functions with more than 4 arguments can't use strings. If I don't add ty_real for every single argument, the function will display an error message. This also means the number of arguments for external functions is limited to 11 instead of the claimed 16, since external_define uses 5 arguments already and doesn't accept more than 16 arguments either. I know it's not really a big issue since extensions are easier and faster anyway, but I use external_define/external_call all the time during development (especially for ExtremePhysics). I don't want to create a new GEX, install it, create a new EXE, and run it with the Visual Studio debugger every time I change something to the DLL.

It would be even better if you could remove the 4 argument limit for strings (without a huge number of if/else statements). Maybe with some inline assembly? Or some hackery :P.

For example:
double myfunction(double x, const char* y, double z) {
    printf("x = %f\n", x);
    printf("y = %s\n", y);
    printf("z = %f\n", z);
    return 0.0;
}

You can call it like this:
struct memblock {
    char mem[sizeof(double)*16];
    inline unsigned int write_real(double x, unsigned int pos) {
        *((double*)(mem+pos)) = x;
        return pos+sizeof(double);
    }
    inline unsigned int write_string(const char* x, unsigned int pos) {
        *((const char**)(mem+pos)) = x;
        return pos+sizeof(const char*);
    }
};

double (*funcptr)(memblock) = (double (*)(memblock))(&myfunction); // horrible hack but it will work (for cdecl at least)

memblock m;
unsigned int pos = 0;
double res;

// call function
pos = m.write_real(5.0, pos);
pos = m.write_string("hello world", pos);
pos = m.write_real(42.0, pos);
res = (*funcptr)(m);
It works for cdecl. For stdcall you have to add some inline assembly to fix the stack pointer:
unsigned int stack;
__asm {
    mov stack, esp;
}
res = (*funcptr)(m);
__asm {
    mov esp, stack;
}
It does result in a 'Runtime Check Failure' in Debug Mode, but it works :). I'm not sure whether this is possible in Delphi though. If it's possible, you won't need the calling convention anymore.
  • 0

#18 sabriath

sabriath

    12013

  • GMC Member
  • 3147 posts

Posted 09 April 2011 - 12:07 AM

It would be even better if you could remove the 4 argument limit for strings (without a huge number of if/else statements). Maybe with some inline assembly? Or some hackery

I actually provided the code involved in the suggestion topic for the string limit ability of GM....not sure why YYG team hadn't thought of it before. The code would actually go something like this:
void external_call(void far* f, gmvar* v, short c)
{
  for(register short i = c - 1; i >= 0; i--)
  {
    switch(v[i]->type)
    {
      case ty_string:
        asm(v[i]->str){push %0}
        break;
      case ty_real:
        asm(v[i]->real){push %0}
        break;
    }
  }
  asm(f){callf %0}
}

A better way is to actually write the 'for' and 'switch' as actual assembly so that the stack is guaranteed to not be used by the compiler during the 'push' sequences. For 'cdecl' you would create a separate function that would save the stack pointer prior to pushing values and restore it after the call (similar to a frame push/pop).



Ontopic:

Dim functionality sounds good, but I would rather see it either using string naming or as an actual statement, using parenthesis makes it feel like the result is what you are dimming. For example:

var i[10]; //dims 'i' as a 10 element array set to '0'
dim i[20]; //redims 'i' as a 20 element array, set the new elements to '0'
redim("i",30); //redims 'i' as a 30 element array, set the new elements to '0'

looks a lot better than:

dim(i, 20);

because "dim" is a statement, not a function call, while 'redim' can be used as a function call so that the name can be referenced rather than evaluated. Other notes: variable_local_dim, variable_global_dim for those that are not strict-scoped in 'var' context.


Arguments:

Never understood why GM does arguments the way it does....but if it'll make it faster, go ahead, it's still wrong though (they should be stack-implemented, position reference saved, and boundary checked when 'argument' is used...keeps everything in a single reference frame for recursion purposes and maintains speed of operation, and "freeing memory" is simply popping the stack pointer out of the frame).

Edited by sabriath, 09 April 2011 - 12:08 AM.

  • 0

#19 IceMetalPunk

IceMetalPunk

    InfiniteIMPerfection

  • Retired Staff
  • 9260 posts
  • Version:Unknown

Posted 09 April 2011 - 12:41 AM

As long as we have an argc variable (or, to keep with GM's naming conventions, argument_count), that should be all we need. A double-check for unpassed parameters could also be useful for new users, but the argument_count is really all that's needed if you program correctly.

Instead of throwing errors, though, if you simply add in a NULL type, you can set the unpassed arguments to NULL and be done with it all.

-IMP ;) :)
  • 0

#20 gnysek

gnysek

    GMC Member

  • GMC Member
  • 324 posts
  • Version:GM:Studio

Posted 09 April 2011 - 10:15 AM

I know that some begginer users write:

var i;
for (i=0; i<10; i+=1) {
    tab[i] = 1;
}

and then to access tab[0] they writing:

tab = 15;

This should also give error.
  • 0




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users