minetest/src/craftdef.cpp
Jozef Behran a51909bb64 Speed up the craft definition handling (#8097)
The craft definition handling code that collects the names of
the craftable nodes suffers from vector reallocation
performance hits, slowing down instances with lots of
crafting recipes (VanessaE's DreamBuilder and most public
server some to my mind when thinking about this). As in each
instance the size of the resulting vector is already known,
add a reserve() call before the offending loops to allocate
the needed chunk of memory within the result vector in one
go, getting rid of the overhead.
2019-01-13 15:11:47 +01:00

1111 lines
29 KiB
C++

/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "craftdef.h"
#include "irrlichttypes.h"
#include "log.h"
#include <sstream>
#include <set>
#include <algorithm>
#include "gamedef.h"
#include "inventory.h"
#include "util/serialize.h"
#include "util/string.h"
#include "util/numeric.h"
#include "util/strfnd.h"
#include "exceptions.h"
inline bool isGroupRecipeStr(const std::string &rec_name)
{
return str_starts_with(rec_name, std::string("group:"));
}
inline u64 getHashForString(const std::string &recipe_str)
{
/*errorstream << "Hashing craft string \"" << recipe_str << '"';*/
return murmur_hash_64_ua(recipe_str.data(), recipe_str.length(), 0xdeadbeef);
}
static u64 getHashForGrid(CraftHashType type, const std::vector<std::string> &grid_names)
{
switch (type) {
case CRAFT_HASH_TYPE_ITEM_NAMES: {
std::ostringstream os;
bool is_first = true;
for (const std::string &grid_name : grid_names) {
if (!grid_name.empty()) {
os << (is_first ? "" : "\n") << grid_name;
is_first = false;
}
}
return getHashForString(os.str());
} case CRAFT_HASH_TYPE_COUNT: {
u64 cnt = 0;
for (const std::string &grid_name : grid_names)
if (!grid_name.empty())
cnt++;
return cnt;
} case CRAFT_HASH_TYPE_UNHASHED:
return 0;
}
// invalid CraftHashType
assert(false);
return 0;
}
// Check if input matches recipe
// Takes recipe groups into account
static bool inputItemMatchesRecipe(const std::string &inp_name,
const std::string &rec_name, IItemDefManager *idef)
{
// Exact name
if (inp_name == rec_name)
return true;
// Group
if (isGroupRecipeStr(rec_name) && idef->isKnown(inp_name)) {
const struct ItemDefinition &def = idef->get(inp_name);
Strfnd f(rec_name.substr(6));
bool all_groups_match = true;
do {
std::string check_group = f.next(",");
if (itemgroup_get(def.groups, check_group) == 0) {
all_groups_match = false;
break;
}
} while (!f.at_end());
if (all_groups_match)
return true;
}
// Didn't match
return false;
}
// Deserialize an itemstring then return the name of the item
static std::string craftGetItemName(const std::string &itemstring, IGameDef *gamedef)
{
ItemStack item;
item.deSerialize(itemstring, gamedef->idef());
return item.name;
}
// (mapcar craftGetItemName itemstrings)
static std::vector<std::string> craftGetItemNames(
const std::vector<std::string> &itemstrings, IGameDef *gamedef)
{
std::vector<std::string> result;
result.reserve(itemstrings.size());
for (const auto &itemstring : itemstrings) {
result.push_back(craftGetItemName(itemstring, gamedef));
}
return result;
}
// Get name of each item, and return them as a new list.
static std::vector<std::string> craftGetItemNames(
const std::vector<ItemStack> &items, IGameDef *gamedef)
{
std::vector<std::string> result;
result.reserve(items.size());
for (const auto &item : items) {
result.push_back(item.name);
}
return result;
}
// convert a list of item names, to ItemStacks.
static std::vector<ItemStack> craftGetItems(
const std::vector<std::string> &items, IGameDef *gamedef)
{
std::vector<ItemStack> result;
result.reserve(items.size());
for (const auto &item : items) {
result.emplace_back(std::string(item), (u16)1,
(u16)0, gamedef->getItemDefManager());
}
return result;
}
// Compute bounding rectangle given a matrix of items
// Returns false if every item is ""
static bool craftGetBounds(const std::vector<std::string> &items, unsigned int width,
unsigned int &min_x, unsigned int &max_x,
unsigned int &min_y, unsigned int &max_y)
{
bool success = false;
unsigned int x = 0;
unsigned int y = 0;
for (const std::string &item : items) {
// Is this an actual item?
if (!item.empty()) {
if (!success) {
// This is the first nonempty item
min_x = max_x = x;
min_y = max_y = y;
success = true;
} else {
if (x < min_x) min_x = x;
if (x > max_x) max_x = x;
if (y < min_y) min_y = y;
if (y > max_y) max_y = y;
}
}
// Step coordinate
x++;
if (x == width) {
x = 0;
y++;
}
}
return success;
}
// Removes 1 from each item stack
static void craftDecrementInput(CraftInput &input, IGameDef *gamedef)
{
for (auto &item : input.items) {
if (item.count != 0)
item.remove(1);
}
}
// Removes 1 from each item stack with replacement support
// Example: if replacements contains the pair ("bucket:bucket_water", "bucket:bucket_empty"),
// a water bucket will not be removed but replaced by an empty bucket.
static void craftDecrementOrReplaceInput(CraftInput &input,
std::vector<ItemStack> &output_replacements,
const CraftReplacements &replacements,
IGameDef *gamedef)
{
if (replacements.pairs.empty()) {
craftDecrementInput(input, gamedef);
return;
}
// Make a copy of the replacements pair list
std::vector<std::pair<std::string, std::string> > pairs = replacements.pairs;
for (auto &item : input.items) {
// Find an appropriate replacement
bool found_replacement = false;
for (auto j = pairs.begin(); j != pairs.end(); ++j) {
if (inputItemMatchesRecipe(item.name, j->first, gamedef->idef())) {
if (item.count == 1) {
item.deSerialize(j->second, gamedef->idef());
found_replacement = true;
pairs.erase(j);
break;
}
ItemStack rep;
rep.deSerialize(j->second, gamedef->idef());
item.remove(1);
found_replacement = true;
output_replacements.push_back(rep);
break;
}
}
// No replacement was found, simply decrement count by one
if (!found_replacement && item.count > 0)
item.remove(1);
}
}
// Dump an itemstring matrix
static std::string craftDumpMatrix(const std::vector<std::string> &items,
unsigned int width)
{
std::ostringstream os(std::ios::binary);
os << "{ ";
unsigned int x = 0;
for(std::vector<std::string>::size_type i = 0;
i < items.size(); i++, x++) {
if (x == width) {
os << "; ";
x = 0;
} else if (x != 0) {
os << ",";
}
os << '"' << items[i] << '"';
}
os << " }";
return os.str();
}
// Dump an item matrix
std::string craftDumpMatrix(const std::vector<ItemStack> &items,
unsigned int width)
{
std::ostringstream os(std::ios::binary);
os << "{ ";
unsigned int x = 0;
for (std::vector<ItemStack>::size_type i = 0;
i < items.size(); i++, x++) {
if (x == width) {
os << "; ";
x = 0;
} else if (x != 0) {
os << ",";
}
os << '"' << (items[i].getItemString()) << '"';
}
os << " }";
return os.str();
}
/*
CraftInput
*/
std::string CraftInput::dump() const
{
std::ostringstream os(std::ios::binary);
os << "(method=" << ((int)method) << ", items="
<< craftDumpMatrix(items, width) << ")";
return os.str();
}
/*
CraftOutput
*/
std::string CraftOutput::dump() const
{
std::ostringstream os(std::ios::binary);
os << "(item=\"" << item << "\", time=" << time << ")";
return os.str();
}
/*
CraftReplacements
*/
std::string CraftReplacements::dump() const
{
std::ostringstream os(std::ios::binary);
os<<"{";
const char *sep = "";
for (const auto &repl_p : pairs) {
os << sep
<< '"' << (repl_p.first)
<< "\"=>\"" << (repl_p.second) << '"';
sep = ",";
}
os << "}";
return os.str();
}
/*
CraftDefinitionShaped
*/
std::string CraftDefinitionShaped::getName() const
{
return "shaped";
}
bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const
{
if (input.method != CRAFT_METHOD_NORMAL)
return false;
// Get input item matrix
std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
unsigned int inp_width = input.width;
if (inp_width == 0)
return false;
while (inp_names.size() % inp_width != 0)
inp_names.emplace_back("");
// Get input bounds
unsigned int inp_min_x = 0, inp_max_x = 0, inp_min_y = 0, inp_max_y = 0;
if (!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x,
inp_min_y, inp_max_y))
return false; // it was empty
std::vector<std::string> rec_names;
if (hash_inited)
rec_names = recipe_names;
else
rec_names = craftGetItemNames(recipe, gamedef);
// Get recipe item matrix
unsigned int rec_width = width;
if (rec_width == 0)
return false;
while (rec_names.size() % rec_width != 0)
rec_names.emplace_back("");
// Get recipe bounds
unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0;
if (!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x,
rec_min_y, rec_max_y))
return false; // it was empty
// Different sizes?
if (inp_max_x - inp_min_x != rec_max_x - rec_min_x ||
inp_max_y - inp_min_y != rec_max_y - rec_min_y)
return false;
// Verify that all item names in the bounding box are equal
unsigned int w = inp_max_x - inp_min_x + 1;
unsigned int h = inp_max_y - inp_min_y + 1;
for (unsigned int y=0; y < h; y++) {
unsigned int inp_y = (inp_min_y + y) * inp_width;
unsigned int rec_y = (rec_min_y + y) * rec_width;
for (unsigned int x=0; x < w; x++) {
unsigned int inp_x = inp_min_x + x;
unsigned int rec_x = rec_min_x + x;
if (!inputItemMatchesRecipe(
inp_names[inp_y + inp_x],
rec_names[rec_y + rec_x], gamedef->idef())) {
return false;
}
}
}
return true;
}
CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
return CraftOutput(output, 0);
}
CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef));
}
void CraftDefinitionShaped::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
IGameDef *gamedef) const
{
craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
}
CraftHashType CraftDefinitionShaped::getHashType() const
{
assert(hash_inited); // Pre-condition
bool has_group = false;
for (const auto &recipe_name : recipe_names) {
if (isGroupRecipeStr(recipe_name)) {
has_group = true;
break;
}
}
if (has_group)
return CRAFT_HASH_TYPE_COUNT;
return CRAFT_HASH_TYPE_ITEM_NAMES;
}
u64 CraftDefinitionShaped::getHash(CraftHashType type) const
{
assert(hash_inited); // Pre-condition
assert((type == CRAFT_HASH_TYPE_ITEM_NAMES)
|| (type == CRAFT_HASH_TYPE_COUNT)); // Pre-condition
std::vector<std::string> rec_names = recipe_names;
std::sort(rec_names.begin(), rec_names.end());
return getHashForGrid(type, rec_names);
}
void CraftDefinitionShaped::initHash(IGameDef *gamedef)
{
if (hash_inited)
return;
hash_inited = true;
recipe_names = craftGetItemNames(recipe, gamedef);
}
std::string CraftDefinitionShaped::dump() const
{
std::ostringstream os(std::ios::binary);
os << "(shaped, output=\"" << output
<< "\", recipe=" << craftDumpMatrix(recipe, width)
<< ", replacements=" << replacements.dump() << ")";
return os.str();
}
/*
CraftDefinitionShapeless
*/
std::string CraftDefinitionShapeless::getName() const
{
return "shapeless";
}
bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) const
{
if (input.method != CRAFT_METHOD_NORMAL)
return false;
// Filter empty items out of input
std::vector<std::string> input_filtered;
for (const auto &item : input.items) {
if (!item.name.empty())
input_filtered.push_back(item.name);
}
// If there is a wrong number of items in input, no match
if (input_filtered.size() != recipe.size()) {
/*dstream<<"Number of input items ("<<input_filtered.size()
<<") does not match recipe size ("<<recipe.size()<<") "
<<"of recipe with output="<<output<<std::endl;*/
return false;
}
std::vector<std::string> recipe_copy;
if (hash_inited)
recipe_copy = recipe_names;
else {
recipe_copy = craftGetItemNames(recipe, gamedef);
std::sort(recipe_copy.begin(), recipe_copy.end());
}
// Try with all permutations of the recipe,
// start from the lexicographically first permutation (=sorted),
// recipe_names is pre-sorted
do {
// If all items match, the recipe matches
bool all_match = true;
//dstream<<"Testing recipe (output="<<output<<"):";
for (size_t i=0; i<recipe.size(); i++) {
//dstream<<" ("<<input_filtered[i]<<" == "<<recipe_copy[i]<<")";
if (!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i],
gamedef->idef())) {
all_match = false;
break;
}
}
//dstream<<" -> match="<<all_match<<std::endl;
if (all_match)
return true;
} while (std::next_permutation(recipe_copy.begin(), recipe_copy.end()));
return false;
}
CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
return CraftOutput(output, 0);
}
CraftInput CraftDefinitionShapeless::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
return CraftInput(CRAFT_METHOD_NORMAL, 0, craftGetItems(recipe, gamedef));
}
void CraftDefinitionShapeless::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
IGameDef *gamedef) const
{
craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
}
CraftHashType CraftDefinitionShapeless::getHashType() const
{
assert(hash_inited); // Pre-condition
bool has_group = false;
for (const auto &recipe_name : recipe_names) {
if (isGroupRecipeStr(recipe_name)) {
has_group = true;
break;
}
}
if (has_group)
return CRAFT_HASH_TYPE_COUNT;
return CRAFT_HASH_TYPE_ITEM_NAMES;
}
u64 CraftDefinitionShapeless::getHash(CraftHashType type) const
{
assert(hash_inited); // Pre-condition
assert(type == CRAFT_HASH_TYPE_ITEM_NAMES
|| type == CRAFT_HASH_TYPE_COUNT); // Pre-condition
return getHashForGrid(type, recipe_names);
}
void CraftDefinitionShapeless::initHash(IGameDef *gamedef)
{
if (hash_inited)
return;
hash_inited = true;
recipe_names = craftGetItemNames(recipe, gamedef);
std::sort(recipe_names.begin(), recipe_names.end());
}
std::string CraftDefinitionShapeless::dump() const
{
std::ostringstream os(std::ios::binary);
os << "(shapeless, output=\"" << output
<< "\", recipe=" << craftDumpMatrix(recipe, recipe.size())
<< ", replacements=" << replacements.dump() << ")";
return os.str();
}
/*
CraftDefinitionToolRepair
*/
static ItemStack craftToolRepair(
const ItemStack &item1,
const ItemStack &item2,
float additional_wear,
IGameDef *gamedef)
{
IItemDefManager *idef = gamedef->idef();
if (item1.count != 1 || item2.count != 1 || item1.name != item2.name
|| idef->get(item1.name).type != ITEM_TOOL
|| itemgroup_get(idef->get(item1.name).groups, "disable_repair") == 1) {
// Failure
return ItemStack();
}
s32 item1_uses = 65536 - (u32) item1.wear;
s32 item2_uses = 65536 - (u32) item2.wear;
s32 new_uses = item1_uses + item2_uses;
s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5);
if (new_wear >= 65536)
return ItemStack();
if (new_wear < 0)
new_wear = 0;
ItemStack repaired = item1;
repaired.wear = new_wear;
return repaired;
}
std::string CraftDefinitionToolRepair::getName() const
{
return "toolrepair";
}
bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const
{
if (input.method != CRAFT_METHOD_NORMAL)
return false;
ItemStack item1;
ItemStack item2;
for (const auto &item : input.items) {
if (!item.empty()) {
if (item1.empty())
item1 = item;
else if (item2.empty())
item2 = item;
else
return false;
}
}
ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
return !repaired.empty();
}
CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
ItemStack item1;
ItemStack item2;
for (const auto &item : input.items) {
if (!item.empty()) {
if (item1.empty())
item1 = item;
else if (item2.empty())
item2 = item;
}
}
ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
return CraftOutput(repaired.getItemString(), 0);
}
CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
std::vector<ItemStack> stack;
stack.emplace_back();
return CraftInput(CRAFT_METHOD_COOKING, additional_wear, stack);
}
void CraftDefinitionToolRepair::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
IGameDef *gamedef) const
{
craftDecrementInput(input, gamedef);
}
std::string CraftDefinitionToolRepair::dump() const
{
std::ostringstream os(std::ios::binary);
os << "(toolrepair, additional_wear=" << additional_wear << ")";
return os.str();
}
/*
CraftDefinitionCooking
*/
std::string CraftDefinitionCooking::getName() const
{
return "cooking";
}
bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) const
{
if (input.method != CRAFT_METHOD_COOKING)
return false;
// Filter empty items out of input
std::vector<std::string> input_filtered;
for (const auto &item : input.items) {
const std::string &name = item.name;
if (!name.empty())
input_filtered.push_back(name);
}
// If there is a wrong number of items in input, no match
if (input_filtered.size() != 1) {
/*dstream<<"Number of input items ("<<input_filtered.size()
<<") does not match recipe size (1) "
<<"of cooking recipe with output="<<output<<std::endl;*/
return false;
}
// Check the single input item
return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
}
CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
return CraftOutput(output, cooktime);
}
CraftInput CraftDefinitionCooking::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
std::vector<std::string> rec;
rec.push_back(recipe);
return CraftInput(CRAFT_METHOD_COOKING,cooktime,craftGetItems(rec,gamedef));
}
void CraftDefinitionCooking::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
IGameDef *gamedef) const
{
craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
}
CraftHashType CraftDefinitionCooking::getHashType() const
{
if (isGroupRecipeStr(recipe_name))
return CRAFT_HASH_TYPE_COUNT;
return CRAFT_HASH_TYPE_ITEM_NAMES;
}
u64 CraftDefinitionCooking::getHash(CraftHashType type) const
{
if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
return getHashForString(recipe_name);
}
if (type == CRAFT_HASH_TYPE_COUNT) {
return 1;
}
// illegal hash type for this CraftDefinition (pre-condition)
assert(false);
return 0;
}
void CraftDefinitionCooking::initHash(IGameDef *gamedef)
{
if (hash_inited)
return;
hash_inited = true;
recipe_name = craftGetItemName(recipe, gamedef);
}
std::string CraftDefinitionCooking::dump() const
{
std::ostringstream os(std::ios::binary);
os << "(cooking, output=\"" << output
<< "\", recipe=\"" << recipe
<< "\", cooktime=" << cooktime << ")"
<< ", replacements=" << replacements.dump() << ")";
return os.str();
}
/*
CraftDefinitionFuel
*/
std::string CraftDefinitionFuel::getName() const
{
return "fuel";
}
bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) const
{
if (input.method != CRAFT_METHOD_FUEL)
return false;
// Filter empty items out of input
std::vector<std::string> input_filtered;
for (const auto &item : input.items) {
const std::string &name = item.name;
if (!name.empty())
input_filtered.push_back(name);
}
// If there is a wrong number of items in input, no match
if (input_filtered.size() != 1) {
/*dstream<<"Number of input items ("<<input_filtered.size()
<<") does not match recipe size (1) "
<<"of fuel recipe with burntime="<<burntime<<std::endl;*/
return false;
}
// Check the single input item
return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
}
CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
{
return CraftOutput("", burntime);
}
CraftInput CraftDefinitionFuel::getInput(const CraftOutput &output, IGameDef *gamedef) const
{
std::vector<std::string> rec;
rec.push_back(recipe);
return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef));
}
void CraftDefinitionFuel::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
IGameDef *gamedef) const
{
craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
}
CraftHashType CraftDefinitionFuel::getHashType() const
{
if (isGroupRecipeStr(recipe_name))
return CRAFT_HASH_TYPE_COUNT;
return CRAFT_HASH_TYPE_ITEM_NAMES;
}
u64 CraftDefinitionFuel::getHash(CraftHashType type) const
{
if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
return getHashForString(recipe_name);
}
if (type == CRAFT_HASH_TYPE_COUNT) {
return 1;
}
// illegal hash type for this CraftDefinition (pre-condition)
assert(false);
return 0;
}
void CraftDefinitionFuel::initHash(IGameDef *gamedef)
{
if (hash_inited)
return;
hash_inited = true;
recipe_name = craftGetItemName(recipe, gamedef);
}
std::string CraftDefinitionFuel::dump() const
{
std::ostringstream os(std::ios::binary);
os << "(fuel, recipe=\"" << recipe
<< "\", burntime=" << burntime << ")"
<< ", replacements=" << replacements.dump() << ")";
return os.str();
}
/*
Craft definition manager
*/
class CCraftDefManager: public IWritableCraftDefManager
{
public:
CCraftDefManager()
{
m_craft_defs.resize(craft_hash_type_max + 1);
}
virtual ~CCraftDefManager()
{
clear();
}
virtual bool getCraftResult(CraftInput &input, CraftOutput &output,
std::vector<ItemStack> &output_replacement, bool decrementInput,
IGameDef *gamedef) const
{
output.item = "";
output.time = 0;
// If all input items are empty, abort.
bool all_empty = true;
for (const auto &item : input.items) {
if (!item.empty()) {
all_empty = false;
break;
}
}
if (all_empty)
return false;
std::vector<std::string> input_names;
input_names = craftGetItemNames(input.items, gamedef);
std::sort(input_names.begin(), input_names.end());
// Try hash types with increasing collision rate, and return if found.
for (int type = 0; type <= craft_hash_type_max; type++) {
u64 hash = getHashForGrid((CraftHashType) type, input_names);
/*errorstream << "Checking type " << type << " with hash " << hash << std::endl;*/
// We'd like to do "const [...] hash_collisions = m_craft_defs[type][hash];"
// but that doesn't compile for some reason. This does.
auto col_iter = (m_craft_defs[type]).find(hash);
if (col_iter == (m_craft_defs[type]).end())
continue;
const std::vector<CraftDefinition*> &hash_collisions = col_iter->second;
// Walk crafting definitions from back to front, so that later
// definitions can override earlier ones.
for (std::vector<CraftDefinition*>::size_type
i = hash_collisions.size(); i > 0; i--) {
CraftDefinition *def = hash_collisions[i - 1];
/*errorstream << "Checking " << input.dump() << std::endl
<< " against " << def->dump() << std::endl;*/
if (def->check(input, gamedef)) {
// Check if the crafted node/item exists
CraftOutput out = def->getOutput(input, gamedef);
ItemStack is;
is.deSerialize(out.item, gamedef->idef());
if (!is.isKnown(gamedef->idef())) {
infostream << "trying to craft non-existent "
<< out.item << ", ignoring recipe" << std::endl;
continue;
}
// Get output, then decrement input (if requested)
output = out;
if (decrementInput)
def->decrementInput(input, output_replacement, gamedef);
/*errorstream << "Check RETURNS TRUE" << std::endl;*/
return true;
}
}
}
return false;
}
virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
IGameDef *gamedef, unsigned limit=0) const
{
std::vector<CraftDefinition*> recipes;
auto vec_iter = m_output_craft_definitions.find(output.item);
if (vec_iter == m_output_craft_definitions.end())
return recipes;
const std::vector<CraftDefinition*> &vec = vec_iter->second;
recipes.reserve(limit ? MYMIN(limit, vec.size()) : vec.size());
for (std::vector<CraftDefinition*>::size_type i = vec.size();
i > 0; i--) {
CraftDefinition *def = vec[i - 1];
if (limit && recipes.size() >= limit)
break;
recipes.push_back(def);
}
return recipes;
}
virtual bool clearCraftRecipesByOutput(const CraftOutput &output, IGameDef *gamedef)
{
auto vec_iter = m_output_craft_definitions.find(output.item);
if (vec_iter == m_output_craft_definitions.end())
return false;
std::vector<CraftDefinition*> &vec = vec_iter->second;
for (auto def : vec) {
// Recipes are not yet hashed at this point
std::vector<CraftDefinition*> &unhashed_inputs_vec = m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
std::vector<CraftDefinition*> new_vec_by_input;
/* We will preallocate necessary memory addresses, so we don't need to reallocate them later.
This would save us some performance. */
new_vec_by_input.reserve(unhashed_inputs_vec.size());
for (auto &i2 : unhashed_inputs_vec) {
if (def != i2) {
new_vec_by_input.push_back(i2);
}
}
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].swap(new_vec_by_input);
}
m_output_craft_definitions.erase(output.item);
return true;
}
virtual bool clearCraftRecipesByInput(CraftMethod craft_method, unsigned int craft_grid_width,
const std::vector<std::string> &recipe, IGameDef *gamedef)
{
bool all_empty = true;
for (const auto &i : recipe) {
if (!i.empty()) {
all_empty = false;
break;
}
}
if (all_empty)
return false;
CraftInput input(craft_method, craft_grid_width, craftGetItems(recipe, gamedef));
// Recipes are not yet hashed at this point
std::vector<CraftDefinition*> &unhashed_inputs_vec = m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
std::vector<CraftDefinition*> new_vec_by_input;
bool got_hit = false;
for (std::vector<CraftDefinition*>::size_type
i = unhashed_inputs_vec.size(); i > 0; i--) {
CraftDefinition *def = unhashed_inputs_vec[i - 1];
/* If the input doesn't match the recipe definition, this recipe definition later
will be added back in source map. */
if (!def->check(input, gamedef)) {
new_vec_by_input.push_back(def);
continue;
}
CraftOutput output = def->getOutput(input, gamedef);
got_hit = true;
auto vec_iter = m_output_craft_definitions.find(output.item);
if (vec_iter == m_output_craft_definitions.end())
continue;
std::vector<CraftDefinition*> &vec = vec_iter->second;
std::vector<CraftDefinition*> new_vec_by_output;
/* We will preallocate necessary memory addresses, so we don't need
to reallocate them later. This would save us some performance. */
new_vec_by_output.reserve(vec.size());
for (auto &vec_i : vec) {
/* If pointers from map by input and output are not same,
we will add 'CraftDefinition*' to a new vector. */
if (def != vec_i) {
/* Adding dereferenced iterator value (which are
'CraftDefinition' reference) to a new vector. */
new_vec_by_output.push_back(vec_i);
}
}
// Swaps assigned to current key value with new vector for output map.
m_output_craft_definitions[output.item].swap(new_vec_by_output);
}
if (got_hit)
// Swaps value with new vector for input map.
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].swap(new_vec_by_input);
return got_hit;
}
virtual std::string dump() const
{
std::ostringstream os(std::ios::binary);
os << "Crafting definitions:\n";
for (int type = 0; type <= craft_hash_type_max; ++type) {
for (auto it = m_craft_defs[type].begin();
it != m_craft_defs[type].end(); ++it) {
for (std::vector<CraftDefinition*>::size_type i = 0;
i < it->second.size(); i++) {
os << "type " << type
<< " hash " << it->first
<< " def " << it->second[i]->dump()
<< "\n";
}
}
}
return os.str();
}
virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef)
{
verbosestream << "registerCraft: registering craft definition: "
<< def->dump() << std::endl;
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].push_back(def);
CraftInput input;
std::string output_name = craftGetItemName(
def->getOutput(input, gamedef).item, gamedef);
m_output_craft_definitions[output_name].push_back(def);
}
virtual void clear()
{
for (int type = 0; type <= craft_hash_type_max; ++type) {
for (auto &it : m_craft_defs[type]) {
for (auto &iit : it.second) {
delete iit;
}
it.second.clear();
}
m_craft_defs[type].clear();
}
m_output_craft_definitions.clear();
}
virtual void initHashes(IGameDef *gamedef)
{
// Move the CraftDefs from the unhashed layer into layers higher up.
std::vector<CraftDefinition *> &unhashed =
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
for (auto def : unhashed) {
// Initialize and get the definition's hash
def->initHash(gamedef);
CraftHashType type = def->getHashType();
u64 hash = def->getHash(type);
// Enter the definition
m_craft_defs[type][hash].push_back(def);
}
unhashed.clear();
}
private:
//TODO: change both maps to unordered_map when c++11 can be used
std::vector<std::map<u64, std::vector<CraftDefinition*> > > m_craft_defs;
std::map<std::string, std::vector<CraftDefinition*> > m_output_craft_definitions;
};
IWritableCraftDefManager* createCraftDefManager()
{
return new CCraftDefManager();
}