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.
Next, 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.
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()
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.
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
askey
- The second argument
{ name: 'Alice', job: :wizard, level: 1 }
aspayload
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:
mruby | GDScript | |
---|---|---|
true | ⇒ | true |
false | ⇒ | false |
nil | ⇒ | null |
Float | ⇒ | float |
Integer | ⇒ | int |
Symbol | ⇒ | StringName |
String | ⇒ | String |
Hash | ⇒ | Dictionary |
Array | ⇒ | Array |
Range | ⇒ | Array |
Time | ⇒ | Dictionary |
(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.