ds_map_exists() question

csanyk

Member
ds_map_exists() takes two arguments: the id of the ds_map, and a key to check to see if it exists.

Am I missing something, or is there no way to check if a ds_map exists without checking to see if it has a key?

If I were to create a new, empty ds_map, how could I tell if it simply exists?
 
I

icuurd12b42

Guest
ds_exists takes a ds and a type enum.

In any case the function will not help you in most cases due to the fact ds ids are re-used... so you will eventually get false positives
 

Juju

Member
Until YYG get around to fixing this long-standing oversight...

Code:
///ds_create( type, [width, height] )
//
// 2016/11/11 @jujuadams

if ( argument_count < 1 ) return undefined;

var _ds, _type = argument[0];
switch( _type ) {
    case ds_type_grid:     _ds = ds_grid_create( argument[1], argument[2] ); break;
    case ds_type_list:     _ds = ds_list_create();     break;
    case ds_type_map:      _ds = ds_map_create( );     break;
    case ds_type_priority: _ds = ds_priority_create(); break;
    case ds_type_queue:    _ds = ds_queue_create();    break;
    case ds_type_stack:    _ds = ds_stack_create();    break;
    default: return undefined; break;
}

return _ds + 0.1*_type;
Code:
///ds_destroy( ds )
//
// 2016/11/11 @jujuadams

var _ds = argument0;

switch( ds_get_type( _ds ) ) {
    case ds_type_grid:     ds_grid_destroy( _ds );     break;
    case ds_type_list:     ds_list_destroy( _ds );     break;
    case ds_type_map:      ds_map_destroy( _ds );      break;
    case ds_type_priority: ds_priority_destroy( _ds ); break;
    case ds_type_queue:    ds_queue_destroy( _ds );    break;
    case ds_type_stack:    ds_stack_destroy( _ds );    break;
}
Code:
///ds_get_type( ds )
//
// 2016/11/11 @jujuadams

return frac( argument0 ) * 10;
Code:
///ds_extant( ds )
//
// 2016/12/20 @jujuadams

return ds_exists( argument0, ds_get_type( argument0 ) );
Data structures get cast as ints internally so no other action is needed.
 

Mike

nobody important
GMC Elder
Performance wise, you WANT to reuse handles. Internally (just now) these are simple array indexes so data structure look ups are fast. If you don't reuse them you need to use a dictionary and having to look this up every time you access a data structure would be incredibly painful. You'd lose a big chunk of performance with data structures to stop handle reuse. That's just not worth it in my book.
 

csanyk

Member
Performance wise, you WANT to reuse handles. Internally (just now) these are simple array indexes so data structure look ups are fast. If you don't reuse them you need to use a dictionary and having to look this up every time you access a data structure would be incredibly painful. You'd lose a big chunk of performance with data structures to stop handle reuse. That's just not worth it in my book.
Interesting, and that's a reasonable explanation. SO how then do I check to see if [handle] == [the datastructure I'm looking for]?
 

GMWolf

aka fel666
Performance wise, you WANT to reuse handles.
So we just forget about correctness?
Thats simply ridiculous! If a create a data structure, store it in a variable, then delete that data structure, GM should never, ever claim that that data structure exists! It will just lead to many, many unfixable bugs!
In fact, that explains some of the really wier behaviour i have been having before...

Seriously, YYG, get your s**t straight!
Use a hash map or something. Ive tested a simmilar system. If you use linear probing, you should be fine, since data structures tend to be create then destroyed before the hashes wrap back to the front of the table.
hashing an integer is... just, returning that integer, and because you will only get clashes once the hashes wrap back around to the front of the table, you should only get poor performance if you never free any data structures. Just in case many data structures are created early, then kept througout the game (say it holds global information), then dont use linear probing, but quadratic probing instead.

You'd lose a big chunk of performance with data structures to stop handle reuse
Well, not really. At least, not significantly. Perhaps a factor of 2.
But given how GM runs already.... I doubt GM users care too much about down to the metal level performance.

If YYG truely cared about down to the metal level of performance, perhaps we would have typing and a bit more compiler love? Oh, and actually efficient data structures. Optimization starts with data structures: not implementation details. Would love to see heaps, and linked lists!
 
Last edited:

Mike

nobody important
GMC Elder
Bollocks. Who's to say what is "correct" usage. We say we'll hand you back a handle and we do, it's up to you to manage them properly. If you free one, free the handle properly. Your problem is you're not deleting them in your own code properly, don't blame us for that. Just get your allocation and freeing sorted in a consistent manner, and you won't have any issues with it.
Only extensions where you might not have access to the internal code to latch onto does this matter, and even there you can do your own alloc/free that users call.

Just because you don't care about slow downs doesn't mean no one else does. I use grids extensively in tight loops and a 2x slow down would seriously hurt.

There's also nothing to stop you doing linked lists as it stands. Just use the ID of either the instance or the data structure and you can do a 1D linked list, or use 2 variables and you can do a 2D one. I use them all the time.
 

csanyk

Member
So we just forget about correctness?
Thats simply ridiculous! If a create a data structure, store it in a variable, then delete that data structure, GM should never, ever claim that that data structure exists! It will just lead to many, many unfixable bugs!
In fact, that explains some of the really wier behaviour i have been having before...
GMEngine: "You only asked me if a data structure of this type exists, not whether this data structure is identical to the one you were thinking of. So technically, I am correct."

It's like looking in a telephone directory to see if an address exists, and then being disappointed when you find out that the person who used to live there died, and now someone else is there.

Kindof hard to fault the phone directory.

But maybe we should get a proper GUID for the data structure, and not an address?
 

GMWolf

aka fel666
Bollocks. Who's to say what is "correct" usage.
Anyone can comment on what they deem correct. The users are especially important. They are the one who use the product after all.
There are general consensus about what is 'correct' when it comes to things like language design.

If you free one, free the handle properly.
Not always possible and Seperation of concerns is also quite important.

