[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: RE: Adding custom bulit-in type
- From: Roger Leigh <rleigh@...>
- Date: Thu, 27 Jul 2023 20:20:33 +0000
Hi John,
You don't necessarily need to create a new base type for this. I have attached an example of transparently wrapping the "glm" library to provide a set of vector and matrix types in Lua including all of the operator overloads. It was just a quick proof of concept, so I'm sure there's some room for improvement. I used sol2 to do the wrapping, so doing it using the basic C API would be a bit more verbose, but equally achievable.
Kind regards,
Roger
> -----Original Message-----
> From: John W <jwdevel@gmail.com>
> Sent: Thursday, July 27, 2023 6:13 PM
> To: Lua mailing list <lua-l@lists.lua.org>
> Subject: Adding custom bulit-in type
>
> I'm sure someone has done this before, but so far I haven't found any clear
> examples from the mailing archives or online. Maybe my search terms are off.
>
> Basically, I want to patch the Lua interpreter to support an additional type
> (alongside 'number', 'table', etc).
>
> From reading the code, it seems that I need to add an entry to the "Value"
> union in lobject.h, and follow the various implications of that.
>
> Some brief details:
>
> The type I'm adding is a "vec2" (x,y pair), which conveniently fits into the
> same space as a 64-bit integer in my case, so it does not need to be a
> "reference type". No heap allocation needed. Also, I'm not planning on
> changing the parser, if I can avoid it - not adding any "vec2" keyword or
> anything, just a global "vec2(x,y)" constructor function. However, I *do* want
> to be able to write "foo.x = 3", so there has to be some sort of awareness of
> ".x" syntax - maybe a metatable, similar to how the 'string' type has methods
> attached to it?
>
> Does anyone have any example of doing something like this? It would save
> me some time tracking down all the details (and probably getting some
> wrong). Maybe a guide/tutorial, or a patch that someone has made
> previously?
>
> I'm using Lua 5.3, but would still be interested in other versions, as a point of
> reference.
>
> Thanks
> -John
//
// Created by rleigh on 18/09/2022.
//
#include "LuaGLM.h"
#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>
namespace {
template <typename T, typename U>
auto fetchIndex(T const &c, typename T::length_type idx) -> U const & {
if (idx < c.length()) {
return c[idx];
} else {
throw std::logic_error("GLM index out of range");
}
}
template <typename T, typename U>
auto fetchIndex(T &c, typename T::length_type idx) -> U & {
if (idx < c.length()) {
return c[idx];
} else {
throw std::logic_error("GLM index out of range");
}
}
template <typename T, typename U>
auto storeIndex(T &c, typename T::length_type idx, U const &v) -> void {
if (idx < c.length()) {
c[idx] = v;
} else {
throw std::logic_error("GLM index out of range");
}
}
template <typename T> void createVec(sol::state &state, const char *name) {
state.new_usertype<T>(
name,
sol::constructors<T(), T(float const &),
T(float const &, float const &)>(),
sol::meta_function::multiplication,
sol::overload(
sol::resolve<T(T const &, T const &)>(
&glm::operator*),
sol::resolve<T(T const &, glm::vec1 const &)>(
&glm::operator*),
sol::resolve<T(T const &, float)>(&glm::operator*)),
sol::meta_function::division,
sol::overload(
sol::resolve<T(T const &, T const &)>(
&glm::operator/),
sol::resolve<T(T const &, glm::vec1 const &)>(
&glm::operator/),
sol::resolve<T(T const &, float)>(&glm::operator/)),
sol::meta_function::addition,
sol::overload(
sol::resolve<T(T const &, T const &)>(
&glm::operator+),
sol::resolve<T(T const &, glm::vec1 const &)>(
&glm::operator+),
sol::resolve<T(T const &, float)>(&glm::operator+)),
sol::meta_function::subtraction,
sol::overload(
sol::resolve<T(T const &, T const &)>(
&glm::operator-),
sol::resolve<T(T const &, glm::vec1 const &)>(
&glm::operator-),
sol::resolve<T(T const &, float)>(&glm::operator-)),
sol::meta_function::index,
sol::resolve<const float &(T const &, glm::length_t)>(fetchIndex),
sol::meta_function::new_index,
sol::resolve<void(T &, glm::length_t, float const &)>(
storeIndex));
}
} // namespace
void luaGlmInit(sol::state &state) {
createVec<glm::vec1>(state, "vec1");
createVec<glm::vec2>(state, "vec2");
createVec<glm::vec3>(state, "vec3");
createVec<glm::vec4>(state, "vec4");
state.new_usertype<glm::mat2>(
"mat2",
sol::constructors<glm::mat2(), glm::mat2(float const &),
glm::mat2(float const &, float const &, float const &,
float const &)>(),
sol::meta_function::multiplication,
sol::overload(
sol::resolve<glm::mat2(glm::mat2 const &, glm::mat2 const &)>(
&glm::operator*),
sol::resolve<glm::mat2(glm::mat2 const &, float)>(&glm::operator*)),
sol::meta_function::division,
sol::overload(
sol::resolve<glm::mat2(glm::mat2 const &, glm::mat2 const &)>(
&glm::operator/),
sol::resolve<glm::mat2(glm::mat2 const &, float)>(&glm::operator/)),
sol::meta_function::addition,
sol::overload(
sol::resolve<glm::mat2(glm::mat2 const &, glm::mat2 const &)>(
&glm::operator+),
sol::resolve<glm::mat2(glm::mat2 const &, float)>(&glm::operator+)),
sol::meta_function::subtraction,
sol::overload(
sol::resolve<glm::mat2(glm::mat2 const &, glm::mat2 const &)>(
&glm::operator-),
sol::resolve<glm::mat2(glm::mat2 const &, float)>(&glm::operator-)),
sol::meta_function::index,
sol::resolve<glm::vec2 &(glm::mat2 &, glm::length_t)>(fetchIndex),
sol::meta_function::new_index,
sol::resolve<void(glm::mat2 &, glm::length_t, glm::vec2 const &)>(
storeIndex));
state.new_usertype<glm::mat3>(
"mat3",
sol::constructors<glm::mat3(), glm::mat3(float const &),
glm::mat3(float const &, float const &, float const &,
float const &, float const &, float const &,
float const &, float const &, float const &)>(),
sol::meta_function::multiplication,
sol::overload(
sol::resolve<glm::mat3(glm::mat3 const &, glm::mat3 const &)>(
&glm::operator*),
sol::resolve<glm::mat3(glm::mat3 const &, float)>(&glm::operator*)),
sol::meta_function::division,
sol::overload(
sol::resolve<glm::mat3(glm::mat3 const &, glm::mat3 const &)>(
&glm::operator/),
sol::resolve<glm::mat3(glm::mat3 const &, float)>(&glm::operator/)),
sol::meta_function::addition,
sol::overload(
sol::resolve<glm::mat3(glm::mat3 const &, glm::mat3 const &)>(
&glm::operator+),
sol::resolve<glm::mat3(glm::mat3 const &, float)>(&glm::operator+)),
sol::meta_function::subtraction,
sol::overload(
sol::resolve<glm::mat3(glm::mat3 const &, glm::mat3 const &)>(
&glm::operator-),
sol::resolve<glm::mat3(glm::mat3 const &, float)>(&glm::operator-)),
sol::meta_function::index,
sol::resolve<glm::vec3 &(glm::mat3 &, glm::length_t)>(fetchIndex),
sol::meta_function::new_index,
sol::resolve<void(glm::mat3 &, glm::length_t, glm::vec3 const &)>(
storeIndex));
state.new_usertype<glm::mat4>(
"mat4",
sol::constructors<glm::mat4(), glm::mat4(float const &),
glm::mat4(float const &, float const &, float const &, float const &,
float const &, float const &, float const &, float const &,
float const &, float const &, float const &, float const &,
float const &, float const &, float const &, float const &)>(),
sol::meta_function::multiplication,
sol::overload(
sol::resolve<glm::mat4(glm::mat4 const &, glm::mat4 const &)>(
&glm::operator*),
sol::resolve<glm::mat4(glm::mat4 const &, float const&)>(&glm::operator*)),
sol::meta_function::division,
sol::overload(
sol::resolve<glm::mat4(glm::mat4 const &, glm::mat4 const &)>(
&glm::operator/),
sol::resolve<glm::mat4(glm::mat4 const &, float const&)>(&glm::operator/)),
sol::meta_function::addition,
sol::overload(
sol::resolve<glm::mat4(glm::mat4 const &, glm::mat4 const &)>(
&glm::operator+),
sol::resolve<glm::mat4(glm::mat4 const &, float const&)>(&glm::operator+)),
sol::meta_function::subtraction,
sol::overload(
sol::resolve<glm::mat4(glm::mat4 const &, glm::mat4 const &)>(
&glm::operator-),
sol::resolve<glm::mat4(glm::mat4 const &, float const&)>(&glm::operator-)),
sol::meta_function::index,
sol::resolve<glm::vec4 &(glm::mat4 &, glm::length_t)>(fetchIndex),
sol::meta_function::new_index,
sol::resolve<void(glm::mat4 &, glm::length_t, glm::vec4 const &)>(
storeIndex));
}
//
// Created by rleigh on 18/09/2022.
//
#ifndef VLK_LUAGLM_H
#define VLK_LUAGLM_H
#include <sol/sol.hpp>
void luaGlmInit(sol::state& state);
#endif // VLK_LUAGLM_H
//
// Created by rleigh on 18/09/2022.
//
#include <string>
#include <gtest/gtest.h>
#include "LuaGLM.h"
#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>
TEST(LuaGLM, Init) {
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::math,
sol::lib::debug, sol::lib::string, sol::lib::table);
luaGlmInit(lua);
lua.safe_script("print(\"Lua loaded GLM libraries\")");
}
template <typename T>
class LuaGLM : public testing::Test {
private:
void SetUp() override {
lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::math,
sol::lib::debug, sol::lib::string, sol::lib::table);
luaGlmInit(lua);
}
public:
sol::state lua;
static const T initVal;
static const std::string initStr;
static const std::string initLuaStr;
};
template <typename T>
class LuaGLMVec : public LuaGLM<T>
{};
template <typename T>
class LuaGLMMat : public LuaGLM<T>
{};
TYPED_TEST_CASE_P(LuaGLMVec);
TYPED_TEST_CASE_P(LuaGLMMat);
template<>
const glm::vec1 LuaGLM<glm::vec1>::initVal{2.4f};
template<>
const std::string LuaGLM<glm::vec1>::initStr{"vec1.new(2.4)"};
template<>
const std::string LuaGLM<glm::vec1>::initLuaStr{"vec1(2.400000)"};
template<>
const glm::vec2 LuaGLM<glm::vec2>::initVal{2.4f, 4.6f};
template<>
const std::string LuaGLM<glm::vec2>::initStr{"vec2.new(2.4, 4.6)"};
template<>
const std::string LuaGLM<glm::vec2>::initLuaStr{"vec2(2.400000, 4.600000)"};
template<>
const glm::vec3 LuaGLM<glm::vec3>::initVal{2.4f, 4.6f, 1.5f};
template<>
const std::string LuaGLM<glm::vec3>::initStr{"vec3.new(2.4, 4.6, 1.5)"};
template<>
const std::string LuaGLM<glm::vec3>::initLuaStr{"vec3(2.400000, 4.600000, 1.500000)"};
template<>
const glm::vec4 LuaGLM<glm::vec4>::initVal{2.4f, 4.6f, 1.5f, 9.3f};
template<>
const std::string LuaGLM<glm::vec4>::initStr{"vec4.new(2.4, 4.6, 1.5, 9.3)"};
template<>
const std::string LuaGLM<glm::vec4>::initLuaStr{"vec4(2.400000, 4.600000, 1.500000, 9.300000)"};
template<>
const glm::mat2 LuaGLM<glm::mat2>::initVal{2.4f, 4.6f, 3.9f, 1.6f};
template<>
const std::string LuaGLM<glm::mat2>::initStr{"mat2.new(2.4, 4.6, 3.9, 1.6)"};
template<>
const std::string LuaGLM<glm::mat2>::initLuaStr{"mat2x2((2.400000, 4.600000), (3.900000, 1.600000))"};
template<>
const glm::mat3 LuaGLM<glm::mat3>::initVal{2.4f, 4.6f, 3.9f, 1.6f, 3.5f, 8.1f, 7.4f, 0.7f, 5.4f};
template<>
const std::string LuaGLM<glm::mat3>::initStr{"mat3.new(2.4, 4.6, 3.9, 1.6, 3.5, 8.1, 7.4, 0.7, 5.4)"};
template<>
const std::string LuaGLM<glm::mat3>::initLuaStr{"mat3x3((2.400000, 4.600000, 3.900000), (1.600000, 3.500000, 8.100000), (7.400000, 0.700000, 5.400000))"};
template<>
const glm::mat4 LuaGLM<glm::mat4>::initVal{2.4f, 4.6f, 3.9f, 1.6f, 3.5f, 8.1f, 7.4f, 0.7f, 5.4f, 14.5f, 11.4f, 10.5f, 13.6f, 13.7f, 16.4f, 15.1f};
template<>
const std::string LuaGLM<glm::mat4>::initStr{"mat4.new(2.4, 4.6, 3.9, 1.6, 3.5, 8.1, 7.4, 0.7, 5.4, 14.5, 11.4, 10.5, 13.6, 13.7, 16.4, 15.1)"};
template<>
const std::string LuaGLM<glm::mat4>::initLuaStr{"mat4x4((2.400000, 4.600000, 3.900000, 1.600000), (3.500000, 8.100000, 7.400000, 0.700000), (5.400000, 14.500000, 11.400000, 10.500000), (13.600000, 13.700000, 16.400000, 15.100000))"};
#define ASSERT_GLM_VEC_EQ(Vec1, Vec2) \
for (typename TypeParam::length_type i = 0; i < TypeParam::length(); ++i) { \
ASSERT_FLOAT_EQ(Vec1[i], Vec2[i]); \
}
#define ASSERT_GLM_MAT_EQ(Mat1, Mat2) \
for (typename TypeParam::length_type i = 0; i < TypeParam::length(); ++i) { \
for (typename TypeParam::col_type::length_type j = 0; j < TypeParam::col_type::length(); ++j) { \
ASSERT_FLOAT_EQ(Mat1[i][j], Mat2[i][j]); \
} \
}
TYPED_TEST_P(LuaGLMVec, ConstructExt) {
sol::state& lua = this->lua;
lua["vec"] = TypeParam{this->initVal};
lua.safe_script("print(vec)");
lua.safe_script("str = tostring(vec)");
TypeParam ret = lua["vec"];
std::string str = lua["str"];
ASSERT_GLM_VEC_EQ(ret, this->initVal);
ASSERT_EQ(str, this->initLuaStr);
}
TYPED_TEST_P(LuaGLMMat, ConstructExt) {
sol::state &lua = this->lua;
lua["mat"] = TypeParam{this->initVal};
lua.safe_script("print(mat)");
lua.safe_script("str = tostring(mat)");
TypeParam ret = lua["mat"];
std::string str = lua["str"];
ASSERT_GLM_MAT_EQ(ret, this->initVal);
ASSERT_EQ(str, this->initLuaStr);
}
TYPED_TEST_P(LuaGLMVec, ConstructInt) {
sol::state& lua = this->lua;
std::string script{"vec = "};
script += this->initStr;
lua.safe_script(script);
lua.safe_script("print(vec)");
lua.safe_script("str = tostring(vec)");
TypeParam ret = lua["vec"];
std::string str = lua["str"];
ASSERT_GLM_VEC_EQ(ret, this->initVal);
ASSERT_EQ(str, this->initLuaStr);
}
TYPED_TEST_P(LuaGLMMat, ConstructInt) {
sol::state &lua = this->lua;
std::string script{"mat = "};
script += this->initStr;
lua.safe_script(script);
lua.safe_script("print(mat)");
lua.safe_script("str = tostring(mat)");
TypeParam ret = lua["mat"];
std::string str = lua["str"];
ASSERT_GLM_MAT_EQ(ret, this->initVal);
ASSERT_EQ(str, this->initLuaStr);
}
TYPED_TEST_P(LuaGLMVec, MultiplyScalar) {
sol::state& lua = this->lua;
lua["vec"] = TypeParam{this->initVal};
lua.safe_script("print(vec)");
lua.safe_script("vecm = vec * 2.0");
lua.safe_script("print(vecm)");
TypeParam ret = lua["vecm"];
ASSERT_GLM_VEC_EQ(ret, (this->initVal * 2.0f));
}
TYPED_TEST_P(LuaGLMMat, MultiplyScalar) {
sol::state &lua = this->lua;
lua["mat"] = TypeParam{this->initVal};
lua.safe_script("print(mat)");
lua.safe_script("matm = mat * 2.0");
lua.safe_script("print(matm)");
TypeParam ret = lua["matm"];
ASSERT_GLM_MAT_EQ(ret, (this->initVal * 2.0f));
}
TYPED_TEST_P(LuaGLMVec, DivideScalar) {
sol::state& lua = this->lua;
lua["vec"] = TypeParam{this->initVal};
lua.safe_script("print(vec)");
lua.safe_script("vecm = vec / 1.5");
lua.safe_script("print(vecm)");
TypeParam ret = lua["vecm"];
ASSERT_GLM_VEC_EQ(ret, (this->initVal / 1.5f));
}
TYPED_TEST_P(LuaGLMMat, DivideScalar) {
sol::state &lua = this->lua;
lua["mat"] = TypeParam{this->initVal};
lua.safe_script("print(mat)");
lua.safe_script("matm = mat / 1.5");
lua.safe_script("print(matm)");
TypeParam ret = lua["matm"];
ASSERT_GLM_MAT_EQ(ret, (this->initVal / 1.5f));
}
TYPED_TEST_P(LuaGLMVec, AddScalar) {
sol::state& lua = this->lua;
lua["vec"] = TypeParam{this->initVal};
lua.safe_script("print(vec)");
lua.safe_script("vecm = vec + 3.2");
lua.safe_script("print(vecm)");
TypeParam ret = lua["vecm"];
ASSERT_GLM_VEC_EQ(ret, (this->initVal + 3.2f));
}
TYPED_TEST_P(LuaGLMMat, AddScalar) {
sol::state &lua = this->lua;
lua["mat"] = TypeParam{this->initVal};
lua.safe_script("print(mat)");
lua.safe_script("matm = mat + 3.2");
lua.safe_script("print(matm)");
TypeParam ret = lua["matm"];
ASSERT_GLM_MAT_EQ(ret, (this->initVal + 3.2f));
}
TYPED_TEST_P(LuaGLMVec, SubtractScalar) {
sol::state& lua = this->lua;
lua["vec"] = TypeParam{this->initVal};
lua.safe_script("print(vec)");
lua.safe_script("vecm = vec - 1.43");
lua.safe_script("print(vecm)");
TypeParam ret = lua["vecm"];
ASSERT_GLM_VEC_EQ(ret, (this->initVal - 1.43f));
}
TYPED_TEST_P(LuaGLMMat, SubtractScalar) {
sol::state &lua = this->lua;
lua["mat"] = TypeParam{this->initVal};
lua.safe_script("print(mat)");
lua.safe_script("matm = mat - 1.43");
lua.safe_script("print(matm)");
TypeParam ret = lua["matm"];
ASSERT_GLM_MAT_EQ(ret, (this->initVal - 1.43f));
}
#if 0
TEST_F(LuaGLM, Vec1MultiplyVec1) {
lua.safe_script("vec = vec1.new(2.4)\n"
"vec = vec * vec\n"
"print(vec)");
glm::vec1 expected(5.76f);
glm::vec1 observed = lua["vec"];
ASSERT_FLOAT_EQ(observed[0], expected[0]);
}
TEST_F(LuaGLM, Vec1DivideVec1) {
lua.safe_script("vec = vec1.new(2.4)\n"
"vec = vec / vec\n"
"print(vec)");
glm::vec1 expected(1.0f);
glm::vec1 observed = lua["vec"];
ASSERT_FLOAT_EQ(observed[0], expected[0]);
}
TEST_F(LuaGLM, Vec1AddScalar) {
lua.safe_script("vec = vec1.new(2.4)\n"
"vec = vec + 2.0\n"
"print(vec)");
glm::vec1 expected(4.4f);
glm::vec1 observed = lua["vec"];
ASSERT_FLOAT_EQ(observed[0], expected[0]);
}
TEST_F(LuaGLM, Vec1AddVec1) {
lua.safe_script("vec = vec1.new(2.4)\n"
"vec = vec + vec\n"
"print(vec)");
glm::vec1 expected(4.8f);
glm::vec1 observed = lua["vec"];
ASSERT_FLOAT_EQ(observed[0], expected[0]);
}
TEST_F(LuaGLM, Vec1SubtractScalar) {
lua.safe_script("vec = vec1.new(2.4)\n"
"vec = vec - 2.0\n"
"print(vec)");
glm::vec1 expected(0.4f);
glm::vec1 observed = lua["vec"];
ASSERT_FLOAT_EQ(observed[0], expected[0]);
}
TEST_F(LuaGLM, Vec1SubtractVec1) {
lua.safe_script(R"(vec = vec1.new(2.4)
vec = vec - vec
print(vec))");
glm::vec1 expected(0.0f);
glm::vec1 observed = lua["vec"];
ASSERT_FLOAT_EQ(observed[0], expected[0]);
}
#endif
REGISTER_TYPED_TEST_CASE_P(LuaGLMVec,
ConstructExt,
ConstructInt,
MultiplyScalar,
DivideScalar,
AddScalar,
SubtractScalar);
REGISTER_TYPED_TEST_CASE_P(LuaGLMMat,
ConstructExt,
ConstructInt,
MultiplyScalar,
DivideScalar,
AddScalar,
SubtractScalar);
INSTANTIATE_TYPED_TEST_CASE_P(Vec1, LuaGLMVec, ::testing::Types<glm::vec1>);
INSTANTIATE_TYPED_TEST_CASE_P(Vec2, LuaGLMVec, ::testing::Types<glm::vec2>);
INSTANTIATE_TYPED_TEST_CASE_P(Vec3, LuaGLMVec, ::testing::Types<glm::vec3>);
INSTANTIATE_TYPED_TEST_CASE_P(Vec4, LuaGLMVec, ::testing::Types<glm::vec4>);
INSTANTIATE_TYPED_TEST_CASE_P(Mat2, LuaGLMMat, ::testing::Types<glm::mat2>);
INSTANTIATE_TYPED_TEST_CASE_P(Mat3, LuaGLMMat, ::testing::Types<glm::mat3>);
INSTANTIATE_TYPED_TEST_CASE_P(Mat4, LuaGLMMat, ::testing::Types<glm::mat4>);