4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VCI(VirtualCast Interactive)Advent Calendar 2023

Day 24

エラー文「attempt to perform operations with a script private resource on a shared resource」について

Last updated at Posted at 2023-12-23

はじめに

VキャスでLuaを触っていると、色々なエラー文に出会うかと思います。
ここでは、珍しいエラー文「attempt to perform operations with a script private resource on a shared resource」に出会ったしまったので、どういったパターンでこのエラー文が発生してしまうのか、説明していきます。
2023100801042636-1_!入室注意! わいず実験場_小山内シノブ.jpg

原因について

今回の原因に関してシンプルに書くと、Vキャス側で予め用意されているmessageがtable型でtable内の要素をtable型で書き換えようとする際に発生するエラーでした。
ここでmessageをどうやって書き換えるのかと、疑問に思われる方がほとんどかと思いますが、これはmessageがtable型で参照渡し、その後渡した側の変数を変更すると、messageを書き換えようとすることとなり、エラーとなります。
ただし、messageを渡した変数自体をtable型で書き換えたり、table内の要素をnumber型で書き換えたりした際には、エラーは発生しませんでした。
ここまでで、恐らくほとんどの方は理解されたかと思いますが、自分はプログラミング何もわからん勢で、Luaを触り始めた頃は「さ、参照渡し。。。?」状態だったので、そんな方達向けに説明していきます!
まずは、値渡しと参照渡しを説明し、続いて、今回のエラーがどのように発生していくのか、具体例を挙げて説明していきます。

値渡しと参照渡し

簡単に説明すると、

  • 値渡し  ・・・ 変数の値を変数に代入する
  • 参照渡し ・・・ 変数同士を紐づける(参照先を紐づける)

というようなイメージです。
説明を見ているだけでは、理解しにくいかと思いますので、具体例を見ていきましょう。

まずは値渡しの確認から。
結果としては、”b”の値を変えても、”a”の値は影響されず、bは0から1に変更され、aは0のままとなっています。

main.lua
local a = 0
print("a1 : "..a)
local b = a
print("b1 : "..b)
b = 1
print("a2 : "..a)
print("b2 : "..b)

スクリーンショット (159).png

続いて、参照渡しの確認を行います。
tableの変数を、別の変数に入れようとした際、参照渡しとなります。
(Luaではそうなるみたいですが、他の言語だとどうなんでしょう?)
”d”のtableの値の変更に影響を受け、d[1]が0から1に変更されると、c[1]も0から1に変更されています。
”c”と”d”が紐づけられた状態となっています。
この紐づけられた先(c)が、messageとなると、エラー文が発生します。

main.lua
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])

スクリーンショット (158).png

参照渡しの回避方法

次の章で実際にエラー文を確認するまでに、参照渡しとならない方法を確認していきます。
参照渡しを回避する方法は、3つあります。(他にもあるかも)

  • tableの中身を一つずつ値渡しする
  • table.unpackを使う
  • 一旦jsonに変換する

以上となります。
それぞれ確認してみましょう。

tableの中身を一つずつ値渡しする方法

まずは、tableの中身を一つずつ値渡しする方法です。
変数がtable型でなければ、参照渡しにはならないので、数字の状態で渡せば、値渡しとなり、”c”は”d”の影響を受けなくなります。
無事、d[1]を0から1に変更しても、c[1]は0のままとなるようになりました。
ただし、この方法では、tableの中身が増えてきた際に、少し手間となりますよね。
そうした場合は、続く2つの方法で渡すことを考えましょう。

main.lua
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])

スクリーンショット (160).png

table.unpackを使う方法

続いて、table.unpackを使う方法です。
結果は先のものと一緒ですので、写真は省略します。
この方法では、table.unpackを使って、数字で指定されたtableの要素に分解して、{}でtable要素に再構築しています。
こちらはdに渡す際に、table要素で渡していますが、一旦、分解・再構築を経ているので、”c”と”d”は影響されない状態となっています。
ただし、table.unpackは、tableのキーが数字で指定されているモノしか、分解されないので、tableのキーが文字列で指定している場合には、使用できません。
そうした場合のは、次の方法を用いると解決できます。

main.lua
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”は影響されない状態となっています。
こちらは、キーが文字列でも対応することができますが、数字と文字列が混合する場合には、数字の要素のみが変換されて、文字列の要素は変換されないようです。
キーに数字と文字列が混在する場合には、少し工夫が必要となります。

main.lua
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つの例が、エラーが発生しない場合です。

main.lua
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
main.lua
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型で変更を行いにいくとエラーとなります。

main.lua
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型を受け渡す場合には、上記の「参照渡しの回避方法」を用いたほうがよいかと思います。

main.lua
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を作る上で、大変悩まされました!)
そんな折には、上記で説明した「参照渡しの回避方法」を思い出してみてくださいね!

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?