Преглед изворни кода

Further improve Lua binding for Variant and VariantMap classes.
- VariantMap can be indexed by a StringHash object directly.
- Bind a generic Get() method for Variant class to automatically "unbox" the stored object and assign it to a type less Lua object.
- Prevent Lua VM crashes when invalid type name is being passed when calling GetVoidPtr() and GetPtr().

Yao Wei Tjong 姚伟忠 пре 10 година
родитељ
комит
4c7f8664b6
2 измењених фајлова са 183 додато и 12 уклоњено
  1. 12 3
      Docs/Reference.dox
  2. 171 9
      Source/Urho3D/LuaScript/pkgs/Core/Variant.pkg

+ 12 - 3
Docs/Reference.dox

@@ -765,7 +765,7 @@ The binding of Urho3D C++ classes is accomplished with the tolua++ library, whic
 
 
 As seen above from the event handling examples, VariantMap handling is similar to both C++ and AngelScript. To get a variant object back from a map, index the map by its key as a string. A nil value is returned when the map's key does not exist. Then use one of the variant getter method to return the actual Lua object stored inside the variant object. These getter methods normally do not take any parameter, except GetPtr() and GetVoidPtr() which take a string parameter representing a Lua user type that the method would use to cast the return object into. The GetPtr() is used to get a reference counted object while the GetVoidPtr() is used to get a POD value object.
 As seen above from the event handling examples, VariantMap handling is similar to both C++ and AngelScript. To get a variant object back from a map, index the map by its key as a string. A nil value is returned when the map's key does not exist. Then use one of the variant getter method to return the actual Lua object stored inside the variant object. These getter methods normally do not take any parameter, except GetPtr() and GetVoidPtr() which take a string parameter representing a Lua user type that the method would use to cast the return object into. The GetPtr() is used to get a reference counted object while the GetVoidPtr() is used to get a POD value object.
 
 
-You can also use the VariantMap as a pseudo Lua table to store any variant value objects in your script. The VariantMap class would try its best to convert any Lua object into a variant object and store the variant object using the provided key as index. The key can be a string or an unsigned integer. When a particular data type conversion is not being supported yet, an empty variant object would be stored instead. So, be careful if you are using this feature.
+You can also use the VariantMap as a pseudo Lua table to store any variant value objects in your script. The VariantMap class would try its best to convert any Lua object into a variant object and store the variant object using the provided key as index. The key can be a string or an unsigned integer or even a StringHash object. When a particular data type conversion is not being supported yet, an empty variant object would be stored instead. So, be careful if you are using this feature. You can also use one of the Variant class constructors to construct a %Variant object first before assigning it to the VariantMap, but this operation would be slower than direct conversion. The purpose of using VariantMap in this way is to facilitate objects passing between Lua and C++ as has been shown in the event handling mechanism above. When creating objects on Lua side, you have to make sure they are not garbage collected by Lua while there are still references pointing to them on C++ side, especially when the objects are not reference counted.
 
 
 \code
 \code
 local myMap = VariantMap()
 local myMap = VariantMap()
