Skip to content

Commit 120155f

Browse files
p-ouellettesfan5
authored andcommitted
Fix some issues with minetest.clear_craft (#8712)
* Fix some issues with minetest.clear_craft - Fix memory leak - Fix crafts with an output count not being cleared when clearing by input. - Fix recipe list being reversed when clearing by input. * Add CraftInput::empty()
1 parent 86d7f84 commit 120155f

File tree

6 files changed

+119
-124
lines changed

6 files changed

+119
-124
lines changed

games/minimal/mods/test/crafting.lua

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
local function test_clear_craft()
2+
minetest.log("info", "Testing clear_craft")
3+
-- Clearing by output
4+
minetest.register_craft({
5+
output = "foo",
6+
recipe = {{"bar"}}
7+
})
8+
minetest.register_craft({
9+
output = "foo 4",
10+
recipe = {{"foo", "bar"}}
11+
})
12+
assert(#minetest.get_all_craft_recipes("foo") == 2)
13+
minetest.clear_craft({output="foo"})
14+
assert(minetest.get_all_craft_recipes("foo") == nil)
15+
-- Clearing by input
16+
minetest.register_craft({
17+
output = "foo 4",
18+
recipe = {{"foo", "bar"}}
19+
})
20+
assert(#minetest.get_all_craft_recipes("foo") == 1)
21+
minetest.clear_craft({recipe={{"foo", "bar"}}})
22+
assert(minetest.get_all_craft_recipes("foo") == nil)
23+
end
24+
test_clear_craft()
25+
26+
local function test_get_craft_result()
27+
minetest.log("info", "test_get_craft_result()")
28+
-- normal
29+
local input = {
30+
method = "normal",
31+
width = 2,
32+
items = {"", "default:coal_lump", "", "default:stick"}
33+
}
34+
minetest.log("info", "torch crafting input: "..dump(input))
35+
local output, decremented_input = minetest.get_craft_result(input)
36+
minetest.log("info", "torch crafting output: "..dump(output))
37+
minetest.log("info", "torch crafting decremented input: "..dump(decremented_input))
38+
assert(output.item)
39+
minetest.log("info", "torch crafting output.item:to_table(): "..dump(output.item:to_table()))
40+
assert(output.item:get_name() == "default:torch")
41+
assert(output.item:get_count() == 4)
42+
-- fuel
43+
local input = {
44+
method = "fuel",
45+
width = 1,
46+
items = {"default:coal_lump"}
47+
}
48+
minetest.log("info", "coal fuel input: "..dump(input))
49+
local output, decremented_input = minetest.get_craft_result(input)
50+
minetest.log("info", "coal fuel output: "..dump(output))
51+
minetest.log("info", "coal fuel decremented input: "..dump(decremented_input))
52+
assert(output.time)
53+
assert(output.time > 0)
54+
-- cook
55+
local input = {
56+
method = "cooking",
57+
width = 1,
58+
items = {"default:cobble"}
59+
}
60+
minetest.log("info", "cobble cooking input: "..dump(output))
61+
local output, decremented_input = minetest.get_craft_result(input)
62+
minetest.log("info", "cobble cooking output: "..dump(output))
63+
minetest.log("info", "cobble cooking decremented input: "..dump(decremented_input))
64+
assert(output.time)
65+
assert(output.time > 0)
66+
assert(output.item)
67+
minetest.log("info", "cobble cooking output.item:to_table(): "..dump(output.item:to_table()))
68+
assert(output.item:get_name() == "default:stone")
69+
assert(output.item:get_count() == 1)
70+
end
71+
test_get_craft_result()

games/minimal/mods/test/init.lua

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ pseudo = PseudoRandom(13)
99
assert(pseudo:next() == 22290)
1010
assert(pseudo:next() == 13854)
1111

12-
dofile(minetest.get_modpath("test") .. "/player.lua")
13-
dofile(minetest.get_modpath("test") .. "/formspec.lua")
12+
local modpath = minetest.get_modpath("test")
13+
dofile(modpath .. "/player.lua")
14+
dofile(modpath .. "/formspec.lua")
15+
dofile(modpath .. "/crafting.lua")

games/minimal/mods/test/player.lua

-48
Original file line numberDiff line numberDiff line change
@@ -74,51 +74,3 @@ local function run_player_tests(player)
7474
minetest.chat_send_all("All tests pass!")
7575
end
7676
minetest.register_on_joinplayer(run_player_tests)
77-
78-
79-
local function test_get_craft_result()
80-
minetest.log("info", "test_get_craft_result()")
81-
-- normal
82-
local input = {
83-
method = "normal",
84-
width = 2,
85-
items = {"", "default:coal_lump", "", "default:stick"}
86-
}
87-
minetest.log("info", "torch crafting input: "..dump(input))
88-
local output, decremented_input = minetest.get_craft_result(input)
89-
minetest.log("info", "torch crafting output: "..dump(output))
90-
minetest.log("info", "torch crafting decremented input: "..dump(decremented_input))
91-
assert(output.item)
92-
minetest.log("info", "torch crafting output.item:to_table(): "..dump(output.item:to_table()))
93-
assert(output.item:get_name() == "default:torch")
94-
assert(output.item:get_count() == 4)
95-
-- fuel
96-
local input = {
97-
method = "fuel",
98-
width = 1,
99-
items = {"default:coal_lump"}
100-
}
101-
minetest.log("info", "coal fuel input: "..dump(input))
102-
local output, decremented_input = minetest.get_craft_result(input)
103-
minetest.log("info", "coal fuel output: "..dump(output))
104-
minetest.log("info", "coal fuel decremented input: "..dump(decremented_input))
105-
assert(output.time)
106-
assert(output.time > 0)
107-
-- cook
108-
local input = {
109-
method = "cooking",
110-
width = 1,
111-
items = {"default:cobble"}
112-
}
113-
minetest.log("info", "cobble cooking input: "..dump(output))
114-
local output, decremented_input = minetest.get_craft_result(input)
115-
minetest.log("info", "cobble cooking output: "..dump(output))
116-
minetest.log("info", "cobble cooking decremented input: "..dump(decremented_input))
117-
assert(output.time)
118-
assert(output.time > 0)
119-
assert(output.item)
120-
minetest.log("info", "cobble cooking output.item:to_table(): "..dump(output.item:to_table()))
121-
assert(output.item:get_name() == "default:stone")
122-
assert(output.item:get_count() == 1)
123-
end
124-
test_get_craft_result()

src/craftdef.cpp

+31-69
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,15 @@ std::string craftDumpMatrix(const std::vector<ItemStack> &items,
287287
CraftInput
288288
*/
289289

290+
bool CraftInput::empty() const
291+
{
292+
for (const auto &item : items) {
293+
if (!item.empty())
294+
return false;
295+
}
296+
return true;
297+
}
298+
290299
std::string CraftInput::dump() const
291300
{
292301
std::ostringstream os(std::ios::binary);
@@ -906,18 +915,7 @@ class CCraftDefManager: public IWritableCraftDefManager
906915
std::vector<ItemStack> &output_replacement, bool decrementInput,
907916
IGameDef *gamedef) const
908917
{
909-
output.item = "";
910-
output.time = 0;
911-
912-
// If all input items are empty, abort.
913-
bool all_empty = true;
914-
for (const auto &item : input.items) {
915-
if (!item.empty()) {
916-
all_empty = false;
917-
break;
918-
}
919-
}
920-
if (all_empty)
918+
if (input.empty())
921919
return false;
922920

923921
std::vector<std::string> input_names;
@@ -1002,84 +1000,48 @@ class CCraftDefManager: public IWritableCraftDefManager
10021000
return recipes;
10031001
}
10041002

1005-
virtual bool clearCraftRecipesByOutput(const CraftOutput &output, IGameDef *gamedef)
1003+
virtual bool clearCraftsByOutput(const CraftOutput &output, IGameDef *gamedef)
10061004
{
1007-
auto vec_iter = m_output_craft_definitions.find(output.item);
1005+
auto to_clear = m_output_craft_definitions.find(output.item);
10081006

1009-
if (vec_iter == m_output_craft_definitions.end())
1007+
if (to_clear == m_output_craft_definitions.end())
10101008
return false;
10111009

1012-
std::vector<CraftDefinition*> &vec = vec_iter->second;
1013-
for (auto def : vec) {
1010+
for (auto def : to_clear->second) {
10141011
// Recipes are not yet hashed at this point
1015-
std::vector<CraftDefinition*> &unhashed_inputs_vec = m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
1016-
std::vector<CraftDefinition*> new_vec_by_input;
1017-
/* We will preallocate necessary memory addresses, so we don't need to reallocate them later.
1018-
This would save us some performance. */
1019-
new_vec_by_input.reserve(unhashed_inputs_vec.size());
1020-
for (auto &i2 : unhashed_inputs_vec) {
1021-
if (def != i2) {
1022-
new_vec_by_input.push_back(i2);
1023-
}
1024-
}
1025-
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].swap(new_vec_by_input);
1012+
std::vector<CraftDefinition *> &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0];
1013+
defs.erase(std::remove(defs.begin(), defs.end(), def), defs.end());
1014+
delete def;
10261015
}
1027-
m_output_craft_definitions.erase(output.item);
1016+
m_output_craft_definitions.erase(to_clear);
10281017
return true;
10291018
}
10301019

1031-
virtual bool clearCraftRecipesByInput(CraftMethod craft_method, unsigned int craft_grid_width,
1032-
const std::vector<std::string> &recipe, IGameDef *gamedef)
1020+
virtual bool clearCraftsByInput(const CraftInput &input, IGameDef *gamedef)
10331021
{
1034-
bool all_empty = true;
1035-
for (const auto &i : recipe) {
1036-
if (!i.empty()) {
1037-
all_empty = false;
1038-
break;
1039-
}
1040-
}
1041-
if (all_empty)
1022+
if (input.empty())
10421023
return false;
10431024

1044-
CraftInput input(craft_method, craft_grid_width, craftGetItems(recipe, gamedef));
10451025
// Recipes are not yet hashed at this point
1046-
std::vector<CraftDefinition*> &unhashed_inputs_vec = m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
1047-
std::vector<CraftDefinition*> new_vec_by_input;
1026+
std::vector<CraftDefinition *> &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0];
1027+
std::vector<CraftDefinition *> new_defs;
10481028
bool got_hit = false;
1049-
for (std::vector<CraftDefinition*>::size_type
1050-
i = unhashed_inputs_vec.size(); i > 0; i--) {
1051-
CraftDefinition *def = unhashed_inputs_vec[i - 1];
1052-
/* If the input doesn't match the recipe definition, this recipe definition later
1053-
will be added back in source map. */
1029+
for (auto def : defs) {
10541030
if (!def->check(input, gamedef)) {
1055-
new_vec_by_input.push_back(def);
1031+
new_defs.push_back(def);
10561032
continue;
10571033
}
1058-
CraftOutput output = def->getOutput(input, gamedef);
10591034
got_hit = true;
1060-
auto vec_iter = m_output_craft_definitions.find(output.item);
1061-
if (vec_iter == m_output_craft_definitions.end())
1035+
std::string output = def->getOutput(input, gamedef).item;
1036+
delete def;
1037+
auto it = m_output_craft_definitions.find(craftGetItemName(output, gamedef));
1038+
if (it == m_output_craft_definitions.end())
10621039
continue;
1063-
std::vector<CraftDefinition*> &vec = vec_iter->second;
1064-
std::vector<CraftDefinition*> new_vec_by_output;
1065-
/* We will preallocate necessary memory addresses, so we don't need
1066-
to reallocate them later. This would save us some performance. */
1067-
new_vec_by_output.reserve(vec.size());
1068-
for (auto &vec_i : vec) {
1069-
/* If pointers from map by input and output are not same,
1070-
we will add 'CraftDefinition*' to a new vector. */
1071-
if (def != vec_i) {
1072-
/* Adding dereferenced iterator value (which are
1073-
'CraftDefinition' reference) to a new vector. */
1074-
new_vec_by_output.push_back(vec_i);
1075-
}
1076-
}
1077-
// Swaps assigned to current key value with new vector for output map.
1078-
m_output_craft_definitions[output.item].swap(new_vec_by_output);
1040+
std::vector<CraftDefinition *> &outdefs = it->second;
1041+
outdefs.erase(std::remove(outdefs.begin(), outdefs.end(), def), outdefs.end());
10791042
}
10801043
if (got_hit)
1081-
// Swaps value with new vector for input map.
1082-
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].swap(new_vec_by_input);
1044+
defs.swap(new_defs);
10831045

10841046
return got_hit;
10851047
}

src/craftdef.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ struct CraftInput
8080
method(method_), width(width_), items(items_)
8181
{}
8282

83+
// Returns true if all items are empty.
84+
bool empty() const;
85+
8386
std::string dump() const;
8487
};
8588

@@ -431,9 +434,8 @@ class IWritableCraftDefManager : public ICraftDefManager
431434
virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
432435
IGameDef *gamedef, unsigned limit=0) const=0;
433436