Just get your allocation and freeing sorted in a consistent manner, and you won't have any issues with it.
I like making frameworks, and things. I dont always know what the user does.

Only extensions where you might not have access to the internal code to latch onto does this matter, and even there you can do your own alloc/free that users call.
What if the asset is the one doing the freeing, without the user knowing? (like surfaces get freed).
Then the user doent know when to free, and the asset doesnt know what variables to set to 'undefined'.
here, the only way around this is to supply a callback script so the user can then set all his varibles to undefined. But come on... really?

There is a general rule in computer science: separation of concerns.
Now, needing to keep track of every variable referencing a data structure, and setting them to 'undefined' when i free a data structure, thats hardly sepearating concerns.

Whats more, that can be increadibly ineficient! What if i have a grid, that stores lists. The same list can referenced mutiple times in the grid.
If i free a list, i would have to itterate through every cell in the grid, checking to see if that list is the one being freed in order to set it to undefined? Bollocks to that!


Regardless, what use is ds_exists if i dont know if im talking about the ds i created at the start of the game, or the ds i just created now.


GMEngine: You only asked me if a data structure of this type exists, not whether this data structure is identical to the one you were thinking of. So technically, I am correct.
Well, actually, you are asking: "does a data structure of this type, with this index exists?"
But, it would be better to have: "Does the data structure with this index exist?" ie: has it been created? has it been destroyed. Nice and specific about 1 data structure you got a handle of before.
Not any data structure that, through sheer luck, got the same index as the one you created before.

It's like looking in a telephone directory to see if an address exists, and then being disappointed when you find out that the person who used to live there died, and now someone else is there.
Isnt it great computer science is all about virtualization these days: Even with C you dont use physical memory.
Besides, you described the problem entirely: You are not asking for the person by name, but the person by adress. Thats a pretty bad idea. especially since you may recognise that wasnt the persone you where looking for, but the computer has no way of knowing!



But maybe we should get a proper GUID for the data structure, and not an address?
What we get is an id to refference DS' with. Not and address.
But, because it is reused, we get al the dissadvantages of an address, with none of the advantages. Great :)

Infact, YYG like to use the term 'id'. That means identifier, not index.
To a user, that will make it seem like you get a unique identifier. Having it be an index in a table instead is quite ambiguous!
 
Last edited:

csanyk

Member
Bollocks. Who's to say what is "correct" usage. We say we'll hand you back a handle and we do, it's up to you to manage them properly. If you free one, free the handle properly. Your problem is you're not deleting them in your own code properly, don't blame us for that. Just get your allocation and freeing sorted in a consistent manner, and you won't have any issues with it.
Only extensions where you might not have access to the internal code to latch onto does this matter, and even there you can do your own alloc/free that users call.
Mike I want to make sure I understand this... If I understand correctly, if I did this:

a = ds_stack_create(); //returns first available handle, let's say 0
ds_stack_destroy(a); //ds_stack handle 0 is freed

b = ds_stack_create(); //returns first available handle, again 0
if ds_exists(a, ds_stack); //a is still equal to 0, and a ds_stack exists at that handle...

Would I expect that last line to return true or false?

I think it should return false; the ds_stack that a was assigned to was destroyed.

When I created b, did it re-use the first available address for a ds_stack, which had moments before been assigned to the ds_stack that had been created for a? Because if so, that's kinda terrible.

Or does this re-use of ds handles happen only after a long time, like if I create 2^32+1 ds_stacks? Because if that's the case, I'd expect that I would almost never encounter this in practice... I guess if you're only using an 2^16 or 2^8 address space for ds handles, that would make it more likely to happen...
 
Last edited:

csanyk

Member
Anyone can comment on what they deem correct. The users are especially important. They are the one who use the product after all.
There are general consensus about what is 'correct' when it comes to things like language design.


Not always possible and Seperation of concerns is also quite important.


I like making frameworks, and things. I dont always know what the user does.


What if the asset is the one doing the freeing, without the user knowing? (like surfaces get freed).
Then the user doent know when to free, and the asset doesnt know what variables to set to 'undefined'.
here, the only way around this is to supply a callback script so the user can then set all his varibles to undefined. But come on... really?

There is a general rule in computer science: separation of concerns.
Now, needing to keep track of every variable referencing a data structure, and setting them to 'undefined' when i free a data structure, thats hardly sepearating concerns.

Whats more, that can be increadibly ineficient! What if i have a grid, that stores lists. The same list can referenced mutiple times in the grid.
If i free a list, i would have to itterate through every cell in the grid, checking to see if that list is the one being freed in order to set it to undefined? Bollocks to that!


Regardless, what use is ds_exists if i dont know if im talking about the ds i created at the start of the game, or the ds i just created now.



Well, actually, you are asking: "does a data structure of this type, with this index exists?"
But, it would be better to have: "Does the data structure with this index exist?" ie: has it been created? has it been destroyed. Nice and specific about 1 data structure you got a handle of before.
Not any data structure that, through sheer luck, got the same index as the one you created before.


Isnt it great computer science is all about virtualization these days: Even in C you dont use physical memory.


What we get is an id to refference DS' with. Not and address.
But, because it is reused, we get al the dissadvantages of an address, with none of the advantages. Great :)
You understand the points I was getting at pretty well. The GM Engine telling you that a ds_exists when it's handle is no longer the one you were asking about is pretty bad. I would go so far as to say that it's incorrect, even if @Mike is a more experienced programmer and definitely knows the code we're talking about better than I can, I still have to say that this feels wrong.
 

GMWolf

aka fel666
Mike I want to make sure I understand this... If I understand correctly, if I did this:

a = ds_stack_create();
ds_stack_destroy(a);

b = ds_stack_create();
if ds_exists(a, ds_stack);

Would I expect that last line to return true or false?

I think it should return false; the ds_stack that a was assigned to was destroyed.

When I created b, did it re-use the first available address for a ds_stack, which had moments before been assigned to the ds_stack that had been created for a? Because if so, that's kinda terrible.