@@ -781,13 +781,22 @@ print(myMap["I am a table"]:GetRawBuffer()[3], myMap["I am a table"]:GetRawBuffe
 -- output: 255    200
 -- output: 255    200
 
 
 local hash = StringHash("secret key")
 local hash = StringHash("secret key")
-myMap[hash.value] = Vector2(3, 4)
-print(myMap[hash.value].typeName, myMap[hash.value]:GetVector2():Length())
+myMap[hash] = Vector2(3, 4)
+print(myMap[hash].typeName, myMap[hash]:GetVector2():Length())
 -- output: Vector2    5
 -- output: Vector2    5
 \endcode
 \endcode
 
 
 As shown in the above example, you can either use GetRawBuffer() or GetBuffer() to get the unsigned char array stored in a variant object. It also shows that VariantMap is capable of converting a Lua table containing an array of unsigned char to a variant object stored as buffer. You may want to know that it is capable of converting a Lua table containing an array of variant objects or an array of string objects to be stored as VariantVector and StringVector, respectively, as well. It also converts any Lua primitive data types and all Urho3D classes that are exposed to Lua like all the math classes, reference counted classes, POD classes, resource reference class. etc.
 As shown in the above example, you can either use GetRawBuffer() or GetBuffer() to get the unsigned char array stored in a variant object. It also shows that VariantMap is capable of converting a Lua table containing an array of unsigned char to a variant object stored as buffer. You may want to know that it is capable of converting a Lua table containing an array of variant objects or an array of string objects to be stored as VariantVector and StringVector, respectively, as well. It also converts any Lua primitive data types and all Urho3D classes that are exposed to Lua like all the math classes, reference counted classes, POD classes, resource reference class. etc.
 
 
+Inline with C++ and AngelScript, in Lua you have to call one of the %Variant's getter method to "unbox" the actual object stored inside a %Variant object. However, specifically in Lua, there is a generic Get() method which takes advantage of Lua being type less, so the method can unbox a %Variant object and assign the return object to a type less Lua object. It takes one optional string parameter representing a Lua user type that the method would use to cast the return object into. The parameter is used for cases where a type casting is required when returning object from %Variant object stroring a void pointer or a refcounted pointer and for the case of requesting a %VectorBuffer to be returned for %Variant object storing an unsigned char buffer. The parameter is ignored for all other cases. Following up to use the same example above, we can index the map and access the stored objects as so:
+
+\code
+print(myMap[1]:Get("Spline").interpolationMode)
+print(myMap["I am a table"]:Get("VectorBuffer"):ReadByte())
+print(myMap["I am a table"]:Get()[2])
+print(myMap[hash]:Get():Length())
+\endcode
+
 For the rest of the functions and classes, see the generated \ref LuaScriptAPI "Lua script API reference". Also, look at the Lua counterparts of the sample applications in the bin/Data/LuaScripts directory and compare them to the C++ and AngelScript versions to familiarize yourself with how things are done on the Lua side.
 For the rest of the functions and classes, see the generated \ref LuaScriptAPI "Lua script API reference". Also, look at the Lua counterparts of the sample applications in the bin/Data/LuaScripts directory and compare them to the C++ and AngelScript versions to familiarize yourself with how things are done on the Lua side.
 
 
 \section LuaScripting_Allocation Object allocation & Lua garbage collection
 \section LuaScripting_Allocation Object allocation & Lua garbage collection

+ 171 - 9
Source/Urho3D/LuaScript/pkgs/Core/Variant.pkg

@@ -129,7 +129,7 @@ class Variant
 
 
     int GetInt() const;
     int GetInt() const;
     unsigned GetUInt() const;
     unsigned GetUInt() const;
-    StringHash GetStringHash();
+    StringHash GetStringHash() const;
     bool GetBool() const;
     bool GetBool() const;
     float GetFloat() const;
     float GetFloat() const;
     double GetDouble() const;
     double GetDouble() const;
@@ -154,6 +154,8 @@ class Variant
     const Matrix3x4& GetMatrix3x4() const;
     const Matrix3x4& GetMatrix3x4() const;
     const Matrix4& GetMatrix4() const;
     const Matrix4& GetMatrix4() const;
 
 
+    void* Get(const char* type = 0) const;      // "Generic" get which is only possible in Lua scripting
+
     VariantType GetType() const;
     VariantType GetType() const;
     String GetTypeName() const;
     String GetTypeName() const;
     String ToString() const;
     String ToString() const;
@@ -341,8 +343,17 @@ static int tolua_CoreLuaAPI_Variant_GetVoidPtr00(lua_State* tolua_S)
  if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetVoidPtr'", NULL);
  if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetVoidPtr'", NULL);
 #endif
 #endif
  {
  {
-     void* tolua_ret = self->GetVoidPtr();
-     tolua_pushusertype(tolua_S, tolua_ret, type);
+     if (type)
+     {
+         luaL_getmetatable(tolua_S, type);
+         if (!lua_isnil(tolua_S, -1))
+         {
+             lua_pop(tolua_S, 1);
+             tolua_pushusertype(tolua_S, static_cast<void*>(self->GetVoidPtr()), type);
+         }
+     }
+     else
+         lua_pushnil(tolua_S);
  }
  }
  }
  }
  return 1;
  return 1;
