MRubyD: A new mruby virtual machine implemented in pure C#
MRubyD 是一个用 C# 编写的全新 mruby 虚拟机,旨在与 C# 游戏引擎无缝集成,并高度兼容 Ruby。它利用 C# 的特性实现高性能和可扩展性,目前处于预览版。主要特性包括 C# 实现、高性能、Ruby API 兼容性(开发中)以及丰富的库集成。文章还介绍了其局限性、未来计划、安装方法和基本用法,包括执行字节码、处理 `MRubyValue`、通过 C# 定义 Ruby 类/模块/方法,以及编译 .mrb 文件的方法。
MRubyD:一个用纯 C# 实现的全新 mruby 虚拟机
MRubyD 是一个全新的 mruby 虚拟机,完全使用 C# 实现。 "MRubyD" 的名字代表 mruby for dotnet,并致敬了著名的替代实现 mruby/c。 它的设计目标是与基于 C# 的游戏引擎无缝集成,并强调 ruby 级别的兼容性。 MRubyD 利用最新的 C# 功能,以实现高性能和高可扩展性。
注意: 此库目前为预览版。
特性
- 用 C# 实现: 利用 C# 的强大功能,确保与基于 C# 的游戏引擎无缝集成。
- 高性能: 充分利用现代 C# 语言特性,例如托管指针、
Span
以及 .NET 运行时 GC 和 JIT 编译器带来的性能优势,以提供卓越的速度和效率。 - 与 Ruby 级别的 API 高度兼容(开发中): 旨在用于具有一定资源量的软件,例如游戏。 因此,我们专注于 Ruby API 的兼容性。 在发布时,所有 opcode 都已实现,并通过了来自 mruby 仓库的 syntax.rb 测试。
- 丰富的库集成和可扩展性: 与原始 C 实现相比,从 Ruby 调用 C# 丰富的库非常简单,这使得 VM 具有高度的可扩展性。
局限性(预览版)
此版本为预览版,具有以下约束:
- 内置类型和方法仍在实现中。
- 有关当前支持的方法,请参考 ruby test 等。
- 我们正在努力支持默认内置于 mruby 中的所有方法。
- 尚未实现
private
和protected
可见性。(mruby 在 3.4 版本中对此提供了支持) - 该项目仅提供 VM 实现; 它不包含编译器。 要编译 mruby 脚本,你需要原生 mruby-compiler。
最新路线图
- 实现内置 ruby 库
- 支持 Fiber
- 将所有 ruby 代码移植到 C# (出于性能考虑)
- Unity 集成
- 新版本的 VitalRouter.MRuby。
安装
dotnet add package MRubyD
基本用法
执行字节码
def fibonacci(n)
return n if n <= 1
fibonacci(n - 1) + fibonacci(n - 2)
end
fibonacci 10
$ mrbc -o fibonaci.mrbc fibonacci.rb
using MRubyD;
// 读取 .mrb 字节码。
var bytes = File.ReadAllBytes("fibonacci.mrb");
// 初始化状态
var state = MRubyState.Create();
// 执行字节码
var result = state.Exec(bytes);
result.IsInteger //=> true
result.IntegerValue //=> 55
这是一个执行字节码的示例。 有关如何将 Ruby 源代码转换为 mruby 字节码的信息,请参见 如何编译 .mrb 部分。
处理 MRubyValue
上面的 result
是 MRubyValue
。 它代表一个 Ruby 值。
value.IsNil //=> 如果是 nil 则为 true
value.IsInteger //=> 如果是 integer 则为 true
value.IsFloat //=> 如果是 float 则为 true
value.IsSymbol //=> 如果是 Symbol 则为 true
value.IsObject //=> 如果是任何已分配的对象类型则为 true
value.VType //=> 将已知的 ruby 类型作为 C# 枚举获取。
value.IntegerValue //=> 作为 C# Int64 获取
value.FloatValue //=> 作为 C# float 获取
value.SymbolValue //=> 作为 `Symbol` 获取
value.As<RString>() //=> 作为对象值获取
// 模式匹配
if (vlaue.Object is RString str)
{
// ...
}
swtich (value)
{
case { IsInteger: true }:
// ...
break;
case { Object: RString str }:
// ...
break;
}
var intValue = MRubyValue.From(100); // 创建 int 值
var floatValue = MRubyValue.From(1.234f); // 创建 float 值
var objValue = MRubyValue.From(str); // 创建已分配的 ruby 对象值
通过 C# 定义 ruby 类/模块/方法
// 创建 MRubyState 对象。
var state = MRubyState.Create();
// 定义类
var classA = state.DefineClass(Intern("A"u8), c =>
{
// 采用必需参数的方法定义。
c.DefineMethod(Intern("plus100"u8), (state, self) =>
{
var arg0 = state.GetArgAsIntegeger(0); // 获取第一个参数(索引:0)
return MRubyValue.From(arg0 + 100);
});
// 采用块参数的方法定义。
c.DefineMethod(Intern("method2"), (state, self) =>
{
var arg0 = state.GetArg(0);
var blockArg = state.GetBlockArg();
if (!blockArg.IsNil)
{
// 执行 `Proc#call`
state.Send(blockArg, state.Intern("call"u8), arg0);
}
});
// 其他复杂参数...
c.DefineMethod(Intern("method3"), (state, self) =>
{
var keywordArg = state.GetKeywordArg(state.Intern("foo"))
Console.WriteLine($"foo: {keywordArg}");
// 参数类型检查
state.EnsureValueType(keywordArg, MrubyVType.Integer);
var restArguments = state.GetRestArguments();
for (var i = 0; i < restArguments.Length; i++)
{
Console.WriteLine($"rest arg({i}: {restArguments[i]})");
}
});
// 类方法
c.DefineClassMethod(Intern("classmethod1"), (state, self) =>
{
var str = state.NewString($"hoge fuga");
return MRubyValue.From(str);
});
});
// Monkey patching
classA.DefineMethod(Intern("additional_method1"u8), (state, self) => { /* ... */ });
// 定义模块
var moduleA = state.DefineModule(Intern("ModuleA");)
state.DefineMethod(moduleA, Intern("additional_method2"u8), (state, self) => MRubyValue.From(123));
state.IncludeModule(classA, moduleA);
a = A.new
a.plus100(123) #=> 223
a.method2(1) { |a| a } #=> 1
a.additionoa_method2 #=> 123
A.classmethod1 #=> "hoge fuga"
Symbol/String
mruby 中的字符串表示形式为 utf8。 因此,要从 C# 生成 ruby 字符串,内部使用了 Utf8StringInterpolation。
// 创建字符串字面量。
var str1 = state.NewString("HOGE HOGE"u8);
// 通过插值创建字符串
var x = 123;
var str2 = state.NewString($"x={x}");
// 包装 MRubyValue..
var strValue = MRubyValue.From(str1);
mruby 中有一个类似于 String 的概念,称为 Symbol
。 与 String 一样,它使用 utf8 字符串创建,但在内部它是一个 uint 整数。 符号通常用于方法 ID 和类 ID。
要从 C# 创建符号,请使用 Intern
。
// 符号字面量
var sym1 = state.Intern("sym"u8)
// 来自字符串的符号
var sym2 = state.ToSymbol(state.NewString("sym2"u8));
如何编译 .mrb
MRubyD 仅包含 mruby 虚拟机。 因此,在执行 .rb 源代码之前,需要将其转换为 .mrb 字节码。 基本上,你需要 mruby 项目提供的原生编译器。
$ git clone git@github.com:mruby/mruby.git
$ cd mruby
$ rake
$ ./build/host/bin/mrubc
MRubyD.Compiler
为了简化从 C# 进行编译,我们还提供了 MRubyD.Compiler 包,它是原生编译器的简单包装器。
注意: 此 MRubyD.Compiler 包是原生二进制文件的简单包装器。 目前,提供了适用于 linux (x64/arm64)、macOS (x64/arm64) 和 windows (x64) 的构建。
dotnet add package MRubyD.Compiler
using MRubyD.Compiler;
var source = """
def a
1
end
a
"""u8;
var state = MRubyState.Create();
var compiler = MRubyCompiler.Create(state);
var irep = compiler.Compile(source);
state.Exec(irep); // => 1
LICENSE
MIT