Skip to main content

Hello Ruby!

Create a scene

First, create a scene. Any node will work, but in this example, I'll create one using a Control node.

Create a scene

Next, attach a GDScript.

Attach a GDScript

method_missing signal

Let's write the GDScript as follows:

extends Control

@onready var res := ReDScribe.new()


func _ready() -> void:
res.method_missing.connect(_method_missing)
res.perform("""
Alice says: 'Hello Ruby! ❤'
""")


func _method_missing(method_name: String, args: Array) -> void:
print_debug('[method_missing] ', method_name, ': ', args)

Then run the scene.

method_missing signal

When you run it, you'll see the following output in the Output panel:

[method_missing] Alice: [{ &"says": "Hello Ruby! ❤" }]
At: res://control.gd:14:_method_missing()

method_missing example output

What happened?

Let’s go through it piece by piece.

1. extends Control

extends Control

This scene inherits the Control node. This statement has nothing to do with ReDScribe processing.

2. @onready var res := ReDScribe.new()

@onready var res := ReDScribe.new()

An instance of ReDScribe is created and assinged to the res variable. In Godot, member variables used within a node are defined at the beginning like this.

The @onready modifier causes this to be called when the scene in the ready state, i.e. when the node and its children have entered the scene tree. This is often used to initialize member variables.

3. func _ready() -> void:

func _ready() -> void:
res.method_missing.connect(_method_missing)
res.perform("""
Alice says: 'Hello Ruby! ❤'
""")

The _method_missing function is set up to receive ReDScribe's method_missing signal.

res.perform runs Ruby code.

4. func _method_missing(method_name: String, args: Array) -> void:

If you run the following in Ruby, Ruby will interpret it as passing the argument says: 'Hello Ruby! ❤' to the Alice method.

Alice says: 'Hello Ruby! ❤'

Since there is no method named Alice defined in Ruby, a method_missing error occurs, but ReDScribe emits a signal when method_missing occurs.

The _method_missing function now receives the method_missing signal.

func _method_missing(method_name: String, args: Array) -> void:
print_debug('[method_missing] ', method_name, ': ', args)

So, the output was displayed in the Output panel as shown below.

[method_missing] Alice: [{ &"says": "Hello Ruby! ❤" }]

Architecture

ReDScribe creates an mruby execution unit called mrb_state for each instance. Therefore, the namespace is closed for each instance. Create a ReDScribe instance for the unit where you want to write DSL.

Architecture

Object#method_missing emits the method_missing signal, but it can also emit a channel signal using Godot.emit_signal.

channel signal

Try rewriting the GDScript as follows:

extends Control

@onready var res := ReDScribe.new()


func _ready() -> void:
res.method_missing.connect(_method_missing)
res.channel.connect(_subscribe) # Added
res.perform("""
Alice says: 'Hello Ruby! ❤'

# Added
puts "Welcome to the world of Ruby v#{RUBY_VERSION}, powered by #{RUBY_ENGINE} 💎"

# Added
Godot.emit_signal :spawn, { name: 'Alice', job: :wizard, level: 1 }
""")


func _method_missing(method_name: String, args: Array) -> void:
print_debug('[method_missing] ', method_name, ': ', args)


# Added
func _subscribe(key: StringName, payload: Variant) -> void:
print_debug('[subscribe] ', key, ': ', payload)

The following should appear in the Output panel.

[method_missing] Alice: [{ &"says": "Hello Ruby! ❤" }]
At: res://control.gd:19:_method_missing()
Welcome to the world of Ruby v3.4, powered by mruby 💎
[subscribe] spawn: { &"name": "Alice", &"job": &"wizard", &"level": 1 }
At: res://control.gd:23:_subscribe()

By executing Godot.emit_signal, a channel signal was emitted.

Godot.emit_signal :spawn, { name: 'Alice', job: :wizard, level: 1 }

The emitted signal is received by the _subscribe(key: StringName, payload: Variant) function.

  • The first argument :spawn as key
  • The second argument { name: 'Alice', job: :wizard, level: 1 } as payload

In Ruby, :spawn is a Symbol class, but when received by GDScript it becomes a StringName type.

The GDScript Variant type is a type that represents any type. Use this type when you want to process any data that is passed to to.

Type conversions

perform executes Ruby (mruby) code, and the data passed via the method_missing or channel signals is converted to GDScript types as follows:

mrubyGDScript
truetrue
falsefalse
nilnull
Floatfloat
Integerint
SymbolStringName
StringString
HashDictionary
ArrayArray
RangeArray
TimeDictionary
(Others)String
(#inspect called)

Which signal should you use?

Alice says: 'Hello Ruby! ❤'

If you want to receive the channel signal instead of method_missing, you can write it as follows.

extends Control

@onready var res := ReDScribe.new()


func _ready() -> void:
res.channel.connect(_subscribe)
res.perform("""
def Alice(**args)
Godot.emit_signal :action, { name: 'Alice', **args }
end

Alice says: 'Hello Ruby! ❤'
""")


func _subscribe(key: StringName, payload: Variant) -> void:
print_debug('[subscribe] ', key, ': ', payload)

[subscribe] action: { &"name": "Alice", &"says": "Hello Ruby! ❤" }
At: res://control.gd:18:_subscribe()

I defined an Alice method that internally calls Godot.emit_signal.

def Alice(**args)
Godot.emit_signal :action, { name: 'Alice', **args }
end

If you'd like to use channel signals, you'll need to know a bit more about Ruby.

For casual use, try the method_missing signal. If you're looking to implement more advanced behavior, consider using channel signal instead.