OptionButton does not work properly with large in-bound custom IDs (> 32 bits)
Godot version
v3.5.1.stable.official [6fed1ffa3]
System information
Linux (6.1.x-LTS, 64-bit), X11, GLES2
Issue description
Option button method void add_item(label: String, id: int = -1) is expected to accept custom ids of type int.
Methods get_item_id and get_selected_id are also expected to return int.
The built-in integer type (int) is described as "Signed 64-bit integer type" in the documentation.
OptionButton seems not to work properly with in-bound large integers used as custom id (let's say 2^63 - 4).
This issue may also exist in the underlying
PopupMenu.
Steps to reproduce
- Create an
OptionButtonand fill it with items having large integers (i.e.>= 2^31 - 1) as their custom ID - Get selected item's ID
extends Control
const BITS: int = 63 # Any value `>= 32` causes the issue
onready var BIG_INT: int = (pow(2, BITS) as int) - 1
onready var Options: OptionButton = $Rows/OptionButton
onready var SelectedIdx: Label = $Rows/SelectionIndex
onready var SelectedId: Label = $Rows/SelectedId
onready var SelectedIdIndirect: Label = $Rows/SelectedIdIndirect
func _ready():
# ...
for idx in range(0, 9):
var unqiue_id: int = (BIG_INT - idx)
var name = "Index: {0}, UID: {1}".format([idx, unqiue_id])
Options.add_item(name, unqiue_id)
# ...
Options.connect("item_selected", self, "handle_selection", [], CONNECT_DEFERRED)
handle_selection() # (for the initial view)
pass
func handle_selection(idx: int = -1) -> void:
SelectedIdx.set_text("Selected option index: from signal = %s, from control = %s" % [idx, Options.get_selected()])
SelectedId.set_text("UID from `get_selected_id()` method: %s" % Options.get_selected_id())
SelectedIdIndirect.set_text("UID from `get_item_id(get_selected())` method: %s" % Options.get_item_id(Options.get_selected()))
pass
-
Using
BITS = 31we get:
-
Using
BITS = 63we get:
Minimal reproduction project
Godot v4.0.stable.official [92bee43ad] also behaves the same way.
v4.0 can convert and work with the attached minimal reproduction project.
In my project (Godot 3.5), OptionButton get_item_id and get_selected_id returns negative value of the actual id.
For instance, when id is 2934624645 it returns -1360342651. It doesn't happen always but most of the time in my case. Also I generate these numbers with RandomNumberGenerator.randi() which is definitely 32-bit number.
So looks like it just supports 16-bit number.
OptionButton does not support 64 bit IDs, they are C++ int i.e. 32 bit, documentation should mention this though
2934624645 is not a valid 32 bit signed integer, as it is larger than 2147483647
OptionButtondoes not support 64 bit IDs, they are C++inti.e. 32 bit, documentation should mention this though
So randi() is unsigned 32bit too but when pass the number to the id of the OptionButton item, it returns negative value bt get_item_id, otherwise it supports signed 32bit number ?
Randi generates an unsigned int, but it's representable as the 64 bit value, something else goes weird I suspect and it's converted to signed int
Will take a look at documenting this later today
Updating documentation is a decent way to address this issue, and I'm fine with it; but there may be other matters to consider as well.
32-bit IDs are good enough for most cases, but they become quite restrictive when your IDs are composite values representing namespaces or any other set of data structured through different segments. Examples are distributed ID systems such as Snowflake or Arrow's. A 53-bit (to fit into a double-precision floating-point) or even more flexible 64-bit integer is a common choice in such scenarios. That headroom is optimal, somewhere between simple unique (~32-bit) integers and pretty larger (~128-bit) UUIDs. It is not a requirement of course. I think of it as a decision on ergonomics.
Currently, one can work around the limited number of available bits, using a second layer of abstraction (e.g. metadata); but it would be much more convenient for the end user to have the luxury of 64 bits in the engines level.
Agreed, but this would be a compatibility issue so would be a more complicated issue to work with (and would not be available to 3.x probably, and not at all to 4.0.x, or 3.5.x), I think a proposal for this would be in order
I didn't saw the compatibility braking side of it coming! Considering it not being an urgent matter and pretty much a rare case, I restrain myself from pushing it any further. Although a proposal to change this will have my vote, communicating the restrictions to the game developers through documentation seems just fine.
Any time a return value or a function parameter changes type it is compatibility breaking, so even if a change would be decided on a first step would be documentation, especially considering the versions that won't get the change
I am very much in favour of changing this to a 64 bit value, and as you articulated it very well it'd be great if you opened a proposal for it