Photon Server Plugin — 機能追加(1)

在 Photon Server 4 裡提供了一種新的程式設計方法: Photon Plugins (稱之為外掛or插件), 它主要設計目的, 就是希望用較方便快速的程式掛接方式, 來達成在 Server-Side 的 Game / Room 的機能追加.

Photon Plugins 只能使用在 Enterprise Cloud 或是 自有主機(Self-Hosted)的 Photon Server v4 上面.

註: 如果用的是之前的 Photon 3 版本, 那麼要追加或是擴展伺服器的功能應用, 還是必須透過一般的繼承方式才能做到.

有時我們只是想寫點簡單功能或是想快速的與 Client 互相測試一些想法, 用這種新的 Plugins 的方式就非常合適. 
下面, 我們先來瞭解一下外掛的基本概念與流程, 之後再來試試在遊戲中加入 Server-Side 的計數 (Counting / Scoring) 之類的小功能吧~

概念:

從定義上來說, 每個 Photon Plugin 會有著唯一的識別名稱, 外掛的程式內容則是要實作出對應於我們想處理的事件的 callback (例如: OnJoin, OnLeave ). 我們所寫的外掛程式會編譯成 dll 組件檔, 而後再將 dll 組件檔放置到我們的 Photon Server 上, 或是上傳到 Enterprise Cloud 裡.

每次, 當 Client 要求建立個新的 Room 時, Photon Server 會自動載入有相關聯的設定好的 dll 組件檔. 而觸發這個外掛生成的 Room , 在外掛的程式中會稱之為 Host, 生成實體之後的外掛程式就可以直接存取這個 Host 裡的參數, 或是做其它的伺服器端的運算, 或是與外部 WebServices 連結等等; 
並且, Host 與這個外掛實體 Plugin 有著同樣的程式生命週期(lifecycle), 它們是一對一的關係, 也就是 Host (Room) 結束時, 它的相關外掛實體 plguin 也會結束.

基本流程:

想加入客製化的服務邏輯, 必須把我們的程式碼掛接注入(inject)到 Photon Server 預先定義好的接口 (Hooks). 目前 Photon Server 對於 Game Server 的掛接種類, 僅有與 Room 的相關事件所觸發的接口才有定義與支援 (例如: OpJoinRoom).

雖然不支援自定接口, 但我們可以用 RaiseEvent+Broadcast 的方式做到自定事件的處理機制. Photon Server 對於外掛串接起來的機制, 順序如下面六個步驟:

  1. Intercept the hook call -- 攔截 hook 呼叫
    當 Callback 被觸發時, Host 會先把控制權轉移到實體 plugins 上.
  2. Alter call info -- 修改呼叫資訊, (可為選項)
    在真正要執行 hook call 前, 先存取並修改由 client/server 所送出的請求資料(request).
  3. Inject custom code -- 注入串接自定的一些程式功能, (可為選項)
    在執行 hook call 前, 先與 Host 做一些處理(例如, 發出個 HTTP request, 查詢 room/actor 的情況, 設定計數值 等等).
  4. Process hook call -- 處理原本的hook 呼叫
    真正決定要如何處理執行這個請求的地方. 詳細可再參考 “ICallInfo Processing Methods”
  5. Inject custom code -- 再次注入串接自定的一些程式功能, (可為選項)
    執行後, 原本由 client/server 所送出的請求會成為 “as read-only”. 但在處理執行後, plugin 依然可以跟 Host 互相聯絡互動, 存取更改資料.
  6. Return -- 返回, 結束
    Plugin 把控制權返還給 Host, 結束此次工作.

在 Photon Server 4 SDK 中, 有個 Webhooks 1.2 的原始碼, 它本身即是一個很好的 Photon Plugins 範例, 有興趣的話也可以先行研究研究, 我們稍後也會作一篇 WebHooks 的使用介紹 ~ 👍

最小的外掛 (Minimal Plugin)

外掛的元素, 分成三部份, 就是 Plugin 本身, 生成外掛的 Factory, 以及設定在 Photon Server 上的配置資訊; 所以我們先來看一下最小的外掛長什麼樣子, 之後才好一步一步的擴大它的機能.

(1) The Plugin 本身

最簡單而且也是最建議的作成 plugins 的方式就是擴展 PluginBase Class, 這樣會比直接繼承並實作所有的 IGamePlugin 的介面還來得好. 而後我們就可以只要覆寫我們想要的某一項功能就好.
而最小的 Plugin 的寫法, 就只要覆寫 PluginBase.Name 的值就好. 而它也是這個 Plugin 的識別值.

註: Default 不應該拿來當 plugin 的名字.

(2) The Factory 功能

Photon Plugin 的生成, 用的是軟體設計模式中的 factory design pattern, 所以 Plugins 可以依 Client 在建立 Room 時, 設定要載入的外掛名稱字串, factory 就會建立生成個相對應的 Plugin 的實體.

Factory Class 是同屬於 plugins 組件實作的一部份, 所以它們的 namespace 會是相同的. 對每個 Room 而言, factory 就是負責生成相對應的 plugin instance 外掛實例而存在的.
在進入 IPluginFactory.Create 時, 觸發這個外掛生成機制的 Room 稱之為Host, 會是以 IPluginHost 的型別傳進來, 我們便可透過它來存取 Room 的資料以及功能呼叫.
隨後我們再呼叫 IGamePlugin.SetupInstance 時, 也會把這個 Host 值傳過去, 這樣前後用的都是同樣的參考, 即都是針對同樣的 Room 在做服務.

