はじめに
VキャスでLuaを触っていると、色々なエラー文に出会うかと思います。
ここでは、珍しいエラー文「attempt to perform operations with a script private resource on a shared resource」に出会ったしまったので、どういったパターンでこのエラー文が発生してしまうのか、説明していきます。
原因について
今回の原因に関してシンプルに書くと、Vキャス側で予め用意されているmessageがtable型でtable内の要素をtable型で書き換えようとする際に発生するエラーでした。
ここでmessageをどうやって書き換えるのかと、疑問に思われる方がほとんどかと思いますが、これはmessageがtable型で参照渡し、その後渡した側の変数を変更すると、messageを書き換えようとすることとなり、エラーとなります。
ただし、messageを渡した変数自体をtable型で書き換えたり、table内の要素をnumber型で書き換えたりした際には、エラーは発生しませんでした。
ここまでで、恐らくほとんどの方は理解されたかと思いますが、自分はプログラミング何もわからん勢で、Luaを触り始めた頃は「さ、参照渡し。。。?」状態だったので、そんな方達向けに説明していきます!
まずは、値渡しと参照渡しを説明し、続いて、今回のエラーがどのように発生していくのか、具体例を挙げて説明していきます。
値渡しと参照渡し
簡単に説明すると、
- 値渡し ・・・ 変数の値を変数に代入する
- 参照渡し ・・・ 変数同士を紐づける(参照先を紐づける)
というようなイメージです。
説明を見ているだけでは、理解しにくいかと思いますので、具体例を見ていきましょう。
まずは値渡しの確認から。
結果としては、”b”の値を変えても、”a”の値は影響されず、bは0から1に変更され、aは0のままとなっています。
local a = 0
print("a1 : "..a)
local b = a
print("b1 : "..b)
b = 1
print("a2 : "..a)
print("b2 : "..b)
続いて、参照渡しの確認を行います。
tableの変数を、別の変数に入れようとした際、参照渡しとなります。
(Luaではそうなるみたいですが、他の言語だとどうなんでしょう?)
”d”のtableの値の変更に影響を受け、d[1]が0から1に変更されると、c[1]も0から1に変更されています。
”c”と”d”が紐づけられた状態となっています。
この紐づけられた先(c)が、messageとなると、エラー文が発生します。
local c = {}
c[1] = 0
print("c1 : "..c[1])
local d = c
print("d1 : "..d[1])
d[1] = 1
print("c2 : "..c[1])
print("d2 : "..d[1])
参照渡しの回避方法
次の章で実際にエラー文を確認するまでに、参照渡しとならない方法を確認していきます。
参照渡しを回避する方法は、3つあります。(他にもあるかも)
- tableの中身を一つずつ値渡しする
- table.unpackを使う
- 一旦jsonに変換する
以上となります。
それぞれ確認してみましょう。
tableの中身を一つずつ値渡しする方法
まずは、tableの中身を一つずつ値渡しする方法です。
変数がtable型でなければ、参照渡しにはならないので、数字の状態で渡せば、値渡しとなり、”c”は”d”の影響を受けなくなります。
無事、d[1]を0から1に変更しても、c[1]は0のままとなるようになりました。
ただし、この方法では、tableの中身が増えてきた際に、少し手間となりますよね。
そうした場合は、続く2つの方法で渡すことを考えましょう。
local c = {}
c[1] = 0
print("c1 : "..c[1])
local d = {}
d[1] = c[1]
print("d1 : "..d[1])
d[1] = 1
print("c2 : "..c[1])
print("d2 : "..d[1])
table.unpackを使う方法
続いて、table.unpackを使う方法です。
結果は先のものと一緒ですので、写真は省略します。
この方法では、table.unpackを使って、数字で指定されたtableの要素に分解して、{}でtable要素に再構築しています。
こちらはdに渡す際に、table要素で渡していますが、一旦、分解・再構築を経ているので、”c”と”d”は影響されない状態となっています。
ただし、table.unpackは、tableのキーが数字で指定されているモノしか、分解されないので、tableのキーが文字列で指定している場合には、使用できません。
そうした場合のは、次の方法を用いると解決できます。
local c = {}
c[1] = 0
print("c1 : "..c[1])
local d = {table.unpack(c)}
print("d1 : "..d[1])
d[1] = 1
print("c2 : "..c[1])
print("d2 : "..d[1])
一旦jsonに変換する方法
最後の方法は、一旦jsonに変換する方法です。
「json、何それ、美味しいの?」と思う方もいらっしゃると思います(自分もまだそんな状態です)が、いろんなデータをエクスポート・インポートする際のフォーマットの一つと思えばいいかと思います。
tableのデータを、json.serializeで、一旦jsonのデータに変換し、json.parseでtable型に戻しているので、”c”と”d”は影響されない状態となっています。
こちらは、キーが文字列でも対応することができますが、数字と文字列が混合する場合には、数字の要素のみが変換されて、文字列の要素は変換されないようです。
キーに数字と文字列が混在する場合には、少し工夫が必要となります。
local c = {}
c[1] = 0
print("c1 : "..c[1])
local d = json.parse(json.serialize(c))
print("d1 : "..d[1])
d[1] = 1
print("c2 : "..c[1])
print("d2 : "..d[1])
エラー文の再現
さぁ、ここからエラー文の再現を行っていきます。
message関数を使い、tableを飛ばして、元の変数に入れ直して、変更を加えるという操作をしていきます。
最初のほうで説明しましたが、messageを渡した変数自体をtable型で書き換えたり、table内の要素をnumber型で書き換えたりした際には、エラーは発生しなかったので、そちらの確認をまずしていきます。
以下の2つの例が、エラーが発生しない場合です。
local c = {{0}}
function cAddProcess(sender, name, message)
c = message
c = {{0}}
end
vci.message.On("cAdd", cAddProcess)
function onUse(use)
if use == "Obj" then
vci.message.Emit("cAdd", c)
end
end
local c = {{0}}
function cAddProcess(sender, name, message)
c = message
c[1][1] = 0
end
vci.message.On("cAdd", cAddProcess)
function onUse(use)
if use == "Obj" then
vci.message.Emit("cAdd", c)
end
end
続いて、エラーが発生する場合です。
最初のほうで説明しましたが、table型のmessageを変数(c)に入れ、変数内の要素を、table型で変更を行いにいくとエラーとなります。
local c = {{0}}
function cAddProcess(sender, name, message)
c = message
c[1] = {0}
end
vci.message.On("cAdd", cAddProcess)
function onUse(use)
if use == "Obj" then
vci.message.Emit("cAdd", c)
end
end
このエラーに対して、先にご紹介した「参照渡しの回避方法」を使っていくと、エラーを回避できるようになります。
ここでは、一旦jsonに変換する方法でエラーを回避します。
一応、参照渡しを回避する以外にも、c[1]=message[1]で1次元の配列に落として渡せば、エラーにならないことは確認できていますが、不要な参照渡しは回避したほうがよいので、messageでtable型を受け渡す場合には、上記の「参照渡しの回避方法」を用いたほうがよいかと思います。
local c = {{0}}
function cAddProcess(sender, name, message)
c = json.parse(json.serialize(message))
c[1] = {0}
end
vci.message.On("cAdd", cAddProcess)
function onUse(use)
if use == "Obj" then
vci.message.Emit("cAdd", c)
end
end
おわりに
ここでは、エラー文「attempt to perform operations with a script private resource on a shared resource」を回避する方法として、参照渡しの回避方法を説明してきました。
エラーを回避する以外にも、table型を処理していく上で、参照渡しを経由して、意図しない動作が発生してしまうことがあります。(自分も麻雀VCIを作る上で、大変悩まされました!)
そんな折には、上記で説明した「参照渡しの回避方法」を思い出してみてくださいね!