1. ホーム
  2. スクリプト・コラム
  3. ルア

Lua Observerパターンの解析 イベント配信システム構築のためのベストプラクティス

2022-01-09 05:43:47

I. 前書き

あるゲームでモジュールAがユーザのコイン数を変更し、モジュールBとモジュールCの両方がユーザのコイン数に依存する機能を提供している場合、モジュールAがコイン数を変更したときにモジュールBとモジュールCに通知する必要がある問題を考えてみよう。従来は、モジュールAがモジュールBとモジュールCのオブジェクトを保持し、それぞれオブジェクトインターフェースを呼び出して、"おい、ユーザーの金貨数を10枚に変更したぞ "と伝える方法がとられていました。

しかし、これではいくつもの問題が生じてしまいます。

  • モジュールAはモジュールBとCを参照しており、これらは強く結合されている
  • モジュールAのコイン数修正メソッドは、モジュールBとCのメソッドを呼び出します。この2つのモジュールが変更されると(たとえば、モジュールBのコイン数を受け取るためのインターフェース名が変わったり、モジュールCがコイン数の変更を知る必要がなくなったり)、モジュールAもまた {を使用します。 {を使用します。 コイン数の変更を知る必要がある別のモジュールDがある場合、その必要性に応じてAモジュールも修正する必要がある

上記の問題を解決するために、私たちは自然にObserverパターンを思いつきました。

II. オブザーバーパターン

ここで、Observerパターンとは何かについて簡単に説明します。オブジェクト間の一対多の依存関係を定義し、あるオブジェクトの状態が変化したときに、その依存関係(オブザーバーと呼ばれる)がすべて通知され、自動的に更新されるようにする。

オブザーバーパターンの利点は、オブジェクト同士が疎結合であること、オブジェクトが状態を変更する際に、オブザーバーが誰であるかを知る必要がなく、ただ通知されればよいことである。オブザーバは、通知を出したオブジェクトに影響を与えることなく、いつでも追加・削除することができる。そして、イベント配信システムは、オブザーバパターンの具体的な実装である

III. イベント配信システム

イベント配信システムの中核には、以下のような機能が必要である。

  • オブジェクトが変化したとき、その時点でイベントが発生すると仮定し、すべてのオブザーバに通知するイベントをディスパッチするためのインタフェースを提供することができます。
  • オブザーバーが必要なイベントを受信できるように、イベントのリスニング登録のためのインターフェイスを提供する必要があります。
  • {を使用します。 また、オブザーバが自ら登録を解除できるように、リスナーの登録を解除するためのインターフェースを提供する必要がある {を使用します。 また、購読時に優先順位を設定し、優先順位が高いほど先に通知されるようにすることが望ましい

IV. イベント配信システムを利用した問題解決

まず、イベント配信システムを使って、上記のような問題に対処するとどのようになるかを見てみましょう。

モジュールAは金塊修正イベントを配信するだけでよく、モジュールBとCは金塊修正イベントを購読するだけでよく、その後通知を受け取ることができます。とてもシンプルだと思いませんか?

local B = class()
function B:on_money_change( money )
    print(money, "B receive event")
end
-- Subscribe to the money change event
EventSystem:on(Event.MoneyChanged, B.on_money_change, {target = B})

local C = class()
function C:on_money_change( money )
    print(money, "C receive event")
end
EventSystem:on(Event.MoneyChanged, C.on_money_change, {target = C})
-- Dispatch the money change event in module A, the current money is 10
EventSystem:emit(Event.MoneyChanged, 10)

次に、これを詳しく見てみましょう。 EventSystem イベント配信システムをLuaで実装。

イベント配信システムを実装する際には、以下のような特殊なケースに注意する必要があり、読者はコード内のこれらのポットホールの処理に注目することができます。

  • イベントディスパッチ処理中にイベントをサブスクライブすると、サブスクリプションが優先され、順番に慎重に処理される必要があります
  • {を使用します。 イベント配信中にイベントの登録を解除する場合は、直接削除ではなく、マーカー削除を使用する必要があります。
  • イベントディスパッチ処理中にイベントがディスパッチされた場合、イベントディスパッチが完了したかどうかを判断する方法

説明を簡単にするため、以下のコードでは、重要でないコードを省略し --- ... の代わりに

V. リスニングイベントインタフェースの登録

function EventSystem:on( event, func, params )
    --- ...
    local event_listener = self._listeners[event]
    params = params or {}
    local priority = params.priority or 0
    local target = params.target
    --- local target = params.target
    local cb = {target = target, func = func, id = id, priority = priority}
    table.insert(event_listener.list, cb)
    id = id + 1
    if priority > 0 then
        event_listener.need_sort = true
        self:sort(event_listener)
    end
end

on メソッドで event パラメータは、リスニングに登録するイベント名を示す。 func パラメータは、イベント発生時に起動されるコールバック関数を示す。 {コード は、登録されたリスナーのターゲットを設定することができる追加のパラメータを示します。 params (関連するすべてのリスナーを後方登録するために使用できる) また、登録するリスナーの優先順位を設定し、優先順位の高いものから実行されるようにします。

target メソッドの実装は比較的簡単で、主に登録に関連する情報を on テーブルを呼び出しますが、登録されたリスナーが優先されるにもかかわらず、依然として

event_listener

の場合、イベントはまだディスパッチされている最中です。このとき {コード であれば、イベントのディスパッチが完了します。

使用不可 table.insert sort この置換の理由は、もしトリガーされたコールバックで別のイベントがディスパッチされ、再帰を形成する場合、2番目のディスパッチが function EventSystem:sort( listener ) if listener.need_sort == true and listener.exit_count == 0 then table.sort(listener.list, function ( a, b ) if a.priority == b.priority then return a.id < b.id else return a.priority > b.priority end end) listener.need_sort = false; end end から sort これは、ディスパッチイベントのディスパッチステータスが正しく表示されない原因となります。

VIII. もっと見る

イベント配信システムの完全なソースコードは、以下にあります。 こちら をクリックすると、テストケースを見ることができます。 こちら をご覧ください。
オブジェクト指向(コード内で使用するクラスキーワード)、コンポーネントシステム、サブモジュールのロードなど、Lua関連の設計や使い方についてはGitHubのリポジトリをご覧ください LuaKit

上記は、イベント配信システム構築のためのLua Observerパターンのベストプラクティスの詳細の分析であり、イベント配信システムを構築するLua Observerパターンの詳細については、スクリプトホームの他の関連記事に注意を払うしてください!。