公式で紹介されているNimからSFMLを呼び出すサンプルの実行方法
概要
C++を簡単に呼び出せると噂され公式にコードが貼られるも、実行用のコマンドは貼られていないNimのスクリプトを実行できるようにします。
本当に簡単に呼び出せます。
今回は公式で紹介されているSFMLのバインドを呼び出す方法を紹介します。
SFMLを知らない方のために軽くSFMLの説明をするとOpenGLをラップしたゲーム用のライブラリです。
同じ分野のライブラリとしてはSDL2が存在しますが、SDLはCで書かれているのに対してSFMLはC++で書かれていて、リソースの管理が楽になっています。
環境
Nimの実行環境、SFMLのヘッダとライブラリ、DLLを用意します。
ソースをコピペ
{.passL: "-lsfml-graphics -lsfml-system -lsfml-window".}
type
VideoMode* {.importcpp: "sf::VideoMode".} = object
RenderWindowObj {.importcpp: "sf::RenderWindow".} = object
RenderWindow* = ptr RenderWindowObj
Color* {.importcpp: "sf::Color".} = object
Event* {.importcpp: "sf::Event".} = object
{.push cdecl, header: "<SFML/Graphics.hpp>".}
proc videoMode*(modeWidth, modeHeight: cuint,
modeBitsPerPixel: cuint = 32): VideoMode
{.importcpp: "sf::VideoMode(@)", constructor.}
proc newRenderWindow*(mode: VideoMode, title: cstring): RenderWindow
{.importcpp: "new sf::RenderWindow(@)", constructor.}
proc pollEvent*(window: RenderWindow, event: var Event): bool
{.importcpp: "#.pollEvent(@)".}
proc newColor*(red, green, blue, alpha: uint8): Color
{.importcpp: "sf::Color(@)", constructor.}
proc clear*(window: RenderWindow, color: Color)
{.importcpp: "#.clear(@)".}
proc display*(window: RenderWindow)
{.importcpp: "#.display()".}
# SFMLの最小コード
var window = newRenderWindow(videoMode(640, 480), "Hello SFML")
var running = true
while running:
var event: Event
discard window.pollEvent(event)
window.clear(newColor(0, 0, 0, 0))
window.display()
Nim公式のコードをコピペして、SFMLのループの最小のコードを書きます。
これでコンパイルして実行すればSFMLでWindowを作ることができます。
コマンド
nim --run cpp main.nim
しかし上記コマンドではコンパイルが失敗します。
Nimはコンパイルすると一旦C/C++のコードに変換され、それをC/C++のコンパイラがコンパイルするのですが、そのC/C++のコンパイラがSFMLのヘッダとリンカの場所がわからないのが原因です。
なのでC/C++のコンパイラにSFMLの場所を教えてあげれば解決します。
nim --run --cincludes:SFMLがあるインクルードディレクトリ --clibdir:SFMLがあるライブラリディレクトリ cpp main.nim
cincludesが-Iに、clibdirが-Lに相当します。
これで無事にコンパイルすることができます。
enumを渡す
無事に実行することはできましたが、このままではターミナルから強制終了するしかアプリケーションを終了できません。
なのでsf::Keyboard::isKeypressedを呼び出して、特定のキーが押されたらアプリケーションを終了できるようにします。
その過程でenumの渡し方を説明します。
type KeyCode* {.pure, size: sizeof(cint).} = enum
Unknown = -1, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U,
V, W, X, Y, Z, Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9,
Escape, LControl, LShift, LAlt, LSystem, RControl, RShift, RAlt, RSystem,
Menu, LBracket, RBracket, SemiColon, Comma, Period, Quote, Slash, BackSlash,
Tilde, Equal, Dash, Space, Return, Back, Tab, PageUp, PageDown, End, Home,
Insert, Delete, Add, Subtract, Multiply, Divide, Left, Right, Up, Down,
Numpad0, Numpad1, Numpad2, Numpad3, Numpad4, Numpad5, Numpad6, Numpad7,
Numpad8, Numpad9, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14,
F15, Pause, Count
(nim-csfmlより引用 https://github.com/oprypin/nim-csfml)
Nim側でenumを設定して、それを渡します。
C++側のenumの値はintなので(厳密には違います)こちらもintを指定して、あとは宣言する順番さえ一緒なら問題ありません。
proc isKeyPressed*(code: KeyCode): bool
{.importcpp: "sf::Keyboard::isKeyPressed(@)".}
while running:
var event: Event
discard window.pollEvent(event)
if isKeyPressed(KeyCode.Escape):
running = false
window.clear(newColor(0, 0, 0, 0))
window.display()
sf::Keyboad::isKeyPressedのバインド関数を追加して、キーを渡します。
例ではエスケープキーを渡すことでアプリケーションを終了できるようにしています。
この例で示したとおり、C++でのデータはメモリ上でのサイズと順番さえ合っていれば問題ないので、Nim側でそれを保証したデータを渡すことでデータをやり取りすることができます。
解放処理
ptrで宣言された型はNimのGCでは追跡されなくなるので
for i in 0..10:
var window = newRenderWindow(sfvideoMode(640, 480), "memory leak")
のようなコードはメモリリークします。
それを避けるために明示的に開放する必要があります。
proc destroy(this: var RenderWindow)
{.importcpp: "delete #".}
for i in 0..10:
var window = newRenderWindow(sfvideoMode(640, 480), "memory leak")
window.destroy()
これで無事に開放することができます。
おわりに
C++の関数を呼び出すこと自体は非常に簡単です。
値を渡すのもC/C++はデータのサイズと並びさえ一緒なら問題がないため、そこまで苦労することもないのでしょうか。
ただ仕方ないのですがオブジェクトは自身で管理する必要があります。
仮に自身でオブジェクトをラップしたとしてもNimはGCを採用している関係上、デストラクタとの相性が悪く開放処理は自身で書く必要があります。
一応、deferはありますがゲームではオブジェクトの寿命がユーザーの入力によって大きく左右されるので相性はあまり良くありません。
メモリ管理が嫌でGCを使用しているのに、メモリ管理をするのは本末転倒で何とかしたいのですが、私の知識では良い解決法が思い浮かびません。
とは言えここまで気軽にC++を呼び出せるプログラミング言語も中々無いので、この気軽さは素晴らしいと思います。
参考
Nim公式
https://nim-lang.org/features.html
C言語版SFMLのNimバインディング
https://github.com/oprypin/nim-csfml
コンパイルオプション
https://nim-lang.org/docs/nimc.html