Error-reporting: improve UI/display behaviour
Assess how critical the error is, and based on this, show a popup instead of the dialog in some cases. Add commands to allow delayed display of the main dialog, and stepping through multiple error reports in the dialog. Also add a new error category for shader errors, since these are always emitted from the render thread and we can’t easily attribute them to an aircraft, scenery or core feature.
This commit is contained in:
parent
e8952b6c0f
commit
8a33f86e46
1 changed files with 89 additions and 12 deletions
|
@ -74,10 +74,13 @@ enum class Aggregation {
|
||||||
MultiPlayer,
|
MultiPlayer,
|
||||||
Unknown, ///< error coudln't be attributed more specifcially
|
Unknown, ///< error coudln't be attributed more specifcially
|
||||||
OutOfMemory, ///< seperate category to give it a custom message
|
OutOfMemory, ///< seperate category to give it a custom message
|
||||||
Traffic
|
Traffic,
|
||||||
|
ShadersEffects
|
||||||
};
|
};
|
||||||
|
|
||||||
// these should correspond to simgear::ErrorCode enum
|
// these should correspond to simgear::ErrorCode enum
|
||||||
|
// they map to translateable strings in fgdata/Translations/sys.xml
|
||||||
|
|
||||||
string_list static_errorIds = {
|
string_list static_errorIds = {
|
||||||
"error-missing-shader",
|
"error-missing-shader",
|
||||||
"error-loading-texture",
|
"error-loading-texture",
|
||||||
|
@ -116,7 +119,8 @@ string_list static_categoryIds = {
|
||||||
"error-category-multiplayer",
|
"error-category-multiplayer",
|
||||||
"error-category-unknown",
|
"error-category-unknown",
|
||||||
"error-category-out-of-memory",
|
"error-category-out-of-memory",
|
||||||
"error-category-traffic"};
|
"error-category-traffic",
|
||||||
|
"error-category-shaders"};
|
||||||
|
|
||||||
class RecentLogCallback : public simgear::LogCallback
|
class RecentLogCallback : public simgear::LogCallback
|
||||||
{
|
{
|
||||||
|
@ -221,6 +225,7 @@ public:
|
||||||
|
|
||||||
bool haveShownToUser = false;
|
bool haveShownToUser = false;
|
||||||
OccurrenceVec errors;
|
OccurrenceVec errors;
|
||||||
|
bool isCritical = false;
|
||||||
|
|
||||||
bool addOccurence(const ErrorOcurrence& err);
|
bool addOccurence(const ErrorOcurrence& err);
|
||||||
};
|
};
|
||||||
|
@ -241,7 +246,6 @@ public:
|
||||||
|
|
||||||
void collectError(simgear::LoadFailure type, simgear::ErrorCode code, const std::string& details, const sg_location& location)
|
void collectError(simgear::LoadFailure type, simgear::ErrorCode code, const std::string& details, const sg_location& location)
|
||||||
{
|
{
|
||||||
SG_LOG(SG_GENERAL, SG_WARN, "Error:" << static_errorTypeIds.at(static_cast<int>(type)) << " from " << static_errorIds.at(static_cast<int>(code)) << "::" << details << "\n\t" << location.asString());
|
|
||||||
ErrorOcurrence occurrence{code, type, details, location, time(nullptr)};
|
ErrorOcurrence occurrence{code, type, details, location, time(nullptr)};
|
||||||
|
|
||||||
// snapshot the top of the context stacks into our occurence data
|
// snapshot the top of the context stacks into our occurence data
|
||||||
|
@ -255,9 +259,24 @@ public:
|
||||||
auto it = getAggregateForOccurence(occurrence);
|
auto it = getAggregateForOccurence(occurrence);
|
||||||
|
|
||||||
// add to the occurence, if it's not a duplicate
|
// add to the occurence, if it's not a duplicate
|
||||||
if (it->addOccurence(occurrence)) {
|
if (!it->addOccurence(occurrence)) {
|
||||||
it->lastErrorTime.stamp();
|
return; // duplicate, nothing else to do
|
||||||
_reportsDirty = true;
|
}
|
||||||
|
|
||||||
|
// log it once we know it's not a duplicate
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "Error:" << static_errorTypeIds.at(static_cast<int>(type)) << " from " << static_errorIds.at(static_cast<int>(code)) << "::" << details << "\n\t" << location.asString());
|
||||||
|
|
||||||
|
it->lastErrorTime.stamp();
|
||||||
|
_reportsDirty = true;
|
||||||
|
|
||||||
|
const auto ty = it->type;
|
||||||
|
// decide if it's a critical error or not
|
||||||
|
if ((ty == Aggregation::OutOfMemory) || (ty == Aggregation::MainAircraft) || (ty == Aggregation::InputDevice)) {
|
||||||
|
it->isCritical = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == simgear::ErrorCode::LoadEffectsShaders) {
|
||||||
|
// it->isCritical = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,8 +349,13 @@ public:
|
||||||
});
|
});
|
||||||
assert(it != _aggregated.end());
|
assert(it != _aggregated.end());
|
||||||
_activeReportIndex = std::distance(_aggregated.begin(), it);
|
_activeReportIndex = std::distance(_aggregated.begin(), it);
|
||||||
|
_displayNode->setBoolValue("have-next", _activeReportIndex < (_aggregated.size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
flightgear::sentryReportUserError(static_categoryIds.at(catId), detailsTextStream.str());
|
void sendReportToSentry(AggregateReport& report)
|
||||||
|
{
|
||||||
|
const int catId = static_cast<int>(report.type);
|
||||||
|
flightgear::sentryReportUserError(static_categoryIds.at(catId), _displayNode->getStringValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeReportToStream(const AggregateReport& report, std::ostream& os) const;
|
void writeReportToStream(const AggregateReport& report, std::ostream& os) const;
|
||||||
|
@ -341,6 +365,7 @@ public:
|
||||||
|
|
||||||
bool dismissReportCommand(const SGPropertyNode* args, SGPropertyNode*);
|
bool dismissReportCommand(const SGPropertyNode* args, SGPropertyNode*);
|
||||||
bool saveReportCommand(const SGPropertyNode* args, SGPropertyNode*);
|
bool saveReportCommand(const SGPropertyNode* args, SGPropertyNode*);
|
||||||
|
bool showErrorReportCommand(const SGPropertyNode* args, SGPropertyNode*);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto ErrorReporter::ErrorReporterPrivate::getAggregateForOccurence(const ErrorReporter::ErrorReporterPrivate::ErrorOcurrence& oc)
|
auto ErrorReporter::ErrorReporterPrivate::getAggregateForOccurence(const ErrorReporter::ErrorReporterPrivate::ErrorOcurrence& oc)
|
||||||
|
@ -427,6 +452,13 @@ auto ErrorReporter::ErrorReporterPrivate::getAggregateForOccurence(const ErrorRe
|
||||||
return getAggregate(Aggregation::FGData);
|
return getAggregate(Aggregation::FGData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// becuase we report shader errors from the main thread, they don't
|
||||||
|
// get attributed. Collect them into their own category, which also
|
||||||
|
// means we can display a more specific message
|
||||||
|
if (oc.code == simgear::ErrorCode::LoadEffectsShaders) {
|
||||||
|
return getAggregate(Aggregation::ShadersEffects);
|
||||||
|
}
|
||||||
|
|
||||||
return getAggregate(Aggregation::Unknown);
|
return getAggregate(Aggregation::Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,7 +551,6 @@ bool ErrorReporter::ErrorReporterPrivate::dismissReportCommand(const SGPropertyN
|
||||||
|
|
||||||
// clear any values underneath displayNode?
|
// clear any values underneath displayNode?
|
||||||
|
|
||||||
|
|
||||||
_nextShowTimeout.stamp();
|
_nextShowTimeout.stamp();
|
||||||
_reportsDirty = true; // set this so we check for another report to present
|
_reportsDirty = true; // set this so we check for another report to present
|
||||||
_activeReportIndex = -1;
|
_activeReportIndex = -1;
|
||||||
|
@ -527,6 +558,39 @@ bool ErrorReporter::ErrorReporterPrivate::dismissReportCommand(const SGPropertyN
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ErrorReporter::ErrorReporterPrivate::showErrorReportCommand(const SGPropertyNode* args, SGPropertyNode*)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> g(_lock);
|
||||||
|
|
||||||
|
if (_aggregated.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->getBoolValue("next")) {
|
||||||
|
_activeReportIndex++;
|
||||||
|
if (_activeReportIndex >= _aggregated.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (args->hasChild("index")) {
|
||||||
|
_activeReportIndex = args->getIntValue("index");
|
||||||
|
if ((_activeReportIndex < 0) || (_activeReportIndex >= _aggregated.size())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_activeReportIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& report = _aggregated.at(_activeReportIndex);
|
||||||
|
presentErrorToUser(report);
|
||||||
|
|
||||||
|
auto gui = globals->get_subsystem<NewGUI>();
|
||||||
|
if (!gui->getDialog("error-report")) {
|
||||||
|
gui->showDialog("error-report");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ErrorReporter::ErrorReporterPrivate::saveReportCommand(const SGPropertyNode* args, SGPropertyNode*)
|
bool ErrorReporter::ErrorReporterPrivate::saveReportCommand(const SGPropertyNode* args, SGPropertyNode*)
|
||||||
{
|
{
|
||||||
if (_activeReportIndex < 0) {
|
if (_activeReportIndex < 0) {
|
||||||
|
@ -667,7 +731,8 @@ void ErrorReporter::preinit()
|
||||||
|
|
||||||
void ErrorReporter::init()
|
void ErrorReporter::init()
|
||||||
{
|
{
|
||||||
if (!d->_enabledNode) {
|
const auto developerMode = false; // fgGetBool("sim/developer-mode");
|
||||||
|
if (developerMode || !d->_enabledNode) {
|
||||||
SG_LOG(SG_GENERAL, SG_INFO, "Error reporting disabled");
|
SG_LOG(SG_GENERAL, SG_INFO, "Error reporting disabled");
|
||||||
simgear::setFailureCallback(simgear::FailureCallback());
|
simgear::setFailureCallback(simgear::FailureCallback());
|
||||||
simgear::setErrorContextCallback(simgear::ContextCallback());
|
simgear::setErrorContextCallback(simgear::ContextCallback());
|
||||||
|
@ -677,6 +742,7 @@ void ErrorReporter::init()
|
||||||
|
|
||||||
globals->get_commands()->addCommand("dismiss-error-report", d.get(), &ErrorReporterPrivate::dismissReportCommand);
|
globals->get_commands()->addCommand("dismiss-error-report", d.get(), &ErrorReporterPrivate::dismissReportCommand);
|
||||||
globals->get_commands()->addCommand("save-error-report-data", d.get(), &ErrorReporterPrivate::saveReportCommand);
|
globals->get_commands()->addCommand("save-error-report-data", d.get(), &ErrorReporterPrivate::saveReportCommand);
|
||||||
|
globals->get_commands()->addCommand("show-error-report", d.get(), &ErrorReporterPrivate::showErrorReportCommand);
|
||||||
|
|
||||||
// cache these values here
|
// cache these values here
|
||||||
d->_fgdataPathPrefix = globals->get_fg_root().utf8Str();
|
d->_fgdataPathPrefix = globals->get_fg_root().utf8Str();
|
||||||
|
@ -686,6 +752,7 @@ void ErrorReporter::init()
|
||||||
void ErrorReporter::update(double dt)
|
void ErrorReporter::update(double dt)
|
||||||
{
|
{
|
||||||
bool showDialog = false;
|
bool showDialog = false;
|
||||||
|
bool showPopup = false;
|
||||||
bool havePendingReports = false;
|
bool havePendingReports = false;
|
||||||
|
|
||||||
// beginning of locked section
|
// beginning of locked section
|
||||||
|
@ -731,7 +798,13 @@ void ErrorReporter::update(double dt)
|
||||||
const auto ageSec = (n - report.lastErrorTime).toSecs();
|
const auto ageSec = (n - report.lastErrorTime).toSecs();
|
||||||
if (ageSec > NoNewErrorsTimeout) {
|
if (ageSec > NoNewErrorsTimeout) {
|
||||||
d->presentErrorToUser(report);
|
d->presentErrorToUser(report);
|
||||||
showDialog = true;
|
if (report.isCritical) {
|
||||||
|
showDialog = true;
|
||||||
|
} else {
|
||||||
|
showPopup = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
d->sendReportToSentry(report);
|
||||||
|
|
||||||
// if we show one report, don't consider any others for now
|
// if we show one report, don't consider any others for now
|
||||||
break;
|
break;
|
||||||
|
@ -745,8 +818,9 @@ void ErrorReporter::update(double dt)
|
||||||
}
|
}
|
||||||
} // end of locked section
|
} // end of locked section
|
||||||
|
|
||||||
if (showDialog && flightgear::isHeadlessMode()) {
|
if (flightgear::isHeadlessMode()) {
|
||||||
showDialog = false;
|
showDialog = false;
|
||||||
|
showPopup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not call into another subsystem with our lock held,
|
// do not call into another subsystem with our lock held,
|
||||||
|
@ -754,7 +828,6 @@ void ErrorReporter::update(double dt)
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
auto gui = globals->get_subsystem<NewGUI>();
|
auto gui = globals->get_subsystem<NewGUI>();
|
||||||
gui->showDialog("error-report");
|
gui->showDialog("error-report");
|
||||||
|
|
||||||
// this needs a bit more thought, disabling for the now
|
// this needs a bit more thought, disabling for the now
|
||||||
#if 0
|
#if 0
|
||||||
// pause the sim when showing the popup
|
// pause the sim when showing the popup
|
||||||
|
@ -762,6 +835,9 @@ void ErrorReporter::update(double dt)
|
||||||
pauseArgs->setBoolValue("force-pause", true);
|
pauseArgs->setBoolValue("force-pause", true);
|
||||||
globals->get_commands()->execute("do_pause", pauseArgs);
|
globals->get_commands()->execute("do_pause", pauseArgs);
|
||||||
#endif
|
#endif
|
||||||
|
} else if (showPopup) {
|
||||||
|
SGPropertyNode_ptr popupArgs(new SGPropertyNode);
|
||||||
|
globals->get_commands()->execute("show-error-notification-popup", popupArgs, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -770,6 +846,7 @@ void ErrorReporter::shutdown()
|
||||||
if (d->_enabledNode) {
|
if (d->_enabledNode) {
|
||||||
globals->get_commands()->removeCommand("dismiss-error-report");
|
globals->get_commands()->removeCommand("dismiss-error-report");
|
||||||
globals->get_commands()->removeCommand("save-error-report-data");
|
globals->get_commands()->removeCommand("save-error-report-data");
|
||||||
|
globals->get_commands()->removeCommand("show-error-report");
|
||||||
sglog().removeCallback(d->_logCallback.get());
|
sglog().removeCallback(d->_logCallback.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue