Discussion Universal Getter + Setter: Workaround for nested accessors

Discussion in 'GameMaker Studio 2 Community Tech Support' started by FrostyCat, Aug 6, 2018.

  1. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    3,227
    Universal Getter + Setter (GMS 2.x)

    Overview
    This GML extension adds a functional equivalent of the chained accessor feature on the roadmap, allowing you to reach into deeply nested arrays and data structures in one line. It also adds the ability to use negative index numbers to count backwards from the end on arrays, lists and grids, similar to Python and Ruby.

    Downloads
    GitHub: Download | Repository

    Examples
    Code:
    var json = @'{ "a": [1, 2, { "b": 3 }]}',
       json_data = json_decode(json);
    show_message(Get(json_data, "a", 2, "b")); //3
    show_message(Get(json_data, "a", -2)); //2
    Code:
    var nested_array = [[1, 1], [4, 4], [9, 9]];
    Set(nested_array, -1, 1, 16);
    show_message(Get(nested_array, 2, 0)); //9
    show_message(Get(nested_array, 2, 1)); //16
     
    immortalx, Amon, 00.Archer and 6 others like this.
  2. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    1,995
    This is nice, though could be further improved - I think a good amount of time is spent on string alloc/dealloc/concat here

    I took a shot at tweaking the getter,
    • Instead of casting index to real and concatenating typeofs, indexes are assumed to be numeric if they are not arrays/strings (you get an error on non-numeric indexing anyway)
    • Instead of constructing stack_top & co. and switching on their first item, they are constructed on startup, macros are pointing to global variables containing them, and there's a switch on array reference (which is OK in VM, YYC, and JS all alike). This also eliminates oddities if an index is an arbitrary 1-element array due to user error.
    • Since Pos is only used with these scripts, it's items are tagged and pooled.
      This is 20% slower on VM but ~25% faster on YYC.
      More importantly though, it allows to eliminate unobvious behaviour if index happens to be an unrelated 2-item array per chance.
    This results in 2x..4x performance boost (see attachments)
    Code:
    /// @param array_or_ds
    /// @param ...indexes
    var r = argument[0];
    for (var i = 1; i < argument_count; ++i) {
        var k = argument[i];
        if (is_array(k)) {
            if (array_length_1d(k) > 0) {
                var k0, k1;
                if (k[0] == global.g_ypos_tag) {
                    k0 = k[1];
                    k1 = k[2];
                    ds_stack_push(global.g_ypos_pool, k);
                } else {
                    k0 = k[0];
                    k1 = k[1];
                    // not showing an error because we're comparing non-pooled
                    //show_error("Invalid index `" + string(k) + "` [" + string(i) + "]", 1);
                }
                if (is_array(r)) {
                    if (k0 < 0) k0 += array_height_2d(r);
                    if (k1 < 0) k1 += array_length_2d(r, k0);
                    r = r[k0, k1];
                } else {
                    if (k0 < 0) k0 += ds_grid_width(r);
                    if (k1 < 0) k1 += ds_grid_height(r);
                    r = r[#k0, k1];
                }
            } else {
                if (is_array(r)) {
                    show_error("Invalid index `" + string(k) + "` [" + string(i) + "]", 1);
                } else switch (k) {
                    case ystack_top: r = ds_stack_top(r); break;
                    case yqueue_head: r = ds_queue_head(r); break;
                    case ypq_min: r = ds_priority_find_min(r); break;
                    case ypq_max: r = ds_priority_find_max(r); break;
                    default: show_error("Invalid index `" + string(k) + "` [" + string(i) + "]", 1);
                }
            }
        } else if (is_string(k)) {
            r = r[?k];
        } else {
            if (is_array(r)) {
                if (k < 0) k += array_length_1d(r);
                r = r[k];
            } else {
                if (k < 0) k += ds_list_size(r);
                r = r[|k];
            }
        }
    }
    return r;
    Project and tester: https://www.dropbox.com/s/kaew87g89imumcw/yget.yyz?dl=0

    Can make it fit the original format and assemble a PR if you'd like.
     

    Attached Files:

  3. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    3,227
    @YellowAfterlife:

    Thank you so much for your investigation! I would definitely welcome a pull request if you are interested.
     
    00.Archer likes this.
  4. 00.Archer

    00.Archer Member

    Joined:
    Jul 24, 2018
    Posts:
    24
    This will help us writing cleaner code. Thanks for sharing!
     
  5. immortalx

    immortalx Member

    Joined:
    Sep 6, 2018
    Posts:
    66
    I'd normally say sorry to bump this thread, but I'm not sorry because this is truly a lifesaver.
    I was pointed here by YellowAfterLife after a discussion in this thread and I initially couldn't import the extension.
    When I tried to import UniversalGetSet.yy from the releases page, i got an error popup:
    Code:
    The external project file cannot be read to import this resource
    Today I gave it another shot by trying to import the same file from the source and it worked!

    I don't know what could be the problem, so I thought I'd let you know.

    In my project I have arrays stored inside a ds_list and I previously had to do this:
    Code:
    var arr = ds_list_find_value(t,6);
    arr[5] = false;
    t[| 6] = arr;
    
    and now it's as simple as this:
    Code:
    Set(t,6,5,false)
    Really, really powerful. Thank you very much!
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice