Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mruby ] Improve exception handlers #94

Merged
merged 1 commit into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1195,27 +1195,46 @@ context.Load(
var result = context.Evaluate<int>("mymethod(7)");
// => 700

// Syntax error and runtime error on the Ruby side can be supplemented with try/catch.
try
{
context.Evaluate<int>("raise 'ERRRO!'");
}
catch (Exception ex)
{
// ...
}

// Execute scripts, including the async method including VitalRouter, such as command publishing.
var script = context.CompileScript("3.times { |i| cmd :text, body: \"Hello Hello #{i}\" }");
await script.RunAsync();

// When a syntax error is detected, CompileScript throws an exception.
try
{
context.CompileScript("invalid invalid invalid");
}
catch (Exception ex)
{
}

// The completed script can be reused.
await script.RunAsync();

script.Dispose();
```

By default, syntax errors and runtime errors that occur in mruby scripts are reported via `UnityEngine.Debug.LogError`.
To customize this, do the following:

```cs
MRubyContext.GlobalErrorHandler = exception =>
// You can supplement Ruby runtime errors by try/catch RunAsync.
try
{
UnityEngine.Debug.LogError(exception);
};
await script.RunAsync();
}
catch (Exception ex)
{
// ...
}

script.Dispose();
```

Also, if you want to handle logs sent from the mruby side, do as follows:
if you want to handle logs sent from the mruby side, do as follows:

```cs
MRubyContext.GlobalLogHandler = message =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using AOT;
using Unity.Collections;
Expand Down Expand Up @@ -95,6 +96,12 @@ static void WriteHeader(void* ptr, uint size)

public class MRubyContext : SafeHandle
{
[ThreadStatic]
static Exception? lastScriptError;

[ThreadStatic]
static bool captureScriptErrors;

public static unsafe MRubyContext Create()
{
NativeMethods.MrbAllocfSet(MRubyAllocator.AllocPersistent);
Expand All @@ -113,9 +120,21 @@ public static MRubyContext Create(Router router, MRubyCommandPreset commandPrese
return context;
}

public static Action<Exception> GlobalErrorHandler { get; set; } = UnityEngine.Debug.LogError;
public static Action<Exception> GlobalErrorHandler { get; set; } = OnScriptError;
public static Action<string> GlobalLogHandler { get; set; } = UnityEngine.Debug.Log;

static void OnScriptError(Exception ex)
{
if (captureScriptErrors)
{
lastScriptError = ex;
}
else
{
UnityEngine.Debug.LogError(ex);
}
}

public MRubySharedState SharedState { get; }

public Router Router
Expand Down Expand Up @@ -194,7 +213,21 @@ public unsafe MrbValueHandle EvaluateUnsafe(ReadOnlySpan<byte> rubySource)
Bytes = ptr,
Length = rubySource.Length
};
resultValue = NativeMethods.MrbLoad(DangerousGetPtr(), source);
try
{
lastScriptError = null;
captureScriptErrors = true;
resultValue = NativeMethods.MrbLoad(DangerousGetPtr(), source);
if (lastScriptError != null)
{
throw lastScriptError;
}
}
finally
{
captureScriptErrors = false;
lastScriptError = null;
}
}
return new MrbValueHandle(resultValue, this);
}
Expand All @@ -215,15 +248,26 @@ public unsafe MRubyScript CompileScript(ReadOnlySpan<byte> rubySource)
Bytes = ptr,
Length = rubySource.Length
};
var scriptPtr = NativeMethods.ScriptCompile(DangerousGetPtr(), source);
if (scriptPtr == null)
try
{
throw new MRubyScriptCompileException();
captureScriptErrors = true;
lastScriptError = null;

var scriptPtr = NativeMethods.ScriptCompile(DangerousGetPtr(), source);
if (scriptPtr == null)
{
throw new MRubyScriptCompileException();
}

var script = new MRubyScript(this, scriptPtr);
MRubyScript.Scripts.TryAdd(script.ScriptId, script);
return script;
}
finally
{
captureScriptErrors = false;
lastScriptError = null;
}

var script = new MRubyScript(this, scriptPtr);
MRubyScript.Scripts.TryAdd(script.ScriptId, script);
return script;
}
}

Expand Down
28 changes: 28 additions & 0 deletions src/VitalRouter.Unity/Assets/VitalRouter.Tests/MRubyScriptTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,34 @@ public async Task Subscribers()
Assert.That(stateCommand!.IntValue, Is.EqualTo(123));
Assert.That(stateCommand!.StringValue, Is.EqualTo("hoge moge"));
}

[Test]
public void CatchSynctaxError()
{
var router = new Router();
var commandPreset = new TestCommandPreset();
var ctx = MRubyContext.Create(router, commandPreset);

Assert.Throws<MRubyScriptCompileException>(() => ctx.CompileScript("1 1"));
}

[Test]
public void CatchRuntimeError()
{
var router = new Router();
var commandPreset = new TestCommandPreset();
var ctx = MRubyContext.Create(router, commandPreset);

var script = ctx.CompileScript( "raise 'hoge fuga'");
Assert.ThrowsAsync<MRubyScriptException>(async () => await script.RunAsync());
}

[Test]
public void CatchEvaluateError()
{
var ctx = MRubyContext.Create();
Assert.Throws<MRubyScriptException>(() => ctx.Load( "raise 'hoge fuga'"));
}
}
}
#endif
Loading