GMS 2.3+ ds_map_add_map behaving strangely

Niften

Member
Hi,

I'm writing a level editor, and I've been using json files with ds_maps/lists without any issues. Today I came across an issue I haven't encountered before - I am creating a ds_map and adding it to another map using the ds_map_add_map function (which I have done many times throughout this project). After using this function, the map is not added and nothing changes.

A few notes:
  1. I am aware of the new json_stringify functionality with structs and arrays, and will not be using it right now.
  2. I am able to modify the key "goto" normally otherwise.
  3. I am using json_encode and debug mode to look at the map.
  4. The value of the key "goto" is -1 prior to any changes.
  5. ds_map_add_map(dialogueEditMap, "goto", ds_map_create()); does not work either

GML:
var newGotoMap = ds_map_create();
log(json_minify(json_encode(newGotoMap)));
ds_map_add_map(dialogueEditMap, "goto", newGotoMap);
log(json_minify(json_encode(dialogueEditMap)));
Here is the output from the logs, showing that there is no change in the larger dialogueEditMap:
Code:
[09:31:53][LOG] {}
[09:31:53][LOG] {"spr":-1,"author":"Admin","goto":-1,"statement":"Welcome! We hope you have fun here.","choices":[]}
The new ds_map should be showing up under the "goto" key.

Any help would be appreciated.
 
Last edited:

Niften

Member
Does the same thing happen if you put some information in the newGotoMap first?
Yes. newGotoMap usually has information - I just changed it to an empty map to see if the problem was my makeMap function which creates newGotoMap, but it yields the same results as ds_map_create. Thanks for the reply!
 
Last edited:

samspade

Member
what do the log and json_minify functions look like? Also, I would step through this in the debugger so that you can see what GM thinks happened on the line in question. First, did it get added at all. Second, did it get added as a map. You'll know if it got added if there is data of course, and you'll know it got added as a map if it is expandable. Otherwise, it would just be a number.