Or does this re-use of ds handles happen only after a long time, like if I create 2^32+1 ds_stacks? Because if that's the case, I'd expect that I would almost never encounter this in practice... I guess if you're only using an 2^16 or 2^8 address space for ds handles, that would make it more likely to happen...
Guess what, it returns true! HAHA, nice one YYG! very nice!
here is the code i used:
Code:
var a = ds_stack_create();
ds_stack_destroy(a);

var b = ds_stack_create();
show_message(string( ds_exists(a, ds_type_stack)));

And yes, it feels wrong because IT IS wrong!
Seriously, who though it was a good idea! Use unique identifiers already!
 

csanyk

Member
Guess what, it returns true! HAHA, nice one YYG! very nice!
here is the code i used:
Code:
var a = ds_stack_create();
ds_stack_destroy(a);

var b = ds_stack_create();
show_message(string( ds_exists(a, ds_type_stack)));

And yes, it feels wrong because IT IS wrong!
Seriously, who though it was a good idea! Use unique identifiers already!
upload_2016-12-20_11-35-47.jpeg

This is TOTALLY a bug. Even if it works "as intended", the intent and design is wrong. This needs to be fixed.
 
Last edited:

Juju

Member
Might I suggest a slight modification to the code:
Code:
var a = ds_stack_create();
ds_stack_destroy(a);
a = noone;

var b = ds_stack_create();
show_message(string( ds_exists(a, ds_type_stack)));
This is what should be done whenever a data structure is destroyed / freed from memory. It is also needed when working with buffers and vbuffs. This recommended practice is not in the manual.

For the purposes of debugging code that has multiple kinds of data structure occupying one numerical value makes things painful and, dare I say it, unintuitive. Makes data structures rather forbidding to learn, and whilst I can't remember a lot about my early days in GM now, I do distinctly remember getting frustrated with this.
 
Last edited:

GMWolf

aka fel666
Might I suggest a modification to the code:

Code:
var a = ds_stack_create();
ds_stack_destroy(a);
a = noone;

var b = ds_stack_create();
show_message(string( ds_exists(a, ds_type_stack)));
This is what should be done whenever a data structure is destroyed / freed from memory. This is certainly needed when working with buffers and vbuffs.

However, for the purposes of debugging code having multiple kinds of data structure occupying one numerical value makes things painful and unintuitive. Makes data structures rather forbidding to learn, and whilst I can't remember a lot about my early days in GM now, I do distinctly remember getting frustrated with this.
Whats more, that can be increadibly ineficient! What if i have a grid, that stores lists. The same list can referenced mutiple times in the grid.
If i free a list, i would have to itterate through every cell in the grid, checking to see if that list is the one being freed in order to set it to undefined? Bollocks to that!
 

csanyk

Member
JuJu's workaround helps, a little, but Fel666 is right, if you have multiple aliases to the same ds_ it quickly becomes unmanageable to update all of them when the ds_ is destroyed.
 

GMWolf

aka fel666
JuJu's workaround helps, a little, but Fel666 is right, if you have multiple aliases to the same ds_ it quickly becomes unmanageable to update all of them when the ds_ is destroyed.
Not to mention, JuJus workaround is just that. A workaround. Exactly what we have been blaming YYG of since... Well, since before in started using GM!
They make poor decisions, then suggest some half baked workaround.

I'm pretty close to being done with GM at this rate... Even toying around with it becomes hard when there are explicit features, like reusing id's, that feel like they where put in place just to f**k with you!
 

Mike

nobody important
GMC Elder
This isn't a work around, it's proper practice. If you're in C++ and you free memory, you set the pointer to NULL so you know it's free. in GML you should set it to undefined so you know it's freed. You allocate and set a variable, you free something, you clear the variable. This is standard coding practice, and if you're not doing this, you're doing it wrong.

surfaces DO use unique handles but this is for a few reasons. First, you don't refer to them line after line after line like you do with data structures, and second they are disposed of without you knowing so you WILL query them and you need to know the handle is unique so that you've not gotten a different one.

As I said, this would benefit extensions as they can not always know, or be hooked into the allocation or freeing of resources. Normally, I'd have the extension do the free/delete and then have the game call that to allocate/free things. This means both know when things are being freed.

But for things completely in a game, there is no reason to not know if something is freed - your the one freeing it.

You are returned a "handle", handles can be ANYTHING. This is true of any language. You have to deal with this, and its not a hard problem to solve. As it stands, you are allocated an INDEX (currently - and subject to change), and as such if you free something, that "slot" becomes free and will reallocate. This is the way ALL previous game makers worked, they are all array based so that look up is very fast. One day, we might change this to be pointers and so the "handle" will suddenly "may" suddenly be unique. But actually... memory is reused as well, so even the pointed MIGHT be the same as another old one. You will never get away from the fact that you should be clearing your variables anyway.

"Slot" allocation is used all over the place in games, as it keeps memory down, reuses assets and is fast to allocate and index. Unless we move to pointers, I don't see this changing just because you're not freeing your variables properly, and even if we DO swap to pointers, there's nothing saying we won't reuse old classes allocated internally so we don't have to reallocate things. Pooling of resources like this is also standard practice, but may still mean you get the same pointer back. Memory allocation is expensive, and constant freeing and reallocating fragments memory and causes memory usage to slowly climb, and can cause you to run out - just because your allocating and freeing badly.

All of this points to reuse as how you want to do things.
 

JaimitoEs

Member
Do you want to know what´s happen here? YYG is an old-school programming team. This team tend to stay clamped on 80s-90s year and it appear don´t want to evolute in things are not necessary . Regarding GMS2 credits, not to much people are working like engine programmers. YOYOGAMES, is time to change your view, destroy the "playgame saloon" and work hard with your company, giving more job offers to new system engineers. You have a good base of customers and you are adquiried by Playtech, winning a lot of sales with humble bundle, so spend more on R&D. @Mike is a legendary programmer, but if you are still clamped in oldschool, no matters if you have 30 years experience in programming, a new graduated engineer can think out the box and improve things.....

