mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-26 03:52:01 +02:00
Merge pull request #3454 from IvanSavenko/mod_debug
Better error-reporting for modding system errors
This commit is contained in:
commit
206b4a0588
@ -111,7 +111,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
|
||||
// - another mod attempts to add object into this mod (technically can be supported, but might lead to weird edge cases)
|
||||
// - another mod attempts to edit object from this mod that no longer exist - DANGER since such patch likely has very incomplete data
|
||||
// so emit warning and skip such case
|
||||
logMod->warn("Mod %s attempts to edit object %s from mod %s but no such object exist!", data.meta, name, modName);
|
||||
logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.meta, name, objectName, modName);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -157,6 +157,33 @@ void ContentTypeHandler::loadCustom()
|
||||
|
||||
void ContentTypeHandler::afterLoadFinalization()
|
||||
{
|
||||
for (auto const & data : modData)
|
||||
{
|
||||
if (data.second.modData.isNull())
|
||||
{
|
||||
for (auto node : data.second.patches.Struct())
|
||||
logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.meta, node.first, data.first);
|
||||
}
|
||||
|
||||
for(auto & otherMod : modData)
|
||||
{
|
||||
if (otherMod.first == data.first)
|
||||
continue;
|
||||
|
||||
if (otherMod.second.modData.isNull())
|
||||
continue;
|
||||
|
||||
for(auto & otherObject : otherMod.second.modData.Struct())
|
||||
{
|
||||
if (data.second.modData.Struct().count(otherObject.first))
|
||||
{
|
||||
logMod->warn("Mod '%s' have added object with name '%s' that is also available in mod '%s'", data.first, otherObject.first, otherMod.first);
|
||||
logMod->warn("Two objects with same name were loaded. Please use form '%s:%s' if mod '%s' needs to modify this object instead", otherMod.first, otherObject.first, data.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler->afterLoadFinalization();
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,7 @@ CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameW
|
||||
result.name = typeAndName.second;
|
||||
result.callback = callback;
|
||||
result.optional = optional;
|
||||
result.dynamicType = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -160,6 +161,7 @@ CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameA
|
||||
result.name = typeAndName.second;
|
||||
result.callback = callback;
|
||||
result.optional = optional;
|
||||
result.dynamicType = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -197,58 +199,121 @@ std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & scope,
|
||||
{
|
||||
assert(state != ELoadingState::LOADING);
|
||||
|
||||
auto idList = getPossibleIdentifiers(ObjectCallback::fromNameAndType(scope, type, name, std::function<void(si32)>(), silent));
|
||||
|
||||
if (idList.size() == 1)
|
||||
return idList.front().id;
|
||||
if (!silent)
|
||||
logMod->error("Failed to resolve identifier %s of type %s from mod %s", name , type ,scope);
|
||||
|
||||
return std::optional<si32>();
|
||||
auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function<void(si32)>(), silent);
|
||||
return getIdentifierImpl(options, silent);
|
||||
}
|
||||
|
||||
std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & type, const JsonNode & name, bool silent) const
|
||||
{
|
||||
assert(state != ELoadingState::LOADING);
|
||||
|
||||
auto idList = getPossibleIdentifiers(ObjectCallback::fromNameAndType(name.meta, type, name.String(), std::function<void(si32)>(), silent));
|
||||
auto options = ObjectCallback::fromNameAndType(name.meta, type, name.String(), std::function<void(si32)>(), silent);
|
||||
|
||||
if (idList.size() == 1)
|
||||
return idList.front().id;
|
||||
if (!silent)
|
||||
logMod->error("Failed to resolve identifier %s of type %s from mod %s", name.String(), type, name.meta);
|
||||
|
||||
return std::optional<si32>();
|
||||
return getIdentifierImpl(options, silent);
|
||||
}
|
||||
|
||||
std::optional<si32> CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent) const
|
||||
{
|
||||
assert(state != ELoadingState::LOADING);
|
||||
|
||||
auto idList = getPossibleIdentifiers(ObjectCallback::fromNameWithType(name.meta, name.String(), std::function<void(si32)>(), silent));
|
||||
|
||||
if (idList.size() == 1)
|
||||
return idList.front().id;
|
||||
if (!silent)
|
||||
logMod->error("Failed to resolve identifier %s from mod %s", name.String(), name.meta);
|
||||
|
||||
return std::optional<si32>();
|
||||
auto options = ObjectCallback::fromNameWithType(name.meta, name.String(), std::function<void(si32)>(), silent);
|
||||
return getIdentifierImpl(options, silent);
|
||||
}
|
||||
|
||||
std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & fullName, bool silent) const
|
||||
{
|
||||
assert(state != ELoadingState::LOADING);
|
||||
|
||||
auto idList = getPossibleIdentifiers(ObjectCallback::fromNameWithType(scope, fullName, std::function<void(si32)>(), silent));
|
||||
auto options = ObjectCallback::fromNameWithType(scope, fullName, std::function<void(si32)>(), silent);
|
||||
return getIdentifierImpl(options, silent);
|
||||
}
|
||||
|
||||
std::optional<si32> CIdentifierStorage::getIdentifierImpl(const ObjectCallback & options, bool silent) const
|
||||
{
|
||||
auto idList = getPossibleIdentifiers(options);
|
||||
|
||||
if (idList.size() == 1)
|
||||
return idList.front().id;
|
||||
if (!silent)
|
||||
logMod->error("Failed to resolve identifier %s from mod %s", fullName, scope);
|
||||
|
||||
showIdentifierResolutionErrorDetails(options);
|
||||
return std::optional<si32>();
|
||||
}
|
||||
|
||||
void CIdentifierStorage::showIdentifierResolutionErrorDetails(const ObjectCallback & options) const
|
||||
{
|
||||
auto idList = getPossibleIdentifiers(options);
|
||||
|
||||
logMod->error("Failed to resolve identifier '%s' of type '%s' from mod '%s'", options.name, options.type, options.localScope);
|
||||
|
||||
if (options.dynamicType && options.type.empty())
|
||||
{
|
||||
bool suggestionFound = false;
|
||||
|
||||
for (auto const & entry : registeredObjects)
|
||||
{
|
||||
if (!boost::algorithm::ends_with(entry.first, options.name))
|
||||
continue;
|
||||
|
||||
suggestionFound = true;
|
||||
logMod->error("Perhaps you wanted to use identifier '%s' from mod '%s' instead?", entry.first, entry.second.scope);
|
||||
}
|
||||
|
||||
if (suggestionFound)
|
||||
return;
|
||||
}
|
||||
|
||||
if (idList.empty())
|
||||
{
|
||||
// check whether identifier is unavailable due to a missing dependency on a mod
|
||||
ObjectCallback testOptions = options;
|
||||
testOptions.localScope = ModScope::scopeGame();
|
||||
testOptions.remoteScope = {};
|
||||
|
||||
auto testList = getPossibleIdentifiers(testOptions);
|
||||
if (testList.empty())
|
||||
{
|
||||
logMod->error("Identifier '%s' of type '%s' does not exists in any loaded mod!", options.name, options.type);
|
||||
}
|
||||
else
|
||||
{
|
||||
// such identifiers exists, but were not picked for some reason
|
||||
if (options.remoteScope.empty())
|
||||
{
|
||||
// attempt to access identifier from mods that is not dependency
|
||||
for (auto const & testOption : testList)
|
||||
{
|
||||
logMod->error("Identifier '%s' exists in mod %s", options.name, testOption.scope);
|
||||
logMod->error("Please add mod '%s' as dependency of mod '%s' to access this identifier", testOption.scope, options.localScope);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// attempt to access identifier in form 'modName:object', but identifier is only present in different mod
|
||||
for (auto const & testOption : testList)
|
||||
{
|
||||
logMod->error("Identifier '%s' exists in mod '%s' but identifier was explicitly requested from mod '%s'!", options.name, testOption.scope, options.remoteScope);
|
||||
if (options.dynamicType)
|
||||
logMod->error("Please use form '%s.%s' or '%s:%s.%s' to access this identifier", options.type, options.name, testOption.scope, options.type, options.name);
|
||||
else
|
||||
logMod->error("Please use form '%s' or '%s:%s' to access this identifier", options.name, testOption.scope, options.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logMod->error("Multiple possible candidates:");
|
||||
for (auto const & testOption : idList)
|
||||
{
|
||||
logMod->error("Identifier %s exists in mod %s", options.name, testOption.scope);
|
||||
if (options.dynamicType)
|
||||
logMod->error("Please use '%s:%s.%s' to access this identifier", testOption.scope, options.type, options.name);
|
||||
else
|
||||
logMod->error("Please use '%s:%s' to access this identifier", testOption.scope, options.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CIdentifierStorage::registerObject(const std::string & scope, const std::string & type, const std::string & name, si32 identifier)
|
||||
{
|
||||
assert(state != ELoadingState::FINISHED);
|
||||
@ -362,17 +427,7 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request) const
|
||||
}
|
||||
|
||||
// error found. Try to generate some debug info
|
||||
if(identifiers.empty())
|
||||
logMod->error("Unknown identifier!");
|
||||
else
|
||||
logMod->error("Ambiguous identifier request!");
|
||||
|
||||
logMod->error("Request for %s.%s from mod %s", request.type, request.name, request.localScope);
|
||||
|
||||
for(const auto & id : identifiers)
|
||||
{
|
||||
logMod->error("\tID is available in mod %s", id.scope);
|
||||
}
|
||||
showIdentifierResolutionErrorDetails(request);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -381,26 +436,16 @@ void CIdentifierStorage::finalize()
|
||||
assert(state == ELoadingState::LOADING);
|
||||
|
||||
state = ELoadingState::FINALIZING;
|
||||
bool errorsFound = false;
|
||||
|
||||
while ( !scheduledRequests.empty() )
|
||||
{
|
||||
// Use local copy since new requests may appear during resolving, invalidating any iterators
|
||||
auto request = scheduledRequests.back();
|
||||
scheduledRequests.pop_back();
|
||||
|
||||
if (!resolveIdentifier(request))
|
||||
errorsFound = true;
|
||||
resolveIdentifier(request);
|
||||
}
|
||||
|
||||
debugDumpIdentifiers();
|
||||
|
||||
if (errorsFound)
|
||||
logMod->error("All known identifiers were dumped into log file");
|
||||
|
||||
assert(errorsFound == false);
|
||||
state = ELoadingState::FINISHED;
|
||||
|
||||
}
|
||||
|
||||
void CIdentifierStorage::debugDumpIdentifiers()
|
||||
|
@ -32,6 +32,7 @@ class DLL_LINKAGE CIdentifierStorage
|
||||
std::string name; /// string ID
|
||||
std::function<void(si32)> callback;
|
||||
bool optional;
|
||||
bool dynamicType;
|
||||
|
||||
/// Builds callback from identifier in form "targetMod:type.name"
|
||||
static ObjectCallback fromNameWithType(const std::string & scope, const std::string & fullName, const std::function<void(si32)> & callback, bool optional);
|
||||
@ -69,6 +70,8 @@ class DLL_LINKAGE CIdentifierStorage
|
||||
bool resolveIdentifier(const ObjectCallback & callback) const;
|
||||
std::vector<ObjectData> getPossibleIdentifiers(const ObjectCallback & callback) const;
|
||||
|
||||
void showIdentifierResolutionErrorDetails(const ObjectCallback & callback) const;
|
||||
std::optional<si32> getIdentifierImpl(const ObjectCallback & callback, bool silent) const;
|
||||
public:
|
||||
CIdentifierStorage();
|
||||
virtual ~CIdentifierStorage() = default;
|
||||
|
Loading…
x
Reference in New Issue
Block a user