為了簡單起見, 下面的 PluginFactory 會直接回傳 CustomPlugin 的一個實體, 先忽略檢查 Client 端所要求的 plugin 名稱.

(3) Photon Server Configuration 配置 (Photon Server)

把下面的 XML node 加到我們的 GameServer Application 裡的 Photon.LoadBalancing.dll.config 的檔案中. 我們可以很簡單的藉由改變 <PluginSettings> 元素的 Enabled 的值來啟用或是不啟用這個外掛設定.

其中的 <Plugin> 裡的屬性值 AssemblyName, Version, Type 都是必須填的, 其它的屬性參數則是可以依程式要求而附加上去.
* AssemblyName : 外掛的 dll 組件檔案名稱
* Version : 這個外掛的版本.
* Type : 關於生成這個外掛的 PluginFactory 的全名.

好了, 上面這些, 就是最簡單的 Photon Plugin 建立與設定, 現在對於在 Photon Server 外掛個程式來達到機能擴展, 應該有了初步的瞭解吧, 或是已經暈頭轉向了呢?! 😀👻🎯

那麼, 接下來我們趕快動手做個小型的功能: Server-Side Counting (Scoring) , 是做在伺服器端的計數功能, 實際感受一下 Photon Plugins 的機能追加的方便與實用性吧~

Server-Side 實作

這裡用的是 Visual Studio 中的製作類別庫(.dll)的專案功能, 我們直接建立一個叫 RaiseEventTestPlugin 的專案吧~

Visual Studio 中, 製作個類別庫(DLL)的專案

如上圖, 我們選用的 framework 版本是 .NET Framework 4 ; 專案的位置路徑倒是可以隨意, 只要能知道最後產出的 .dll 在哪裡就好了.

上面的程式碼就是我們的外掛程式本身, 我們先叫它 RaiseEventTestPlugin 就好, 在這個外掛中, 我們專注在接收從 Client 發送過來的特定事件, 也就是我們在這個外掛中, 所要攔截的是 OnRaiseEvent.

注意這裡面有個變數 CallsCount, 會在外掛程式生成時初始化為 0, 當每次在這個 Room 裡面的 Client 呼叫 PhotonNetwork.RaiseEvent, 而且傳遞事件碼 EventCode = 1 時, 伺服器端的這個外掛程式 OnRaiseEvent 會執行起來, 而 CallsCount 就會計數一次, 接著會把 CallsCount 的值傳遞給所有在這個 Room 裡面的 Client 端去做後續處理. 這種方式, 也是一種 Client 與Server 的雙向溝通, 我們可以利用像這樣的機制去發展其它的功能~

上面的程式碼是我們的 Factory, 為了程式碼不要太過複雜, 我們一樣先略過 pluginName 的值, 直接生出一個 RaiseEventTestPlugin 實體來~

如上圖, 我們的 Photon Server Plugin 專案就這麼簡單, 二個 C# 檔案就好, 建立好之後, 我們就可以選 建置方案 的功能來生成 dll 組件檔了, 產出的檔案群會是在專案的 bin\release 或是 bin\debug 裡面, 再將所有檔案其複製到 Photon Server 目錄所在的 deploy\Plugins\MyTestPlugins\bin 裡, 如下:

Photon Server → deploy → Plugins → MyTestPlugins → bin 裡的組件們

而為了要讓 Photon Server 能找到這個外掛組件檔, 我們就要在 PhotonServer 目錄下的 deploy\Loadbalancing\GameServer\bin 裡找一個檔案叫 Photon.LoadBalancing.dll.config, 然後把 PluginSettings 設成 Enabled=”true”, 再把裡面的值填入我們的外掛組件相關設定, 就像下圖黃色的字串所示~

PhotonServer 目錄中的 deploy\Loadbalancing\GameServer\bin 的設定檔

因為Photon Server 的設計中, 一個 Photon Server Application 只能有相對應的一個 Plugin, 所以我們得先把它 config 設定檔原有的 WebHooksPlugin 1.2 給註解掉(上圖灰色字串部份), 然後換上我們建立的 dll 組件檔 (上圖黃色字串部份). 並注意要把 PluginSettingsEnabled 設定成 true 唷!

啟動 Photon Server

上面的三個部份 (plugin 本體, factory 生成, config 設定) 都建立完成後, 就可以啟動我們的 Photon Server 了, 還記得嗎, 把 PhotonControl.exe 執行起來後, IP 設定好, 再選 LoadBalancing (MyCloud) → Start as Application, 如此, 就可以在 Game Server 的 Log 檔中看到如下方藍色部份, 我們的 RaiseEventTestPlugin 組件檔成功的被 Photon Server 找到並安全地載入了.

Game Server 裡的 DLL 已找到並載入完成

目前, plugin 載入完成只是先行載入 plugin 的相關資訊到記憶體中, 而後在每次新的 Room 被建立時才會一併的被 Game Server 透過 factory 的生成機制, 真正的生成一個專門服務的 Plugin 實體出來.

更進階的用法, 還可以依 Client 端在建立 Room 時透過名稱字串作為選項傳給 Photon Server, 就可以選擇想要生出什麼樣的伺服器端的機能追加服務唷! 這種機制是不是很棒呢 ! 👍😀👏🏻

我們休息一下, 讓腦袋想一想這篇的流程與運用後, 再接著看 Client-Side 的實作部份, 是怎麼與 Server-Side 的東西串起來運作的呢~ Stay Tuned ! 🎯