I´m afraid with the Roadmap of this product, looking the time-space between updates and how many things are changed, it appears you are working 2 hours per day in the engine and the rest of time.... i don´t know....

Surely i´m wrong with this post, i don´t want you think i´m trying to destroy the company, but my vision with the future of GameMaker is surely shared by a good amount of people.....

I bought Gms2, but i´m already looking alternatives engines and learning it.... Make something "easy to use" than the competence is not synonymous of "limited of features", and the "easy to use" is relative, cause other engines with scriptable editor and better resources managment are easier to use...

Well, now you can tell me, why you don´t use other engine instead of this? I really like GM, i like the community and i really like your work with export platforms, in this way you have done a really good job with the automatic processing...
 
Last edited:

csanyk

Member
This isn't a work around, it's proper practice. If you're in C++ and you free memory, you set the pointer to NULL so you know it's free. in GML you should set it to undefined so you know it's freed. You allocate and set a variable, you free something, you clear the variable. This is standard coding practice, and if you're not doing this, you're doing it wrong.

surfaces DO use unique handles but this is for a few reasons. First, you don't refer to them line after line after line like you do with data structures, and second they are disposed of without you knowing so you WILL query them and you need to know the handle is unique so that you've not gotten a different one.

As I said, this would benefit extensions as they can not always know, or be hooked into the allocation or freeing of resources. Normally, I'd have the extension do the free/delete and then have the game call that to allocate/free things. This means both know when things are being freed.

But for things completely in a game, there is no reason to not know if something is freed - your the one freeing it.

You are returned a "handle", handles can be ANYTHING. This is true of any language. You have to deal with this, and its not a hard problem to solve. As it stands, you are allocated an INDEX (currently - and subject to change), and as such if you free something, that "slot" becomes free and will reallocate. This is the way ALL previous game makers worked, they are all array based so that look up is very fast. One day, we might change this to be pointers and so the "handle" will suddenly "may" suddenly be unique. But actually... memory is reused as well, so even the pointed MIGHT be the same as another old one. You will never get away from the fact that you should be clearing your variables anyway.

"Slot" allocation is used all over the place in games, as it keeps memory down, reuses assets and is fast to allocate and index. Unless we move to pointers, I don't see this changing just because you're not freeing your variables properly, and even if we DO swap to pointers, there's nothing saying we won't reuse old classes allocated internally so we don't have to reallocate things. Pooling of resources like this is also standard practice, but may still mean you get the same pointer back. Memory allocation is expensive, and constant freeing and reallocating fragments memory and causes memory usage to slowly climb, and can cause you to run out - just because your allocating and freeing badly.

All of this points to reuse as how you want to do things.
I think it would be more acceptable if the handles weren't instantly re-allocated once freed.

Like, it's one thing if I create 2^32 data structures and get 0 reissued on creating the 2^32+1st. But to have it hand out 0 again immediately after it was just freed seems pretty dangerous.

Lastly, if clearing your variables after destroying the ds that they reference is standard and recommended practice, can we get an update to the docs to tell us to do this? As @Fel666 pointed out above, it's not mentioned anywhere. Until it is, you can't really blame users for not knowing.
 

GMWolf

aka fel666
If you're in C++ and you free memory, you set the pointer to NULL so you know it's free.
Now why do i use GM again?

There's also nothing to stop you doing linked lists as it stands. Just use the ID of either the instance or the data structure and you can do a 1D linked list, or use 2 variables and you can do a 2D one. I use them all the time.
So do i. infact i made a whole collection of data strutures, complete with itterators. But seriously: building those was a pain. And they are still pretty slow. because, yeah, shocker, GML has never been all that fast.

@YellowAfterlife gave me a bit of useful advice for handling the situation as it currently stands. Very clever way to clear multiple references to the same ds!

https://csanyk.com/2016/12/gamemaker-data-structures-cautionary-tale/
well, sure, we can wrap in an array... But them i really dont know why im using GM.

Seriously: I consider GML one of the hardest languages to use!
What exactly is the advantage of using GML if it has all the donwsides of C, C++, but none of the upsides? Not to mention extra downsides when it comes to performance, and features.
 

csanyk

Member
Seriously: I consider GML one of the hardest languages to use!
What exactly is the advantage of using GML if it has all the donwsides of C, C++, but none of the upsides? Not to mention extra downsides when it comes to performance, and features.
GML is easy to use if you stay within the lines. It's a domain specific language, used to control the GM engine, with its quirks and limitations. Think of it that way, and have a good understanding of the runtime engine, and it's pretty easy. If you want a general purpose language that allows you to get right close to the metal, then C/C++ is where you need to be.

It'd be a fun thought experiment to see GML be re-imagined as a set of libraries for C++, and let people who want to use it that way build on top of it.
 
Could you please post the relevant information here in the thread?
Sure. I know how much of a pain finding answers is if links go dead, so here:

YellowAfterlife's Twitter said:
A way to deal with this is by wrapping the "shareable" ID into a 1-element array, so that =-1 ing source purges all refs.
Code:
//original declaration:

a[0] = ds_stack_create();
//aliases
b = a[0];
c = a[0];

//destroy the data structure AND clear the variable
ds_stack_destroy(a[0]);
a = -1; // this destroys the array a[], and therefore kills b and c's references to it

d[0] = ds_stack_create();

ds_exists(a[0], ds_type_stack); //returns false
ds_exists(b, ds_type_stack); //returns false, despite not knowing to clear b
ds_exists(c, ds_type_stack); //returns false, despite not knowing to clear c
ds_exists(d[0], ds_type_stack); //returns true
 

GMWolf

aka fel666
GML is easy to use if you stay within the lines.
But apparently it isnt. If in free a data structure I need to set all references to that data structure to undefined. Hardly makes things easy, does it?