434-
virtual bool clearCraftRecipesByOutput(const CraftOutput &output, IGameDef *gamedef) = 0;
435-
virtual bool clearCraftRecipesByInput(CraftMethod craft_method,
436-
unsigned int craft_grid_width, const std::vector<std::string> &recipe, IGameDef *gamedef) = 0;
437+
virtual bool clearCraftsByOutput(const CraftOutput &output, IGameDef *gamedef) = 0;
438+
virtual bool clearCraftsByInput(const CraftInput &input, IGameDef *gamedef) = 0;
437439

438440
// Print crafting recipes for debugging
439441
virtual std::string dump() const=0;

src/script/lua_api/l_craft.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ int ModApiCraft::l_clear_craft(lua_State *L)
294294
std::string type = getstringfield_default(L, table, "type", "shaped");
295295
CraftOutput c_output(output, 0);
296296
if (!output.empty()) {
297-
if (craftdef->clearCraftRecipesByOutput(c_output, getServer(L))) {
297+
if (craftdef->clearCraftsByOutput(c_output, getServer(L))) {
298298
lua_pushboolean(L, true);
299299
return 1;
300300
}
@@ -351,7 +351,13 @@ int ModApiCraft::l_clear_craft(lua_State *L)
351351
throw LuaError("Unknown crafting definition type: \"" + type + "\"");
352352
}
353353

354-
if (!craftdef->clearCraftRecipesByInput(method, width, recipe, getServer(L))) {
354+
std::vector<ItemStack> items;
355+
items.reserve(recipe.size());
356+
for (const auto &item : recipe)
357+
items.emplace_back(item, 1, 0, getServer(L)->idef());
358+
CraftInput input(method, width, items);
359+
360+
if (!craftdef->clearCraftsByInput(input, getServer(L))) {
355361
warningstream << "No craft recipe matches input" << std::endl;
356362
lua_pushboolean(L, false);
357363
return 1;

0 commit comments

Comments
 (0)