@@ -373,8 +384,17 @@ static int tolua_CoreLuaAPI_Variant_GetPtr00(lua_State* tolua_S)
  if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetPtr'", NULL);
  if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetPtr'", NULL);
 #endif
 #endif
  {
  {
-     RefCounted* tolua_ret = self->GetPtr();
-     tolua_pushusertype(tolua_S, (void*)tolua_ret, type);
+     if (type)
+     {
+         luaL_getmetatable(tolua_S, type);
+         if (!lua_isnil(tolua_S, -1))
+         {
+             lua_pop(tolua_S, 1);
+             tolua_pushusertype(tolua_S, static_cast<void*>(self->GetPtr()), type);
+         }
+     }
+     else
+         lua_pushnil(tolua_S);
  }
  }
  }
  }
  return 1;
  return 1;
@@ -385,6 +405,139 @@ static int tolua_CoreLuaAPI_Variant_GetPtr00(lua_State* tolua_S)
 #endif
 #endif
 }
 }
 
 
+#define TOLUA_DISABLE_tolua_CoreLuaAPI_Variant_Get00
+static int tolua_CoreLuaAPI_Variant_Get00(lua_State* tolua_S)
+{
+#ifndef TOLUA_RELEASE
+ tolua_Error tolua_err;
+ if (
+ !tolua_isusertype(tolua_S,1,"const Variant",0,&tolua_err) ||
+ !tolua_isstring(tolua_S,2,1,&tolua_err) ||
+ !tolua_isnoobj(tolua_S,3,&tolua_err)
+ )
+ goto tolua_lerror;
+ else
+#endif
+ {
+  const Variant* self = (const Variant*)  tolua_tousertype(tolua_S,1,0);
+  String type = String(tolua_tostring(tolua_S,2,0));
+#ifndef TOLUA_RELEASE
+ if (!self) tolua_error(tolua_S,"invalid 'self' in function 'Get'", NULL);
+#endif
+ {
+    switch (self->GetType())
+    {
+    case VAR_INT:
+        tolua_pushnumber(tolua_S, (lua_Number)self->GetInt());
+        break;
+
+    case VAR_BOOL:
+        tolua_pushboolean(tolua_S,(int)self->GetBool());
+        break;
+
+    case VAR_FLOAT:
+        tolua_pushnumber(tolua_S, (lua_Number)self->GetFloat());
+        break;
+
+    case VAR_DOUBLE:
+        tolua_pushnumber(tolua_S, (lua_Number)self->GetDouble());
+        break;
+
+    case VAR_VECTOR2:
+        tolua_pushusertype(tolua_S, (void*)&self->GetVector2(), "const Vector2");
+        break;
+
+    case VAR_VECTOR3:
+        tolua_pushusertype(tolua_S, (void*)&self->GetVector3(), "const Vector3");
+        break;
+
+    case VAR_VECTOR4:
+        tolua_pushusertype(tolua_S, (void*)&self->GetVector4(), "const Vector4");
+        break;
+
+    case VAR_QUATERNION:
+        tolua_pushusertype(tolua_S, (void*)&self->GetQuaternion(), "const Quaternion");
+        break;
+
+    case VAR_COLOR:
+        tolua_pushusertype(tolua_S, (void*)&self->GetColor(), "const Color");
+        break;
+
+    case VAR_STRING:
+        tolua_pushurho3dstring(tolua_S, (const char*)self->GetString());
+        break;
+
+    case VAR_BUFFER:
+        if (type == "VectorBufer")
+        {
+            // Make a new local copy
+            tolua_pushusertype(tolua_S, Mtolua_new(VectorBuffer(self->GetVectorBuffer())), "const VectorBuffer");
+            tolua_register_gc(tolua_S, lua_gettop(tolua_S));
+        }
+        else
+            ToluaPushPODVector<unsigned char>(0.f, tolua_S, (void*)&self->GetBuffer(), "unsigned char");
+        break;
+
+    case VAR_VOIDPTR:
+        return tolua_CoreLuaAPI_Variant_GetVoidPtr00(tolua_S);
+
+    case VAR_PTR:
+        return tolua_CoreLuaAPI_Variant_GetPtr00(tolua_S);
+
+    case VAR_RESOURCEREF:
+        tolua_pushusertype(tolua_S, (void*)&self->GetResourceRef(), "const ResourceRef");
+        break;
+
+    case VAR_RESOURCEREFLIST:
+        tolua_pushusertype(tolua_S, (void*)&self->GetResourceRefList(), "const ResourceRefList");
+        break;
+
+    case VAR_VARIANTVECTOR:
+        ToluaPushVector<Variant>(tolua_S, (void*)&self->GetVariantVector(), "Variant");
+        break;
+
+    case VAR_VARIANTMAP:
+        tolua_pushusertype(tolua_S, (void*)&self->GetVariantMap(), "const VariantMap");
+        break;
+
+    case VAR_STRINGVECTOR:
+        ToluaPushVector<String>(tolua_S, (void*)&self->GetStringVector(), "String");
+        break;
+
+    case VAR_INTRECT:
+        tolua_pushusertype(tolua_S, (void*)&self->GetIntRect(), "const IntRect");
+        break;
+
+    case VAR_INTVECTOR2:
+        tolua_pushusertype(tolua_S, (void*)&self->GetIntVector2(), "const IntVector2");
+        break;
+
+    case VAR_MATRIX3:
+        tolua_pushusertype(tolua_S, (void*)&self->GetMatrix3(), "const Matrix3");
+        break;
+
+    case VAR_MATRIX3X4:
+        tolua_pushusertype(tolua_S, (void*)&self->GetMatrix3x4(), "const Matrix3x4");
+        break;
+
+    case VAR_MATRIX4:
+        tolua_pushusertype(tolua_S, (void*)&self->GetMatrix4(), "const Matrix4");
+        break;
+
+    default:
+        lua_pushnil(tolua_S);
+        break;
+    }
+ }
+ }
+ return 1;
+#ifndef TOLUA_RELEASE
+ tolua_lerror:
+ tolua_error(tolua_S,"#ferror in function 'Get'.",&tolua_err);
+ return 0;
+#endif
+}
+
 $}
 $}
 
 
 class VariantMap
 class VariantMap