If you want a general purpose language that allows you to get right close to the metal, then C/C++ is where you need to be.
I don't want to be close to the metal. I like my java, C# and JavaScript. Nice and abstract.
I like GML too, for similar reasons. That's why I feel like having to deal with data structure id's as if they where addresses is quite anoying.

My point is: GML is supposed to be simple, is it not? If it isnt simple, then what does it do for us?

(When I say 'simple', I really mean 'easy to use'. In fact, simple languages are often harder to use.)
 

csanyk

Member
But apparently it isnt. If in free a data structure I need to set all references to that data structure to undefined. Hardly makes things easy, does it?


I don't want to be close to the metal. I like my java, C# and JavaScript. Nice and abstract.
I like GML too, for similar reasons. That's why I feel like having to deal with data structure id's as if they where addresses is quite anoying.

My point is: GML is supposed to be simple, is it not? If it isnt simple, then what does it do for us?

(When I say 'simple', I really mean 'easy to use'. In fact, simple languages are often harder to use.)
It's simpler to use to do simple things. Originally that's all it was really intended to do, really simple stuff with DnD. You can check a collision without knowing anything at all about masks, matrix maths, or trigonometry. That's where GM is easy. It's also a completely self-contained IDE with its own sprite and image editors, not just a code editor. You don't need to know how to architect your project, a basic project structure is imposed, and you can do simple games without understanding filesystems, or inheritance, or how to read external binary resources like images and sounds, or how to output them to the display or audio device.... The engine does all that.

Over time, GM has grown into what it is today. Not surprisingly, this growth wasn't planned entirely perfectly, and so defects that were present from the early days have been kept along for backward compatibility reasons. I won't defend GM as being perfect, because it isn't. I want to see it improved (while preserving or improving ease of use) as much as anyone else. But it does do quite a bit for us. We maybe don't see all that, or don't use all that, or don't appreciate all that. But if you wanted to write your own engine from the ground up that does everything GMS does, you'd see it is quite a lot of work to get that much done.
 

GMWolf

aka fel666
It's simpler to use to do simple things. Originally that's all it was really intended to do, really simple stuff with DnD. You can check a collision without knowing anything at all about masks, matrix maths, or trigonometry. That's where GM is easy. It's also a completely self-contained IDE with its own sprite and image editors, not just a code editor. You don't need to know how to architect your project, a basic project structure is imposed, and you can do simple games without understanding filesystems, or inheritance, or how to read external binary resources like images and sounds, or how to output them to the display or audio device.... The engine does all that.

Over time, GM has grown into what it is today. Not surprisingly, this growth wasn't planned entirely perfectly, and so defects that were present from the early days have been kept along for backward compatibility reasons. I won't defend GM as being perfect, because it isn't. I want to see it improved (while preserving or improving ease of use) as much as anyone else. But it does do quite a bit for us. We maybe don't see all that, or don't use all that, or don't appreciate all that. But if you wanted to write your own engine from the ground up that does everything GMS does, you'd see it is quite a lot of work to get that much done.
Yeah, those are all engine, IDE and library features.
I'm really talking about the language features themselves....

I never said there was an issue with the engine. The engine is the reason I stick with gm for many projects. But the language? GML? I really don't know...
 

csanyk

Member
Yeah, those are all engine, IDE and library features.
I'm really talking about the language features themselves....

I never said there was an issue with the engine. The engine is the reason I stick with gm for many projects. But the language? GML? I really don't know...
Fair. But I guess my point is that you can't easily separate the language from the rest. They go together. You can't really use GML outside of GMS projects, and you can't use GMS to do projects in other languages, or with a different runtime.

Looking at the syntax of GML, yeah, sure there's a lot of stuff that I think is iffy. Not having hierarchic namespaces, so you have to do stuff like sprite_draw() rather than sprite.draw(), or lacking overrides, so you have to do sprite_draw, sprite_draw_ext() rather than just have a two sprite_draw() functions with different signatures, for example. Or having to receive script arguments as an array named argument[0..15]. It's yucky. Having the function instance_number() rather than instance_count()... I mean there's stuff like that all over the place.

This bit with data structures reusing their handles is a bit gross, but it's pretty rare. I've been using GM since 8.1, and only just ran into this today. It surprised me, and turned my stomach a bit, but with @YellowAfterlife and @Mike's advice, I can cope with it, so it's not a disaster. It may not be elegant, but it's workable.

I mean, just look at the thousands of great games that have been made with GM over the years, despite its limitations and flaws. It's a viable tool. Could it be better? Sure. Is it getting better? Definitely! Is everything ever going to be perfect? I doubt it. But I like what I can do with it. I won't let that stop me from relentlessly asking for it to be as good as it can be. But I'm not quite as prepared to abandon GMS for C++.
 

GMWolf

aka fel666
I'm close to abandoning it, but not there yet.

But that is exactly the point: I'm pushing for GML to get better! I was hoping for major changes in GMS2. Unfortunately it seems YYG preferred backwards compatibility over a better product. Fair enough.
But reusing id's. That could be changed, right?
 

csanyk

Member
I'm close to abandoning it, but not there yet.

But that is exactly the point: I'm pushing for GML to get better! I was hoping for major changes in GMS2. Unfortunately it seems YYG preferred backwards compatibility over a better product. Fair enough.
But reusing id's. That could be changed, right?
There's certainly nothing wrong with using other tools. Lots of people "graduate" from GM to becoming "real programmers" using languages like Java, C, C++, C#, Python, whatever. Nothing wrong with that, and it's to GM's credit that it has that power to move new programmers up the challenge curve to where they can pick up other languages.

