Page 1 of 1

[2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 6:11 am
by hgschmie
consider this code:

local handler = {
[defines.events.on_gui_checked_state_changed] = 'onSort',
}

local child = element.add {
type = 'checkbox',
...
tags = {
value = sort_value,
entity_type = entity_type,
handler = handler,
},
}


Looking at this in the debugger shows this:
Screenshot 2025-08-18 at 23.06.16.png
Screenshot 2025-08-18 at 23.06.16.png (18.27 KiB) Viewed 720 times
so the numeric index 3 (which is on_gui_checked_state_changed) in the handler array has been converted to a string.

However, using on_click, which is 1 shows this:
Screenshot 2025-08-18 at 23.08.37.png
Screenshot 2025-08-18 at 23.08.37.png (20.32 KiB) Viewed 720 times
This seems to be a problem with an array that does not start at 1. It gets converted to a table (and all keys are now strings). If the array starts at 1, it is treated as an array.

This came as a surprise as lua clearly supports sparse arrays. But it seems the conversion from/to the C++ code when calling LuaGuiElement.add) gets confused.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 6:17 am
by boskid
In lua, there is no special type for array table or for dictionary table, this is all user space interpretation of a generic lua table based on the table keys and lack of gaps within numeric indexes. I will just throw this to Not a bug.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 7:10 pm
by hgschmie
While I agree with the "it is all user space interpretation", the surprising part (and the bug IMHO) is that one should be able to expect to read back from the table what you wrote. So when I create a new LuaGuiElement with tags:

Code: Select all


child =  parent.add {
    ...
    tags = {
        xxx = {
            [defines.events.on_gui_checked_state_changed] = 'onSort' 
        }
    },
    ...
}
Now I would expect to be able to read back child.tags.xxx[defines.events.on_gui_checked_state_changed] and get the 'onSort' value. And that works if this is defines.events.on_click because that happens to be 1.

So now, my code has turned into

Code: Select all

event = defines.events.on_gui_checked_state_changed
foo = child.tags.xxx[event] or child.tags.xxx[tostring(event)]


which seems less than ideal.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 7:23 pm
by boskid
Ok, lets slow down and reiterate. When i run following command:

Code: Select all

/c
  local foo = { [3] = "onSort inside foo" }
  game.print(serpent.line(foo))
  game.print(foo[3])

  local bar = { [1] = "onSort inside bar" }
  game.print(serpent.line(bar))
  game.print(bar[1])

  local qux = { "onSort inside qux" }
  game.print(serpent.line(qux))
  game.print(qux[1])
i get following output:

Code: Select all

{[3] = "onSort inside foo"}
onSort inside foo
{"onSort inside bar"}
onSort inside bar
{"onSort inside qux"}
onSort inside qux
Why is it working even when it was not supposed to work based on your report?

If you get a table dump in a form of { "bar" }, that means function that does the dumping realized that this table consists of single record of key=1 value="bar", and to save space, instead of dumping it as { [1] = "bar" }, a shorter format was used. This value is still available at index 1. Can you show me a piece of code that does not work when a table is put into tags then retrieved?

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 7:32 pm
by hgschmie
boskid wrote: Tue Aug 19, 2025 7:23 pm Ok, lets slow down and reiterate. When i run following command:

Code: Select all

/c
  local foo = { [3] = "onSort inside foo" }
  game.print(serpent.line(foo))
  game.print(foo[3])

  local bar = { [1] = "onSort inside bar" }
  game.print(serpent.line(bar))
  game.print(bar[1])

  local qux = { "onSort inside qux" }
  game.print(serpent.line(qux))
  game.print(qux[1])
i get following output:

Code: Select all

{[3] = "onSort inside foo"}
onSort inside foo
{"onSort inside bar"}
onSort inside bar
{"onSort inside qux"}
onSort inside qux
Why is it working even when it was not supposed to work based on your report?

If you get a table dump in a form of { "bar" }, that means function that does the dumping realized that this table consists of single record of key=1 value="bar", and to save space, instead of dumping it as { [1] = "bar" }, a shorter format was used. This value is still available at index 1. Can you show me a piece of code that does not work when a table is put into tags then retrieved?
Yes, this is all correct. The problem is that "passing a table with values as a tag into the "LuaGuiElement.add" method makes it return a LuaGuiElement instance which contains the tags, but *there* the keys have been replaced with strings *unless* it is the index 1.

This is not a general lua table interpretation problem. My assumption is that the GUI code is written in C++ and the data I pass into C++ is marshalled into some internal representation and when I read it back from the child element created by LuaGuiElement.add, the table gets converted back from the C++ interpretation. Somewhere in that process, the keys are converted from numbers to strings.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 7:34 pm
by hgschmie
You can not reproduce this by using the lua command line or the debugger console; it is specific to LuaGuiElement.add and tags.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 7:59 pm
by boskid
Ok finally i see, you are reporting that a numeric key becomes a string key while saved into tags and then read back. Essentially a reproduction is that running this command:

Code: Select all

/c
  local foo = { [3] = "bar" }
  game.print(serpent.line(foo), {skip=defines.print_skip.never})

  storage.checkbox = game.player.gui.top.add{
    type = 'checkbox',
    state = true,
    tags =
    {
      handler = foo
    }
  }
  foo = storage.checkbox.tags.handler

  game.print(serpent.line(foo), {skip=defines.print_skip.never})
produces this output:

Code: Select all

{[3] = "bar"}
{["3"] = "bar"}
and it is expected that both lines are equal.

I was looking at the value of "handler" in your watch list while i was supposed to look at the value of child.tags.handler.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 8:17 pm
by hgschmie
Yes. Sorry for not being clearer in the original post, it was late at night. :-)

The “numeric key becomes a string key” is the problem/bug.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Aug 19, 2025 9:05 pm
by Bilka
See also 104565

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Mon Sep 15, 2025 1:31 pm
by Rseding91
Thanks for the report however I don't believe there's any way to reasonably fix this. The way the values are stored internally only supports pure-number sequential keys (starting at 1), or pure string keys.

At best, we can update the Lua docs to mention this limitation.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Mon Sep 15, 2025 8:54 pm
by hgschmie
I’d consider “mark it clearly in the docs” a reasonable outcome. I spent quite some time chasing this and trying to reproduce the problem.

Another possible fix would be “only ever return string keys” but I understand that this would break some existing mods.

Re: [2.0.64] LuaGuiElement.add woes with sparse arrays?

Posted: Tue Sep 16, 2025 2:07 pm
by Bilka
Documented for 2.0.67.