Skip to main content

Error handling

This guide describes the current development API. The callback signatures below differ from the v0.1.1 release.

Rhea reports recoverable loading, preparation, and instantiation results through rhea::ErrorReport. A report contains an operation status and a list of individual rhea::Error diagnostics. Lower-level Orion requests use the separate orion::Error enum; Orion does not depend on Rhea.

Checking an operation result​

Include <Rhea/Error.h>. A default-constructed ErrorReport represents success with no diagnostics.

worlds->openWorld("//user/MyWorld/world.scene", [](rhea::ErrorReport report) {
if (report.isCanceled()) {
// A superseded request usually needs no error dialog.
return;
}

if (report.isError()) {
for (auto& error : report.errors) {
// Display or log the message, resource address, and context.
}
return;
}

// The world-opening operation completed successfully.
});

Use the report status to determine the overall result:

StatusMeaning
ErrorStatus::SUCCESSThe operation succeeded.
ErrorStatus::FAILUREThe operation failed.
ErrorStatus::CANCELEDThe operation was canceled or superseded.

report.isError() is true for both failure and cancellation. report.isCanceled() is true only when the overall status is CANCELED. Failure takes precedence over cancellation when reports are merged. A report can therefore contain a cancellation diagnostic while its overall status remains FAILURE.

Use each diagnostic's getCode() for specific recovery decisions. Do not interpret message strings or numeric enum values as an API. Suppress cancellation messages only when appropriate for the application's workflow.

Reports are passed by value. Move a report when transferring it to another callback and it is no longer needed locally. Callbacks may run synchronously or later; capture temporary data by value and keep objects referenced through this alive until completion.

Reading and displaying diagnostics​

An individual Error contains:

APIMeaning
getCode()A machine-readable category.
getMessage()A human-readable description.
getResourceId()The failing resource, when known.
getContext()Additional key/value entries.
getCause()An optional underlying cause.
getSeverness()The existing integer severity field; it does not determine report status.

A world-opening failure can identify a missing texture rather than the selected world. Preserve that address and use context to identify the referring scene.

The following formatter includes all report entries and their cause chains. Errors are copied because several current accessors are not const-qualified. Cause traversal is bounded to avoid an infinite loop if application code creates a cycle.

#include <Rhea/Error.h>
#include <string>

std::string formatError(rhea::Error error)
{
std::string text;
for (int depth = 0; depth < 16 && error.isError(); ++depth) {
if (!text.empty())
text += "\nCaused by:\n";

text += error.getMessage().empty()
? "Operation failed."
: error.getMessage();

if (!error.getResourceId().empty())
text += "\nResource: " + error.getResourceId();

for (const auto& entry : error.getContext())
text += "\n" + entry.key + ": " + entry.value;

auto cause = error.getCause();
if (!cause)
break;
error = *cause;
}
return text;
}

std::string formatReport(const rhea::ErrorReport& report)
{
std::string text;
for (const auto& error : report.errors) {
if (!text.empty())
text += "\n\n";
text += formatError(error);
}

if (text.empty() && report.isError())
text = report.isCanceled() ? "Operation canceled." : "Operation failed.";

return text;
}

Context and causes are optional and depend on the loader and failure stage. Parser line information is not guaranteed to be available in the callback even when it appears in the engine log.

Error categories​

Error::CodeInterpretation
NONENo error; ignored by ErrorReport::addError().
NOT_FOUNDResource, realm, path, or referenced object not found.
ACCESS_DENIEDPermission was denied.
AUTHENTICATION_FAILEDAuthentication is required or failed.
UNSUPPORTED_FORMATThe resource is unsuitable for the requested operation.
INVALID_ARGUMENTAn invalid argument was supplied.
INVALID_DEFINITIONInvalid configuration or scene structure.
INVALID_DATAData could not be parsed or validated.
UNKNOWN_TYPEA required type is not registered. Check spelling, enabled Fabrics, registrations, and version compatibility.
DEPENDENCY_FAILEDRequired dependencies failed; inspect the associated diagnostics or cause.
CANCELEDThe request was canceled or superseded.
UNAVAILABLEA required capability or operation is unavailable.
RESOURCE_ERRORResource access, transport, or stream failure; this does not imply malformed contents.

Existing failure sites may use broader categories. An unknown type does not by itself establish which module is missing.

Callback signatures​

Argument order differs across APIs:

OperationCallback
Application::loadResource()(Component*, ErrorReport)
ResourceList::load() / Unit::ReadyHandler(ErrorReport)
ResourceService::loadAsync()(ResourceList*, ErrorReport)
WorldService::openWorld()(ErrorReport)
WorldService::loadScene()(Scene*, ErrorReport)
Resource::LoadedHandler(ErrorReport, Unit*)
Resource::ReadyHandler / PrepareHandler(ErrorReport)
SceneResource::spawn() / InstanceHandler(ErrorReport, Unit*)