GMS has also come a long way over the years. But GML is not a general purpose language and I don't expect it ever will be, and there really isn't a point in it becoming something else that already exists anyway. (An example of this, they didn't re-implement GLSL by wrapping a bunch of GML-style function names around it; they wisely added shaders to GMS by giving us access to GLSL.)

Even if/when you do move on to more powerful languages, you might find that GMS still holds value as a rapid prototyper for doing quick proof of concepts of game ideas that would be burdensome to implement from scratch in a general-purpose language.

Meanwhile, YYG are working at making GMS a more viable tool for serious developers. Considering everything they have to do, I think they're largely focused on the right priorities, and doing the best they can (for the most part, I mean I still reserve the right to challenge them on decisions, to request things, to report bugs, etc.)

Whether they're the best in the business at what they do, better than whatever competition you want to compare against, I'm not able to say. But I will say that in 6 years of using their product, I've learned a lot about programming, and produced a few little games that I probably couldn't have done with other tools, with the amount of skill/knowledge that I had at the time.

I will always want GMS to improve; at some point though you have to just accept what it is and do with it what you can.
 

GMWolf

aka fel666
I will always want GMS to improve; at some point though you have to just accept what it is and do with it what you can.
That is paradoxical. How can you both always push for its improvement, but also accept how it currently is?
 

csanyk

Member
Nonsense. It's not paradoxical, it's necessary! If I didn't use it, I wouldn't care if it improved!

It's usable, not perfect. I want it to be improved, so I can do more/easier.
 

csanyk

Member
Well, hang on. I finally got home and tried this out, and found out it doesn't work.
Code:
//original declaration:

a[0] = ds_stack_create();
//aliases
b = a[0];
c = a[0];

//destroy the data structure AND clear the variable
ds_stack_destroy(a[0]);
a = -1; // this destroys the array a[], and therefore kills b and c's references to it

d[0] = ds_stack_create();

ds_exists(a[0], ds_type_stack); //returns false
ds_exists(b, ds_type_stack); //[s]returns false, despite not knowing to clear b[/s] still references the destroyed stack's id, which now belongs to d[0] and will return true.
ds_exists(c, ds_type_stack); //[s]returns false, despite not knowing to clear c[s] still references the destroyed stack's id, which now belongs to d[0] and will return true.
ds_exists(d[0], ds_type_stack); //returns true
Let's examine. If I do this:
Code:
a[0] = 10;
b = a[0];
a = -1;
a = -1, which clears the array.
a[0] is undefined, and will cause the game to throw an error if you try to reference it (which is a problem).
b is still == 10. Which means that if a[0] had been set to the handle for a ds_* then b would still be pointing to it.

I must not have understood what @YellowAfterlife was telling me, and goofed up my implementation. Either that, or he gave off-the-cuff advice without testing it first. But he's very good with GML so it would surprise me if that's so. I think it's more likely that I misunderstood his hint.

I hope he (or someone else who can figure out where I'm going wrong) reads this and helps me out.
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
Let's examine. If I do this:
Code:
a[0] = 10;b = a[0];a = -1;
a = -1, which clears the array.
a[0] is undefined, and will cause the game to throw an error if you try to reference it (which is a problem).
b is still == 10. Which means that if a[0] had been set to the handle for a ds_* then b would still be pointing to it.
`a[@0] = -1`, not `a[0] = -1`, keep in mind the copy-on-write behaviour that GameMaker uses for arrays.
 
F

fxokz

Guest
Not to mention, JuJus workaround is just that. A workaround. Exactly what we have been blaming YYG of since... Well, since before in started using GM!
They make poor decisions, then suggest some half baked workaround.

I'm pretty close to being done with GM at this rate... Even toying around with it becomes hard when there are explicit features, like reusing id's, that feel like they where put in place just to f**k with you!
Do you want to know what´s happen here? YYG is an old-school programming team. This team tend to stay clamped on 80s-90s year and it appear don´t want to evolute in things are not necessary . Regarding GMS2 credits, not to much people are working like engine programmers. YOYOGAMES, is time to change your view, destroy the "playgame saloon" and work hard with your company, giving more job offers to new system engineers. You have a good base of customers and you are adquiried by Playtech, winning a lot of sales with humble bundle, so spend more on R&D. @Mike is a legendary programmer, but if you are still clamped in oldschool, no matters if you have 30 years experience in programming, a new graduated engineer can think out the box and improve things.....

I´m afraid with the Roadmap of this product, looking the time-space between updates and how many things are changed, it appears you are working 2 hours per day in the engine and the rest of time.... i don´t know....

Surely i´m wrong with this post, i don´t want you think i´m trying to destroy the company, but my vision with the future of GameMaker is surely shared by a good amount of people.....

I bought Gms2, but i´m already looking alternatives engines and learning it.... Make something "easy to use" than the competence is not synonymous of "limited of features", and the "easy to use" is relative, cause other engines with scriptable editor and better resources managment are easier to use...

Well, now you can tell me, why you don´t use other engine instead of this? I really like GM, i like the community and i really like your work with export platforms, in this way you have done a really good job with the automatic processing...
I'm close to abandoning it, but not there yet.

But that is exactly the point: I'm pushing for GML to get better! I was hoping for major changes in GMS2. Unfortunately it seems YYG preferred backwards compatibility over a better product. Fair enough.
But reusing id's. That could be changed, right?
I just read everything and I have 1 question. Is gamemaker really worth abandoning over small data structure glitches? Are they really that noticable?
 

GMWolf

aka fel666
I just read everything and I have 1 question. Is gamemaker really worth abandoning over small data structure glitches? Are they really that noticable?
Haha no. As i said. I'm close, but not there yet.
But its not just the data structure 'glitches', GML is full of wierness I don't quite agree with.

But I do like GameMaker. And probably will keep using it.
 

csanyk

Member
`a[@0] = -1`, not `a[0] = -1`, keep in mind the copy-on-write behaviour that GameMaker uses for arrays.
Hmm, OK I think I was doing this the wrong way by "clearing" the array by assigning a = -1, not a[0] = -1 OR a[@ 0] = -1.

If I use a[@ 0] = -1, then a[] still exists, so references to the a[0] index will still be valid, and won't throw errors. That solves that part of the problem.

I can also see that I can use [@ ] acessorss to modify an array by reference within a script, so that will be useful. I'm planning on writing a set of ds_safety_* scripts so I can do this easily without having to think about it ever again, and this will be helpful for that.

But how I do the aliases? If I do b = a[@ 0] does that mean b will always point to the reference to a[0] and be updated whenever a[0] changes? It seems not. If I do this:

a[0] = 10;
b = a[@ 0];
a[@ 0] = -1;
draw_string(x, y, a[0] + " " + b);

I get "-1 10" which indicates that b is not an alias to a[0], and is getting assigned a copy of the value stored at a[0] rather than pointing directly at it, so when a[0] changes, even with a[@ 0] b still points to an outdated value...
 
I

icuurd12b42

Guest
I just read everything and I have 1 question. Is gamemaker really worth abandoning over small data structure glitches? Are they really that noticable?
Yeah, I been at the point Fell666 is talking about.

GameMaker can be a real mind f^%k combined with your undetected mistakes and other limitation, the ds_exists is an example of such a function that can ruin your day, especially if you ever had to open a json file of unknown data...

@csanyk

if you have an array in an array you should clear the entry that has the array

arr1 = [1,2,3,4,5];
arr2 = [1,2,3,4,5];
arr2 = [arr1,arr2];

arr2[@0] = 0;
arr2[@1] = 0;
arr2 = 0;

if you have an array in a ds_ then you should set that entry in the ds to 0 before you destroy it

ds_list[|0] = arr1;
ds_list[|1] = arr2;
...
ds_list[|0] = 0;
ds_list[|1] = 0;

Now you only need to do this if the garbage collector is not doing it's job fast enough and you find your game consuming memory to the point where it gets critical.
 
Last edited by a moderator:

csanyk

Member
@csanyk

if you have an array in an array you should clear the entry that has the array

arr1 = [1,2,3,4,5];
arr2 = [1,2,3,4,5];
arr2 = [arr1,arr2];

arr2[@0] = 0;
arr2[@1] = 0;
arr2 = 0;

if you have an array in a ds_ then you should set that entry in the ds to 0 before you destroy it

ds_list[|0] = arr1;
ds_list[|1] = arr2;
...
ds_list[|0] = 0;
ds_list[|1] = 0;

Now you only need to do this if the garbage collector is not doing it's job fast enough and you find your game consuming memory to the point where it gets critical.
That's not what I'm trying to do. I'm trying to work around the problem that JuJu suggested a solution to: when destroying a data structure, set the variable that pointed to it to undefined immediately after, so it cannot reference a newly-created data structure by mistake.

Juju's suggstion was:

Code:
ds_list_destroy(a);
a = noone;
...which helps because now a no longer points at a potential id for a data structure...

@Mike suggested setting a = undefined; which I don't think was a reserved word in GMS1.x, but is in 2.x, and would be more correct than noone, which is supposed to be for instances.

At any rate, we still have a problem if we copy the reference to the data structure to other variables, and lose track of them, like so:

Code:
a = ds_stack_create();
b = a; //oops i just forgot about b
ds_stack_destroy(a);
c = ds_stack_create();
ds_exists(b, ds_type_stack); // returns true since c took the handle that a just gave up
@YellowAfterlife suggested storing the id to the ds_stack in an array somehow, so that the array could be cleared, and as a result, any variables that we forgot about that copied the reference to the ds_structure that we failed to clean up will be updated automatically, somehow, because b would be a reference to a, not a copy of it.

I tried to implement it but it didn't work, and I can't figure it out because I don't fully understand @YellowAfterlife's suggestion yet.

I am still trying to get that to work.

Ideally, I would like to create a script like this:

Code:
///ds_safety_stack_destroy:
ds_destroy(argument0[@ 0]);
argument0[@ 0]  = -1;
But even then I still don't understand how that will prevent b from holding the value of the destroyed stack's id, since as far as I can tell I still have copied a[0] into b, rather than made b reference a[0].

I'm not sure it can be done in GML, and I'm sure I still don't understand what @YellowAfterlife was getting at yet.
 
I

icuurd12b42

Guest
I miss that part.

if you wrap the ds in an array
///my_ds_list_create()
var arr;
arr[0] = ds_list_create();

and you have a destroy
///my_ds_list_destroy( arr as container)
var arr = argument0;
ds_list_destroy(arr[0])
arr[@0] = -1;

and you have a get_ds
///my_ds_arr_to_ds(arr as container)
var arr = argument0;
return arr[0];

and every user of a ds list uses the arr as container and never hold the ds id (arr[0] in the array) for more than the duration they need it, though the arr container may last a long time, for as long as it exists and it's list was destroyed, the returning value for my my_ds_arr_to_ds will be -1, an invalid list

SO there is less chance of a ds created and assigned the value of an old ds be used by some code that did not know the ds is no longer the right one.

for example
ds1 = ds_list_create();
ds_list_add(ds1, 1,2,3,4,5,6);
//ds1 is id 0;
an_instance_id.ds = ds1;
an instance is using that first list...
then somewhere
ds_list_destroy(ds1);
...later
ds2 = ds_list_create();
ds_list_add(ds2, 10,11,12,13);
ds2 has an index of 0 same as the defunct ds1
an_instance is still referencing that 0 as it's .ds... but it's the wrong list. and THAT happens all the time

now if you use the wrapper

ds1 = my_ds_list_create();
ds_list_add(my_ds_arr_to_ds(ds1), 1,2,3,4,5,6);
//ds1 is an arr;
an_instance_id.ds = ds1;
an instance is using that array...
then somewhere
my_ds_list_destroy(ds1); //the arr[0] is destroyed and set to -1, the array remains until the garbage collector decides it's gone
...later
ds2 = my_ds_list_create();
ds_list_add(my_ds_arr_to_ds(ds2), 10,11,12,13);
ds2 is it's own array, though arr[0]'s list id is the same as it was for ds1 when it's list was valid
an_instance is still referencing that arr as a ds container but it arr[0] is -1 so the ds_list is invalid

SO what happens when you try to do ds_list_add() with arr[0] which is -1? you get an error. and that is what is desired.
 
Last edited by a moderator:

csanyk

Member
Hmm.... I think maybe I'll try to solve this problem if/when I run into it as a practical obstacle to a real problem. So far, my projects have been simple enough that I haven't had to deal with recycled data structure id's. But that said, I'm getting more into them lately, and it's likely only a matter of time... but I'm still too new with them to feel comfortable wrapping my mind around all the nesting, and it just seems like it'll over-complicate things to where I'll find it too difficult to troubleshoot.

My initial thought was that if it was fairly simple to wrap a ds_ inside a structure that could be accessed by reference, then when the reference to the ds_ was set to a value that could never be the id of a ds, it would be possible to guarantee that any additional variables that pointed to that reference would return false when run through ds_exists(). I don't want the project to throw an error (I'm good enough at making that happen already!) I want ds_exists() to return false when a ds gets destroyed, (somehow) working around the engine's re-use of handles.
 
I

icuurd12b42

Guest
You could add your my_ds_exists()
var arr = argument0;
return (arr[0] !=-1);

believe me you want to get an error more than having some code manipulate a ds list that's no longer holding the content the code was written for
 

csanyk

Member
You could add your my_ds_exists()
var arr = argument0;
return (arr[0] !=-1);

believe me you want to get an error more than having some code manipulate a ds list that's no longer holding the content the code was written for
I agree, I would want the game to throw an error if it tried to reference a non-existent data structure.

That's not really the problem I was trying to solve though. I was trying to get around the problem with ds_exists, whereby it will return true when the handle that you were asking it about has been re-used by some new data structure that isn't the one your object has in mind when it checks to see if the data structure exists.

To be honest, though, I'm having a hard time envisioning a situation when I'd try to access a data structure, not knowing whether it exists or not. Maybe it's my inexperience with data structures, or maybe I'm just a very careful coder, but when I've used data structures, up until now, I always know that the data structure's going to exist. I have never written code where I thought, "Well, maybe it'll exist at this point, maybe not, but what the heck, I'm going to try to get a value out of it."

So I'm not sure how much of a problem this is, really. @Mike seems pretty assured that the engine's reuse ds handles isn't a problem, and maybe he's right. But in that case, it makes me wonder why they even have a ds_exists function at all.

But I say that realizing that there's a universe of possibility with data structures that I've yet to comprehend... a lot of this, for me, is about building my understaning of GML and programming so I have the mental tools at my disposal when it comes time to use them.

I haven't actually found it necessary to use ds_exists(), other than for debugging (this whole thread started because I was having trouble accessing a ds_stack I'd put inside a ds_map, after returning it from a script, and I wanted to verify that the stack and the map were coming through the return intact.) Once I got it worked out, my eventual solution had no reason to check whether either structure existed -- I knew when it existed, and when it didn't, at any point in the code, so I never needed to check.
 
