OpenSiv3D スクリプト機能で遊んでみる

# はじめに
[OpenSiv3D v0.3.0](https://github.com/Siv3D/OpenSiv3D)がリリースされた。

新機能はいろいろあると思うが、旧版のころから楽しみにしていたスクリプト機能が使えるようになったようなのでさっそく試してみた。

## ManagedScript

スクリプト側のMain関数で制御したい時に使用できる
スクリプトの変更をリアルタイムに反映させてくれるので便利

```cpp:Main.cpp
# include <Siv3D.hpp> // OpenSiv3D v0.3.0

void Main()
{
	ManagedScript main(U"script.as");

	while (System::Update())
	{
		main.run();
	}
}
```

C++側はたったこれだけでよい。楽ちん

```as:script.as
void Main()
{
	while (System::Update())
	{
		if (KeySpace.down())
		{
			Print <<"Hoge";
		}
	}
}
```
スクリプト側ではほぼC++と同じような文法で書くことができる。

ちなみに内部で使われているのは
AngelScriptのようで[ドキュメント](https://www.angelcode.com/angelscript/sdk/docs/manual/index.html)を読めばだいたいの文法は学ぶことができそう。

# Script

`ManagedScript`の内部で使用されている汎用的なスクリプト機能。

## F5を押したときにリロードしてみる
### Script::setSystemUpdateCallback
この関数を使用すればスクリプト側の`System::Update`の返り値を制御できる

### Script::getFunction
関数テンプレートになっておりスクリプト側の関数を呼ぶことができる。

```cpp:Main.cpp
void Main()
{
	Script main(U"script.as");

	// F5を押したらスクリプト側のSystem::Updateはfalseが返る
	main.setSystemUpdateCallback([]() {
		return !KeyF5.down();
	});

	while (System::Update())
	{
		main.reload();
		auto mainFunc = main.getFunction<void()>(U"Main");
		String exception;
		mainFunc.tryCall(exception);
	}
}
```

# 応用編

## スクリプト側の関数に参照渡し

### ダメな例

```cpp:Main.cpp
# include <Siv3D.hpp> // OpenSiv3D v0.3.0

void Main()
{
	Script script(U"script.as");
	auto increment = script.getFunction<void(int&)>(U"Increment");

	int a = 0;
	while (System::Update())
	{
		if (KeySpace.down())
		{
			increment(a);
			Print << a;
		}
	}
}
```
```as:script.as
void Increment(int& a)
{
	a++;
}
```

無理やった

### 改造する
`Script.hpp`を開き`s3d::detail::SetArg`を以下のように改造してみる

```cpp:Script.hpp
		template <class Type>
		inline void SetArg(const std::shared_ptr<ScriptModuleData>& moduleData, uint32 argIndex, const Type& value)
		{
			if constexpr(std::is_pointer_v<Type>)
			{
				moduleData->context->SetArgAddress(argIndex, reinterpret_cast<void*>(value));
			}
			else
			{
				moduleData->context->SetArgObject(argIndex, const_cast<Type*>(&value));
			}
		}

```
呼ぶ側もポインタ渡しに変更

```cpp:Main.cpp
# include <Siv3D.hpp> // OpenSiv3D v0.3.0

void Main()
{
	Script script(U"script.as");
	auto increment = script.getFunction<void(int*)>(U"Increment");

	int a = 0;
	while (System::Update())
	{
		if (KeySpace.down())
		{
			increment(&a);
			Print << a;
		}
	}
}
```

こうするといける！！
正規ルートはないのか……

## 自作classをスクリプト側から呼ぶ

## 型定義の登録

以下のようなクラスを作りスクリプトから呼べるようにする

```Character.hpp
#pragma once
#include<Siv3D.hpp>

class Character 
{
public:
	Vec2 m_pos;
	void update() 
	{
		Print << U"common update";
	}

	void draw()const 
	{
		Print << U"common draw";
	}
};

void RegisterScriptCharacter(AngelScript::asIScriptEngine* engine);
```

オープンソースでやっているやり方をほぼコピペでできる

```Script_Character.cpp
#include"Character.h"

using namespace AngelScript;

using BindType = Character;

namespace
{
	void DefaultConstruct(BindType* self)
	{
		new(self) BindType();
	}

	void CopyConstruct(const BindType& other, BindType* self)
	{
		new(self) BindType(other);
	}

	void Destruct(BindType* self)
	{
		self->~BindType();
	}
}


void RegisterScriptCharacter(asIScriptEngine* engine)
{
	constexpr char TypeName[] = "Character";

	int32 r = 0;
	// 型
	r = engine->RegisterObjectType(TypeName, sizeof(BindType), asOBJ_VALUE | asGetTypeTraits<BindType>()); assert(r >= 0);

	// メンバ変数
	r = engine->RegisterObjectProperty(TypeName, "Vec2 m_pos", asOFFSET(BindType, m_pos)); assert(r >= 0);

	// コンストラクタ
	r = engine->RegisterObjectBehaviour(TypeName, asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(DefaultConstruct), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour(TypeName, asBEHAVE_CONSTRUCT, "void f(const Character& in)", asFUNCTION(CopyConstruct), asCALL_CDECL_OBJLAST); assert(r >= 0);

	// デストラクタ
	r = engine->RegisterObjectBehaviour(TypeName, asBEHAVE_DESTRUCT, "void f()", asFUNCTION(Destruct), asCALL_CDECL_OBJLAST); assert(r >= 0);

	// メソッド
	r = engine->RegisterObjectMethod(TypeName, "void update()", asMETHOD(BindType, update), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod(TypeName, "void draw() const", asMETHOD(BindType, draw), asCALL_THISCALL); assert(r >= 0);

	// operator
	r = engine->RegisterObjectMethod(TypeName, "Character& opAssign(const Character& in)", asMETHODPR(BindType, operator =, (const BindType&), BindType&), asCALL_THISCALL); assert(r >= 0);

}
```
### 実際に使ってみる


``` cpp:Main.cpp
# include <Siv3D.hpp> // OpenSiv3D v0.3.0
#include"Character.h"

void Main()
{
	// 自作class の登録
	RegisterScriptCharacter(ScriptManager::GetEngine());
	Script script(U"script.as");

	Character chara;
	chara.m_pos = Vec2(0, 0);
	auto update = script.getFunction<void(Character*)>(U"Update");
	auto draw = script.getFunction<void(Character*)>(U"Draw");

	while (System::Update())
	{
		update(&chara);
		draw(&chara);
	}
}
```

```as:script.as
float speed = 3;

void Update(Character& chara)
{
	if(KeyRight.pressed())
	{
		chara.m_pos.x += speed;
	}
	if(KeyLeft.pressed())
	{
		chara.m_pos.x -= speed;
	}
}

Texture tex("example/siv3d-kun.png");
void Draw(const Character& chara)
{
 	tex.drawAt(chara.m_pos);
}
```

ただしC++側から登録したclassはスクリプト側で継承とかはできないっぽい
設定のパラメータかえればできるのだろうか？？

#まとめ

- OpenSiv3D v0.3.0がリリースされたよ
- スクリプト機能はいいぞ
- スクリプト側の関数に参照渡しするのはやっかい
- C++で定義したclassを自由にスクリプト側でも使えるようにできる
    - 継承はできないみたいだが？