@@ -411,12 +564,13 @@ static int VariantMapIndexEventHandler(lua_State* tolua_S)
         key = StringHash(lua_tostring(tolua_S, 2));
         key = StringHash(lua_tostring(tolua_S, 2));
     else if (t == LUA_TNUMBER)
     else if (t == LUA_TNUMBER)
         key = StringHash((unsigned)lua_tonumber(tolua_S, 2));
         key = StringHash((unsigned)lua_tonumber(tolua_S, 2));
-    else
+    else if (t == LUA_TUSERDATA)
     {
     {
-        lua_pushnil(tolua_S);
-        return 1;
+        tolua_Error error;
+        if (tolua_isusertype(tolua_S, 2, "StringHash", 0, &error))
+            key = *static_cast<StringHash*>(tolua_tousertype(tolua_S, 2, 0));
     }
     }
-    Variant* variant = static_cast<const VariantMap*>(tolua_tousertype(tolua_S, 1, 0))->operator [](key);
+    Variant* variant = key ? static_cast<const VariantMap*>(tolua_tousertype(tolua_S, 1, 0))->operator [](key) : 0;
     if (variant)
     if (variant)
         tolua_pushusertype(tolua_S, variant, "Variant");
         tolua_pushusertype(tolua_S, variant, "Variant");
     else
     else
@@ -432,6 +586,14 @@ static int VariantMapNewIndexEventHandler(lua_State* tolua_S)
         key = StringHash(lua_tostring(tolua_S, 2));
         key = StringHash(lua_tostring(tolua_S, 2));
     else if (t == LUA_TNUMBER)
     else if (t == LUA_TNUMBER)
         key = StringHash((unsigned)lua_tonumber(tolua_S, 2));
         key = StringHash((unsigned)lua_tonumber(tolua_S, 2));
+    else if (t == LUA_TUSERDATA)
+    {
+        tolua_Error error;
+        if (tolua_isusertype(tolua_S, 2, "StringHash", 0, &error))
+            key = *static_cast<StringHash*>(tolua_tousertype(tolua_S, 2, 0));
+        else
+            return 0;
+    }
     else
     else
         return 0;
         return 0;
     Variant& variant = static_cast<VariantMap*>(tolua_tousertype(tolua_S, 1, 0))->operator [](key);     // autovivification
     Variant& variant = static_cast<VariantMap*>(tolua_tousertype(tolua_S, 1, 0))->operator [](key);     // autovivification