Open20

Godot で mruby を使うメモ

tkmfujisetkmfujise

godot-cpp-template をクローン。
その後、mruby を git submodule として追加。

$ ls -1
LICENSE.md
README.md
SConstruct
__pycache__
bin
demo
godot-cpp
icon
methods.py
mruby
src
tmp

以下を書いた。

src/redscribe.cpp
#include <mruby.h>
#include <mruby/compile.h>

void ReDScribe::test_ruby() {
  mrb_state* mrb = mrb_open();
  if (!mrb) {
    // handle error
    return;
  }

  mrb_load_string(mrb, "1+1");
  mrb_close(mrb);
}

mruby の build_config を修正。

mruby/build_condig/default.rb
  conf.cc do |cc|
    cc.flags << '/MT'
  end

64 Native Tools Command Prompt for VS 2022 を起動して rake コマンドを実行

> cd mruby
> rake

すると、mruby\build\host\lib フォルダに libmruby.lib などが生成される。

SConstruct に以下追加。

SConstruct
mruby_include_path = "mruby/build/host/include"
mruby_library_path = "mruby/build/host/lib"

# mruby
env.Append(CPPPATH=[mruby_include_path])
env.Append(LIBPATH=[mruby_library_path])
env.Append(LIBS=["libmruby"])

# エラーが出たので追加
env.Append(LIBS=["Ws2_32"])

scons コマンドを実行するととりあえずエラーは出なくなった。(よくわかってない。copilot に聞いて言われるがまま試した)

tkmfujisetkmfujise

上記を Rakefile にした。rake コマンドでビルドできた。

Rakefile
task :default => :all

task :all => :mruby_build do
  sh 'scons'
end

task :mruby_build do
  cd 'mruby' do
    ENV['CFLAGS'] = '/MT'
    sh 'rake clean'
    sh 'rake'
  end
end
tkmfujisetkmfujise
src/redscirbe.h
#ifndef REDSCRIBE_H
#define REDSCRIBE_H

#include <godot_cpp/classes/resource.hpp>
#include <mruby.h>

namespace godot {

class ReDScribe : public Resource {
  GDCLASS(ReDScribe, Resource)
  
private:
  void set_dsl_error(const String &p_dsl_error);
  String get_dsl_error() const;

protected:
  static void _bind_methods();

public:
  ReDScribe();
  ~ReDScribe();

  String dsl = "";
  String dsl_error = "";

  void set_dsl(const String &p_dsl);
  String get_dsl() const;

  void execute_dsl();
};

}

#endif
src/redscribe.cpp
#include "redscribe.h"
#include <godot_cpp/core/class_db.hpp>
#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/data.h>
#include <mruby/string.h>

using namespace godot;

void ReDScribe::_bind_methods() {
  ClassDB::bind_method(D_METHOD("execute_dsl"), &ReDScribe::execute_dsl);
  ClassDB::bind_method(D_METHOD("set_dsl", "dsl"), &ReDScribe::set_dsl);
  ClassDB::bind_method(D_METHOD("get_dsl"), &ReDScribe::get_dsl);
  ADD_PROPERTY(PropertyInfo(Variant::STRING, "dsl"), "set_dsl", "get_dsl");
  ClassDB::bind_method(D_METHOD("set_dsl_error", "dsl_error"), &ReDScribe::set_dsl_error);
  ClassDB::bind_method(D_METHOD("get_dsl_error"), &ReDScribe::get_dsl_error);
  ADD_PROPERTY(PropertyInfo(Variant::STRING, "dsl_error"), "set_dsl_error", "get_dsl_error");
}

ReDScribe::ReDScribe() {
  // initialize
}

ReDScribe::~ReDScribe() {
  // cleanup
}

void ReDScribe::set_dsl(const String &p_dsl) {
  dsl = p_dsl;
}

String ReDScribe::get_dsl() const {
  return dsl;
}

void ReDScribe::set_dsl_error(const String &p_dsl_error) {
  dsl_error = p_dsl_error;
}

String ReDScribe::get_dsl_error() const {
  return dsl_error;
}

