zigではまったもの
開発環境構築
2024/04/17時点で0.12系を使うための手順
バージョン管理ツールのzigupをインストールする
Windows + scoopの場合
- scoopでzigupをインストールする
scoop install zigup
- 環境変数PATHへ
%HOMEPATH%\.local\bin
を追加する
macOSの場合
-
https://github.com/marler8997/zigup/releases/ から最新版をダウンロード、展開して
zigup
を~/.local/bin
へコピー、chmod +x ~/.local/bin/zigup
する -
~/.zprofile
などのシェルの設定ファイルに以下を追記するexport PATH=$HOME/.local/bin:$PATH
-
zigup
を実行するとブロックされるのでシステム設定 > プライバシーとセキュリティからzigup
を許可する - 再度
zigup
を実行すると「“zigup”が悪質なソフトウェアかどうかをAppleでは確認できないため、このソフトウェアは開けません。」のダイアログが出るので「開く」を選択する
zigup master
で0.13系zigをインストール
zigのバージョンに合わせたzlsをインストールする
Windows + Powershellの場合
git clone https://github.com/zigtools/zls.git
cd zls
zig build -Doptimize=ReleaseSafe
mkdir ~\.local\bin
cp zig-out\bin\zls.exe ~\.local\bin
macOSの場合
git clone https://github.com/zigtools/zls.git
cd zls
zig build -Doptimize=ReleaseSafe
mkdir -p ~/.local/bin
cp zig-out/bin/zls ~/.local/bin
chmod +x ~/.local/bin/zls
zipup masterのバージョン確認
zigup fetch-index
で実行時点でインストールできるバージョンがjsonで出力される。
jq と組み合わせて zigup fetch-index | jq '.master.version'
で確認できる。
neovim向けにzlsを使う
Plug 'neovim/nvim-lspconfig'
Plug 'ziglang/zig.vim'
let g:mapleader = ","
:lua << EOF
-- Setup language servers
local lspconfig = require('lspconfig')
lspconfig.zls.setup{}
-- Global mappings.
-- See `:help vim.diagnostic.*` for documentation on any of the below functions
vim.keymap.set('n', '<Leader>e', vim.diagnostic.open_float)
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev)
vim.keymap.set('n', ']d', vim.diagnostic.goto_next)
vim.keymap.set('n', '<Leader>q', vim.diagnostic.setloclist)
-- Use an on_attach function to only map the following keys
-- after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
-- Mappings.
-- See `:help vim.lsp.*` for documentation on any of the below functions
local bufopts = { noremap=true, silent=true, buffer=bufnr }
vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts)
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts)
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts)
vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts)
vim.keymap.set('n', '<Leader>f', vim.lsp.buf.format, bufopts)
vim.keymap.set('n', '<Leader>D', vim.lsp.buf.type_definition, bufopts)
end
-- Use a loop to conveniently call 'setup' on multiple servers and
-- map buffer local keybindings when the language server attaches
local servers = { 'zls' }
for _, lsp in pairs(servers) do
require('lspconfig')[lsp].setup {
on_attach = on_attach,
flags = {
-- This will be the default in neovim 0.7+
debounce_text_changes = 150,
},
settings = {
solargraph = {
diagnostics = false
}
}
}
end
EOF
参考
テストケースでコマンドライン引数を与えられない
zigでコマンドライン引数を受け取れる std.process.argsAlloc
や std.os.argv
はネイティブAPIを直接呼び出しているのでテストケースから与える事はできない。
zig test -- a b c
のようにテスト実行時なら引数を与える事はできる。
そのためmain関数は引数を取り出す所までにしてそれを受け取る関数を切り出してそれをテストするのがベターである。
godot-zigからprintを呼び出す
以下のコードでGodotのコンソールへメッセージを出力できる
const Godot = @import("godot");
const UtilityFunctions = Godot.UtilityFunctions;
const print = UtilityFunctions.print;
const Self = @This();
pub usingnamespace Godot.Object;
base: Godot.Object,
pub fn init(self: *Self) void {
const name = @typeName(@TypeOf(self));
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer std.debug.assert(gpa.deinit() == .ok);
const msg = std.fmt.allocPrint(allocator, "init {s}", .{name}) catch @panic("allocPrint");
defer allocator.free(msg);
print(v(msg), .{});
}
fn s(str: []const u8) String {
return String.initFromUtf8Chars(str);
}
fn v(str: []const u8) Variant {
return Variant.initFrom(s(str));
}
GDScriptからはfree()
でメモリ解放しないと終了時にメモリリークエラーが出るので注意
extends Node
var test: Test
func _ready() -> void:
print("Hello GodotZig!")
test = Test.new()
func _exit_tree() -> void:
test.free()
以下のように usingnamespace
と base
を RefCounted
に変えると RefCounted
クラスを継承したクラスとして振る舞うので参照がなくなった時点で自動で free
されるのでGDScriptからの扱いが楽になる。
const Godot = @import("godot");
const UtilityFunctions = Godot.UtilityFunctions;
const print = UtilityFunctions.print;
const Self = @This();
pub usingnamespace Godot.RefCounted;
base: Godot.RefCounted,
pub fn init(self: *Self) void {
const name = @typeName(@TypeOf(self));
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer std.debug.assert(gpa.deinit() == .ok);
const msg = std.fmt.allocPrint(allocator, "init {s}", .{name}) catch @panic("allocPrint");
defer allocator.free(msg);
print(v(msg), .{});
}
fn s(str: []const u8) String {
return String.initFromUtf8Chars(str);
}
fn v(str: []const u8) Variant {
return Variant.initFrom(s(str));
}
godot-zigでGodot.Stringを[]u8へ変換する
Godot.stringToAscii
でStringに格納されている文字列を n
へ取り出せる。
const value = String.initFromUtf8Chars("Hello");
var buf: [256]u8 = undefined;
const n = Godot.stringToAscii(value, &buf);
godot-zigでカスタムクラスにカスタムコンストラクタを定義する
結論。Godot Engine 4.2.2時点のGDExtensionではGDScriptの_init(arg)のような引数付きコンストラクタを作れない。
以下は調査の足跡
以下の行はGodot Engine 4.2.2でコンストラクタの判定式
コンストラクタの場合の後続処理
rustのgdextでは以下のようにinit関数からコンストラクタを生成している
godot-goの実装ではcreate_callbackで_init(arg)に相当する関数を登録してるっぽい。
あとはfree_callbackとreference_callbackの用途と渡すべきものを調べる。
-
create_instance_bind
: 調査中 -
free_instance_bind
: Objectのデストラクタから呼ばれる関数 -
reference_bind
: RefCounted::referenceから呼ばれる -
unreference_bind
: RefCounted::unreferenceから呼ばれる - binding callbacks
-
create_callback
: C#版で必要となってinterfaceが定義されているがGDScript/GDExtensionからは呼ばれてなさそう -
free_callback
: Objectのデストラクタから呼ばれる関数 -
reference_callback
: RefCounted::referenceから引数true(1)で呼ばれて、RefCounted::unreferenceから引数false(0)で呼ばれ、破棄可能ならtrue(1)、破棄不可能ならfalse(0)を返すべき関数
-
p_userdataはGodot.registerClassが呼ばれた時にクラス名の[]u8をStringNameに変換したポインタが格納されている。
GDScriptでCustomClass.new(arg)を呼んだ時に_init(arg)が呼ばれるまでのフローが分かればどうにかなりそう
関数で文字列の配列を受け取る
const std = @import("std");
fn test_fn(names: []const []const u8) void {
for (names) |name| {
std.log.info("{s}", .{name});
}
}
pub fn main() void {
test_fn(&[_][]const u8{"aaa", "b", "cccc"});
}