Error Handling
Pretty much every method of a plugin or a script returns a special type GameResult which is a wrapper over
Result<(), GameError>. This allows you to easily handle various errors that may occur during the code execution by
applying ? operator.
#![allow(unused)]
fn main() {
#[derive(Visit, Reflect, Default, Debug, Clone, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "bf0f9804-56cb-4a2e-beba-93d75371a568")]
#[visit(optional)]
struct MyScript {
handle: Handle<Node>,
}
impl ScriptTrait for MyScript {
fn on_update(&mut self, context: &mut ScriptContext) -> GameResult {
let node = context.scene.graph.try_get(context.handle)?;
println!("{}", node.name());
Ok(())
}
}
}
When an error occurs in any of the methods, the engine simply prints it to the log and continues execution as usual. This is the key difference between errors and standard panic mechanism.
The GameError type can hold errors of pretty much any kind, so any error that implements std::error::Error trait
can be returned.
Backtrace capture
By default, all errors that may occur during the code execution don’t capture the backtrace, which may significantly
complicate tracking of the original source of error. Backtrace capture can be enabled by using enable_backtrace_capture:
method.
#![allow(unused)]
fn main() {
#[derive(Visit, Clone, Reflect, Debug)]
struct MyPlugin;
impl Plugin for MyPlugin {
fn init(&mut self, scene_path: Option<&str>, context: PluginContext) -> GameResult {
// This method can be called at any point in your game, this way you can enable or disable
// enhanced error data collection when needed.
enable_backtrace_capture(true);
Ok(())
}
}
}
This way the engine will print the error message alongside with the backtrace which points to the exact place where the error originates from. Keep in mind that the backtrace capturing process can be very slow, so backtrace capture should be disabled in production builds.
Error Handler
The engine also allows you to handle all errors that may occur during script or plugin code execution. Each plugin has
the Plugin::on_game_error method for that:
#![allow(unused)]
fn main() {
#[derive(Visit, Clone, Reflect, Debug)]
struct MyGame;
// Define an error type for your game first.
#[derive(Debug)]
pub enum MyError {
NoScene,
}
impl std::error::Error for MyError {}
impl Display for MyError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
MyError::NoScene => {
write!(f, "The scene is not specified!")
}
}
}
}
impl Plugin for MyGame {
fn init(&mut self, scene_path: Option<&str>, context: PluginContext) -> GameResult {
match scene_path {
Some(scene_path) => {
context.async_scene_loader.request(scene_path);
Ok(())
}
// Spawn an error.
None => Err(GameError::user(MyError::NoScene)),
}
}
fn on_game_error(&mut self, context: &mut PluginContext, error: &GameError) -> bool {
if let GameErrorKind::UserError(ref err) = error.kind {
if let Some(my_error) = err.downcast_ref::<MyError>() {
// Do something useful, for example show a warning message box.
// ...
// Mark the error as handled.
return true;
}
}
// The rest is unhandled.
false
}
}
}
This method must return either true or false. true means that the error was handled and no further actions from
the engine is needed. false means that the error is still unhandled, and it will be processed by the engine (usually
just printed to the log, but this may change in the future).