static mrb_value
method_missing(mrb_state *mrb, mrb_value self)
{
  /* ここの関数内はよくわかっていない copilot に聞いたコード。これから調べる */
  mrb_sym method_name;
  mrb_value *args;
  mrb_int arg_count;
  mrb_get_args(mrb, "n*", &method_name, &args, &arg_count);
  const char *method_name_str = mrb_sym2name(mrb, method_name);
  return mrb_str_new_cstr(mrb, "method not found: ");
}

void ReDScribe::execute_dsl() {
  mrb_state* mrb = mrb_open();
  if (!mrb) {
    // handle error
    return;
  }

  struct RClass* base_class = mrb->object_class;

  mrb_define_method(mrb, base_class, "method_missing", method_missing, MRB_ARGS_ANY());

  mrb_load_string(mrb, dsl.utf8().get_data());
  if (mrb->exc) {
    dsl_error = "error";
  } else {
    dsl_error = "";
  }
  mrb_close(mrb);
}

で、とりあえずビルド。method_missing で godot 側のメソッドを呼ぶように今後実装予定。
いまは dsl に入れた文字列を mruby で評価して、エラーが出たら、dsl_error に "error" という文字を入れるところまではできた。

Godot 側で、

simple_dsl.gd
extends ReDScribe
class_name SimpleDSL

とすると、以下は動いた。

test/test_simple_dsl.gd
extends GutTest

func test_method_missing():
	var res = SimpleDSL.new()
	res.dsl = 'bar'
	res.execute_dsl()
	assert_eq(res.dsl_error, '')

func test_raise():
	var res = SimpleDSL.new()
	res.dsl = 'raise'
	res.execute_dsl()
	assert_eq(res.dsl_error, 'error')
tkmfujisetkmfujise
src/redscribe.cpp
ReDScribe *gd_context = NULL;

static mrb_value
method_missing(mrb_state *mrb, mrb_value self)
{
  mrb_sym method_name;
  mrb_value *args;
  mrb_int arg_count;

  mrb_get_args(mrb, "n*", &method_name, &args, &arg_count);

  const char *method_name_str = mrb_sym2name(mrb, method_name);

  ReDScribe *instance = get_gdcontext();

  if (instance) {
    Array godot_args;
    Variant result = instance->call(method_name_str, godot_args);
    if (result.get_type() == Variant::STRING) {
      instance->dsl_result = String(result);
    } else {
      instance->dsl_result = "method missing"; 
    }
  }

  return mrb_str_new_cstr(mrb, "method not found: ");
}

void set_gdcontext(ReDScribe *r) {
  gd_context = r;
}

static ReDScribe* get_gdcontext(void) {
  return gd_context;
}

void ReDScribe::execute_dsl() {
  mrb_state* mrb = mrb_open();
  if (!mrb) {
    // handle error
    return;
  }

  set_gdcontext(this);
  struct RClass* base_class = mrb->object_class;

  mrb_define_method(mrb, base_class, "method_missing", method_missing, MRB_ARGS_ANY());

  mrb_load_string(mrb, dsl.utf8().get_data());
  if (mrb->exc) {
    dsl_error = "error";
  } else {
    dsl_error = "";
  }
  mrb_close(mrb);
}

で、instance->call(method_name_str, godot_args) のところで gdscript のメソッドを一応呼ぶことはできたが結果は取得できなかった。

simple_dsl.gd
extends ReDScribe
class_name SimpleDSL

func foo() -> String:
    return 'foo called'

func bar():
    return 'bar called'
var res = SimpleDSL.new()
res.dsl = 'foo'
res.execute_dsl()
res.dsl_result # => ''

res.dsl = 'bar'
res.execute_dsl()
res.dsl_result # => 'method missing'

-> String を書くと cpp 側で String を返す関数として宣言だけされるのか、

Variant result = instance->call(method_name_str, godot_args);
if (result.get_type() == Variant::STRING) {
    instance->dsl_result = String(result);
} else {
    instance->dsl_result = "method missing"; 
}

で、result.get_type()Variant::STRING を満たしていた。

gdextension の中で gdscript で定義した関数を実行したかったが、難しそうなので
gdextension 側で定義した関数を gdscript の中で実行する形にするしかないか。

tkmfujisetkmfujise

method_missing で Signal だけ emit して、gdscript 側で受け取って eval すれば、mruby 側で呼んだメソッドを gdscript 側で実行できるか。一方通行になるけど。

tkmfujisetkmfujise

