1. ホーム
  2. lua

[解決済み] Luaイベントハンドラ

2022-02-19 17:55:59

質問

lua には "event handler" が組み込まれているのですか、それとも lib が用意されているのですか?

例えば、"a = 100"のときにイベントが発生するように。

使うのではなく、何か別のものを。

while true do
 if a == 100 then
   [...]
   break;
 end
end

単純にスリープをつけるとか。while true do"はほんの一例ですが、これはひどいものです。

解決方法は?

Luaはシングルスレッドで動作するため、チェックはコードで明示的に行う必要があります。

変数が変化したときにすぐにコードを実行することを、ウォッチといいます。

もし、あるコードのセットが毎回実行されるような環境でプログラミングをしている場合 フレーム (ゲームなど)であれば、手動で確認することができます。 例えば

WatchedVariables = {
    a = 5,
    b = 22,
}
WatchedVariables_cache = {}
for k,v in pairs(WatchedVariables) do
    WatchedVariables_cache[k] = v
end

function OnFrame()
    print("NEXT FRAME! (possibly 1 second later or something)")
    for k,v in pairs(WatchedVariables) do
        local v_old = WatchedVariables_cache[k]
        if v ~= v_old then
            -- this is the "callback"
            print(tostring(k).." changed from "..tostring(v_old).." to "..tostring(v))

            WatchedVariables_cache[k] = v
        end
     end
 end

 function SomeFunctionThatOperatesSomeTime()
     print("about to change a, brother!")
     WatchedVariables.a = -7
     print("a is changed")
 end

次のフレームで、コールバックコード(print)が実行されます。 この方法の欠点は、コールバックコードがプリントされないことです。 すぐに WatchedVariables.a が設定されています。 -7 , つまり: 出力は次のようになります。

about to change a, brother!
a is changed
NEXT FRAME! (possibly 1 second later or something)
a changed from 5 to -7

この望ましくない可能性のある動作を防止するために セッター 関数を使用することができます。

MyObject = {
    _private_a = 5,
    set_a = function(self, new_value_of_a)
        self._private_a = 5
        -- callback code
        print("a set to "..tostring(new_value_of_a))
    end,
    get_a = function(self)
        return self._private_a
    end
}

function SomeFunctionThatOperatesSomeTime()
     print("about to change a, brother!")
     MyObject:set_a(-7)
     print("a is changed")
 end

このコードの出力は、コールバックがすぐに実行されることを示しています。

about to change a, brother!
a set to -7
a is changed

これをより快適にするために、Luaでは メタテーブル は、このような動作をプログラマが理解できるようにするものです。 例を挙げます。

MyObject = {
    __privates = {
        a = 5,
    }
}
MyObject_meta = {
    __index = function(self, k)
        return rawget(self, "__privates")[k]
    end,
    __newindex = function(self, k, v)
        rawget(self, "__privates")[k] = v
        -- callback code
        print("a set to "..tostring(v))
    end,
}
setmetatable(MyObject, MyObject_meta)

function SomeFunctionThatOperatesSomeTime()
     print("about to change a, brother!")
     MyObject.a = -7
     print("a is changed")
 end

このコードの出力は、前の例と同じになります。

about to change a, brother!
a set to -7
a is changed

以下は、例のケースでの実装です。

MyObject = {
    __privates = {
        a = 5,
    }
    __private_callback = function(self, k, ov, v)
        if k == "a" and v == "100" then
            print("a is 100!")
        end
    end
}
MyObject_meta = {
    __index = function(self, k)
        return rawget(self, "__privates")[k]
    end,
    __newindex = function(self, k, v)
        local privates = rawget(self, "__privates")
        local ov = privates[k]
        privates[k] = v
        rawget(self, "__private_callback")(self, k, ov, v)
    end,
}
setmetatable(MyObject, MyObject_meta)

function SomeFunctionThatOperatesSomeTime()
     MyObject.a = -7 -- prints nothing
     MyObject.a = 100 -- prints "a is 100!"
     MyObject.a = 22 -- prints nothing
 end

なぜ、変数が __privates__private_callback の前にアンダースコアが2つ付いているのは? 一般的なプログラミングでは、アクセスしてはいけないプライベートメンバーの前にアンダースコアを2つ付けるのが慣例となっています。オブジェクト指向の方法論と、JavaやC++などの言語での実装に詳しい人なら、これがどのように privateprotected のキーワードを使用します。

C#言語をご存知の方なら、このように set_a / get_a であり、metatableの実装はアクセッサに似ている( set / get ).