That said, based on your log function, it looks like it is getting added, but getting added as -1. -1 is not a valid map index in GM (unless they've really changed some things somewhere) which means that somewhere between creating the map (also, looking at that in the debugger would be helpful to make sure it gets created as a map) and the ds_map_add_map function it gets corrupted. Seems likely it is somewhere in either the log or minify functions.

Re-read your OP. This is one time where it might actually be helpful to see a screenshot of what the debugger shows for both maps before and after the ds_map_add_map function call.
 

Niften

Member
what do the log and json_minify functions look like? Also, I would step through this in the debugger so that you can see what GM thinks happened on the line in question. First, did it get added at all. Second, did it get added as a map. You'll know if it got added if there is data of course, and you'll know it got added as a map if it is expandable. Otherwise, it would just be a number.

That said, based on your log function, it looks like it is getting added, but getting added as -1. -1 is not a valid map index in GM (unless they've really changed some things somewhere) which means that somewhere between creating the map (also, looking at that in the debugger would be helpful to make sure it gets created as a map) and the ds_map_add_map function it gets corrupted. Seems likely it is somewhere in either the log or minify functions.
Log is a macro for show_debug_message. json_minify is a script that truncates trailing zeros in json strings. It does not get added at all, as -1 is the value that the map holds before any changes. Taking out both the log and minify functions yield the same result in the debugger.

However, we know for sure that the map is created because we can see its contents in the log. Another test can show the difference in the map before and after the ds_map_add_map function is used (for this test I will omit log and json_minify):
GML:
show_debug_message((json_encode(dialogueEditMap)));
ds_map_add_map(dialogueEditMap, "goto", ds_map_create());
show_debug_message("Created goto");
show_debug_message((json_encode(dialogueEditMap)));
The log output confirms there is no change.
Code:
{ "spr": -1.0, "author": "Admin", "goto": -1.0, "statement": "Welcome to the game Nesus! We hope you have fun here.", "choices": [ ] }
Created goto
{ "spr": -1.0, "author": "Admin", "goto": -1.0, "statement": "Welcome to the game Nesus! We hope you have fun here.", "choices": [ ] }
 
Last edited:

samspade

Member
Just to clarify the following code:

GML:
ds_map_add_map(dialogueEditMap, "goto", ds_map_create());
if you inspected it in the debugger would still show "goto" as -1?

What happens if you delete the goto key before adding it as a map or added a new key?

For example:

GML:
show_debug_message((json_encode(dialogueEditMap)));
ds_map_delete(dialogueEditMap, "goto");
show_debug_message((json_encode(dialogueEditMap)));
ds_map_add_map(dialogueEditMap, "goto", ds_map_create());
ds_map_add_map(dialogueEditMap, "test", ds_map_create());
show_debug_message((json_encode(dialogueEditMap)));
 
Last edited:

Niften

Member
Just to clarify the following code:

GML:
ds_map_add_map(dialogueEditMap, "goto", ds_map_create());
if you inspected it in the debugger would still show "goto" as -1?

What happens if you delete the goto key before adding it as a map?
Yes. The same result is observed in the debugger. Deleting the goto key before adding it as a map actually works - the empty map is added. Thank you for your patience - final question: is this expected behavior? Map keys can't have a value in them before adding a map to them?

Thanks a lot.
 

samspade

Member
I'm not sure. I used the accessor when working with maps that weren't JSON related and that sets or replaces depending upon whether it previously exists. I've used json data moderately extensively but maybe I never tried this exact thing before? Assuming this is expected behavior, which it might be (there is after all for regular map functions both add and replace), the work arounds would be to either not add the key before you need it. or to add it originally as a blank map and then just modify the map, or to delete it before adding it. The delete version isn't that bad of an option, as you could just wrap it in a script - e.g. write ds_map_replace_map which first deletes the value then adds it with ds_map_add_map.

(As a side note, with structs you wouldn't have this issue as you would just use the accessor and there's no need to mark structs as anything since they are already a data type, in case you're looking for a reason to switch.)
 

Niften

Member
I'm not sure. I used the accessor when working with maps that weren't JSON related and that sets or replaces depending upon whether it previously exists. I've used json data moderately extensively but maybe I never tried this exact thing before? Assuming this is expected behavior, which it might be (there is after all for regular map functions both add and replace), the work arounds would be to either not add the key before you need it. or to add it originally as a blank map and then just modify the map, or to delete it before adding it. The delete version isn't that bad of an option, as you could just wrap it in a script - e.g. write ds_map_replace_map which first deletes the value then adds it with ds_map_add_map.

(As a side note, with structs you wouldn't have this issue as you would just use the accessor and there's no need to mark structs as anything since they are already a data type, in case you're looking for a reason to switch.)
I was blown away by struct functionality, but I started my editor prior to 2.3 so I am trying to keep my project consistent in using the ds_map/list functions, at least where saving is concerned.

Actually, there is now a ds_map_replace_map function. Maybe this was always there, but I think it was added in 2.3.1. So it seems like the answer is it is intended now and there's a different function to use. If you're using 2.3.1 and haven't reviewed the new map functions, there were a couple added: https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Data_Structures/DS_Maps/DS_Maps.htm.
I think this function replaces an existing map in that key - so, instead of setting "goto" to -1 I could make goto an empty map, and then use this function to replace that empty map.

Again, thanks for your help!
 

chamaeleon

Member
@Niften @samspade
Just a heads-up that it appears to me that any attempt to replace an existing ds_map stored in a ds_map appears to leak memory.
GML:
var foo = ds_map_create();
foo[? "spr"] = -1;
foo[? "author"] = "Admin";
ds_map_add_map(foo, "goto", ds_map_create());
show_debug_message(json_encode(foo));
for (var i = 0; i < 100000; i++) {
    ds_map_replace_map(foo, "goto", ds_map_create());
}
foo[? "goto"][? "dummy"] = 1;
show_debug_message(json_encode(foo));
Doubling the 100000 leads to twice the amount of memory being used for me. I have tried using ds_map_delete() on the entry before as well.

Given that the purpose of using ds_map_add_map and ds_map_add_list is to mark this internally in the data structure (and it's used when destroying the outermost map to destroy contained maps, without leaking, tested by recreating foo and all its content inside the loop after destroying it), it seems to me that this flagging should also be used to free a map before storing a new value. I'd consider the current behavior a bug.
 
Top