mruby => gdscript への型変換


#include <godot_cpp/core/class_db.hpp>

#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/data.h>
#include <mruby/variable.h>
#include <mruby/string.h>
#include <mruby/hash.h>
#include <mruby/array.h>

static int
mrb_hash_variant_set(mrb_state *mrb, mrb_value key, mrb_value value, void *data)
{
  Dictionary *dict = static_cast<Dictionary *>(data);
  Variant godot_key   = mrb_variant(mrb, key);
  Variant godot_value = mrb_variant(mrb, value);
  dict->operator[](godot_key) = godot_value;
  return 0;
}

Dictionary
mrb_hash_variant(mrb_state *mrb, mrb_value hash)
{
  Dictionary dict;
  if (mrb_hash_p(hash)) {
    struct RHash *hash_ptr = mrb_hash_ptr(hash);
    mrb_hash_foreach(mrb, hash_ptr, mrb_hash_variant_set, &dict);
  }
  return dict;
}

Variant
mrb_variant(mrb_state *mrb, mrb_value value)
{
  switch (mrb_type(value)) {
  case MRB_TT_TRUE:
    return true;
  case MRB_TT_FALSE:
    if (mrb_nil_p(value)) {
      return Variant();
    }
    return false;
  case MRB_TT_INTEGER:
    return mrb_integer(value);
  case MRB_TT_FLOAT:
    return mrb_float(value);
  case MRB_TT_SYMBOL:
    return String(mrb_sym2name(mrb, mrb_symbol(value)));
  case MRB_TT_STRING:
    return String(mrb_str_to_cstr(mrb, value));
  case MRB_TT_HASH:
    return mrb_hash_variant(mrb, value);
  case MRB_TT_ARRAY: {
    Array godot_array;
    mrb_int len = RARRAY_LEN(value);
    for (mrb_int i = 0; i < len; i++) {
      mrb_value element = mrb_ary_entry(value, i);
      godot_array.append(mrb_variant(mrb, element));
    }
    return godot_array;
  }
  default:
    return Variant();
  }
}
tkmfujisetkmfujise

method_missing を受け取って gdscript を実行するところまでできた。

src/redscribe.cpp
using namespace godot;

ReDScribe *gd_context = nullptr;

void ReDScribe::_bind_methods() {
  ClassDB::bind_method(D_METHOD("perform", "dsl"), &ReDScribe::perform);
  ClassDB::bind_method(D_METHOD("set_exception", "exception"), &ReDScribe::set_exception);
  ClassDB::bind_method(D_METHOD("get_exception"), &ReDScribe::get_exception);
  ADD_PROPERTY(PropertyInfo(Variant::STRING, "exception"), "set_exception", "get_exception");
  ADD_SIGNAL(MethodInfo("method_missing", 
    PropertyInfo(Variant::STRING, "method_name"),
    PropertyInfo(Variant::ARRAY,  "args"))
  );
}

ReDScribe::ReDScribe() {
  mrb = mrb_open();
  if (!mrb) {
    // handle error
    return;
  }
  set_gdcontext(this);
  struct RClass* base_class = mrb->object_class;
  mrb_define_method(mrb, base_class, "method_missing", method_missing, MRB_ARGS_ANY());
}

static mrb_value
method_missing(mrb_state *mrb, mrb_value self)
{
  mrb_sym method_name;
  mrb_value *args;
  mrb_int arg_count;
  mrb_get_args(mrb, "n*", &method_name, &args, &arg_count);
  String method_name_str = String(mrb_sym2name(mrb, method_name));
  ReDScribe *instance = get_gdcontext();
  if (instance) {
    Array godot_args;
    for (mrb_int i = 0; i < arg_count; i++) {
      godot_args.append(mrb_variant(mrb, args[i]));
    }  
    instance->emit_signal("method_missing", method_name_str, godot_args);
  }
  return mrb_nil_value();
}

void set_gdcontext(ReDScribe *r) {
  gd_context = r;
}

static ReDScribe* get_gdcontext(void) {
  return gd_context;
}

void clear_gdcontext(void) {
  gd_context = nullptr;
}

void ReDScribe::perform(const String &dsl) {
  mrb_load_string(mrb, dsl.utf8().get_data());
  if (mrb->exc) {
    exception = "error";
  } else {
    exception = "";
  }
}
tkmfujisetkmfujise

