{"id":30648146,"url":"https://github.com/hadashia/mrubycs","last_synced_at":"2026-05-23T06:14:58.631Z","repository":{"id":283612968,"uuid":"874198439","full_name":"hadashiA/MRubyCS","owner":"hadashiA","description":"A new mruby virtual machine implemented in C#.","archived":false,"fork":false,"pushed_at":"2026-05-14T14:47:53.000Z","size":42960,"stargazers_count":216,"open_issues_count":6,"forks_count":11,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-05-14T15:07:43.291Z","etag":null,"topics":["mruby"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hadashiA.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"hadashiA"}},"created_at":"2024-10-17T12:20:19.000Z","updated_at":"2026-05-14T14:47:57.000Z","dependencies_parsed_at":"2025-12-31T01:01:08.458Z","dependency_job_id":null,"html_url":"https://github.com/hadashiA/MRubyCS","commit_stats":null,"previous_names":["hadashia/mrubyd","hadashia/mrubycs"],"tags_count":60,"template":false,"template_full_name":null,"purl":"pkg:github/hadashiA/MRubyCS","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hadashiA%2FMRubyCS","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hadashiA%2FMRubyCS/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hadashiA%2FMRubyCS/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hadashiA%2FMRubyCS/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hadashiA","download_url":"https://codeload.github.com/hadashiA/MRubyCS/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hadashiA%2FMRubyCS/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33384627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T04:15:53.637Z","status":"ssl_error","status_checked_at":"2026-05-23T04:15:53.242Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mruby"],"created_at":"2025-08-31T05:45:00.581Z","updated_at":"2026-05-23T06:14:58.623Z","avatar_url":"https://github.com/hadashiA.png","language":"C#","funding_links":["https://github.com/sponsors/hadashiA"],"categories":[],"sub_categories":[],"readme":"# mruby/cs\n\nMRubyCS is a pure C# [mruby](https://github.com/mruby/mruby) virtual machine implementation  It combines high Ruby-level compatibility with the performance and extensibility of modern C#.\n\nEasily embed Ruby into Unity or .NET—empowering users to script game logic while keeping your core engine in C#.\n\n\u003e [!NOTE]\n\u003e [VitalRouter.MRuby](https://github.com/hadashiA/VitalRouter) provides a high-level framework for integrating MRubyCS with Unity (and .NET), including command routing and script lifecycle management.\n\n## Why mruby?\n\nRuby's elegant, expressive syntax makes it ideal for building DSLs (Domain-Specific Languages). Game designers and scenario writers can describe complex game logic — event triggers, dialogue trees, AI behavior — in clean, readable scripts without wrestling with C-like syntax.\n\n```ruby\n# Example: game event DSL\nscene :throne_room_betrayal do\n  sequence do\n    camera.focus_on :king, over: 1.2.seconds\n    king.say \"You have served me well, knight.\"\n    wait 0.5.seconds\n    advisor.move_to :behind_king\n    advisor.say \"Too well, perhaps.\"\n\n    choice do\n      option \"Draw your sword\" do\n        player.equip :longsword\n        goto :combat_phase\n      end\n\n      option \"Kneel\" do\n        player.animate :kneel\n        king.say \"Loyalty. How rare.\"\n        complete_scene\n      end\n    end\n  end\nend\n```\n\n\u003e [!NOTE]\n\u003e [Presentation at RubyKaigi 2026](https://speakerdeck.com/hadashia/mruby-on-c-number-from-vm-implementation-to-game-scripting)\n\n## Features\n\n- Support mruby 4.0 bytecode.\n- **Pure C# implementation/Zero native dependencies mruby VM** — runs anywhere Unity/.NET runs. No per-platform native builds to maintain.\n- **High performance** — leverages .NET JIT, GC, and modern C# optimizations with minimal overhead.\n- **Ruby compatible** — all opcodes implemented; passes mruby's official test suite\n  - [Syntax](https://github.com/hadashiA/MRubyCS/blob/main/tests/MRubyCS.Tests/ruby/test/syntax.rb), [Literals](https://github.com/hadashiA/MRubyCS/blob/main/tests/MRubyCS.Tests/ruby/test/literals.rb), [Lang](https://github.com/hadashiA/MRubyCS/blob/main/tests/MRubyCS.Tests/ruby/test/lang.rb), [Methods](https://github.com/hadashiA/MRubyCS/blob/main/tests/MRubyCS.Tests/ruby/test/methods.rb), [Module](https://github.com/hadashiA/MRubyCS/blob/main/tests/MRubyCS.Tests/ruby/test/module.rb), [Exception](https://github.com/hadashiA/MRubyCS/blob/main/tests/MRubyCS.Tests/ruby/test/exception.rb), ...\n  - Supported classes/modules and their method signatures are published as RBS files under [`sig/`](https://github.com/hadashiA/MRubyCS/tree/main/sig) — `Array`, `Hash`, `String`, `Integer`, `Float`, `Range`, `Proc`, `Symbol`, `Fiber`, `Time`, `Random`, `Enumerable`, `Comparable`, etc.\n  - Enumerable extensions (mruby-enum-ext): see [`sig/enumerable.rbs`](https://github.com/hadashiA/MRubyCS/blob/main/sig/enumerable.rbs)\n  - **Optional (opt-in)** — see [Optional Classes](#optional-classes-opt-in)\n      - [`Regexp`](https://github.com/hadashiA/MRubyCS/blob/main/sig/regexp.rbs) / [`MatchData`](https://github.com/hadashiA/MRubyCS/blob/main/sig/match_data.rbs) (via `mrb.DefineRegexp()`)\n      - [`IO`](https://github.com/hadashiA/MRubyCS/blob/main/sig/io.rbs) / [`File`](https://github.com/hadashiA/MRubyCS/blob/main/sig/file.rbs) / `IOError` (via `mrb.DefineIO()`)\n- **Fiber \u0026 async/await integration** — suspend Ruby execution and await C# async methods without blocking threads.\n- **Prism-based compiler** — uses [mruby-compiler2](https://github.com/picoruby/mruby-compiler2), the next-generation mruby compiler built on [Prism](https://github.com/ruby/prism) (the official CRuby parser), for more accurate and modern Ruby syntax support.\n\n## Performance\n\nIn the .NET JIT environment, execution speeds are equal to or faster than the original native mruby.\n\n\u003cimg width=\"594\" height=\"389\" alt=\"ss 2026-03-04 22 11 01\" src=\"https://github.com/user-attachments/assets/00cd3644-e460-4b21-a41e-661d484fe30c\" /\u003e\n\nThe above results were obtained on macOS with Apple M4 over 10 iterations.\n\nPlease refer to the following for the [benchmark code](https://github.com/hadashiA/MRubyCS/tree/main/sandbox/MRubyCS.Benchmark).\n\n## Limitations\n\n- As of mruby 4.0, almost all bundled classes/methods are supported.\n    - Support for extensions split into [mrbgems](https://github.com/mruby/mruby/tree/master/mrbgems) remains limited.\n- `Regexp` and `IO` / `File` are **opt-in**: Call `mrb.DefineRegexp()` / `mrb.DefineIO()` to add them. See [Optional Classes](#optional-classes-opt-in).\n\n## Table of Contents\n\n- [Installation](#installation)\n    - [NuGet](#nuget)\n    - [Unity](#unity)\n- [Basic Usage](#basic-usage)\n    - [Compiling and Executing Ruby Code](#compiling-and-executing-ruby-code)\n        - [Option A: Pre-compile bytecode](#option-a-pre-compile-bytecode)\n        - [Option B: Using Compiler library (runtime compile)](#option-b-using-compiler-library-runtime-compile)\n        - [Irep](#irep)\n        - [Compiler Reference](#compiler-reference)\n    - [Define ruby class/module/method by C#](#define-ruby-classmodulemethod-by-c)\n        - [Error handling \u0026 validation in C# methods](#error-handling--validation-in-c-methods)\n        - [Constants](#constants)\n    - [Call ruby method from C# side](#call-ruby-method-from-c-side)\n        - [Send with block / keyword arguments](#send-with-block--keyword-arguments)\n        - [Type conversion \u0026 introspection](#type-conversion--introspection)\n        - [Instance variables / class variables / global variables](#instance-variables--class-variables--global-variables)\n        - [Clone / Dup / Freeze](#clone--dup--freeze)\n    - [MRubyValue](#mrubyvalue)\n        - [Symbol/String](#symbolstring)\n        - [Array/Hash](#arrayhash)\n        - [Embedded custom C# data into MRubyValue](#embedded-custom-c-data-into-mrubyvalue)\n- [Optional Classes (opt-in)](#optional-classes-opt-in)\n    - [Regexp](#regexp)\n    - [IO / File](#io--file)\n- [Fiber (Coroutine)](#fiber-coroutine)\n- [Define async Ruby method (FiberScheduler)](#define-async-ruby-method-fiberscheduler)\n    - [Default behavior (no scheduler)](#default-behavior-no-scheduler)\n    - [With a scheduler installed](#with-a-scheduler-installed)\n    - [Defining async Ruby methods with `Await`](#defining-async-ruby-methods-with-await)\n    - [Low-level: `Suspend` + `FiberContinuation`](#low-level-suspend--fibercontinuation)\n    - [Thread routing (`SynchronizationContext`)](#thread-routing-synchronizationcontext)\n    - [Custom Schedulers (subclassing)](#custom-schedulers-subclassing)\n- [MRubyCS.Serializer](#mrubycsserializer)\n\n## Installation\n\n\u003e [!WARNING]\n\u003e The current version supports mruby 4.0 bytecode.\n\u003e Versions 0.70.0 and older supported mruby 3.0 bytecode.\n\u003e If you have bytecode from an older MRubyCS.Compiler (or mrbc), please regenerate it with the latest version.\n\n### NuGet\n\n| Package   | Description    | Latest version |\n|:----------|:---------------|----------------|\n| MRubyCS   |  Main package. A mruby vm implementation. | [![NuGet](https://img.shields.io/nuget/v/MRubyCS)](https://www.nuget.org/packages/MRubyCS) |\n| MRubyCS.Compiler | Compile ruby source code utility. (Native binding)  | [![NuGet](https://img.shields.io/nuget/v/MRubyCS.Compiler)](https://www.nuget.org/packages/MRubyCS.Compiler)   |\n| MRubyCS.Compiler.Cli | dotnet tool for compiling Ruby source to bytecode | [![NuGet](https://img.shields.io/nuget/v/MRubyCS.Compiler.Cli)](https://www.nuget.org/packages/MRubyCS.Compiler.Cli) |\n| MRubyCS.Serializer  | Converting Ruby and C# Objects Between Each Other | [![NuGet](https://img.shields.io/nuget/v/MRubyCS.Serializer)](https://www.nuget.org/packages/MRubyCS.Serializer)  |\n\n### Unity\n\n\u003e [!NOTE]\n\u003e Requirements: Unity 2021.3 or later.\n\n1. Install [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity).\n2. Install following packages via NugetForUnity\n    - Utf8StringInterpolation\n    - MRubyCS\n    - (Optional) MRubyCS.Serializer\n3. (Optional) To install utilities for generating mrb bytecode, refer to the [Compiling and Executing Ruby Code](#compiling-and-executing-ruby-code) section.\n\n## Basic Usage\n\n### Compiling and Executing Ruby Code\n\nmruby allows the compiler and runtime to be separated. By distributing only precompiled bytecode, you can keep the mruby compiler out of your production deployment.\n\n```mermaid\ngraph TB\n    subgraph host[\"host machine\"]\n        A[source code\u003cbr/\u003e.rb files]\n        C[byte-code\u003cbr/\u003e.mrb files]\n        A --\u003e|compile| C\n    end\n    C --\u003e|deploy/install| E\n    subgraph application[\"application\"]\n        D{{mruby VM}}\n        E[byte-code\u003cbr\u003e.mrb files]\n        E --\u003e|execute bytecode| D\n    end\n\n    style D fill:#ff4444,stroke:#cc0000,color:#ffffff,stroke-width:2px\n```\n\nYou can choose whether to deploy precompiled bytecode or raw source code:\n\n- Bytecode only:\n    - extremely compact and recommended for production environments.\n- Source code:\n    - compiled on the target machine.\n    - Note that compilation relies on the native compiler, so supported platforms are limited to those where mruby-compiler runs.\n\n\u003e [!TIP]\n\u003e Option A is recommended for production. Option B is convenient for development and prototyping.\n\n#### Option A: Pre-compile bytecode\n\nPre-compile Ruby source to `.mrb` bytecode with the CLI tool:\n\n```bash\ndotnet tool install -g MRubyCS.Compiler.Cli\nmruby-compiler fibonacci.rb -o fibonacci.mrb\n```\n\nOr with the C# API:\n\n```cs\nusing MRubyCS;\nusing MRubyCS.Compiler;\n\nvar mrb = MRubyState.Create();\nvar compiler = MRubyCompiler.Create(mrb);\n\nvar source = \"\"\"\n    def fibonacci(n)\n      return n if n \u003c= 1\n      fibonacci(n - 1) + fibonacci(n - 2)\n    end\n\n    fibonacci 10\n    \"\"\"u8;\n\n// Compile and save as .mrb file\nusing var compilation = compiler.Compile(source);\nFile.WriteAllBytes(\"fibonacci.mrb\", compilation.AsBytecode());\n```\n\nThen execute the pre-compiled bytecode:\n\n```cs\nusing MRubyCS;\n\nvar mrb = MRubyState.Create();\nvar bytecode = File.ReadAllBytes(\"/path/to/fibonacci.mrb\");\nvar result = mrb.LoadBytecode(bytecode);\n\nresult.IntegerValue //=\u003e 55\n```\n\n#### Option B: Using Compiler library (runtime compile)\n\n```bash\ndotnet add package MRubyCS\ndotnet add package MRubyCS.Compiler\n```\n\n```cs\nusing MRubyCS;\nusing MRubyCS.Compiler;\n\nvar mrb = MRubyState.Create();\nvar compiler = MRubyCompiler.Create(mrb);\n\nvar result = compiler.LoadSourceCode(\"\"\"\n    def fibonacci(n)\n      return n if n \u003c= 1\n      fibonacci(n - 1) + fibonacci(n - 2)\n    end\n\n    fibonacci 10\n    \"\"\"u8);\n\nresult.IntegerValue //=\u003e 55\n```\n\nSee also [MRubyCS.Compiler (library)](#mrubycscompiler-library) for installation details.\n\n#### Irep\n\nYou can also parse bytecode in advance. The result is called `Irep` in mruby terminology. Pre-parsing is useful when you want to execute the same bytecode multiple times without re-parsing overhead.\n\n```cs\nIrep irep = mrb.ParseBytecode(bytecode);\nmrb.Execute(irep);\n```\n\n`Irep` can be executed as is, or converted to `Proc`, `Fiber` before use. For details on Fiber, refer to the [Fiber](#fiber-coroutine) section.\n\n\u003e [!NOTE]\n\u003e - **`Dispose` when finished** — `MRubyState` is `IDisposable`. The VM itself has no unmanaged resources, but an installed `MRubyFiberScheduler` may hold cancellation tokens for parked fibers; `Dispose` cleans those up. A finalizer is in place as a backstop, but explicit disposal is preferred. If you never call `UseFiberScheduler`, omitting `Dispose` is harmless.\n\u003e - **Not thread-safe** — each `MRubyState` instance must be used from a single thread. For multi-threaded scenarios, create a separate instance per thread.\n\n---\n\n#### Compiler Reference\n\nThe MRubyCS runtime is pure C#, but the mrb compiler uses the native prism compiler.\nNote that the compiler's supported target platforms are subject to the following limitations.\n\n##### MRubyCS.Compiler.Cli (dotnet tool)\n\nThe `mruby-compiler` CLI supports additional output formats beyond simple `.mrb`:\n\n```bash\n# Dump bytecode in human-readable format\n$ mruby-compiler input.rb --dump\n\n# Generate C# code with embedded bytecode\n$ mruby-compiler input.rb -o Bytecode.cs --format csharp --csharp-namespace MyApp\n```\n\n\u003e [!TIP]\n\u003e For local tool installation, use `dotnet tool install MRubyCS.Compiler.Cli` and run with `dotnet mruby-compiler`.\n\n| Option | Description |\n|:-------|:------------|\n| `-o`, `--output` | Output file path (default: same directory as input with `.mrb`/`.cs` extension). Use `-` for stdout. |\n| `--dump` | Dump bytecode in human-readable format (outputs to stdout) |\n| `--format` | Output format: `binary` (default) or `csharp` |\n| `--csharp-namespace` | C# namespace for generated code (used with `--format csharp`) |\n| `--csharp-class-name` | C# class name for generated code (used with `--format csharp`) |\n\n##### mrbc (original mruby compiler)\n\nAlternatively, you can use the original [mruby](https://github.com/mruby/mruby) project's compiler.\n\n```bash\n$ git clone git@github.com:mruby/mruby.git\n$ cd mruby\n$ rake\n$ ./build/host/bin/mrbc -o output.mrb input.rb\n```\n\n##### MRubyCS.Compiler (library)\n\n`MRubyCS.Compiler` is a thin wrapper of the C# API for the native compiler.\n\nNOTE: This is a wrapper for native compilers. Currently, only the following platforms are supported:\n\n| OS      | Architecture |\n|:--------|:-------------|\n| Linux   | x64, arm64   |\n| macOS   | x64, arm64   |\n| Windows | x64          |\n\n```bash\ndotnet add package MRubyCS.Compiler\n```\n\n**Unity**: Open the Package Manager window by selecting Window \u003e Package Manager, then click on [+] \u003e Add package from git URL and enter the following URL:\n\n```\nhttps://github.com/hadashiA/MRubyCS.git?path=src/MRubyCS.Unity/Assets/MRubyCS.Compiler.Unity#0.50.3\n```\n\n```cs\nusing MRubyCS.Compiler;\n\nvar source = \"\"\"\ndef f(a)\n  1 * a\nend\n\nf 100\n\"\"\"u8;\n\nvar mrb = MRubyState.Create();\nvar compiler = MRubyCompiler.Create(mrb);\n\n// Compile source code (returns CompilationResult)\nusing var compilation = compiler.Compile(source);\n\n// Convert to irep (internal executable representation)\nvar irep = compilation.ToIrep();\n\n// irep can be used later..\nvar result = mrb.Execute(irep); // =\u003e 100\n\n// Or, get bytecode (mruby calls this format \"Rite\")\n// bytecode can be saved to a file or any other storage\nFile.WriteAllBytes(\"compiled.mrb\", compilation.AsBytecode());\n\n// Can be used later from file\nmrb.LoadBytecode(File.ReadAllBytes(\"compiled.mrb\")); //=\u003e 100\n\n// or, you can evaluate source code directly\nresult = compiler.LoadSourceCode(\"f(100)\"u8);\nresult = compiler.LoadSourceCode(\"f(100)\");\n```\n\n##### Unity AssetImporter\n\nIn Unity, if you install this extension, importing a .rb text file will generate .mrb bytecode as a subasset.\n\nFor example, importing the text file `hoge.rb` into a project will result in the following.\n\n![docs/screenshot_subasset](./docs/screenshot_subasset.png)\n\nThis subasset is a `TextAsset` that can be assigned via the inspector or loaded from code:\n\n``` cs\nvar mrb = MRubyState.Create();\n\nvar bytecodeAsset = (TextAsset)AssetDatabase.LoadAllAssetsAtPath(\"Assets/hoge.rb\")\n       .First(x =\u003e x.name.EndsWith(\".mrb\"));\nmrb.LoadBytecode(bytecodeAsset.GetData\u003cbyte\u003e().AsSpan());\n```\n\nTo read a subasset in Addressables, you would do the following.\n\n```cs\nAddressables.LoadAssetAsync\u003cTextAsset\u003e(\"Assets/hoge.rb[hoge.mrb]\")\n```\n\n##### Hot reload in the Editor\n\nBundling pre-compiled `.mrb` bytecode via the importer is the production path, but it is **not** the only option.\n\nIn environments where MRubyCS.Compiler is supported (macOS, Windows, Linux), it is possible to dynamically load .rb source code at any time, even while it is running.\n\n- **Hot-reload Ruby scripts in Play Mode** — re-`LoadSourceCode` a modified `.rb` file without exiting Play Mode and reattaching the player.\n\n```cs\nusing var compiler = MRubyCompiler.Create(mrb);\nvar src = File.ReadAllText(Path.Combine(Application.streamingAssetsPath, \"scripts/player.rb\"));\ncompiler.LoadSourceCode(src); // re-evaluates, replacing previous definitions\n```\n\n### Define ruby class/module/method by C#\n\n```cs\nvar classA = mrb.DefineClass(mrb.Intern(\"A\"u8), c =\u003e\n{\n    c.DefineMethod(mrb.Intern(\"plus100\"u8), (_, self) =\u003e\n    {\n        var arg0 = mrb.GetArgumentAsIntegerAt(0);\n        return arg0 + 100;\n    });\n});\n```\n\n```ruby\na = A.new\na.plus100(123) #=\u003e 223\n```\n\n#### Block / keyword / rest arguments\n\nMethods can also receive blocks, keyword arguments, and rest arguments:\n\n```cs\nvar classA = mrb.DefineClass(mrb.Intern(\"A\"u8), c =\u003e\n{\n    // Block argument\n    c.DefineMethod(mrb.Intern(\"with_block\"u8), (_, self) =\u003e\n    {\n        var arg0 = mrb.GetArgumentAt(0);\n        var blockArg = mrb.GetBlockArgument();\n        if (!blockArg.IsNil)\n        {\n            mrb.Send(blockArg, mrb.Intern(\"call\"u8), arg0);\n        }\n    });\n\n    // Keyword and rest arguments\n    c.DefineMethod(mrb.Intern(\"with_kwargs\"u8), (_, self) =\u003e\n    {\n        var keywordArg = mrb.GetKeywordArgument(mrb.Intern(\"foo\"u8));\n        mrb.EnsureValueType(keywordArg, MRubyVType.Integer);\n\n        var restArguments = mrb.GetRestArgumentsAfter(0);\n        for (var i = 0; i \u003c restArguments.Length; i++)\n        {\n            Console.WriteLine($\"rest arg({i}: {restArguments[i]})\");\n        }\n    });\n});\n```\n\n#### Class methods / modules\n\n```cs\n// Class method\nvar classA = mrb.DefineClass(mrb.Intern(\"A\"u8), c =\u003e\n{\n    c.DefineClassMethod(mrb.Intern(\"greet\"u8), (_, self) =\u003e\n    {\n        return mrb.NewString(\"hello\"u8);\n    });\n});\n\n// Monkey patching — add methods after class definition\nclassA.DefineMethod(mrb.Intern(\"extra\"u8), (_, self) =\u003e { /* ... */ });\n\n// Define module and include\nvar moduleA = mrb.DefineModule(mrb.Intern(\"ModuleA\"u8));\nmrb.DefineMethod(moduleA, mrb.Intern(\"module_method\"u8), (_, self) =\u003e 123);\nmrb.IncludeModule(classA, moduleA);\n```\n\n```ruby\nA.greet          #=\u003e \"hello\"\nA.new.extra\nA.new.module_method #=\u003e 123\n```\n\n#### Error handling \u0026 validation in C# methods\n\nInside C#-defined methods, you can raise Ruby exceptions and validate arguments:\n\n```cs\nvar myClass = mrb.DefineClass(mrb.Intern(\"MyClass\"u8));\n\nmrb.DefineMethod(myClass, mrb.Intern(\"safe_divide\"u8), (s, self) =\u003e\n{\n    s.EnsureArgumentCount(2, 2); // require exactly 2 arguments\n\n    var a = s.GetArgumentAsIntegerAt(0);\n    var b = s.GetArgumentAsIntegerAt(1);\n\n    if (b == 0)\n    {\n        s.Raise(s.StandardErrorClass, \"division by zero\"u8);\n    }\n    return a / b;\n});\n```\n\n```cs\n// Available validation helpers\nmrb.EnsureArgumentCount(min, max);               // check argument count\nmrb.EnsureValueType(value, MRubyVType.Integer);  // check value type\nmrb.EnsureBlockGiven(block);                     // check block is provided\nmrb.EnsureNotFrozen(value);                      // check object is not frozen\n\n// Raise Ruby exceptions\nmrb.Raise(mrb.StandardErrorClass, \"message\"u8);\nmrb.Raise(mrb.ExceptionClass, mrb.NewString($\"detail: {info}\"));\n```\n\nTo catch Ruby exceptions raised during execution on the C# side:\n\n```cs\ntry\n{\n    mrb.Send(obj, mrb.Intern(\"may_raise\"u8));\n}\ncatch (MRubyRaiseException ex)\n{\n    Console.WriteLine($\"Ruby exception: {ex.Message}\");\n}\n```\n\n#### Constants\n\n```cs\n// Define a constant under Object (global)\nmrb.DefineConst(mrb.Intern(\"MAX_SIZE\"u8), 1024);\n\n// Define a constant under a specific class/module\nmrb.DefineConst(myClass, mrb.Intern(\"VERSION\"u8), mrb.NewString(\"1.0\"u8));\n\n// Check if a constant exists\nmrb.ConstDefinedAt(mrb.Intern(\"MAX_SIZE\"u8));                         //=\u003e true\nmrb.ConstDefinedAt(mrb.Intern(\"VERSION\"u8), myClass);                 //=\u003e true\nmrb.ConstDefinedAt(mrb.Intern(\"VERSION\"u8), myClass, recursive: true); // search ancestors\n\n// Safe lookup\nif (mrb.TryGetConst(mrb.Intern(\"MAX_SIZE\"u8), out var constValue))\n{\n    // use constValue...\n}\n```\n\n### Call ruby method from C# side\n\nUse `mrb.Send()` to call Ruby methods from C#:\n\n```cs\n// Call a class method\nvar classA = mrb.GetConst(mrb.Intern(\"A\"u8), mrb.ObjectClass);\nmrb.Send(classA, mrb.Intern(\"foo=\"u8), 123);\nmrb.Send(classA, mrb.Intern(\"foo\"u8)); //=\u003e 123\n\n// Call a global-scope method — use TopSelf as the receiver\nmrb.Send(mrb.TopSelf, mrb.Intern(\"puts\"u8), mrb.NewString(\"hello\"u8));\n\n// Access instance variables\nvar instanceB = mrb.GetInstanceVariable(mrb.TopSelf, mrb.Intern(\"@b\"u8));\nmrb.Send(instanceB, mrb.Intern(\"bar=\"u8), 456);\nmrb.Send(instanceB, mrb.Intern(\"bar\"u8)); //=\u003e 456\n\n// Resolve nested constants\nvar classC = mrb.Send(mrb.ObjectClass, mrb.Intern(\"const_get\"u8), mrb.NewString(\"M::C\"u8));\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eRuby code assumed by the examples above\u003c/summary\u003e\n\n```ruby\nclass A\n  def self.foo = @@foo\n\n  def self.foo=(x)\n    @@foo = x\n  end\nend\n\nclass B\n  attr_accessor :bar\nend\n@b = B.new\n\nmodule M\n  class C\n    def self.foo = 999\n  end\nend\n```\n\u003c/details\u003e\n\n#### Send with block / keyword arguments\n\n```cs\n// Send with a block (RProc)\nvar proc = mrb.CreateProc(irep);\nmrb.Send(obj, mrb.Intern(\"each\"u8), proc);\n\n// Send with keyword arguments\nmrb.Send(\n    obj,\n    mrb.Intern(\"configure\"u8),\n    args: [],\n    kargs: [new(mrb.Intern(\"verbose\"u8), MRubyValue.True)],\n    block: null);\n```\n\n\u003e [!WARNING]\n\u003e **Unity**: The `Send` overload with `params ReadOnlySpan\u003cMRubyValue\u003e` is not supported because Unity's C# compiler does not support `params ReadOnlySpan\u003cT\u003e`. You must explicitly allocate an array instead:\n\u003e ```cs\n\u003e // This does NOT compile in Unity:\n\u003e // mrb.Send(klass, sym, arg0, arg1);\n\u003e\n\u003e // Use an explicit array:\n\u003e mrb.Send(klass, sym, new MRubyValue[] { arg0, arg1 });\n\u003e ```\n\u003e The single-argument overload `Send(self, methodId, arg0)` works without this workaround.\n\n#### Type conversion \u0026 introspection\n\nThe following examples use `value`, `a`, `b` as `MRubyValue` instances obtained from prior operations (e.g. `Send`, `LoadBytecode`).\n\n```cs\n// Convert values (calls Ruby's to_i / to_f / to_sym internally)\nlong   i = mrb.AsInteger(value);\ndouble f = mrb.AsFloat(value);\nSymbol s = mrb.AsSymbol(value);\n\n// Convert to string (Ruby's to_s / inspect)\nRString str     = mrb.Stringify(value);  // to_s\nRString inspect = mrb.Inspect(value);    // inspect\n\n// Class introspection\nRClass  klass = mrb.ClassOf(value);\nRString name  = mrb.ClassNameOf(value);\n\n// Type checking (Ruby's instance_of? / kind_of?)\nmrb.InstanceOf(value, mrb.StringClass);  //=\u003e true if exact class\nmrb.KindOf(value, mrb.ObjectClass);      //=\u003e true if class or ancestor\n\n// Equality and comparison (calls Ruby's == / \u003c=\u003e)\nmrb.ValueEquals(a, b);   //=\u003e true/false\nmrb.ValueCompare(a, b);  //=\u003e -1, 0, 1\n\n// Check if method exists (Ruby's respond_to?)\nmrb.RespondTo(value, mrb.Intern(\"to_s\"u8)); //=\u003e true\n```\n\n#### Instance variables / class variables / global variables\n\n```cs\n// Instance variables\nmrb.SetInstanceVariable(obj, mrb.Intern(\"@name\"u8), mrb.NewString(\"Alice\"u8));\nvar name = mrb.GetInstanceVariable(obj, mrb.Intern(\"@name\"u8));\nmrb.RemoveInstanceVariable(obj, mrb.Intern(\"@name\"u8));\n\n// Class variables\nmrb.SetClassVariable(myClass, mrb.Intern(\"@@count\"u8), 0);\nvar count = mrb.GetClassVariable(myClass, mrb.Intern(\"@@count\"u8));\n\n// Global variables (the symbol name includes the leading `$`)\nmrb.SetGlobalVariable(mrb.Intern(\"$game_map\"u8), gameMapValue);\nvar gameMap = mrb.GetGlobalVariable(mrb.Intern(\"$game_map\"u8)); // returns nil if undefined\nmrb.GlobalVariableDefined(mrb.Intern(\"$game_map\"u8));            //=\u003e true\nmrb.RemoveGlobalVariable(mrb.Intern(\"$game_map\"u8), out _);\n```\n\n#### Clone / Dup / Freeze\n\n```cs\n// Clone (deep copy with singleton class)\nvar cloned = mrb.CloneObject(value);\n\n// Dup (shallow copy)\nvar duped = mrb.DupObject(value);\n\n// Freeze an object (RObject level)\nvar str = mrb.NewString(\"immutable\"u8);\nstr.MarkAsFrozen();\nstr.IsFrozen //=\u003e true\n```\n\n### `MRubyValue`\n\n`MRubyValue` represents a Ruby value. It is returned from methods like `LoadBytecode`, `Execute`, `Send`, etc.\n\n```cs\nvalue.IsNil //=\u003e true if `nil`\nvalue.IsInteger //=\u003e true if integer\nvalue.IsFloat //=\u003e true if float\nvalue.IsSymbol //=\u003e true if Symbol\nvalue.IsObject //=\u003e true if any allocated object type\n\nvalue.VType //=\u003e get known ruby-type as C# enum.\n\nvalue.IntegerValue //=\u003e get as C# Int64\nvalue.FloatValue //=\u003e get as C# float\nvalue.SymbolValue //=\u003e get as `Symbol`\n\nvalue.As\u003cRString\u003e() //=\u003e get as internal String representation\nvalue.As\u003cRArray\u003e() //=\u003e get as internal Array representation\nvalue.As\u003cRHash\u003e() //=\u003e get as internal Hash representation\n\n// pattern matching\nif (value.Object is RString str)\n{\n    // ...\n}\n\nswitch (value)\n{\n    case { IsInteger: true }:\n        // ...\n        break;\n    case { Object: RString str }:\n        // ...\n        break;\n}\n\n// Creating MRubyValue\nvar intValue = new MRubyValue(100);\nvar floatValue = new MRubyValue(1.234f);\nvar objValue = new MRubyValue(str);\n\n// Implicit conversions are available — useful when passing arguments\nmrb.Send(obj, mrb.Intern(\"method\"u8), 42);       // int → MRubyValue\nmrb.Send(obj, mrb.Intern(\"method\"u8), 3.14);      // double → MRubyValue\nmrb.Send(obj, mrb.Intern(\"method\"u8), true);       // bool → MRubyValue\nmrb.Send(obj, mrb.Intern(\"method\"u8), sym);        // Symbol → MRubyValue\nmrb.Send(obj, mrb.Intern(\"method\"u8), rstring);    // RObject → MRubyValue\n\n// Static constants\nMRubyValue.Nil   // Ruby nil\nMRubyValue.True  // Ruby true\nMRubyValue.False // Ruby false\n\n// Boolean / truthiness\nvalue.BoolValue //=\u003e C# bool\nvalue.Truthy    //=\u003e true unless nil or false (Ruby semantics)\nvalue.Falsy     //=\u003e true if nil or false\n```\n\n#### Symbol/String\n\nThe string representation within mruby is utf8.\nTherefore, to generate a ruby string from C#, [Utf8StringInterpolation](https://github.com/Cysharp/Utf8StringInterpolation) is used internally.\n\n\n```cs\n// Create string literal.\nvar str1 = mrb.NewString(\"HOGE HOGE\"u8); // use u8 literal (C# 11 or newer)\nvar str2 = mrb.NewString($\"FOO BAR\"); // use string interpolation\n\nvar x = 123;\nvar str3 = mrb.NewString($\"x={x}\");\n\n// wrap MRubyValue..\nMRubyValue strValue = str1;\n```\n\nThere is a concept in mruby similar to String called `Symbol`.\nLike String, it is created using utf8 strings, but internally it is a uint integer.\nSymbols are usually used for method IDs and class IDs.\n\nTo create a symbol from C#, use `Intern`.\n\n```cs\n// symbol literal\nvar sym1 = mrb.Intern(\"sym\");\n\n// create symbol from string interporation\nvar x = 123;\nvar sym2 = mrb.Intern($\"sym{x}\");\n\n// symbol to utf8 bytes\nmrb.NameOf(sym1); //=\u003e \"sym\"u8\nmrb.NameOf(sym2); //=\u003e \"sym123\"u8\n\n// create symbol from string\nvar sym2 = mrb.AsSymbol(mrb.NewString($\"hoge\"));\n```\n\n\u003e [!NOTE]\n\u003e Both `Intern(\"str\")` and `Intern(\"str\"u8)` are valid, but the u8 literal is faster. We recommend using the u8 literal whenever possible.\n\n`RString` also provides methods for in-place manipulation and direct UTF-8 byte access:\n\n```cs\nvar str = mrb.NewString(\"hello\"u8);\n\n// UTF-8 byte access\nReadOnlySpan\u003cbyte\u003e bytes = str.AsSpan(); // raw UTF-8 bytes\n\n// In-place modification\nstr.Concat(\" world\"u8);  // append bytes\nstr.Upcase();             // \"HELLO WORLD\"\nstr.Downcase();           // \"hello world\"\nstr.Capitalize();         // \"Hello world\"\nstr.Chomp();              // remove trailing newline\nstr.Chop();               // remove last character\n```\n\n#### Array/Hash\n\n`RArray` and `RHash` are the internal representations of Ruby's `Array` and `Hash`.\n\n```cs\n// Create array\nvar array = mrb.NewArray(3); // with capacity\nvar array2 = mrb.NewArray(1, 2, 3);\n\n// Access elements (supports negative indices)\nvar first = array2[0];   //=\u003e 1\nvar last  = array2[-1];  //=\u003e 3\n\n// Add elements\narray.Push(100);\narray.Push(200);\n\n// Get length\narray.Length //=\u003e 2\n\n// Iterate over elements\nforeach (var item in array)\n{\n    Console.WriteLine(item.IntegerValue);\n}\n\n// Pop / Shift\nif (array.TryPop(out var popped)) { /* ... */ }\nvar shifted = array.Shift(); // remove and return first element\n\n// Extract RArray from MRubyValue\nvar value = mrb.LoadBytecode(bytecode); // returns MRubyValue\nvar arr = value.As\u003cRArray\u003e();\n```\n\n```cs\n// Create hash\nvar hash = mrb.NewHash();\n\n// Set values (key can be any MRubyValue — Symbol, String, Integer, etc.)\nhash[mrb.Intern(\"name\"u8)] = mrb.NewString(\"Alice\"u8);\nhash[mrb.Intern(\"age\"u8)]  = 30;\n\n// Get values\nvar name = hash[mrb.Intern(\"name\"u8)];\n\n// Check existence\nhash.ContainsKey(mrb.Intern(\"name\"u8)); //=\u003e true\nhash.TryGetValue(mrb.Intern(\"age\"u8), out var age); //=\u003e true, age = 30\n\n// Get length\nhash.Length //=\u003e 2\n\n// Iterate over key-value pairs\nforeach (var kv in hash)\n{\n    // kv.Key, kv.Value are MRubyValue\n}\n\n// Delete\nhash.TryDelete(mrb.Intern(\"age\"u8), out var deleted);\n\n// Extract RHash from MRubyValue\nvar hashValue = mrb.LoadBytecode(bytecode);\nvar h = hashValue.As\u003cRHash\u003e();\n```\n\n#### Embedded custom C# data into MRubyValue\n\nYou can stuff any C# object into an `MRubyValue` via `RData`. The `RData.Data` property accepts any `object` and can be freely get/set from C#.\n\nThis is useful when calling C# functionality from Ruby methods defined in C#.\n\n```cs\nclass YourCustomClass\n{\n    public string Value { get; set; }\n}\n\nvar csharpInstance = new YourCustomClass { Value = \"abcde\" };\n\nvar mrb = MRubyState.Create();\n\nvar data = new RData(csharpInstance);\nmrb.SetConst(mrb.Intern(\"MYDATA\"u8), mrb.ObjectClass, data);\n\n// Use custom data from ruby\nmrb.DefineMethod(mrb.ObjectClass, mrb.Intern(\"from_csharp_data\"u8), (_, self) =\u003e\n{\n    var dataValue = mrb.GetConst(mrb.Intern(\"MYDATA\"u8), mrb.ObjectClass);\n    var csharpInstance = dataValue.As\u003cRData\u003e().Data as YourCustomClass;\n    // ...\n});\n```\n\n#### Embedded custom C# data with ruby class\n\n```cs\n// Instances of classes that specify `MRubyVType.CSharpData` have `self` as RData.\nvar yourClass = mrb.DefineClass(mrb.Intern(\"MyCustomClass\"u8), mrb.ObjectClass, MRubyVType.CSharpData);\n\n// Define custom `initialize` with C# data\nmrb.DefineMethod(yourClass, mrb.Intern(\"initialize\"u8), (s, self) =\u003e\n{\n    if (self.Object is RData x)\n    {\n        x.Data = new YourCustomClass { Value = \"abcde\" };\n    }\n    return self;\n});\n\n// Use custom C# data\nmrb.DefineMethod(yourClass, mrb.Intern(\"foo_method\"u8), (s, self) =\u003e\n{\n    if (self.Object is RData { Data: YourCustomClass csharpInstance })\n    {\n        // Use C# data..\n        csharpInstance.Value = \"fghij\";\n    }\n    // ...\n});\n\n```\n\n\n## Optional Classes (opt-in)\n\nSome bundled classes are **not** registered by `MRubyState.Create()` so that embedding hosts only pay for the surface area they actually need. Enable them explicitly per `MRubyState` instance:\n\n| Enable with | Adds |\n|---|---|\n| `mrb.DefineRegexp()` | [`Regexp`](https://github.com/hadashiA/MRubyCS/blob/main/sig/regexp.rbs), [`MatchData`](https://github.com/hadashiA/MRubyCS/blob/main/sig/match_data.rbs), and regexp-related `String` methods (`=~` / `match` / `sub` / `gsub` / `scan` / `index`) |\n| `mrb.DefineIO()` | [`IO`](https://github.com/hadashiA/MRubyCS/blob/main/sig/io.rbs), [`File`](https://github.com/hadashiA/MRubyCS/blob/main/sig/file.rbs), `IOError` |\n\nBoth calls are idempotent and must be made **before** compiling/running Ruby code that references the classes.\n\n```cs\nusing var mrb = MRubyState.Create(x =\u003e\n{\n    x.DefineRegexp();\n    x.DefineIO();\n});\n```\n\n### Regexp\n\nOnce enabled, both literal `/.../` regular expressions and `Regexp.new` are available, along with `MatchData` and the regexp-related `String` methods.\n\n```cs\nusing var mrb = MRubyState.Create(x =\u003e\n{\n    x.DefineRegexp();\n});\n\nusing var compiler = MRubyCompiler.Create(mrb);\n\ncompiler.LoadSourceCode(\"\"\"\n    re = /(\\w+)@(\\w+\\.\\w+)/\n    if m = \"contact: alice@example.com\".match(re)\n      puts m[0]        # =\u003e \"alice@example.com\"\n      puts m[1]        # =\u003e \"alice\"\n      puts m[2]        # =\u003e \"example.com\"\n    end\n\n    # case-insensitive flag via Regexp.new\n    Regexp.new(\"hello\", Regexp::IGNORECASE) =~ \"HELLO\"   # =\u003e 0\n\n    # sub / gsub / scan\n    \"foo bar foo\".gsub(/foo/, \"baz\")     # =\u003e \"baz bar baz\"\n    \"a1 b2 c3\".scan(/[a-z]\\d/)           # =\u003e [\"a1\", \"b2\", \"c3\"]\n    \"\"\"u8);\n```\n\n### IO / File\n\n`File.read` / `File.write` provide a quick round-trip; `File.open` returns an `IO`/`File` instance for streaming reads and writes. `IOError` is raised when operating on a closed handle.\n\n```cs\nusing var mrb = MRubyState.Create(x =\u003e\n{\n    x.DefineIO();\n});\n\nusing var compiler = MRubyCompiler.Create(mrb);\n\ncompiler.LoadSourceCode(\"\"\"\n    File.write(\"/tmp/greeting.txt\", \"hello world\")\n    puts File.read(\"/tmp/greeting.txt\")    # =\u003e \"hello world\"\n    puts File.exist?(\"/tmp/greeting.txt\")  # =\u003e true\n\n    f = File.open(\"/tmp/greeting.txt\")\n    begin\n      puts f.read\n    ensure\n      f.close\n    end\n    \"\"\"u8);\n```\n\nWhen a `FiberScheduler` is installed, `IO`/`File` reads and writes route through `MRubyFiberScheduler.Await` so the host thread isn't blocked on stream I/O. See [Defining async Ruby methods with `Await`](#defining-async-ruby-methods-with-await) for the same mechanism applied to host-defined methods.\n\n\n## Fiber (Coroutine)\n\nMRubyCS supports Ruby Fibers, which are lightweight concurrency primitives that allow you to pause and resume code execution. In addition to standard Ruby Fiber features, MRubyCS provides seamless integration with C#'s async/await pattern.\n\n### Basic Fiber Usage\n\n```cs\nusing MRubyCS;\nusing MRubyCS.Compiler;\n\n// Create state and compiler\nvar mrb = MRubyState.Create();\nvar compiler = MRubyCompiler.Create(mrb);\n\n// Define a fiber that yields values\nvar code = \"\"\"\n    Fiber.new do |x|\n      Fiber.yield(x * 2)\n      Fiber.yield(x * 3)\n      x * 4\n    end\n    \"\"\"u8;\n\n// Load the Ruby code as a Fiber\nusing var compilation = compiler.Compile(code);\nvar fiber = mrb.Execute(compilation.ToIrep()).As\u003cRFiber\u003e();\n\n// Resume the fiber with initial value\nvar result1 = fiber.Resume(10);  // =\u003e 20\n\nvar result2 = fiber.Resume(10);  // =\u003e 30\n\nvar result3 = fiber.Resume(10);  // =\u003e 40 (final return value)\n\n// Check if fiber is still alive\nfiber.IsAlive  // =\u003e false\n```\n\nIf you want to execute arbitrary code snippets as fibers, do the following.\n\n```cs\nvar code = \"\"\"\n  x = 1\n  y = 2\n  Fiber.yield (x + y) * 100\n  Fiber.yield (x + y) * 200\n\"\"\"u8;\n\nvar fiber = compiler.LoadSourceCodeAsFiber(code);\n\n// `LoadSourceCodeAsFiber` is same as:\n// using var compilation = compiler.Compile(code);\n// var proc = mrb.CreateProc(compilation.ToIrep());\n// var fiber = mrb.CreateFiber(proc);\n\nfiber.Resume(); //=\u003e 300\nfiber.Resume(); //=\u003e 600\n```\n\n### Async/Await Integration\n\nMRubyCS provides unique C# async integration features for working with Fibers:\n\n```cs\n// Wait for fiber to terminate\nvar code = \"\"\"\n    Fiber.new do |x|\n      Fiber.yield\n      Fiber.yield\n      \"done\"\n    end\n    \"\"\"u8;\n\nusing var compilation = compiler.Compile(code);\nvar fiber = mrb.Execute(compilation.ToIrep()).As\u003cRFiber\u003e();\n\n// Start async wait before resuming\nvar terminateTask = fiber.WaitForTerminateAsync();\n\n// Resume the fiber multiple times\nfiber.Resume();\nfiber.Resume();\nfiber.Resume();\n\n// Wait for completion\nawait terminateTask;\nConsole.WriteLine(\"Fiber has terminated\");\n```\n\nYou can consume fiber results as async enumerable:\n\n```cs\nvar code = \"\"\"\n    Fiber.new do |x|\n      3.times do |i|\n        Fiber.yield(x * (i + 1))\n      end\n    end\n    \"\"\"u8;\n\nusing var compilation = compiler.Compile(code);\nvar fiber = mrb.Execute(compilation.ToIrep()).As\u003cRFiber\u003e();\n\n// Process each yielded value asynchronously\nawait foreach (var value in fiber.AsAsyncEnumerable())\n{\n    Console.WriteLine($\"Yielded: {value.IntegerValue}\");\n}\n```\n\nMRubyCS supports multiple consumers waiting for fiber results simultaneously:\n\n```cs\nusing var compilation = compiler.Compile(code);\nvar fiber = mrb.Execute(compilation.ToIrep()).As\u003cRFiber\u003e();\n\n// Create multiple consumers\nvar consumer1 = Task.Run(async () =\u003e\n{\n    while (fiber.IsAlive)\n    {\n        var result = await fiber.WaitForResumeAsync();\n        Console.WriteLine($\"Consumer 1 received: {result}\");\n    }\n});\n\nvar consumer2 = Task.Run(async () =\u003e\n{\n    while (fiber.IsAlive)\n    {\n        var result = await fiber.WaitForResumeAsync();\n        Console.WriteLine($\"Consumer 2 received: {result}\");\n    }\n});\n\n// Resume fiber and both consumers will receive the results\nfiber.Resume(10);\nfiber.Resume(20);\nfiber.Resume(30);\n\nawait Task.WhenAll(consumer1, consumer2);\n```\n\n\u003e [!CAUTION]\n\u003e Waiting for fiber can be performed in a separate thread.\n\u003e However, MRubyState and mruby methods are not thread-safe.\n\u003e Please note that when using mruby functions, you must always return to the original thread.\n\n### Error Handling in Fibers\n\nExceptions raised within fibers are properly propagated:\n\n```cs\nvar code = \"\"\"\n    Fiber.new do |x|\n      Fiber.yield(x)\n      raise \"Something went wrong\"\n    end\n    \"\"\"u8;\n\nusing var compilation = compiler.Compile(code);\nvar fiber = mrb.Execute(compilation.ToIrep()).As\u003cRFiber\u003e();\n\n// First resume succeeds\nvar result1 = fiber.Resume(10);  // =\u003e 10\n\n// Second resume will throw\ntry\n{\n    fiber.Resume();\n}\ncatch (MRubyRaiseException ex)\n{\n    Console.WriteLine($\"Ruby exception: {ex.Message}\");\n}\n\n// Async wait will also propagate the exception\nvar waitTask = fiber.WaitForResumeAsync();\ntry\n{\n    fiber.Resume();\n    await waitTask;\n}\ncatch (MRubyRaiseException ex)\n{\n    Console.WriteLine($\"Async exception: {ex.Message}\");\n}\n```\n\n### yield/resume from C#\n\nIt is possible to resume/yield from a method defined in C#.\n\n```cs\nmrb.DefineMethod(mrb.FiberClass, mrb.Intern(\"resume_by_csharp\"u8), (state, self) =\u003e\n{\n    return self.As\u003cRFiber\u003e().Resume();\n});\n```\n\n```ruby\n fiber = Fiber.new do\n   3.times do\n     Fiber.yield\n   end\n end\n\n fiber.resume_by_csharp\n```\n\n## Define async Ruby method (FiberScheduler)\n\n### Default behavior (no scheduler)\n\nBy default, no scheduler is installed. In this mode:\n\n- `Kernel#sleep` calls `Thread.Sleep` and blocks the calling thread.\n- `Thread.pass` is a no-op.\n- `IO` / `File` reads \u0026 writes (when registered via `DefineIO()`) use synchronous `Stream.Read` / `Write`.\n- `Fiber#resume` / `Fiber.yield` work exactly as in CRuby.\n- The VM is fully synchronous from C#'s perspective.\n\n```cs\nvar mrb = MRubyState.Create();\nvar compiler = MRubyCompiler.Create(mrb);\n\n// Blocks the calling thread for 1 second.\ncompiler.LoadSourceCode(\"sleep 1; :done\"u8);\n```\n\nThis is the right default for CLI tools and tests that don't need cooperative scheduling.\n\n### With a scheduler installed\n\n`mrb.useFiberScheduler(...)` swaps blocking primitives for cooperative ones. When a non-root fiber calls `sleep`, the VM yields back to its caller instead of blocking; the scheduler arranges for the fiber to be resumed when the deadline expires.\n\n```cs\nusing var mrb = MRubyState.Create(x =\u003e\n{\n    x.UseFiberScheduler();\n});\nusing var compiler = MRubyCompiler.Create(mrb);\n\nvar fiber = compiler.LoadSourceCodeAsFiber(\"\"\"\n    sleep 0.05   // -\u003e same as `await Task.Delay(TimeSpan.FromSeconds(0.05))`\n    Thread.pass  // -\u003e same as `await Task.Yield()`\n    :done\n    \"\"\"u8);\n\nfiber.Resume();\nawait fiber.WaitForTerminateAsync();\n// `sleep`, `pass` did not block any thread; the scheduler wake the fiber.\n```\n\n\u003e [!NOTE]\n\u003e The *root* fiber still falls back to `Thread.Sleep`, even when a scheduler is installed — there is no caller to yield to. The scheduler hooks only fire from inside `Fiber.new { ... }` bodies (including `LoadSourceCodeAsFiber`).\n\n### Defining async Ruby methods with `Await`\n\n`Await(async mrb =\u003e …)` is the high-level convenience for bridging an `async` C# lambda into a Ruby method. The body runs starting on the caller (VM) thread; after the first `await`, thread routing is determined by the ambient `SynchronizationContext` at the await site — the scheduler doesn't install any dispatch of its own.\n\n```cs\nusing var mrb = MRubyState.Create(x =\u003e\n{\n    x.UseFiberScheduler();\n});\n\n// Defines `await_http(url)` — fetches a URL without blocking the VM.\nmrb.DefineMethod(mrb.KernelModule, mrb.Intern(\"await_http\"u8), (state, _) =\u003e\n{\n    var url = state.GetArgumentAsStringAt(0).ToString();\n    state.FiberScheduler!.Await(async mrb =\u003e\n    {\n        using var client = new HttpClient();\n        var body = await client.GetStringAsync(url);\n        return mrb.NewString(body);\n    });\n    return MRubyValue.Nil; // unreached on the async path — Ruby observes body's return\n});\n\nvar fiber = compiler.LoadSourceCodeAsFiber(\"\"\"\n    body = await_http(\"https://example.com\")\n    puts body.length\n    \"\"\"u8);\nfiber.Resume();\nawait fiber.WaitForTerminateAsync();\n```\n\nBody contract:\n\n- The body receives `(MRubyState mrb)`. There is also an allocation-free overload `Await\u003cTState\u003e(TState state, Func\u003cMRubyState, TState, ValueTask\u003cMRubyValue\u003e\u003e body)` — pass closed-over data as `state` plus a `static` lambda to avoid closure allocation on hot paths.\n- Body returns `ValueTask\u003cMRubyValue\u003e`; the value is delivered to Ruby as the apparent return of the host `MRubyMethod`. The host method must still end with `return MRubyValue.Nil;` — that return is unreached on the async path.\n- `OperationCanceledException` from body → fiber resumes with `nil` (CRuby fiber-scheduler convention; the OCE's own token is preserved).\n- Any other exception → delivered as a Ruby exception, catchable by surrounding `begin/rescue`.\n\nTo time out, wire a `CancellationTokenSource` into body via closure:\n\n```cs\nstate.FiberScheduler!.Await(async mrb =\u003e\n{\n    using var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));\n    using var client = new HttpClient();\n    var body = await client.GetStringAsync(url, cancellationSource.Token);\n    return mrb.NewString(body);\n});\n```\n\n### Low-level: `Suspend` + `FiberContinuation`\n\nWhen the resume signal arrives from somewhere other than a single async lambda — an external event source, a `Subject`/`IObservable`, a callback you don't control — use `Suspend()`. It parks the current fiber and returns a `FiberContinuation` handle that arbitrary code can call `Resume(value)` / `SetCancelled()` / `SetException(ex)` on.\n\n```cs\nmrb.DefineMethod(mrb.KernelModule, mrb.Intern(\"await_event\"u8), (state, _) =\u003e\n{\n    var continuation = state.FiberScheduler!.Suspend(); // yields the fiber internally\n\n    myEventSource.Once(payload =\u003e\n    {\n        continuation.Resume(state.NewString(payload));   // arbitrary callback site\n    });\n\n    return MRubyValue.Nil;\n});\n```\n\nMechanics:\n\n- `Suspend()` registers the parking state, then calls `Fiber.yield` to unwind the VM back to the caller of `Resume`. The returned `FiberContinuation` captures the parked fiber.\n- `continuation.Resume(value)` runs `fiber.Resume(value)`. The settle path uses an atomic `TryRemove` on the park slot before completing the underlying `TaskCompletionSource`, so the fiber can re-park (next `sleep`, next `Suspend`) inside the synchronous continuation without hitting \"already parked\".\n- `continuation.SetCancelled()` resumes the fiber with `nil` (cancellation semantics).\n- `continuation.SetException(ex)` injects `ex` as a Ruby exception on resume (catchable by `rescue`).\n- Settling is **one-shot** — the first of `Resume`/`SetCancelled`/`SetException` wins; subsequent calls are no-op.\n- The fiber is yielded *inside* `Suspend` — there's no \"arrange-Resume-before-Suspend\" race window.\n\n\u003e [!TIP]\n\u003e Prefer `Await` when the body fits as a single `async` lambda. Drop to `Suspend` only when you need to hand the continuation to external code that completes asynchronously without an awaitable surface.\n\n### Custom Schedulers (subclassing)\n\n`MRubyFiberScheduler` is a concrete class — host customization is done by subclassing and overriding `KernelSleep` / `Yield` / `Suspend` as needed. The default implementations cover most hosts; subclass only when you need different timer behavior or a custom yield primitive.\n\n\nOverride example — `UnityFiberScheduler` that routes `sleep` / `Thread.pass` through Unity's `Awaitable` instead of `Task.Delay` / `Task.Yield`. This keeps fiber resumes on the player loop (main thread):\n\n```cs\nusing UnityEngine;\nusing System;\nusing System.Threading;\nusing MRubyCS;\n\nclass UnityFiberScheduler : MRubyFiberScheduler\n{\n    public override void KernelSleep(TimeSpan duration, CancellationToken cancellationToken = default)\n    {\n        Await(async _ =\u003e\n        {\n            await Awaitable.WaitForSecondsAsync((float)duration.TotalSeconds, cancellationToken);\n            return MRubyValue.Nil;\n        });\n    }\n\n    public override void Yield(CancellationToken cancellationToken = default)\n    {\n        if (cancellationToken.IsCancellationRequested) return;\n        Await(async _ =\u003e\n        {\n            await Awaitable.NextFrameAsync(cancellationToken);\n            return MRubyValue.Nil;\n        });\n    }\n}\n```\n\n```cs\nmrb.UseFiberScheduler(new UnityFiberScheduler());\n```\n\nContract:\n\n- **All wait hooks yield internally** (CRuby `Fiber::Scheduler` convention). Override implementations must call `fiber.Yield()` before returning — the default impls do this via `Await` → `Suspend`.\n- **No Ruby re-entrancy.** Hooks must not call back into Ruby code (no `state.Send`, no synchronous `fiber.Resume`). `fiber.Yield()` is the one expected call into the VM — it unwinds rather than invokes.\n- **Exceptions are deliverable to Ruby.** Any exception inside `Await`'s body is wrapped and delivered as a Ruby exception on resume; surrounding `begin/rescue` catches it.\n- **No double-parking.** A fiber is only parked under one wait at a time. `Suspend` throws `InvalidOperationException` on a re-park; subclass overrides should preserve this.\n\nSee [`MRubyFiberScheduler.cs`](src/MRubyCS/MRubyFiberScheduler.cs) for the complete reference implementation.\n\n## MRubyCS.Serializer\n\nUsing the MRuby.Serializer package enables conversion between MRubyValue and C# objects.\n\n```cs\n// Deserialize (MRubyValue -\u003e C#)\n\nMRubyValue result1 = mrb.LoadSourceCode(\"111 + 222\");\nMRubyValueSerializer.Deserialize\u003cint\u003e(result1, mrb); //=\u003e 333\n\nMRubyValue result2 = mrb.LoadSourceCode(\"'hoge'.upcase\");\nMRubyValueSerializer.Deserialize\u003cstring\u003e(result2, mrb); //=\u003e \"HOGE\"\n```\n\n```cs\n// Serialize (C# -\u003e MRubyValue)\n\nvar intArray = new int[] { 111, 222, 333 };\n\nMRubyValue value = MRubyValueSerializer.Serialize(intArray, mrb);\n\nvar mrubyArray = value.As\u003cRArray\u003e();\nmrubyArray[0] //=\u003e 111\nmrubyArray[1] //=\u003e 222\nmrubyArray[2] //=\u003e 333\n```\n\n```cs\nMRubyValue mrubyStringValue = MRubyValueSerializer.Serialize(\"hoge fuga\", mrb);\n\n// Use the serialized value...\nmrb.Send(mrubyStringValue, mrb.Intern(\"upcase\"u8)); //=\u003e MRubyValue(\"UPCASE\")\n```\n\n### Builtin Supported types\n\nThe following C# types and MRubyValue type conversions are supported natively:\n\n| mruby     | C#                                                                                                                                                                                                                                                                                                                                                                                      |\n|-----------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `Integer` | `int`, `uint`, `long`, `ulong`, `short`, `ushort`, `byte`, `sbyte`, `char`                                                                                                                                                                                                                                                                                                                |\n| `Float`   | `float`, `double`, `decimal`                                                                                                                                                                                                                                                                                                                                                            |\n| `Array`   | `T`, `List\u003c\u003e`, `T[,]`, `T[,]`, `T[,,]`, \u003cbr /\u003e`Tuple\u003c...\u003e`, `ValueTuple\u003c...\u003e`, \u003cbr /\u003e, `Stack\u003c\u003e`, `Queue\u003c\u003e`, `LinkedList\u003c\u003e`, `HashSet\u003c\u003e`, `SortedSet\u003c\u003e`, \u003cbr /\u003e`Collection\u003c\u003e`, `BlockingCollection\u003c\u003e`, \u003cbr /\u003e`ConcurrentQueue\u003c\u003e`, `ConcurrentStack\u003c\u003e`, `ConcurrentBag\u003c\u003e`, \u003cbr /\u003e`IEnumerable\u003c\u003e`, `ICollection\u003c\u003e`, `IReadOnlyCollection\u003c\u003e`, \u003cbr /\u003e`IList\u003c\u003e`, `IReadOnlyList\u003c\u003e`, `ISet\u003c\u003e` |\n| `Hash`    | `Dictionary\u003c,\u003e`, `SortedDictionary\u003c,\u003e`, `ConcurrentDictionary\u003c,\u003e`, \u003cbr /\u003e`IDictionary\u003c,\u003e`, `IReadOnlyDictionary\u003c,\u003e`                                                                                                                                                                                                                                                                     |\n| `String`  | `string`, `byte[]`                                                                                                                                                                                                                                                                                                                                                                      |\n| `Symbol`  | `Enum`\n| `nil`     | `T?`, `Nullable\u003cT\u003e`                                                                                                                                                                                                                                                                                                                                                                     |\n\n#### Unity-specific types\n\nBy introducing the following packages, serialization of Unity-specific types will also be supported.\n\nOpen the Package Manager window by selecting Window \u003e Package Manager, then click on [+] \u003e Add package from git URL and enter the following URL:\n\n```\nhttps://github.com/hadashiA/MRubyCS.git?path=src/MRubyCS.Unity/Assets/MRubyCS.Serializer.Unity#0.18.1\n```\n\n| mruby                                | C#  |\n|--------------------------------------|:--------------------------------------------------------------------------------------------------------------------|\n| `[Float, Float]`                     | `Vector2`, `Resolution`                                                          |\n| `[Integer, Integer]`                 | `Vector2Int`                      |\n| `[Float, Float, Float]`              | `Vector3`|\n| `[Int, Int, Int]`                    | `Vector3Int` |\n| `[Float, Float, Float, Float]`       | `Vector4`, `Quaternion`, `Rect`, `Bounds`, `Color`|\n| `[Int, Int, Int, Int]`               | `RectInt`, `BoundsInt`, `Color32` |\n\n\n### Naming Convention\n\n- C# property/field names are converted to underscore style in Ruby\n    - e.g) `FooBar` \u003c-\u003e `foo_bar`\n- C# enum values are converted to underscore-style symbols in Ruby\n    - e.g) `EnumType.FooBar` \u003c-\u003e `:foo_bar`\n\n### `[MRubyObject]` attribute\n\nMarking with `[MRubyObject]` enables bidirectional conversion between custom C# types and MRubyValue.\n\n- Converts C# type properties/fields into Ruby world `Hash` key/value pairs.\n- class, struct, and record are all supported.\n- A partial declaration is required.\n- Members that meet the following conditions are converted from mruby:\n    - public fields or properties, or fields or properties with the `[MRubyMember]` attribute.\n    - And have a setter (private is acceptable).\n\n```cs\n[MRubyObject]\npartial struct SerializeExample\n{\n    // this is serializable members\n    public string Id { get; private set; }\n    public int X { get; init; }\n    public int FooBar;\n\n    [MRubyMember]\n    public int Z;\n\n    // ignore members\n    [MRubyIgnore]\n    public float Foo;\n}\n```\n\n```cs\n// Deserialize (MRubyValue -\u003e C#)\n\nvar value = mrb.LoadSourceCode(\"{ id: 'aiueo', x: 1234, foo_bar: 4567, z: 8901 }\");\n\nSerializeExample deserialized = MRubyValueSerializer.Deserialize\u003cSerializeExample\u003e(value, mrb);\ndeserialized.Id     //=\u003e \"aiueo\"\ndeserialized.X      //=\u003e 1234\ndeserialized.FooBar //=\u003e 4567\ndeserialized.Z      //=\u003e 8901\n```\n\n```cs\n// Serialize (C# -\u003e MRubyValue)\nvar value = MRubyValueSerializer.Serialize(new SerializeExample { Id = \"aiueo\", X = 1234, FooBar = 4567 });\n\nvar props = value.As\u003cRHash\u003e();\nprops[mrb.Intern(\"id\"u8)] //=\u003e \"aiueo\"\nprops[mrb.Intern(\"x\"u8)] //=\u003e 1234\nprops[mrb.Intern(\"foo_bar\"u8)] //=\u003e 4567\n```\n\nThe list of properties specified by mruby is assigned to the C# member names that match the key names.\n\nNote:\n- The names on the ruby side are converted to CamelCase.\n   - Example: ruby's `foo_bar` maps to C#'s `FooBar`.\n- The values of C# enums are serialized as Ruby symbols.\n    - Example: `Season.Summer` becomes Ruby's `:summer`.\n\nYou can change the member name specified from Ruby by using `[MRubyMember(\"alias name\")]`.\n\n```cs\n[MRubyObject]\npartial class Foo\n{\n    [MRubyMember(\"alias_y\")]\n    public int Y;\n}\n```\n\nAlso, you can receive data from Ruby via any constructor by using the `[MRubyConstructor]` attribute.\n\n```cs\n[MRubyObject]\npartial class Foo\n{\n    public int X { get; }\n\n    [MRubyConstructor]\n    public Foo(int x)\n    {\n        X = x;\n    }\n}\n```\n\n### Dynamic serialization\n\nSpecifying a `dynamic` type parameter allows conversion to C# Array/Dictionary and primitive types.\n\n```cs\nvar array = mrb.NewArray();\narray.Push(123);\n\nvar result = MRubyValueSerializer.Deserialize\u003cdynamic\u003e(array, mrb);\n\n((object[])result).Length //=\u003e 1\n((object[])result)[0] //=\u003e 123\n```\n\n### Custom Formatter\n\nYou can also customize the conversion of any C# type to an MRubyValue.\n\n```cs\n // custom type example\nstruct Vector3\n{\n    public int X;\n    public int Y;\n    public int Z;\n}\n```\n\n```cs\n// Implement `IMRubyValueFormatter`\nclass CustomVector3Formatter : IMRubyValueFormatter\u003cVector3\u003e\n{\n    public static readonly CustomVector3Formatter Instance = new();\n\n    public MRubyValue Serialize(Vector3 value, MRubyState mrb, MRubyValueSerializerOptions options)\n    {\n        var array = mrb.NewArray();\n        array.Push(value.X);\n        array.Push(value.Y);\n        array.Push(value.Z);\n        return array;\n    }\n    public Vector3 Deserialize(MRubyValue value, MRubyState mrb, MRubyValueSerializerOptions options)\n    {\n        // validation\n        MRubySerializationException.ThrowIfTypeMismatch(value, MRubyVType.Array);\n        MRubySerializationException.ThrowIfNotEnoughArrayLength(value, 3);\n\n        var array = value.As\u003cRArray\u003e();\n        return new Vector3\n        {\n            X = array[0].IntegerValue,\n            Y = array[1].IntegerValue,\n            Z = array[2].IntegerValue,\n        }\n    }\n}\n```\n\nTo set a custom formatter, specify options as an argument to MRubyValueSerializer.\n\nSpecify the enumeration of Formatter and Formatter's Resolver instances.\n`StandardResolver` supports the default behavior, so specify this along with additional formatters.\n\n```cs\n// Create a new formatter resolver.\nvar resolver = CompositeResolver.Create(\n    [CustomVector3Formatter.Instance],\n    [StandardResolver.Instance]\n    );\n\nvar options = new MRubyValueSerializerOptions\n{\n    Resolver = resolver,\n};\n\nvar value = mrb.LoadSourceCode(\"[111, 222, 333]\");\nVector3 deserialized = MRubyValueSerializer.Deserialize\u003cVector3\u003e(value, mrb, options);\ndeserialized.X //=\u003e 111\ndeserialized.Y //=\u003e 222\ndeserialized.Z //=\u003e 333\n```\n\n## LICENSE\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhadashia%2Fmrubycs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhadashia%2Fmrubycs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhadashia%2Fmrubycs/lists"}