Check the report before using a returned object. Successful resource loading does not imply successful scene instantiation, preparation, or activation.

app->loadResource(scene_id, "//meworla/scene",
[](rhea::Component* component, rhea::ErrorReport report) {
if (report.isError()) {
// Log or display formatReport(report).
return;
}

auto* scene_resource = doCast<rhea::SceneResource>(component);
if (!scene_resource) {
// Loading succeeded, but the result is not a scene definition.
return;
}
// Instantiate under a valid parent and check that callback's report too.
});

The snippet assumes the application and scene-resource headers and scene_id are available. A file can load as a generic resource and subsequently be rejected by the world-opening layer.

A ResourceList* supplied by loadAsync() is borrowed for the callback. Do not retain it for later use without an explicit ownership arrangement. A report received by value can be retained independently.

Reporting and aggregating errors in extensions​

Complete each request exactly once. For resource-producing callbacks, return an initialized object on success and nullptr on failure or cancellation. Clean up temporary buffers and partial instances before reporting failure according to the API's ownership contract.

#include <utility>

rhea::Error error(
rhea::Error::Code::INVALID_DEFINITION,
"Required property is missing: mesh",
source_resource_id);
error.addContext("node", "objects/boat");
error.addContext("property", "mesh");

rhea::ErrorReport report;
report.addError(error);
if (handler)
handler(std::move(report), nullptr);

addError() ignores Code::NONE. A cancellation changes a successful report to CANCELED; other error codes change it to FAILURE. Adding a cancellation does not override an existing failure.

Merge reports rather than replacing earlier diagnostics:

accumulated_report.merge(std::move(stage_report));
if (accumulated_report.isError()) {
// Clean up, then return the accumulated report.
}

Merge successful stage reports too, so the forwarding logic does not discard diagnostic information. The current API does not yet provide a defined warning mechanism: adding any non-cancellation error through addError() makes the operation fail, regardless of the integer severity field. Do not advertise success-with-warnings by changing severity alone.

For parallel or independently scheduled dependencies, collect reports until all started operations have completed, then invoke the parent callback once. Continue independent checks, but skip checks whose prerequisites failed. A report list is not a requirement to continue executing invalid objects.

For each dependency error, preserve its code, message, and failing resource address. Add the referring resource as context, for example referenced_by. Useful context conventions also include node, property, reference, line, and stream_error; these are not a fixed schema.

Use the report's list for independent failures. Use Error::cause when one error wraps another:

rhea::Error wrapped(
rhea::Error::Code::DEPENDENCY_FAILED,
"Could not prepare scene dependencies.", scene_id);
wrapped.setCause(std::make_shared<const rhea::Error>(original_error));

Avoid adding the same diagnostic again at each layer or creating cyclic cause chains.

Parser behavior​

simpleparser::IniFileParser::parse() returns its own parser Error enum. Compare it with IniFileParser::Error::PARSE_OK; obtain the number of parsed sections from sections.size().

A syntax error is reported even if earlier sections parsed successfully. Partial sections can remain available to the caller, but the current scene loader rejects a parser failure rather than silently treating that partial definition as complete. This does not establish a general best-effort scene-loading mode. Parser recovery and acceptance of incomplete scenes require explicit policies.

Orion and stream boundaries​

Orion uses orion::Error from <Orion/RequestError.h>. It remains independent of rhea::Error and ErrorReport. Rhea's translateLoadError(error, resource_id), declared in <Rhea/Resources/Resource.h>, converts an Orion code into an individual Rhea error, which can be added to a report.

Orion's IO_PENDING is an intermediate status, not a completed failure. orion::translateTransportError(int) translates Psio network statuses, not HTTP status codes or arbitrary stream integers. For a stream without a documented error domain, report a general RESOURCE_ERROR and retain its number in stream_error.

Current limits and application recovery​

ResourceList aggregates the reports of its load requests rather than retaining only one failure. SceneResource checks the root and configured node types before creating the tree, allowing multiple unknown types to be reported together. Other phases can still stop at their first invalid prerequisite, so the report is not guaranteed to enumerate every defect in an asset package.

Opening a new world starts a new session before its start scene has finished loading. If loading or preparing that scene fails, the previous session is not automatically restored. Keeping an open-browser dialog visible is useful UI behavior, but does not itself provide a rollback of the world session.

Only update recent-world history after a successful opening operation. Present a short heading and the individual failure messages, with resource addresses and an optional details view for context and causes.