やりたいこと

# ここは gdextension 内で定義
module Godot
  class << self
    def emit_signal(name, payload)
      # TODO
      puts "#{name}: #{payload}"
    end
  end
end

# これはユーザが定義。require メソッドを実装予定。 perform の前に実行
class Player
  ATTRIBUTES = %i[name level job] 

  def initialize(name)
    @name = name
  end

  def attributes
    ATTRIBUTES.map{|a| [a, self[a]] }.to_h
  end

  def [](key)
    instance_variable_get(:"@#{key}")
  end

  def []=(key, value)
    instance_variable_set(:"@#{key}", value)
  end

  def emit
    Godot.emit_signal(:add_player, attributes)
  end

  def method_missing(name, *args)
    if ATTRIBUTES.include? name
      self[name] = args[0]
    else
      super
    end
  end
end

def player(name, &block)
  player = Player.new(name)
  player.instance_exec(&block)
  player.emit
end
xxx.gd
func _ready() -> void:
    var res = ReDScribe.new()
    res.subscribe('add_player', add_player)
    res.perform("""
        player 'Alice' do
          level 1
          job   :magician
        end
    """)

func add_player(attributes: Dictionary) -> void:
    var player = Player.new()
    player.set_attributes(attributes)
    add_child(player)
tkmfujisetkmfujise

任意のタイミングでシグナルを発行できるようにした

src/redscribe.cpp
void ReDScribe::_bind_methods() {
  ADD_SIGNAL(MethodInfo("channel", 
    PropertyInfo(Variant::STRING, "key"),
    PropertyInfo(Variant::NIL,    "payload"))
  );
}

static mrb_value
emit_signal(mrb_state *mrb, mrb_value self)
{
  mrb_sym key;
  mrb_value payload;
  mrb_get_args(mrb, "no", &key, &payload);

  ReDScribe *instance = get_gdcontext();
  if (instance) {
    instance->emit_signal("channel",
                          String(mrb_sym2name(mrb, key)),
                          mrb_variant(mrb, payload));
  }
  return mrb_true_value();
}

static void
mrb_define_godot_module(mrb_state *mrb)
{
  struct RClass *godot_module;
  godot_module = mrb_define_module(mrb, "Godot");
  mrb_define_class_method(mrb, godot_module, "emit_signal", emit_signal, MRB_ARGS_REQ(2));
}

と定義。以下のように呼び出し可能になった。

extends Control

@onready var res := ReDScribe.new()

func _ready() -> void:
    res.channel.connect(_subscribe)
    res.perform("""
      Godot.emit_signal :foo, [1, 2]
    """)

func _subscribe(key: String, payload: Variant) -> void:
    print_debug(key, ': ', payload)
tkmfujisetkmfujise

文字列をUTF-8として受け取れるように修正

before

  case MRB_TT_STRING:
    return String(mrb_str_to_cstr(mrb, value));

after

  case MRB_TT_STRING:
    return String::utf8(mrb_string_value_ptr(mrb, value));
tkmfujisetkmfujise

rb ファイルを事前に実行できるようにする。require も定義

#include <godot_cpp/classes/file_access.hpp>

void ReDScribe::_bind_methods() {
  ClassDB::bind_method(D_METHOD("set_boot_file", "boot_file"), &ReDScribe::set_boot_file);
  ClassDB::bind_method(D_METHOD("get_boot_file"), &ReDScribe::get_boot_file);
  ADD_PROPERTY(PropertyInfo(Variant::STRING, "boot_file", PROPERTY_HINT_FILE, "*.rb"), "set_boot_file", "get_boot_file");
}

void ReDScribe::set_boot_file(const String &p_boot_file) {
  boot_file = p_boot_file;
  mrb_execute_file(mrb, p_boot_file);
}

static bool
mrb_execute_file(mrb_state *mrb, String path)
{
  Ref<FileAccess> file = FileAccess::open(path, FileAccess::ModeFlags::READ);
  if (file.is_valid()) {
    String content = file->get_as_text();
    mrb_load_string(mrb, content.utf8().get_data());
    return true;
  } else {
    PRINT("cannot load such file -- " + path);
    return false;
  }
}

