diff --git a/Benchmarks/benchmark_luabridge.cpp b/Benchmarks/benchmark_luabridge.cpp index 8eff0c0d..95c77d5f 100644 --- a/Benchmarks/benchmark_luabridge.cpp +++ b/Benchmarks/benchmark_luabridge.cpp @@ -64,9 +64,8 @@ void table_global_string_get_measure(benchmark::State& state) luabridge::setGlobal(L, kMagicValue, "value"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += static_cast(luabridge::getGlobal(L, "value")); } @@ -78,9 +77,8 @@ void table_global_string_set_measure(benchmark::State& state) lua_State* L = makeLua(); double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; luabridge::setGlobal(L, v, "value"); } @@ -95,9 +93,8 @@ void table_get_measure(benchmark::State& state) luabridge::LuaRef t = luabridge::getGlobal(L, "warble"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += static_cast(t["value"]); } @@ -111,9 +108,8 @@ void table_set_measure(benchmark::State& state) luabridge::LuaRef t = luabridge::getGlobal(L, "warble"); double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; t["value"] = v; } @@ -127,9 +123,8 @@ void table_chained_get_measure(benchmark::State& state) luaDoStringOrThrow(L, "ulahibe = { warble = { value = 24.0 } }", "vanilla chained_get setup"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; luabridge::LuaRef tw = luabridge::getGlobal(L, "ulahibe")["warble"]; x += static_cast(tw["value"]); } @@ -143,9 +138,8 @@ void table_chained_set_measure(benchmark::State& state) luaDoStringOrThrow(L, "ulahibe = { warble = { value = 24.0 } }", "vanilla chained_set setup"); double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; luabridge::LuaRef tw = luabridge::getGlobal(L, "ulahibe")["warble"]; tw["value"] = v; @@ -160,9 +154,8 @@ void c_function_measure(benchmark::State& state) luabridge::getGlobalNamespace(L).addFunction("f", +[](double v) { return v; }); luaDoStringOrThrow(L, "function invoke_f() return f(24.0) end", "vanilla c_function setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_f"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_f"); lua_pop(L, 1); @@ -176,9 +169,8 @@ void lua_function_in_c_measure(benchmark::State& state) luabridge::LuaRef f = luabridge::getGlobal(L, "f"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += static_cast(f(kMagicValue)); } @@ -192,9 +184,8 @@ void c_function_through_lua_in_c_measure(benchmark::State& state) luabridge::LuaRef f = luabridge::getGlobal(L, "f"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += static_cast(f(kMagicValue)); } @@ -208,9 +199,8 @@ void member_function_call_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "vanilla member setup"); luaDoStringOrThrow(L, "function call_member() b:set(b:get() + 1.0) end", "vanilla member closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "call_member"); luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "vanilla call_member"); } @@ -223,9 +213,8 @@ void userdata_variable_access_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "vanilla userdata setup"); luaDoStringOrThrow(L, "function access_var() b.var = b.var + 1.0 return b.var end", "vanilla userdata closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "access_var"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla access_var"); lua_pop(L, 1); @@ -253,9 +242,8 @@ void multi_return_lua_measure(benchmark::State& state) luabridge::getGlobalNamespace(L).addCFunction("f", vanilla_multi_return); luaDoStringOrThrow(L, "function invoke_multi() local a,b=f(24.0) return a+b end", "vanilla multi_return_lua setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_multi"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_multi"); lua_pop(L, 1); @@ -281,9 +269,8 @@ void return_userdata_measure(benchmark::State& state) .addFunction("h", &basic_get_var); luaDoStringOrThrow(L, "function invoke_userdata() return h(f()) end", "vanilla return_userdata setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_userdata"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_userdata"); lua_pop(L, 1); @@ -296,9 +283,8 @@ void optional_success_measure(benchmark::State& state) luaDoStringOrThrow(L, "warble = { value = 24.0 }", "vanilla optional_success setup"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; luabridge::LuaRef tt = luabridge::getGlobal(L, "warble"); if (tt.isTable()) { @@ -320,9 +306,8 @@ void optional_half_failure_measure(benchmark::State& state) luaDoStringOrThrow(L, "warble = { value = 'x' }", "vanilla optional_half_failure setup"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; luabridge::LuaRef tt = luabridge::getGlobal(L, "warble"); if (tt.isTable()) { @@ -343,9 +328,8 @@ void optional_failure_measure(benchmark::State& state) lua_State* L = makeLua(); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; luabridge::LuaRef tt = luabridge::getGlobal(L, "warble"); if (tt.isTable()) { @@ -368,9 +352,8 @@ void userdata_variable_write_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "vanilla userdata_write setup"); luaDoStringOrThrow(L, "function write_var() b.var = 24.0 end", "vanilla userdata_write closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "write_var"); luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "vanilla write_var"); } @@ -383,9 +366,8 @@ void userdata_property_getter_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "vanilla property_getter setup"); luaDoStringOrThrow(L, "function read_getter() return b.val end", "vanilla property_getter closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "read_getter"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla read_getter"); lua_pop(L, 1); @@ -399,9 +381,8 @@ void userdata_property_setter_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "vanilla property_setter setup"); luaDoStringOrThrow(L, "function write_setter() b.val = 24.0 end", "vanilla property_setter closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "write_setter"); luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "vanilla write_setter"); } @@ -414,9 +395,8 @@ void lambda_capture_measure(benchmark::State& state) luabridge::getGlobalNamespace(L).addFunction("f", std::function([extra](double v) { return v + extra; })); luaDoStringOrThrow(L, "function invoke_lambda() return f(24.0) end", "vanilla lambda_capture setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_lambda"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_lambda"); lua_pop(L, 1); @@ -439,9 +419,8 @@ void static_member_function_call_measure(benchmark::State& state) registerCounter(L); luaDoStringOrThrow(L, "function invoke_static() return Counter.static_add(10, 32) end", "vanilla static_member_function setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_static"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_static"); lua_pop(L, 1); @@ -466,9 +445,8 @@ void derived_method_call_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = ComplexAB()", "vanilla derived_method setup"); luaDoStringOrThrow(L, "function call_derived() return obj:ab_func() end", "vanilla derived_method closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "call_derived"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla call_derived"); lua_pop(L, 1); diff --git a/Benchmarks/benchmark_luabridge3.cpp b/Benchmarks/benchmark_luabridge3.cpp index 83659702..35a3db78 100644 --- a/Benchmarks/benchmark_luabridge3.cpp +++ b/Benchmarks/benchmark_luabridge3.cpp @@ -175,9 +175,8 @@ void table_global_string_get_measure(benchmark::State& state) luabridge::setGlobal(L, kMagicValue, "value"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += static_cast(luabridge::getGlobal(L, "value")); } benchmark::DoNotOptimize(x); @@ -188,9 +187,8 @@ void table_global_string_set_measure(benchmark::State& state) lua_State* L = makeLua(); double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; luabridge::setGlobal(L, v, "value"); } @@ -205,9 +203,8 @@ void table_get_measure(benchmark::State& state) luabridge::LuaRef t = luabridge::getGlobal(L, "warble"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += static_cast(t["value"]); } @@ -221,9 +218,8 @@ void table_set_measure(benchmark::State& state) luabridge::LuaRef t = luabridge::getGlobal(L, "warble"); double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; t["value"] = v; } @@ -237,11 +233,10 @@ void table_chained_get_measure(benchmark::State& state) luaDoStringOrThrow(L, "ulahibe = { warble = { value = 24.0 } }", "table_chained_get setup"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; - luabridge::LuaRef tw = luabridge::getGlobal(L, "ulahibe")["warble"]; - x += static_cast(tw["value"]); + auto tw = luabridge::getGlobal(L, "ulahibe")["warble"]["value"]; + x += static_cast(tw); } benchmark::DoNotOptimize(x); @@ -253,12 +248,10 @@ void table_chained_set_measure(benchmark::State& state) luaDoStringOrThrow(L, "ulahibe = { warble = { value = 24.0 } }", "table_chained_set setup"); double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; - luabridge::LuaRef tw = luabridge::getGlobal(L, "ulahibe")["warble"]; - tw["value"] = v; + luabridge::getGlobal(L, "ulahibe")["warble"]["value"] = v; } benchmark::DoNotOptimize(v); @@ -271,9 +264,8 @@ void c_function_measure(benchmark::State& state) luaDoStringOrThrow(L, "function invoke_f() return f(24.0) end", "c_function setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_f"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_f"); lua_pop(L, 1); @@ -287,9 +279,8 @@ void lua_function_in_c_measure(benchmark::State& state) luabridge::LuaRef f = luabridge::getGlobal(L, "f"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += f.call(kMagicValue).valueOr(0.0); } @@ -303,9 +294,8 @@ void c_function_through_lua_in_c_measure(benchmark::State& state) luabridge::LuaRef f = luabridge::getGlobal(L, "f"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += f.call(kMagicValue).valueOr(0.0); } @@ -319,9 +309,8 @@ void member_function_call_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "member_function setup"); luaDoStringOrThrow(L, "function call_member() b:set(b:get() + 1.0) end", "member_function closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "call_member"); luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "call_member"); } @@ -334,9 +323,8 @@ void userdata_variable_access_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "userdata_variable_access setup"); luaDoStringOrThrow(L, "function access_var() return b.var end", "userdata_variable_access closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "access_var"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "access_var"); lua_pop(L, 1); @@ -350,9 +338,8 @@ void userdata_variable_access_large_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = cl()", "userdata_variable_access_large setup"); luaDoStringOrThrow(L, "function access_var_large() return b.var0 end", "userdata_variable_access_large closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "access_var_large"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "access_var_large"); lua_pop(L, 1); @@ -366,9 +353,8 @@ void userdata_variable_access_last_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = cl()", "userdata_variable_access_last setup"); luaDoStringOrThrow(L, "function access_var_last() return b.var49 end", "userdata_variable_access_last closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "access_var_last"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "access_var_last"); lua_pop(L, 1); @@ -382,9 +368,8 @@ void stateful_function_object_measure(benchmark::State& state) luabridge::LuaRef f = luabridge::getGlobal(L, "f"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += f.call(kMagicValue).valueOr(0.0); } @@ -397,9 +382,8 @@ void multi_return_lua_measure(benchmark::State& state) luabridge::getGlobalNamespace(L).addFunction("f", &lb3_multi_return); luaDoStringOrThrow(L, "function invoke_multi() local a,b=f(24.0) return a+b end", "multi_return_lua setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_multi"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_multi"); lua_pop(L, 1); @@ -413,9 +397,8 @@ void multi_return_measure(benchmark::State& state) luabridge::LuaRef f = luabridge::getGlobal(L, "f"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; auto result = f.call>(kMagicValue).valueOr(std::make_tuple(0.0, 0.0)); x += std::get<0>(result); x += std::get<1>(result); @@ -446,9 +429,8 @@ void derived_base_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = ComplexAB()", "base_derived setup"); luaDoStringOrThrow(L, "function call_base() return obj:a_func() + obj:b_func() end", "base_derived closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "call_base"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "call_base"); lua_pop(L, 1); @@ -461,9 +443,8 @@ void optional_success_measure(benchmark::State& state) luaDoStringOrThrow(L, "warble = { value = 24.0 }", "optional_success setup"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; auto result = luabridge::tryGetGlobalField(L, "warble", "value"); x += result ? *result : 1.0; } @@ -477,9 +458,8 @@ void optional_half_failure_measure(benchmark::State& state) luaDoStringOrThrow(L, "warble = { value = 'x' }", "optional_half_failure setup"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; auto result = luabridge::tryGetGlobalField(L, "warble", "value"); x += result ? *result : 1.0; } @@ -492,9 +472,8 @@ void optional_failure_measure(benchmark::State& state) lua_State* L = makeLua(); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; auto result = luabridge::tryGetGlobalField(L, "warble", "value"); x += result ? *result : 1.0; } @@ -511,9 +490,8 @@ void return_userdata_measure(benchmark::State& state) .addFunction("h", &basic_get_var); luaDoStringOrThrow(L, "function invoke_userdata() return h(f()) end", "return_userdata setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_userdata"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_userdata"); lua_pop(L, 1); @@ -527,9 +505,8 @@ void userdata_variable_write_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "userdata_variable_write setup"); luaDoStringOrThrow(L, "function write_var() b.var = 24.0 end", "userdata_variable_write closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "write_var"); luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "write_var"); } @@ -542,9 +519,8 @@ void userdata_property_getter_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "userdata_property_getter setup"); luaDoStringOrThrow(L, "function read_getter() return b.val end", "userdata_property_getter closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "read_getter"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "read_getter"); lua_pop(L, 1); @@ -558,9 +534,8 @@ void userdata_property_setter_measure(benchmark::State& state) luaDoStringOrThrow(L, "b = c()", "userdata_property_setter setup"); luaDoStringOrThrow(L, "function write_setter() b.val = 24.0 end", "userdata_property_setter closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "write_setter"); luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "write_setter"); } @@ -573,9 +548,8 @@ void lambda_capture_measure(benchmark::State& state) luabridge::getGlobalNamespace(L).addFunction("f", [extra](double v) { return v + extra; }); luaDoStringOrThrow(L, "function invoke_lambda() return f(24.0) end", "lambda_capture setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_lambda"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_lambda"); lua_pop(L, 1); @@ -588,9 +562,8 @@ void shared_ptr_return_measure(benchmark::State& state) registerSharedObject(L); luaDoStringOrThrow(L, "function invoke_shared() return get_shared():get() end", "shared_ptr_return setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_shared"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_shared"); lua_pop(L, 1); @@ -604,9 +577,8 @@ void shared_ptr_pass_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = SharedObject()", "shared_ptr_pass setup"); luaDoStringOrThrow(L, "function invoke_pass_shared() return use_shared(obj) end", "shared_ptr_pass closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_pass_shared"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_pass_shared"); lua_pop(L, 1); @@ -619,9 +591,8 @@ void static_member_function_call_measure(benchmark::State& state) registerCounter(L); luaDoStringOrThrow(L, "function invoke_static() return Counter.static_add(10, 32) end", "static_member_function setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_static"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_static"); lua_pop(L, 1); @@ -650,9 +621,8 @@ void derived_method_call_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = ComplexAB()", "derived_method setup"); luaDoStringOrThrow(L, "function call_derived() return obj:ab_func() end", "derived_method closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "call_derived"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "call_derived"); lua_pop(L, 1); @@ -676,9 +646,8 @@ void implicit_inheritance_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = ComplexAB()", "implicit_inheritance setup"); luaDoStringOrThrow(L, "function test_implicit() return call_a(obj) end", "implicit_inheritance closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "test_implicit"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "test_implicit"); lua_pop(L, 1); @@ -710,9 +679,8 @@ void converter_exact_type_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = Vec3Target(1, 2, 3)", "converter_exact_type setup"); luaDoStringOrThrow(L, "function invoke_exact() return sumVec3(obj) end", "converter_exact_type closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_exact"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_exact"); lua_pop(L, 1); @@ -726,9 +694,8 @@ void converter_phase3_value_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = Vec3Source(1, 2, 3)", "converter_phase3_value setup"); luaDoStringOrThrow(L, "function invoke_conv_value() return sumVec3(obj) end", "converter_phase3_value closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_conv_value"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_conv_value"); lua_pop(L, 1); @@ -742,9 +709,8 @@ void converter_phase3_ref_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = Vec3Source(1, 2, 3)", "converter_phase3_ref setup"); luaDoStringOrThrow(L, "function invoke_conv_ref() return sumVec3Ref(obj) end", "converter_phase3_ref closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_conv_ref"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_conv_ref"); lua_pop(L, 1); @@ -758,9 +724,8 @@ void converter_multi_registered_measure(benchmark::State& state) luaDoStringOrThrow(L, "obj = ColorSource(0.5, 1, 0)", "converter_multi_registered setup"); luaDoStringOrThrow(L, "function invoke_conv_multi() return sumVec3(obj) end", "converter_multi_registered closure setup"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua_getglobal(L, "invoke_conv_multi"); luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_conv_multi"); lua_pop(L, 1); diff --git a/Benchmarks/benchmark_sol3.cpp b/Benchmarks/benchmark_sol3.cpp index 17c06bb6..94324088 100644 --- a/Benchmarks/benchmark_sol3.cpp +++ b/Benchmarks/benchmark_sol3.cpp @@ -117,9 +117,8 @@ void table_global_string_get_measure(benchmark::State& state) lua["value"] = kMagicValue; double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += lua["value"].get(); } benchmark::DoNotOptimize(x); @@ -130,9 +129,8 @@ void table_global_string_set_measure(benchmark::State& state) sol::state lua; double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; lua["value"] = v; } @@ -147,9 +145,8 @@ void table_get_measure(benchmark::State& state) sol::table t = lua["warble"]; double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += t["value"].get(); } @@ -163,9 +160,8 @@ void table_set_measure(benchmark::State& state) sol::table t = lua["warble"]; double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; t["value"] = v; } @@ -179,9 +175,8 @@ void table_chained_get_measure(benchmark::State& state) lua.script("ulahibe = { warble = { value = 24.0 } }"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += lua["ulahibe"]["warble"]["value"].get(); } @@ -194,9 +189,8 @@ void table_chained_set_measure(benchmark::State& state) lua.script("ulahibe = { warble = { value = 24.0 } }"); double v = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; v += kMagicValue; lua["ulahibe"]["warble"]["value"] = v; } @@ -210,9 +204,8 @@ void c_function_measure(benchmark::State& state) lua.set_function("f", +[](double value) { return value; }); lua.script("function invoke_f() return f(24.0) end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["invoke_f"](); } } @@ -224,9 +217,8 @@ void lua_function_in_c_measure(benchmark::State& state) sol::function f = lua["f"]; double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += f.call(kMagicValue); } @@ -240,9 +232,8 @@ void c_function_through_lua_in_c_measure(benchmark::State& state) sol::function f = lua["f"]; double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += f.call(kMagicValue); } @@ -255,9 +246,8 @@ void member_function_call_measure(benchmark::State& state) registerBasic(lua); lua.script("b = c.new()\nfunction call_member() b:set(b:get() + 1.0) end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["call_member"](); } } @@ -268,9 +258,8 @@ void userdata_variable_access_measure(benchmark::State& state) registerBasic(lua); lua.script("b = c.new()\nfunction access_var() b.var = b.var + 1.0 return b.var end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["access_var"](); } } @@ -281,9 +270,8 @@ void userdata_variable_access_large_measure(benchmark::State& state) registerBasicLarge(lua); lua.script("b = cl.new()\nfunction access_var_large() b.var0 = b.var0 + 1 return b.var0 end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["access_var_large"](); } } @@ -294,9 +282,8 @@ void userdata_variable_access_last_measure(benchmark::State& state) registerBasicLarge(lua); lua.script("b = cl.new()\nfunction access_var_last() b.var49 = b.var49 + 1 return b.var49 end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["access_var_last"](); } } @@ -308,9 +295,8 @@ void stateful_function_object_measure(benchmark::State& state) sol::function f = lua["f"]; double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; x += f.call(kMagicValue); } @@ -323,9 +309,8 @@ void multi_return_lua_measure(benchmark::State& state) lua.set_function("f", &sol3_multi_return); lua.script("function invoke_multi() local a,b=f(24.0) return a+b end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["invoke_multi"](); } } @@ -337,9 +322,8 @@ void multi_return_measure(benchmark::State& state) sol::function f = lua["f"]; double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; std::tuple values = f.call(kMagicValue); x += std::get<0>(values); x += std::get<1>(values); @@ -370,9 +354,8 @@ void derived_base_measure(benchmark::State& state) lua.script("function call_base() return b:a_func() + b:b_func() end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["call_base"](); } } @@ -383,9 +366,8 @@ void optional_success_measure(benchmark::State& state) lua.script("warble = { value = 24.0 }"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; sol::optional value = lua["warble"]["value"]; x += value.value_or(1.0); } @@ -399,9 +381,8 @@ void optional_half_failure_measure(benchmark::State& state) lua.script("warble = { value = 'x' }"); double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; sol::optional value = lua["warble"]["value"]; x += value.value_or(1.0); } @@ -414,9 +395,8 @@ void optional_failure_measure(benchmark::State& state) sol::state lua; double x = 0; - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; sol::optional value = lua["warble"]["value"]; x += value.value_or(1.0); } @@ -432,9 +412,8 @@ void return_userdata_measure(benchmark::State& state) lua.set_function("h", &basic_get_var); lua.script("function invoke_userdata() return h(f()) end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["invoke_userdata"](); } } @@ -445,9 +424,8 @@ void userdata_variable_write_measure(benchmark::State& state) registerBasic(lua); lua.script("b = c.new()\nfunction write_var() b.var = 24.0 end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["write_var"](); } } @@ -458,9 +436,8 @@ void userdata_property_getter_measure(benchmark::State& state) registerBasicGetterSetter(lua); lua.script("b = c.new()\nfunction read_getter() return b.val end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["read_getter"](); } } @@ -471,9 +448,8 @@ void userdata_property_setter_measure(benchmark::State& state) registerBasicGetterSetter(lua); lua.script("b = c.new()\nfunction write_setter() b.val = 24.0 end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["write_setter"](); } } @@ -485,9 +461,8 @@ void lambda_capture_measure(benchmark::State& state) lua.set_function("f", [extra](double v) { return v + extra; }); lua.script("function invoke_lambda() return f(24.0) end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["invoke_lambda"](); } } @@ -498,9 +473,8 @@ void shared_ptr_return_measure(benchmark::State& state) registerSharedObject(lua); lua.script("function invoke_shared() return get_shared():get() end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["invoke_shared"](); } } @@ -511,9 +485,8 @@ void shared_ptr_pass_measure(benchmark::State& state) registerSharedObject(lua); lua.script("obj = SharedObject()\nfunction invoke_pass_shared() return use_shared(obj) end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["invoke_pass_shared"](); } } @@ -524,9 +497,8 @@ void static_member_function_call_measure(benchmark::State& state) registerCounter(lua); lua.script("function invoke_static() return Counter.static_add(10, 32) end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["invoke_static"](); } } @@ -553,9 +525,8 @@ void derived_method_call_measure(benchmark::State& state) lua["obj"] = &ab; lua.script("function call_derived() return obj:ab_func() end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["call_derived"](); } } @@ -577,9 +548,8 @@ void implicit_inheritance_measure(benchmark::State& state) lua.script("obj = ComplexAB.new()"); lua.script("function test_implicit() return call_a(obj) end"); - for (auto _ : state) + for ([[maybe_unused]] auto _ : state) { - (void) _; lua["test_implicit"](); } } diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 89e3215c..0f9c336b 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -10889,11 +10889,20 @@ class LuaRef : public LuaRefBase { }; + struct BorrowTableRef + { + }; + + struct ChainTableRef + { + }; + public: TableItem(lua_State* L, int tableRef) : LuaRefBase(L) , m_keyRef(luaL_ref(L, LUA_REGISTRYINDEX)) + , m_ownsTableRef(true) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(m_L, 1, detail::error_lua_stack_overflow); @@ -10907,6 +10916,7 @@ class LuaRef : public LuaRefBase TableItem(lua_State* L, int tableRef, const char (&key)[N]) : LuaRefBase(L) , m_keyLiteral(key) + , m_ownsTableRef(true) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(m_L, 1, detail::error_lua_stack_overflow); @@ -10920,6 +10930,7 @@ class LuaRef : public LuaRefBase : LuaRefBase(L) , m_tableRef(tableRef) , m_keyRef(luaL_ref(L, LUA_REGISTRYINDEX)) + , m_ownsTableRef(true) { } @@ -10928,6 +10939,43 @@ class LuaRef : public LuaRefBase : LuaRefBase(L) , m_tableRef(tableRef) , m_keyLiteral(key) + , m_ownsTableRef(true) + { + } + + TableItem(lua_State* L, int tableRef, BorrowTableRef) + : LuaRefBase(L) + , m_tableRef(tableRef) + , m_keyRef(luaL_ref(L, LUA_REGISTRYINDEX)) + { + } + + template + TableItem(lua_State* L, int tableRef, BorrowTableRef, const char (&key)[N]) + : LuaRefBase(L) + , m_tableRef(tableRef) + , m_keyLiteral(key) + { + } + + TableItem(TableItem&& table, ChainTableRef) + : LuaRefBase(table.m_L) + , m_tableRef(std::exchange(table.m_tableRef, LUA_NOREF)) + , m_keyRef(luaL_ref(m_L, LUA_REGISTRYINDEX)) + , m_parentKeyRef(std::exchange(table.m_keyRef, LUA_NOREF)) + , m_parentKeyLiteral(std::exchange(table.m_keyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(table.m_ownsTableRef, false)) + { + } + + template + TableItem(TableItem&& table, ChainTableRef, const char (&key)[N]) + : LuaRefBase(table.m_L) + , m_tableRef(std::exchange(table.m_tableRef, LUA_NOREF)) + , m_parentKeyRef(std::exchange(table.m_keyRef, LUA_NOREF)) + , m_keyLiteral(key) + , m_parentKeyLiteral(std::exchange(table.m_keyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(table.m_ownsTableRef, false)) { } @@ -10939,13 +10987,14 @@ class LuaRef : public LuaRefBase return; #endif - lua_rawgeti(m_L, LUA_REGISTRYINDEX, other.m_tableRef); + other.pushTable(m_L); m_tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + m_ownsTableRef = true; m_keyLiteral = other.m_keyLiteral; if (other.m_keyRef != LUA_NOREF) { - lua_rawgeti(m_L, LUA_REGISTRYINDEX, other.m_keyRef); + other.pushKey(m_L); m_keyRef = luaL_ref(m_L, LUA_REGISTRYINDEX); } } @@ -10955,7 +11004,10 @@ class LuaRef : public LuaRefBase if (m_keyRef != LUA_NOREF) luaL_unref(m_L, LUA_REGISTRYINDEX, m_keyRef); - if (m_tableRef != LUA_NOREF) + if (m_parentKeyRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_parentKeyRef); + + if (m_ownsTableRef && m_tableRef != LUA_NOREF) luaL_unref(m_L, LUA_REGISTRYINDEX, m_tableRef); } @@ -10974,7 +11026,10 @@ class LuaRef : public LuaRefBase : LuaRefBase(other.m_L) , m_tableRef(std::exchange(other.m_tableRef, LUA_NOREF)) , m_keyRef(std::exchange(other.m_keyRef, LUA_NOREF)) + , m_parentKeyRef(std::exchange(other.m_parentKeyRef, LUA_NOREF)) , m_keyLiteral(std::exchange(other.m_keyLiteral, nullptr)) + , m_parentKeyLiteral(std::exchange(other.m_parentKeyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(other.m_ownsTableRef, false)) { } @@ -10985,13 +11040,18 @@ class LuaRef : public LuaRefBase if (m_keyRef != LUA_NOREF) luaL_unref(m_L, LUA_REGISTRYINDEX, m_keyRef); - if (m_tableRef != LUA_NOREF) + if (m_parentKeyRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_parentKeyRef); + if (m_ownsTableRef && m_tableRef != LUA_NOREF) luaL_unref(m_L, LUA_REGISTRYINDEX, m_tableRef); m_L = other.m_L; m_tableRef = std::exchange(other.m_tableRef, LUA_NOREF); m_keyRef = std::exchange(other.m_keyRef, LUA_NOREF); + m_parentKeyRef = std::exchange(other.m_parentKeyRef, LUA_NOREF); m_keyLiteral = std::exchange(other.m_keyLiteral, nullptr); + m_parentKeyLiteral = std::exchange(other.m_parentKeyLiteral, nullptr); + m_ownsTableRef = std::exchange(other.m_ownsTableRef, false); return *this; } @@ -11000,13 +11060,13 @@ class LuaRef : public LuaRefBase TableItem& operator=(const T& v) { #if LUABRIDGE_SAFE_STACK_CHECKS - if (! lua_checkstack(m_L, 2)) + if (! lua_checkstack(m_L, 3)) return *this; #endif const StackRestore stackRestore(m_L); - lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_tableRef); + pushTable(m_L); if (m_keyLiteral != nullptr) { if (! Stack::push(m_L, v)) @@ -11016,7 +11076,7 @@ class LuaRef : public LuaRefBase } else { - lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_keyRef); + pushKey(m_L); if (! Stack::push(m_L, v)) return *this; @@ -11031,13 +11091,13 @@ class LuaRef : public LuaRefBase TableItem& rawset(const T& v) { #if LUABRIDGE_SAFE_STACK_CHECKS - if (! lua_checkstack(m_L, 2)) + if (! lua_checkstack(m_L, 3)) return *this; #endif const StackRestore stackRestore(m_L); - lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_tableRef); + pushTable(m_L); if (m_keyLiteral != nullptr) { lua_pushstring(m_L, m_keyLiteral); @@ -11049,7 +11109,7 @@ class LuaRef : public LuaRefBase } else { - lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_keyRef); + pushKey(m_L); if (! Stack::push(m_L, v)) return *this; @@ -11074,7 +11134,7 @@ class LuaRef : public LuaRefBase return; #endif - lua_rawgeti(L, LUA_REGISTRYINDEX, m_tableRef); + pushTable(L); if (m_keyLiteral != nullptr) { @@ -11082,7 +11142,7 @@ class LuaRef : public LuaRefBase } else { - lua_rawgeti(L, LUA_REGISTRYINDEX, m_keyRef); + pushKey(L); lua_gettable(L, -2); } @@ -11090,25 +11150,53 @@ class LuaRef : public LuaRefBase } template - TableItem operator[](const T& key) const + TableItem operator[](const T& key) const& { const StackRestore stackRestore(m_L); - push(m_L); + if (! Stack::push(m_L, key)) + lua_pushnil(m_L); + + return materializedChildFromStackKey(); + } + + template + TableItem operator[](const T& key) && + { + if (canChain()) + { + if (! Stack::push(m_L, key)) + lua_pushnil(m_L); + + return TableItem(std::move(*this), ChainTableRef{}); + } + + const StackRestore stackRestore(m_L); if (! Stack::push(m_L, key)) lua_pushnil(m_L); - lua_pushvalue(m_L, -2); + return materializedChildFromStackKey(); + } + + template + TableItem operator[](const char (&key)[N]) const& + { + const StackRestore stackRestore(m_L); + + push(m_L); + const auto tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); - lua_remove(m_L, -2); - return TableItem(m_L, tableRef, AdoptTableRef{}); + return TableItem(m_L, tableRef, AdoptTableRef{}, key); } template - TableItem operator[](const char (&key)[N]) const + TableItem operator[](const char (&key)[N]) && { + if (canChain()) + return TableItem(std::move(*this), ChainTableRef{}, key); + const StackRestore stackRestore(m_L); push(m_L); @@ -11164,12 +11252,73 @@ class LuaRef : public LuaRefBase swap(m_L, other.m_L); swap(m_tableRef, other.m_tableRef); swap(m_keyRef, other.m_keyRef); + swap(m_parentKeyRef, other.m_parentKeyRef); swap(m_keyLiteral, other.m_keyLiteral); + swap(m_parentKeyLiteral, other.m_parentKeyLiteral); + swap(m_ownsTableRef, other.m_ownsTableRef); + } + + bool hasParentKey() const + { + return m_parentKeyLiteral != nullptr || m_parentKeyRef != LUA_NOREF; + } + + bool canChain() const + { + return m_ownsTableRef && ! hasParentKey(); + } + + TableItem materializedChildFromStackKey() const + { + push(m_L); + lua_insert(m_L, -2); + + lua_pushvalue(m_L, -2); + const auto tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + lua_remove(m_L, -2); + + return TableItem(m_L, tableRef, AdoptTableRef{}); + } + + void pushTable(lua_State* L) const + { + if (m_tableRef == LUA_REFNIL) + lua_pushnil(L); + else + lua_rawgeti(L, LUA_REGISTRYINDEX, m_tableRef); + + if (m_parentKeyLiteral != nullptr) + { + lua_getfield(L, -1, m_parentKeyLiteral); + lua_remove(L, -2); + } + else if (m_parentKeyRef != LUA_NOREF) + { + pushRef(L, m_parentKeyRef); + lua_gettable(L, -2); + lua_remove(L, -2); + } + } + + void pushKey(lua_State* L) const + { + pushRef(L, m_keyRef); + } + + void pushRef(lua_State* L, int ref) const + { + if (ref == LUA_REFNIL) + lua_pushnil(L); + else + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); } int m_tableRef = LUA_NOREF; int m_keyRef = LUA_NOREF; + int m_parentKeyRef = LUA_NOREF; const char* m_keyLiteral = nullptr; + const char* m_parentKeyLiteral = nullptr; + bool m_ownsTableRef = false; }; friend struct Stack; @@ -11376,7 +11525,10 @@ class LuaRef : public LuaRefBase TableItem operator[](const T& key) const& { if (! Stack::push(m_L, key)) + { + lua_pushnil(m_L); return TableItem(m_L, m_ref); + } return TableItem(m_L, m_ref); } @@ -11385,7 +11537,10 @@ class LuaRef : public LuaRefBase TableItem operator[](const T& key) && { if (! Stack::push(m_L, key)) + { + lua_pushnil(m_L); return TableItem(m_L, m_ref); + } return TableItem(m_L, std::exchange(m_ref, LUA_NOREF), typename TableItem::AdoptTableRef{}); } diff --git a/Images/benchmarks.png b/Images/benchmarks.png index a56c047d..7017040a 100644 Binary files a/Images/benchmarks.png and b/Images/benchmarks.png differ diff --git a/Source/LuaBridge/detail/LuaRef.h b/Source/LuaBridge/detail/LuaRef.h index 575c61d9..21d33a61 100644 --- a/Source/LuaBridge/detail/LuaRef.h +++ b/Source/LuaBridge/detail/LuaRef.h @@ -681,7 +681,13 @@ class LuaRef : public LuaRefBase { }; + struct ChainTableRef + { + }; + public: + // Table items own the table they reference so stored proxies remain valid after the parent LuaRef changes. + // Rvalue indexing can adopt the parent table ref and defer one chained lookup to avoid extra registry churn. //========================================================================================= /** * @brief Construct a TableItem from a table value. @@ -695,6 +701,7 @@ class LuaRef : public LuaRefBase TableItem(lua_State* L, int tableRef) : LuaRefBase(L) , m_keyRef(luaL_ref(L, LUA_REGISTRYINDEX)) + , m_ownsTableRef(true) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(m_L, 1, detail::error_lua_stack_overflow); @@ -708,6 +715,7 @@ class LuaRef : public LuaRefBase TableItem(lua_State* L, int tableRef, const char (&key)[N]) : LuaRefBase(L) , m_keyLiteral(key) + , m_ownsTableRef(true) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(m_L, 1, detail::error_lua_stack_overflow); @@ -721,6 +729,7 @@ class LuaRef : public LuaRefBase : LuaRefBase(L) , m_tableRef(tableRef) , m_keyRef(luaL_ref(L, LUA_REGISTRYINDEX)) + , m_ownsTableRef(true) { } @@ -729,6 +738,28 @@ class LuaRef : public LuaRefBase : LuaRefBase(L) , m_tableRef(tableRef) , m_keyLiteral(key) + , m_ownsTableRef(true) + { + } + + TableItem(TableItem&& table, ChainTableRef) + : LuaRefBase(table.m_L) + , m_tableRef(std::exchange(table.m_tableRef, LUA_NOREF)) + , m_keyRef(luaL_ref(m_L, LUA_REGISTRYINDEX)) + , m_parentKeyRef(std::exchange(table.m_keyRef, LUA_NOREF)) + , m_parentKeyLiteral(std::exchange(table.m_keyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(table.m_ownsTableRef, false)) + { + } + + template + TableItem(TableItem&& table, ChainTableRef, const char (&key)[N]) + : LuaRefBase(table.m_L) + , m_tableRef(std::exchange(table.m_tableRef, LUA_NOREF)) + , m_parentKeyRef(std::exchange(table.m_keyRef, LUA_NOREF)) + , m_keyLiteral(key) + , m_parentKeyLiteral(std::exchange(table.m_keyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(table.m_ownsTableRef, false)) { } @@ -749,13 +780,14 @@ class LuaRef : public LuaRefBase return; #endif - lua_rawgeti(m_L, LUA_REGISTRYINDEX, other.m_tableRef); + other.pushTable(m_L); m_tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + m_ownsTableRef = true; m_keyLiteral = other.m_keyLiteral; if (other.m_keyRef != LUA_NOREF) { - lua_rawgeti(m_L, LUA_REGISTRYINDEX, other.m_keyRef); + other.pushKey(m_L); m_keyRef = luaL_ref(m_L, LUA_REGISTRYINDEX); } } @@ -771,7 +803,10 @@ class LuaRef : public LuaRefBase if (m_keyRef != LUA_NOREF) luaL_unref(m_L, LUA_REGISTRYINDEX, m_keyRef); - if (m_tableRef != LUA_NOREF) + if (m_parentKeyRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_parentKeyRef); + + if (m_ownsTableRef && m_tableRef != LUA_NOREF) luaL_unref(m_L, LUA_REGISTRYINDEX, m_tableRef); } @@ -814,7 +849,10 @@ class LuaRef : public LuaRefBase : LuaRefBase(other.m_L) , m_tableRef(std::exchange(other.m_tableRef, LUA_NOREF)) , m_keyRef(std::exchange(other.m_keyRef, LUA_NOREF)) + , m_parentKeyRef(std::exchange(other.m_parentKeyRef, LUA_NOREF)) , m_keyLiteral(std::exchange(other.m_keyLiteral, nullptr)) + , m_parentKeyLiteral(std::exchange(other.m_parentKeyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(other.m_ownsTableRef, false)) { } @@ -835,13 +873,18 @@ class LuaRef : public LuaRefBase if (m_keyRef != LUA_NOREF) luaL_unref(m_L, LUA_REGISTRYINDEX, m_keyRef); - if (m_tableRef != LUA_NOREF) + if (m_parentKeyRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_parentKeyRef); + if (m_ownsTableRef && m_tableRef != LUA_NOREF) luaL_unref(m_L, LUA_REGISTRYINDEX, m_tableRef); m_L = other.m_L; m_tableRef = std::exchange(other.m_tableRef, LUA_NOREF); m_keyRef = std::exchange(other.m_keyRef, LUA_NOREF); + m_parentKeyRef = std::exchange(other.m_parentKeyRef, LUA_NOREF); m_keyLiteral = std::exchange(other.m_keyLiteral, nullptr); + m_parentKeyLiteral = std::exchange(other.m_parentKeyLiteral, nullptr); + m_ownsTableRef = std::exchange(other.m_ownsTableRef, false); return *this; } @@ -862,13 +905,13 @@ class LuaRef : public LuaRefBase TableItem& operator=(const T& v) { #if LUABRIDGE_SAFE_STACK_CHECKS - if (! lua_checkstack(m_L, 2)) + if (! lua_checkstack(m_L, 3)) return *this; #endif const StackRestore stackRestore(m_L); - lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_tableRef); + pushTable(m_L); if (m_keyLiteral != nullptr) { if (! Stack::push(m_L, v)) @@ -878,7 +921,7 @@ class LuaRef : public LuaRefBase } else { - lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_keyRef); + pushKey(m_L); if (! Stack::push(m_L, v)) return *this; @@ -905,13 +948,13 @@ class LuaRef : public LuaRefBase TableItem& rawset(const T& v) { #if LUABRIDGE_SAFE_STACK_CHECKS - if (! lua_checkstack(m_L, 2)) + if (! lua_checkstack(m_L, 3)) return *this; #endif const StackRestore stackRestore(m_L); - lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_tableRef); + pushTable(m_L); if (m_keyLiteral != nullptr) { lua_pushstring(m_L, m_keyLiteral); @@ -923,7 +966,7 @@ class LuaRef : public LuaRefBase } else { - lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_keyRef); + pushKey(m_L); if (! Stack::push(m_L, v)) return *this; @@ -952,7 +995,7 @@ class LuaRef : public LuaRefBase return; #endif - lua_rawgeti(L, LUA_REGISTRYINDEX, m_tableRef); + pushTable(L); if (m_keyLiteral != nullptr) { @@ -960,7 +1003,7 @@ class LuaRef : public LuaRefBase } else { - lua_rawgeti(L, LUA_REGISTRYINDEX, m_keyRef); + pushKey(L); lua_gettable(L, -2); } @@ -980,25 +1023,53 @@ class LuaRef : public LuaRefBase * @returns A Lua table item reference. */ template - TableItem operator[](const T& key) const + TableItem operator[](const T& key) const& { const StackRestore stackRestore(m_L); - push(m_L); + if (! Stack::push(m_L, key)) + lua_pushnil(m_L); + + return materializedChildFromStackKey(); + } + + template + TableItem operator[](const T& key) && + { + if (canChain()) + { + if (! Stack::push(m_L, key)) + lua_pushnil(m_L); + + return TableItem(std::move(*this), ChainTableRef{}); + } + + const StackRestore stackRestore(m_L); if (! Stack::push(m_L, key)) lua_pushnil(m_L); - lua_pushvalue(m_L, -2); + return materializedChildFromStackKey(); + } + + template + TableItem operator[](const char (&key)[N]) const& + { + const StackRestore stackRestore(m_L); + + push(m_L); + const auto tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); - lua_remove(m_L, -2); - return TableItem(m_L, tableRef, AdoptTableRef{}); + return TableItem(m_L, tableRef, AdoptTableRef{}, key); } template - TableItem operator[](const char (&key)[N]) const + TableItem operator[](const char (&key)[N]) && { + if (canChain()) + return TableItem(std::move(*this), ChainTableRef{}, key); + const StackRestore stackRestore(m_L); push(m_L); @@ -1081,12 +1152,73 @@ class LuaRef : public LuaRefBase swap(m_L, other.m_L); swap(m_tableRef, other.m_tableRef); swap(m_keyRef, other.m_keyRef); + swap(m_parentKeyRef, other.m_parentKeyRef); swap(m_keyLiteral, other.m_keyLiteral); + swap(m_parentKeyLiteral, other.m_parentKeyLiteral); + swap(m_ownsTableRef, other.m_ownsTableRef); + } + + bool hasParentKey() const + { + return m_parentKeyLiteral != nullptr || m_parentKeyRef != LUA_NOREF; + } + + bool canChain() const + { + return m_ownsTableRef && ! hasParentKey(); + } + + TableItem materializedChildFromStackKey() const + { + push(m_L); + lua_insert(m_L, -2); + + lua_pushvalue(m_L, -2); + const auto tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + lua_remove(m_L, -2); + + return TableItem(m_L, tableRef, AdoptTableRef{}); + } + + void pushTable(lua_State* L) const + { + if (m_tableRef == LUA_REFNIL) + lua_pushnil(L); + else + lua_rawgeti(L, LUA_REGISTRYINDEX, m_tableRef); + + if (m_parentKeyLiteral != nullptr) + { + lua_getfield(L, -1, m_parentKeyLiteral); + lua_remove(L, -2); + } + else if (m_parentKeyRef != LUA_NOREF) + { + pushRef(L, m_parentKeyRef); + lua_gettable(L, -2); + lua_remove(L, -2); + } + } + + void pushKey(lua_State* L) const + { + pushRef(L, m_keyRef); + } + + void pushRef(lua_State* L, int ref) const + { + if (ref == LUA_REFNIL) + lua_pushnil(L); + else + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); } int m_tableRef = LUA_NOREF; int m_keyRef = LUA_NOREF; + int m_parentKeyRef = LUA_NOREF; const char* m_keyLiteral = nullptr; + const char* m_parentKeyLiteral = nullptr; + bool m_ownsTableRef = false; }; friend struct Stack; @@ -1475,7 +1607,10 @@ class LuaRef : public LuaRefBase TableItem operator[](const T& key) const& { if (! Stack::push(m_L, key)) + { + lua_pushnil(m_L); return TableItem(m_L, m_ref); + } return TableItem(m_L, m_ref); } @@ -1484,7 +1619,10 @@ class LuaRef : public LuaRefBase TableItem operator[](const T& key) && { if (! Stack::push(m_L, key)) + { + lua_pushnil(m_L); return TableItem(m_L, m_ref); + } return TableItem(m_L, std::exchange(m_ref, LUA_NOREF), typename TableItem::AdoptTableRef{}); } diff --git a/Tests/Source/LuaRefTests.cpp b/Tests/Source/LuaRefTests.cpp index d8dba94b..0dc0adaa 100644 --- a/Tests/Source/LuaRefTests.cpp +++ b/Tests/Source/LuaRefTests.cpp @@ -11,8 +11,25 @@ namespace { int addInts(int a, int b) { return a + b; } + +struct FailingPushKey +{ +}; } // namespace +namespace luabridge { + +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State*, const FailingPushKey&) + { + return makeErrorCode(ErrorCode::InvalidTypeCast); + } +}; + +} // namespace luabridge + struct LuaRefTests : TestBase { }; @@ -1247,6 +1264,220 @@ TEST_F(LuaRefTests, TableItemOperatorIndexAdoptPathAndRawRoundTrip) EXPECT_EQ(stackTopBefore, lua_gettop(L)); } +TEST_F(LuaRefTests, TableItemCopyPushesNilTableRef) +{ + luabridge::LuaRef nilRef(L, luabridge::LuaNil{}); + + auto item = nilRef["key"]; + auto itemCopy = item; + + EXPECT_TRUE(itemCopy.isValid()); + EXPECT_TRUE(item.isValid()); +} + +TEST_F(LuaRefTests, TableItemCopyPushesNilKeyRef) +{ + runLua("result = {}"); + + auto table = result(); + auto item = table[luabridge::LuaNil{}]; + auto itemCopy = item; + + EXPECT_TRUE(itemCopy.isValid()); + EXPECT_TRUE(item.isValid()); +} + +TEST_F(LuaRefTests, LuaRefRvalueOperatorIndexPushFailureKeepsStackBalanced) +{ + runLua("result = {}"); + + auto table = result(); + const int stackTopBefore = lua_gettop(L); + + auto item = std::move(table)[FailingPushKey{}]; + + EXPECT_TRUE(item.isValid()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemRvalueLiteralChainGetSetKeepsStackBalanced) +{ + runLua("result = { outer = { value = 10 } }"); + + const int stackTopBefore = lua_gettop(L); + + EXPECT_EQ(10, result()["outer"]["value"].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + result()["outer"]["value"] = 44; + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + EXPECT_EQ(44, result()["outer"]["value"].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemRvalueDynamicChainGetSetKeepsStackBalanced) +{ + runLua("result = { outer = { value = 11 } }"); + + std::string outerKey = "outer"; + std::string valueKey = "value"; + + const int stackTopBefore = lua_gettop(L); + + auto value = result()[outerKey][valueKey]; + ASSERT_TRUE(value.isValid()); + EXPECT_EQ(11, value.unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + value = 22; + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + EXPECT_EQ(22, result()[outerKey][valueKey].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemStoredRvalueChainOwnsRootTable) +{ + runLua("result = { outer = { value = 7 } }"); + + auto value = result()["outer"]["value"]; + + runLua("result = { outer = { value = 99 } }"); + + EXPECT_EQ(7, value.unsafe_cast()); + + value = 8; + + EXPECT_EQ(8, value.unsafe_cast()); + EXPECT_EQ(99, result()["outer"]["value"].unsafe_cast()); +} + +TEST_F(LuaRefTests, TableItemStoredLvalueItemOwnsParentTable) +{ + runLua("result = { value = 31 }"); + + auto table = result(); + auto value = table["value"]; + + table = luabridge::LuaRef(L); + + EXPECT_EQ(31, value.unsafe_cast()); +} + +TEST_F(LuaRefTests, TableItemRvalueNumericChainGetSetKeepsStackBalanced) +{ + runLua("result = { [3] = { [4] = 12 } }"); + + const int stackTopBefore = lua_gettop(L); + + EXPECT_EQ(12, result()[3][4].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + result()[3][4] = 45; + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + EXPECT_EQ(45, result()[3][4].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemRvalueDeepChainMaterializesAfterDeferredParent) +{ + runLua("result = { a = { b = { c = 5 } } }"); + + const int stackTopBefore = lua_gettop(L); + + EXPECT_EQ(5, result()["a"]["b"]["c"].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + result()["a"]["b"]["c"] = 6; + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + EXPECT_EQ(6, result()["a"]["b"]["c"].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemRvalueDynamicDeepChainMaterializesAfterDeferredParent) +{ + runLua("result = { outer = { inner = { value = 12 } } }"); + + std::string outerKey = "outer"; + std::string innerKey = "inner"; + std::string valueKey = "value"; + + const int stackTopBefore = lua_gettop(L); + + auto value = result()[outerKey][innerKey][valueKey]; + ASSERT_TRUE(value.isValid()); + EXPECT_EQ(12, value.unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + value = 24; + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + EXPECT_EQ(24, result()[outerKey][innerKey][valueKey].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemRvalueDynamicChainUsesNilKeyWhenPushFails) +{ + runLua("result = { outer = { inner = {} } }"); + + std::string outerKey = "outer"; + std::string innerKey = "inner"; + const int stackTopBefore = lua_gettop(L); + + auto chained = result()[outerKey][FailingPushKey{}]; + EXPECT_TRUE(chained.isValid()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + auto materialized = result()[outerKey][innerKey][FailingPushKey{}]; + EXPECT_TRUE(materialized.isValid()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemRvalueChainRawSetAndRawGet) +{ + runLua("result = { outer = { value = 1 } }"); + + const int stackTopBefore = lua_gettop(L); + + result()["outer"]["value"].rawset(73); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); + + auto value = result()["outer"].rawget("value"); + ASSERT_TRUE(value.isNumber()); + EXPECT_EQ(73, luabridge::unsafe_cast(value)); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemRvalueChainUsesParentMetamethod) +{ + runLua("result = setmetatable({}, { __index = function(_, key) if key == 'outer' then return { value = 64 } end end })"); + + const int stackTopBefore = lua_gettop(L); + + EXPECT_EQ(64, result()["outer"]["value"].unsafe_cast()); + EXPECT_EQ(stackTopBefore, lua_gettop(L)); +} + +TEST_F(LuaRefTests, TableItemRvalueChainCopyAndMove) +{ + runLua("result = { outer = { value = 9 } }"); + + auto value = result()["outer"]["value"]; + auto valueCopy = value; + auto valueMoved = std::move(value); + + EXPECT_EQ(9, valueCopy.unsafe_cast()); + EXPECT_EQ(9, valueMoved.unsafe_cast()); + + valueMoved = 10; + + EXPECT_EQ(10, valueCopy.unsafe_cast()); + EXPECT_EQ(10, valueMoved.unsafe_cast()); +} + TEST_F(LuaRefTests, GetClassNameNoMetatable) { // A raw userdata with no metatable causes lua_getmetatable to return 0,