GMS 2.3+ How to add local ds map with dynamic key to ds_map?

I am working on a multiplayer game. Therefore, I want to keep track of the units each player has. Thats why I create dynamic keys for each map.
I want that my ds_map looks like this at the end

GML:
"player1":{
    "unit1":{
        "name": "Infantry",
        "hitpoints": 1
    },
    "unit2":{
        "name":"Infantry",
        "hitpoints":1
    },
},

"player2":{
    "unit1":{
        "name":"Tank",
        "hitpoints":2
    },
    "unit23":{
        "name":"Infantry",
        "hitpoints":1
    }
}
I start with a ds_map which holds the general information of each possible unit.
GML:
function getDataUnits(){
    return json_decode(@'
    {
       "infantry" : {
            "name"            : "Infantry",
            "hitpoints"        : 1
        },

        "tank" : {
            "name"          : "Tank",
            "hitpoints"        : 2
        }
    }');
}

dsm_data = getDataUnits();
Lets say player 1 has one infantry unit and one cavalry unit. I send this information to the server and the server saves it in the following way:
(
GML:
//server side
dsm_players = ds_map_create();

//from the networking stuff
case network.sendUnits:
var unit = "unit_";
var dsm_client = ds_map_create();
var index = buffer_read(buffer, buffer_u8); //0
var unitkey = unit+string(index); //"unit_0"
var unitType = buffer_read(buffer, buffer_string); // e.g. "infantry"
ds_map_add(dsm_client, unitkey, dsm_data[? unitType]);
ds_map_add(dsm_players, socket, dsm_client);

var unit = "unit_";
var dsm_client = ds_map_create();
var index = buffer_read(buffer, buffer_u8); //1
var unitkey = unit+string(index); //"unit_1"
var unitType = buffer_read(buffer, buffer_string); // e.g. "tank"
ds_map_add(dsm_client, unitkey, dsm_data[? unitType]);
ds_map_add(dsm_players, socket, dsm_client);
break;
however, the second unit ("unit_1") does not get added to the ds_map. What did I do wrong?

Furthermore, when I delete dsm_client it also gets deleted out of the dsm_players map. I used to know how to prevent this but I dont have time for it right now.
 

gnysek

Member
Remember, that maps aren't referenced, but you're only getting their id as integer number from 1 to N (deleted maps aren't freeing unused numbers, so this works like counter). So in your case:

GML:
//server side
dsm_players = ds_map_create(); // number of alrady created/deleted ds_maps + 1

...
var dsm_client = ds_map_create(); // number of alrady created/deleted ds_maps + 1

...
var dsm_client = ds_map_create(); // number of alrady created/deleted ds_maps + 1
So, if that's the only code in your game about ds_maps, and it get's called for first time, unit_1 ds_map id will be 3, and I'm sure that show_debug_message(dsm_players[? socket]); returns 3 :)
I know that 3 might mean "3", some object, sprite, script, ds_map, ds_list, ds_something, room, or whatever, so it's up to you to manage your code in a way, that you know this is a ds_map reference (you can use ds_map_exists() too).
 

Nidoking

Member
Lets say player 1 has one infantry unit and one cavalry unit. I send this information to the server and the server saves it in the following way:
(
GML:
//server side
dsm_players = ds_map_create();

//from the networking stuff
case network.sendUnits:
var unit = "unit_";
var dsm_client = ds_map_create();
var index = buffer_read(buffer, buffer_u8); //0
var unitkey = unit+string(index); //"unit_0"
var unitType = buffer_read(buffer, buffer_string); // e.g. "infantry"
ds_map_add(dsm_client, unitkey, dsm_data[? unitType]);
ds_map_add(dsm_players, socket, dsm_client);

var unit = "unit_";
var dsm_client = ds_map_create();
var index = buffer_read(buffer, buffer_u8); //1
var unitkey = unit+string(index); //"unit_1"
var unitType = buffer_read(buffer, buffer_string); // e.g. "tank"
ds_map_add(dsm_client, unitkey, dsm_data[? unitType]);
ds_map_add(dsm_players, socket, dsm_client);
break;
however, the second unit ("unit_1") does not get added to the ds_map. What did I do wrong?
Is this all just a single block, with nothing in the middle? You're setting the same entry in the dsm_players map twice - they both have the same "socket" key value. Since you're using ds_map_add, the second attempt will fail. You don't appear to understand how variable scopes work - you've declared all of these local variables twice, which doesn't make a lot of sense.

Furthermore, when I delete dsm_client it also gets deleted out of the dsm_players map. I used to know how to prevent this but I dont have time for it right now.
You're deleting it. It's deleted. It won't exist anymore if you've deleted it, regardless of where it's stored. If you want to store a copy of it, you'll have to make a copy.

So, if that's the only code in your game about ds_maps, and it get's called for first time, unit_1 ds_map id will be 3, and I'm sure that show_debug_message(dsm_players[? socket]); returns 3
It will return the value stored at the socket key, not the ID of the map.
 

gnysek

Member
It will return the value stored at the socket key, not the ID of the map.
GML:
var dsm_client = ds_map_create();
ds_map_add(dsm_players, socket, dsm_client);
In above code that for sure will be dsm_client variable value, which in this case is equal to ds_map id created few lines earlier ;)
 
Is this all just a single block, with nothing in the middle? You're setting the same entry in the dsm_players map twice - they both have the same "socket" key value. Since you're using ds_map_add, the second attempt will fail. You don't appear to understand how variable scopes work - you've declared all of these local variables twice, which doesn't make a lot of sense.
sorry for beeing unclear there. actually it only gets executed when I send the buffer from the client and it looks like this:
GML:
//this get executed on the server side in Async - Networking
msgid= buffer_read(buffer, buffer_u8);
switch(msgid){
    ...
    case network.sendUnits:
        var unit = "unit_";
        var dsm_client = ds_map_create();
        var index = buffer_read(buffer, buffer_u8);
        var unitkey = unit+string(index);
        var unitType = buffer_read(buffer, buffer_string);
        ds_map_add(dsm_client, unitkey, dsm_data[? unitType]);
        ds_map_add(dsm_players, socket, dsm_client);
    break;
    ...
}
and on the client side this is the code which I use for testing. It is only 1 client connected so there should be only one socket
GML:
function scr_sendUnit(_name, _i){
    buffer_seek(oClient.client_buffer, buffer_seek_start, 0);
    buffer_write(oClient.client_buffer, buffer_u8, network.sendUnits);
    buffer_write(oClient.client_buffer, buffer_u8, _i);
    buffer_write(oClient.client_buffer, buffer_string, _name);
    network_send_packet(oClient.client,oClient.client_buffer,buffer_tell(oClient.client_buffer));
}
GML:
//client side
var inst = "tank";
scr_sendUnit(inst, 0);
var inst = "infantry"
scr_sendUnit(inst, 1);
I want that after executing the code above the ds_map dsm_players should look like this(on the server side):
GML:
"player1":{
    "unit_0":{
        "name": "Infantry",
        "hitpoints": 1
    },
    "unit_1":{
        "name":"Infantry",
        "hitpoints":1
    },
}
however it looks like this:
GML:
"player1":{
    "unit_0":{
        "name": "Infantry",
        "hitpoints": 1
    }
}
 

chamaeleon

Member
You are probably not using all the data received in one buffer. It seems likely you assume only one unit is present for each network async event invocation.
 
You are probably not using all the data received in one buffer. It seems likely you assume only one unit is present for each network async event invocation.
no, thats not the issue here. all the data is in the buffer. I checked in debug mode and the correct data is received but not added. the issue I have is that I can not figure out how to add another ds_map into a nested ds_map.

I've already answered your question.
Well, this is not helping much, though. But I want to add another ds_map to the same "socket" that is the whole point. so what would be the solution here? geting all the ds_maps which are stored under key "socket" save them in another ds_map add the new ds_map to these other ds_map delete the socket and add all the save ds_maps under the key "socket"(which now does not exist anymore) to the top ds_map?
 

chamaeleon

Member
no, thats not the issue here. all the data is in the buffer. I checked in debug mode and the correct data is received but not added. the issue I have is that I can not figure out how to add another ds_map into a nested ds_map.
Verified by something like the following at the end of the async event?
GML:
show_debug_message("Read " + string(buffer_tell(buffer)) + " out of " + string(buffer_get_size()) + " bytes");
 

Nidoking

Member
But I want to add another ds_map to the same "socket" that is the whole point. so what would be the solution here? geting all the ds_maps which are stored under key "socket" save them in another ds_map add the new ds_map to these other ds_map delete the socket and add all the save ds_maps under the key "socket"(which now does not exist anymore) to the top ds_map?
I'm not sure you understand what a ds_map is. A ds_map is a relation between data elements known as "keys" and other data elements known as "values". When you put a key-value pair in a map, the map relates the key to the provided value. One value per key. Now, that value could be a data structure of its own - a struct, an array, another map... but it's ONE value. There is no such thing, in this implementation, as a "socket" in quotes. "socket" in quotes is not a data structure capable of storing information, unless that information is the string "socket" in quotes. "socket" in quotes is a string data type. It's a collection of letters. That's all the information it has. "socket" in quotes. In your dsm_players map, you've associated the key "socket" in quotes with the value (the dsm_client map). That is the ONE value that can be associated with the key "socket" in quotes. You cannot possibly associate the key "socket" in quotes with the dsm_client map and ALSO another value. That is not how maps work. You could associate the key "socket" in quotes with a value that is an array of (the dsm_client map) and then (some other map). But you will need to process it correctly. I would recommend setting the value for the key "socket" in quotes with an empty array first, then add each new dsm_client map to the array as you choose to add them. You could also use an empty ds_map, as you suggest, and map those dsm_client maps to some key. In fact, why is dsm_players even a map? Couldn't that be an array indexed by player number?

I'm pretty sure you aren't scoping your maps correctly. Why don't you write out, in text rather than code, what information is stored in each of these maps and how they're indexed? Someone can give you a sane suggestion so you can organize your data properly and not confuse yourself any further.
 
I'm not sure you understand what a ds_map is. A ds_map is a relation between data elements known as "keys" and other data elements known as "values". When you put a key-value pair in a map, the map relates the key to the provided value. One value per key. Now, that value could be a data structure of its own - a struct, an array, another map... but it's ONE value.
Thank you, now I understand it better and I see that this is what I missed. I just relized that I skipped a step. I thought by adding a ds map I would expand the lists of ds_dsmaps paired with the "key" but I never created such a list in the first place.

So my solution looks like this:
GML:
//holds the data
dsm_data = return json_decode(@'
    {
       "infantry" : {
            "name"        : "Infantry",
            "hitpoints"    : 1
        },

        "cavalary" : {
            "name"        : "Tank",
            "hitpoints" : 3
        }
    }')
//a ds_map which holds
dsm_players = ds_map_create();
//holds the ds maps of the units
dsm_maplist = ds_map_create();

//adding the player to the ds_map dsm_players
ds_map_add_map(dsm_players, "player1", dsm_maplist);


var unit = "unit_";
var _unitType = "infantry";
var _index = 0;
var unitkey = unit+string(_index);
ds_map_add(dsm_players[? "player1"], unitkey, oData.dsm_data[? _unitType]);

_unitType = "cavalary";
_index = 1;
var unitkey = unit+string(_index);
ds_map_add(dsm_players[? "player1"], unitkey, dsm_data[? _unitType]);
With this I have a ds map which is structured like I wanted.

In fact, why is dsm_players even a map? Couldn't that be an array indexed by player number?
I dont know. I think I wanted to keep the number of players dynamic thats why I chose a ds_map.
 

Nidoking

Member
I dont know. I think I wanted to keep the number of players dynamic thats why I chose a ds_map.
In what way is an array not dynamic? In fact, it's more dynamic in GML than it is in most other languages, because you can expand an array just by adding values to it. Again, you don't seem to understand maps. The use case for a map is "I want to store pairs of data where the key values are not small, non-negative integers, or the key values are opaque." Numbers of players make great indices into an array, so while you can use a map, there's absolutely no need to. Your unit indices are also small, non-negative numbers. What's more, you're starting with those numbers and constructing strings. You can just use those index numbers as indices into an array. So you can just use a two-dimensional array, indexed by player number and then by unit number.

Good use cases for maps might be "I want to store a sound resource for each room in my game." Room indices are technically small non-negative integers, but they're opaque, so I would use a map for that. Another might be "Each player types in a unique username, and I need to relate a struct of player data with each name." You could assign an index to each player, but if the usernames are unique, you might use those as map keys instead.
 
I want to store the data of each unit which each players owns at one place. The keys for the players should be dynamic because the value of the sockets which I used in my example actually depends on which player connected first to the server, hence, should I save the multiplayer game and restart the game it may occurre that the units then get switched arround. My example code might have been missleading there.

Thank you very much for taking the time to answer me and help me with my problem. I really appreciate it.
 
Verified by something like the following at the end of the async event?
GML:
show_debug_message("Read " + string(buffer_tell(buffer)) + " out of " + string(buffer_get_size()) + " bytes");
All the required values are in the buffer. The problem was that I tried to pair multiple values to the same key.
 
So, this is my solution for now:
This function is used by the client to send the information to the server
GML:
function scr_sendUnit(_unit, _i, _key){
    buffer_seek(oClient.client_buffer, buffer_seek_start, 0);
    buffer_write(oClient.client_buffer, buffer_u8, network.sendUnits);
    buffer_write(oClient.client_buffer, buffer_u8, _i);
    buffer_write(oClient.client_buffer, buffer_string, _unit);
    buffer_write(oClient.client_buffer, buffer_string, _key);
    network_send_packet(oClient.client,oClient.client_buffer,buffer_tell(oClient.client_buffer));
}
In this part on the server side the information are read out of the buffer. Then it is checked whether the "key" already exists in the ds_map or not. if it doe snot exist yet a local ds map is created and added. The created ds_map is assigned to a local variable so that the ds_map paired with the individual keys in the "oData.dsm_clients" is unique. Then the unit data according to the unit read out of the buffer is read out of the "oData.dsm_dataUnits" and added to the ds_map which is paired with the key.
If a key already exists the unit data according to the unit read out of the buffer is read out of the "oData.dsm_dataUnits" and added to the ds_map which is paired with the key.

GML:
        case network.sendUnits:
            var index = buffer_read(buffer, buffer_u8);
            var unitType = buffer_read(buffer, buffer_string);
            var _key = buffer_read(buffer, buffer_string);
            var unit = "unit_";
            var unitkey = unit+string(index);
            if(!ds_map_exists(oData.dsm_clients, _key))
            {
                var dsm_client = ds_map_create();
                ds_map_add_map(oData.dsm_clients, _key, dsm_client);
                ds_map_add_map(oData.dsm_clients[? _key], unitkey, oData.dsm_dataUnits[? unitType]);
            }else{
                ds_map_add_map(oData.dsm_clients[? _key], unitkey, oData.dsm_dataUnits[? unitType]);
            }
        break;
I am a bit in a hurry and tired so perhaps I have expressed myself unclearly please excuse this.

probably I run into memory leak issue with the local ds_map but shouldnt it be solved when I just add "ds_list_mark_as_map" at the end of the code? I have to do some tests to assure there are no memory leaks.
 
Remember, that maps aren't referenced, but you're only getting their id as integer number from 1 to N (deleted maps aren't freeing unused numbers, so this works like counter). So in your case:
Are you really sure about this? I though they take freed id or does this differ from ds_lists(why should it though?)

GML:
ds_map1 = ds_map_create();// ds_map #0
ds_map2 = ds_map_create();// ds_map #1
ds_map3= ds_map_create();//ds_map #2
ds_map_destroy(ds_map1);
ds_map_destroy(ds_map2);
ds_map4 = ds_map_create();// ds_map #0
And what happens when I assign a local variable to a ds_map without destroying it? e.g.
GML:
function localMap(){
    var local_ds_map = ds_map_create();
}
ds_map1 = ds_map_create();//#0
ds_map2 = ds_map_create();//#1
ds_map_destroy(ds_map1);
ds_map_destroy(ds_map2);
localMap();// ds_map #0
ds_map4 = ds_map_create();// ds_map #1
 

gnysek

Member
It's possible that it changed at some point, I'm using GM for 18 years, so if they change something and don't mention that in release notes, I may have false assumptions based on initial knowledge.
 

Nidoking

Member
GML:
            if(!ds_map_exists(oData.dsm_clients, _key))
            {
                var dsm_client = ds_map_create();
                ds_map_add_map(oData.dsm_clients, _key, dsm_client);
                ds_map_add_map(oData.dsm_clients[? _key], unitkey, oData.dsm_dataUnits[? unitType]);
            }else{
                ds_map_add_map(oData.dsm_clients[? _key], unitkey, oData.dsm_dataUnits[? unitType]);
            }
FYI, you can just put that last line outside the if, because it's the same for both cases. Then you only need it once.
 
Just a little update for myself.

When adding the data maps of the units I created references instead of "independent" maps. So when I changed values inside the map I changed them for all maps.
GML:
        case network.sendUnits:
            var index = buffer_read(buffer, buffer_u8);
            var unitType = buffer_read(buffer, buffer_string);
            var _key = buffer_read(buffer, buffer_string);
            var unit = "unit_";
            var unitkey = unit+string(index);
            if(!ds_map_exists(oData.dsm_clients, _key))
            {
                ds_map_add_map(oData.dsm_clients, _key, ds_map_create());
            }
            ds_map_add_map(oData.dsm_clients[? _key], unitkey, json_decode(json_encode(oData.dsm_dataUnits[? unitType])));
            
        break;
 
Top