static mrb_value
require(mrb_state *mrb, mrb_value self)
{
  mrb_value path;

  mrb_get_args(mrb, "S", &path);
  String gd_path = "res://" + String(mrb_variant(mrb, path));
  if (mrb_execute_file(mrb, gd_path)) {
    return mrb_true_value();
  }
  return mrb_false_value();
}

static void
mrb_define_utility_methods(mrb_state *mrb)
{
  struct RClass* base_class = mrb->object_class;
  mrb_define_method(mrb, base_class, "require", require, MRB_ARGS_REQ(1));
}
tkmfujisetkmfujise

mrubyのビルド

Windows と Mac でビルドして gdextension で使えるところまでできた。
環境変数 MRUBY_CONFIG には相対パスも指定できたので、mruby フォルダ以下はそのままに build_config を指定できた。

build_config/windows.rb
MRuby::Build.new do |conf|
  conf.toolchain :visualcpp

  conf.gembox 'default'

  conf.cc do |cc|
    cc.flags = ['/MT']
  end

  conf.enable_bintest
  conf.enable_test
end
build_config/macos_x86.rb
MRuby::Build.new do |conf|
  conf.toolchain :clang

  conf.gembox 'default'

  conf.cc do |cc|
    cc.flags = ['-arch x86_64']
  end

  conf.linker do |linker|
    linker.flags = ['-arch x86_64']
  end

  conf.enable_bintest
  conf.enable_test
end
build_config/macos_arm64.rb
MRuby::Build.new do |conf|
  conf.toolchain :clang

  conf.gembox 'default'

  conf.cc do |cc|
    cc.flags = ['-arch arm64']
  end

  conf.linker do |linker|
    linker.flags = ['-arch arm64']
  end

  conf.enable_bintest
  conf.enable_test
end

macOS の場合は、x86_64 と arm64 のどちらでもビルドして、最後に lipo コマンドでユニバーサルバイナリにする必要があった。

Rakefile
task :mruby_build do
  def build_config(name = nil)
    if name
      ENV['MRUBY_CONFIG'] = "../../build_config/#{name}"
    else
      ENV['MRUBY_CONFIG'] = nil
    end
    sh 'rake'
  end

  cd 'mruby' do
    case RbConfig::CONFIG['host_os']
    when /mswin|mingw|cygwin/
      build_config 'windows'

    when /darwin/
      libmruby_path = 'build/host/lib/libmruby.a'
      next if File.exist? libmruby_path
      arm64_file = Tempfile.new('libmruby_arm64')
      x86_file   = Tempfile.new('libmruby_x86')

      build_config 'macos_arm64'
      mv libmruby_path, arm64_file.path

      sh 'rake clean'

      build_config 'macos_x86'
      mv libmruby_path, x86_file.path

      sh "lipo -create -output #{libmruby_path} #{arm64_file.path} #{x86_file.path}"
    when /linux/
      build_config # TODO
    else
      build_config # TODO
    end
  end
end
tkmfujisetkmfujise

rb ファイルをファイルシステムのツリーに表示してダブルクリックでメインスクリーンに表示する

プラグインの登録

addons/redscribe/redscribe.gd
@tool
extends EditorPlugin

const Main = preload("res://addons/redscribe/src/main/main.tscn")

var main

func _enter_tree() -> void:
    main = Main.instantiate()
    EditorInterface.get_editor_main_screen().add_child(main)
    _make_visible(false)

func _exit_tree() -> void:
    if main: main.queue_free()

func _has_main_screen() -> bool:
    return true

func _handles(object: Object) -> bool:
    return object is ReDScribeEntry

func _edit(object: Object) -> void:
    if object is ReDScribeEntry:
        main.load_file(object.resource_path)

func _make_visible(visible: bool) -> void:
    if main: main.visible = visible

func _get_plugin_name() -> String:
    return "ReDScribe"

func _get_plugin_icon() -> Texture2D:
    return preload("res://addons/redscribe/assets/icons/editor_icon.svg")

リソースの定義

addons/redscribe/ext/redscribe_entry.gd
@tool
extends Resource
class_name ReDScribeEntry

func get_class():
    return "ReDScribeEntry"

func is_class(value: String):
    return value == "ReDScribeEntry"