I

icuurd12b42

Guest
> I was trying to get around the problem with ds_exists, whereby it will return true when the handle that you were asking it about has been re-used by some new data structure that isn't the one your object has in mind when it checks to see if the data structure exists.

That's exactly what the code I posted does...

Mike has not worked enough with ds to have stumbled on the issue. it's every ds problem I ever had, and yes the bug is mine, stray ds_delete here... and create there (or a json_decode) and the list I use is no longer the right one. When they introduced ds_exist, I was one of the first to point its shortcomings, ds_exist only works if no other ds were created after one was destroyed, in fact as he stated setting your ds variable back to undefined... or -1 when undefined did not exist, is a common method; setting a variable to an invalid value, it only work within the scope where everyone uses that variable, local or global... While relying on ds_exists is actually much more dangerous...

I see plenty of assets that store stuff in ds and call ds_exists in their script to make sure the argument passed, a ds, is valid... it's basically a pointless check. sure it exist... but is it mine still?

related is us begging for ds_list_is_list/map and ds_map_is_list/map for entries in data structures that contain sub ds... for json.
 

csanyk

Member
> I was trying to get around the problem with ds_exists, whereby it will return true when the handle that you were asking it about has been re-used by some new data structure that isn't the one your object has in mind when it checks to see if the data structure exists.

