A Variant isn’t necessarily a basic Urho3D object, so much as it is a sort of catch-all datatype that can hold basic Urho3D objects. In a way, Variants are redundant for Lua, since in Lua, which is dynamically typed, any variable or reference or table member can hold any type, be it a string, a number, a function, or whatever. Such things are not possible in native C++, without using an intermediary such as Variant.
Consider the Lua code:
local thingy=4.0
thingy="thingy"
In Lua, that is no big deal. Happens all the time. thingy is not declared with any particular type, because it’s not necessary to do so. However, in C++, you need to declare your thingy with a particular type:
float thingy=4.0;
You can’t then override thingy with a string type:
thingy="thingy"; // Doesn't work in C++
Since C++ is statically typed, you can’t just assign different types of values to a variable willy-nilly. Most of the time, that’s not an issue. However, one case in which it is nice to be able to do so is in ad-hoc message passing schemes.
In message passing, you have a centralized dispatcher that sends messages from objects to other objects. What, exactly, the content of these messages is is irrelevant. It just takes a message from object A and sends it to object B, and lets B worry about what the message content is. In order to facilitate such a system, you need a data type that can hold an object of any other data type. In order for the body of the message to be generic and flexible, you need an any data type. So that object A could send the message {type=“ReduceHitPoints”, damage=14} to object B, while object B sends the message {type=“ImDead”, whokilledme=objectA} to object C, and the message-passing infrastructure doesn’t need to know the particulars of these messages. You don’t need to come up with dozens of different message structures to facilitate each one; you can just populate a VariantMap with the various data that particular message needs and send it along.
It’s a lot like simply populating a Lua table with the various data you need and sending it along. Same principle, it’s just that Lua was designed from the start to be dynamically typed, while C++ was not. Variant and VariantMap (along with VariantVector) provide a certain amount of dynamic typing to facilitate the passing of ad hoc messages. And even in Lua, since you are interacting with the C++ library, you need to use Variant and VariantMap to pass your messages, since the C++ code expects it. Think of them as a kinda more convoluted Lua table, addressable by string keys, and you’ll be fine.
This touches on your desire to have two tables for eventType and eventData, rather than userdata. Lua would be fine with two tables, but since the C++ engine code doesn’t know what to do with a Lua table, it expects instead a StringHash and a VariantMap. It would be valid to construct the binding such that on the Lua side of things you create a Lua table, and when it is passed to a function expecting a VariantMap it is converted behind-the-scenes. However, that is not the way these bindings were constructed. Doing it the way it does it now keeps the interface and usages similar among Lua, AngelScript and C++. In each case, you construct a VariantMap and populate it with data; doing it differently in Lua alone would break the paradigm and make it more difficult to maintain the bindings, I think.
In the case of my example with SetPixel, the problematic part of that line is the Color(1,1,1,1) that is passed to SetPixel. This constructs a garbage-collected instance of the Color class and passes it. Since it’s garbage-collected (ie, its lifetime is managed by the Lua garbage collector) then it is stashed in an internal table until it is garbage collected. In an inner loop like that, you can end up creating thousands of such objects which cause the table to explode in size. When they are all garbage collected, that internal table isn’t resized to a smaller size, causing a performance drain when looking for other objects to collect.
In addition to the Lua script API page that @weitjong pointed out, I find great value in looking at the .pkg files used by tolua++ to generate the binding code, located in Urho3D/Source/Urho3D/LuaScript/pkgs to see exactly how the bindings are structured and what they require.