addons/redscribe/ext/redscribe_entry_loader.gd
@tool
extends ResourceFormatLoader
class_name ReDScribeEntryLoader

const ReDScribeEntry = preload("./redscribe_entry.gd")
var extension = 'rb'

func _get_recognized_extensions() -> PackedStringArray:
    return PackedStringArray([extension])

func _get_resource_type(path: String) -> String:
    var ext = path.get_extension().to_lower()
    if ext == extension:
        return "ReDScribeEntry"
    return ""

func _handles_type(typename: StringName) -> bool:
    return typename == &"ReDScribeEntry"

func _load(path: String, original_path: String, use_sub_threads: bool, cache_mode: int) -> Variant:
    var res  = ReDScribeEntry.new()
    return res

メインスクリーンに表示するシーンの作成

addons/redscribe/src/main/main.gd
@tool
extends VBoxContainer

func load_file(path: String) -> void:
    %Editor.load_file(path)
addons/redscribe/src/editor/editor.gd
@tool
extends VBoxContainer

var current_file : String : set = set_current_file

func load_file(path: String) -> void:
    current_file = path
    var f = FileAccess.open(path, FileAccess.READ)
    %CodeArea.text = f.get_as_text()
    f.close()
tkmfujisetkmfujise

perform で変数が保持されてないので、
mrb_load_string ではなく、
mrb_load_string_cxt を使う必要がありそう。
=> 違った。

どうもローカル変数を保持するには、mrb_gc_arena_save とかも呼ばないといけない?

簡易REPLができたが、インスタンス変数とメソッドは引き継ぎできているので、ローカル変数はなくてよいか。

実装は以下。

@tool
extends VBoxContainer

var session : ReDScribe

const RELOAD_COMMAND = 'reload!'

var last_result = null : set = set_last_result
var input_histories : PackedStringArray
var history_back_idx := 0

func _ready() -> void:
    init_session()

func init_session() -> void:
    session = ReDScribe.new()
    session.method_missing.connect(_method_missing)
    session.channel.connect(_subscribe)

func perform() -> void:
    var code = str(%Input.text).strip_edges()
    if not code: return
    delete_input()
    match code:
        RELOAD_COMMAND: reload_repl()
        _: execute(code)

func execute(code: String) -> void:
    input_histories.push_back(code)
    output(code)
    session.perform("Godot.emit_signal :input, (%s)" % code)
    if session.exception:
        output("Error: %s" % session.exception)

func reload_repl() -> void:
    output("Session reloading...")
    init_session()
    output("Session reloaded!", true)

func set_last_result(val) -> void:
    last_result = val
    output("=> " + format_output_val(val))

func format_output_val(val) -> String:
    if val is String:
        return '"%s"' % val
    else:
        return str(val)

func output(str: String, clear: bool = false) -> void:
    if clear: %Result.text = ''
    %Result.text += str + "\n"

func history_back() -> void:
    history_back_idx += 1
    %Input.text = input_histories.get(
        input_histories.size() - history_back_idx)

func delete_input() -> void:
    history_back_idx = 0
    %Input.text = ''

func delete_following_input() -> void:
    %Input.text = '' # TODO

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

func _subscribe(key: StringName, payload: Variant) -> void:
    match key:
        'input': last_result = payload
        _: output(
            ("[ %s ] signal emitted: " % key) + format_output_val(payload))

func _on_input_gui_input(event: InputEvent) -> void:
    if event is InputEventKey:
        var k := event as InputEventKey
        if k.pressed:
            match k.keycode:
                KEY_ENTER:
                    if k.ctrl_pressed: perform()
                KEY_U:
                    if k.ctrl_pressed: delete_input()
                KEY_K:
                    if k.ctrl_pressed: delete_following_input()
                KEY_L:
                    if k.ctrl_pressed: output('', true)
                KEY_UP:
                    pass # TODO consider multi line
                    # history_back()

出力結果は以下

x = 1
=> 1
x
[method_missing]: x
=> <null>
@x = 1
=> 1
@x
=> 1
def y
	1
end
=> y
y
=> 1
tkmfujisetkmfujise

REPLでローカル変数を保持できない。
⇒ binding をインスタンス変数に入れて eval したら解決した。
⇒ と思ったが、文字列の式展開で undefind method になった。ローカル変数は諦める。