That's exactly what the code I posted does...
Well thanks for confirming that, and for providing it in the first place; I just meant that, for me to use that code, it's a bit much for me to wrap my head around, so I think I'll stick to simpler stuff until I feel like I really need to use it. Not that you weren't offering a correct solution. Without compiling and running it, I didn't know if it worked or not, or if it solved the problem I was thinking I needed to solve, or if you were a few steps ahead of me and were solving the right problem which was something slightly different form how I was thinking... and then I just realized that I don't need to really be worried about it right now. Lol.

Sometimes I go down a long path just to try to figure something out, not because I need it, but because I want to know, and this is probably one of those times.

Mike has not worked enough with ds to have stumbled on the issue. it's every ds problem I ever had, and yes the bug is mine, stray ds_delete here... and create there (or a json_decode) and the list I use is no longer the right one. When they introduced ds_exist, I was one of the first to point its shortcomings, ds_exist only works if no other ds were created after one was destroyed, in fact as he stated setting your ds variable back to undefined... or -1 when undefined did not exist, is a common method; setting a variable to an invalid value, it only work within the scope where everyone uses that variable, local or global... While relying on ds_exists is actually much more dangerous...

I see plenty of assets that store stuff in ds and call ds_exists in their script to make sure the argument passed, a ds, is valid... it's basically a pointless check. sure it exist... but is it mine still.
Yeah, I mean, I definitely would still feel better if YYG did something about that. GUIDs that stop returning "exists" when the object the GUID identifies no longer exist just seems like the correct way to implement. I know @Mike is claiming there's a compelling speed advantage to re-using the first available id, but I'd really like to see that proven.
 
Top