server | v5 switch to v4  

What's New

Contents

插件API版本升級

插件API組件(PhotonHivePlugin.dll)的版本從1.0.x更新到1.1.0。

這意味著,如果您使用Photon Server Plugins SDK(v5 RC1或更新版本)構建一個包含PhotonHivePlugin.dll 1.1.x的自定義插件,並想在您的伺服器上部署和運行它,伺服器二進制文件需要與該插件的API版本一致。

對於企業客戶來說,若他們的私有雲端需要至少更新到Photon Server v5 RC1。 如果你試圖將該插件上傳到舊的私有雲端中,在創建新房間時將會得到一個錯誤。

Back To Top

新的獨立的配置文件

插件現在被單獨配置在他們自己的文件中。 該文件是 "plugin.config",與之前使用的配置文件在同一個文件夾中。 但配置的語法仍然是相同的。

Back To Top

新的日誌界面

我們增加了新的改進的日誌界面,可以從插件中訪問。

舊的棄用方式

v4版中,我們曾經直接調用IPluginHost的日誌方法。

private IPluginHost pluginHost;

public bool SetupInstance(IPluginHost host, Dictionary<string, string> config, out string errorMsg)
{
    pluginHost = host;
}

///...

this.pluginHost.LogDebug("debug");
this.pluginHost.LogWarning("warning");
this.pluginHost.LogError("error");
this.pluginHost.LogFatal("fatal");

Back To Top

新方法

現在,您應該為每個插件實例創建一個新的IPluginLogger對象,用它來記錄來自您的插件一切,而不是調用IPluginHost日誌方法:

public const string PluginName = "MyPlugin";

private IPluginLogger pluginLogger;

public override bool SetupInstance(IPluginHost host, Dictionary<string, string> config, out string errorMsg)
{
    pluginLogger = host.CreateLogger(PluginName);
    // ...
}

// ...

this.pluginLogger.LogDebug("debug");
this.pluginLogger.LogWarning("warning");
this.pluginLogger.LogError("error");
this.pluginLogger.LogFatal("fatal");

當設置日誌記錄器的名稱(傳遞給IPluginHost.CreateLogger(loggerName))時,我們在日誌記錄器的名稱前加上Plugin.。 例如,如果您設置MyPlugin.MyClass,它將被記錄為Plugin.MyPlugin.MyClass。 上面的代碼片段將在日誌級別被設置為最大級別的情況下產生這些日誌條目:

2020-01-31 17:10:07,394 [1] DEBUG  Plugin.MyPLugin - debug
2020-01-31 17:10:07,901 [1] WARN  Plugin.MyPLugin - warning
2020-01-31 17:10:08,152 [1] ERROR  Plugin.MyPLugin - error
2020-01-31 17:10:08,724 [1] FATAL  Plugin.MyPLugin - fatal

Back To Top

更新鉤子插件

ICallInfo是我們在插件鉤子方法裡面得到的參數,代表了方法調用的上下文。 這個上下文對象需要在鉤子調用后的有限時間內被處理,以保証房間內的正常工作流程。

Back To Top

匿名函數

如果您以前使用匿名函數(lambda表達式或匿名方法)作為HTTP請求的回調(見下面的例子),您可以繼續這樣做。

Example of HTTP callback as anonymous method:

private void SendRequest(ICallInfo callInfo)
{
    HttpRequest request = new HttpRequest
    {
        Callback = delegate
                   {
                       ProcessCallIfNeeded(callInfo);
                   },

Example of HTTP callback as lambda expression:

private void SendRequest(ICallInfo callInfo)
{
    HttpRequest request = new HttpRequest()
    {
        Callback = (response, userObject) => ProcessCallIfNeeded(callInfo),

然而,建議替換過時的HTTP方法調用:

PluginHost.HttpRequest(request);

替換為新的重載方法,該方法需要兩個參數:

PluginHost.HttpRequest(request, callInfo);

Back To Top

老方法

以前我們不得不使用一個變通方法來獲取HTTP響應回調中的ICallInfo對象。 我們使用自定義的可選的HttpRequest.UserState來存儲ICallInfo對象。

Example:

private void CallbackOldLogic(IHttpResponse response, object userObject)
{
    ICallInfo callInfo = userObject as ICallInfo;
    ProcessCallIfNeeded(callInfo);
}

private void SendRequest(string url, ICallInfo callInfo)
{
    HttpRequest request = new HttpRequest
    {
        Url       = url,
        Callback  = CallbackOldLogic,
        UserState = callInfo
    }
    this.PluginHost.HttpRequest(request);

Back To Top

新方法

我們現在在一個新的PluginHost.HttpRequest重載方法中傳遞插件鉤子上下文(ICallInfo)。 然后我們可以從IHttpResponse中獲取。

Example:

private void CallbackNewLogic(IHttpResponse response, object userObject)
{
    ICallInfo callInfo = response.CallInfo;
    ProcessCall(callInfo);
}

private void SendRequest(string url, ICallInfo callInfo, object userObject = null)
{
    HttpRequest request = new HttpRequest
    {
        Url       = url,
        Callback  = CallbackNewLogic,
        UserState = userObject,
        CallInfo  = callInfo
    };
    this.PluginHost.HttpRequest(request, callInfo);

Back To Top

新的異步行為

舊的棄用方式

v4中,當您想為一個插件鉤子延遲處理時,必須為異步HTTP請求或計時器調用ICallInfo.Defer

Example with HTTP request:

private void SendRequest(string url, ICallInfo callInfo)
{
    HttpRequest request = new HttpRequest
    {
        Url       = url,
        Async     = true,
        Callback  = delegate { ProcessCallIfNeeded(callInfo); },
    };
    this.PluginHost.HttpRequest(request);
    callInfo.Defer();
}

Example with timer:

private void DelayLogic(ICallInfo callInfo, Action logic, int delayMs)
{
    PluginHost.CreateOneTimeTimer(
                () =>
                {
                    logic();
                    ProcessCallIfNeeded(callInfo);
                },
                delayMs);
    callInfo.Defer();
}

Back To Top

新方法

現在ICallInfo.Defer被刪除了,取而代之的是我們在內部為您自動推遲鉤子上下文的調用,如果:

  • 您正在發送一個HttpRequest,並且httpRequest.Async = true
  • 您正在創建定時器

Example with HTTP request:

private void SendRequest(string url, ICallInfo callInfo)
{
    HttpRequest request = new HttpRequest
    {
        Url       = url,
        Async     = true,
        Callback  = delegate { ProcessCallIfNeeded(callInfo); },
        CallInfo  = callInfo
    };
    this.PluginHost.HttpRequest(callInfo, request);
}

Example with timer:

private void DelayLogic(ICallInfo callInfo, Action logic, int delayMs)
{
    PluginHost.CreateOneTimeTimer(
                callInfo,
                () =>
                {
                    logic();
                    ProcessCallIfNeeded(callInfo);
                },
                delayMs);
}

Back To Top

Fibers訪問

Fiber是一個任務列表,它以先進先出的方式一個接一個地被執行。 這並不意味著它們是在一個線程中執行的。 實際上,它們是在許多線程中執行的,但都是一個接一個的。 因此,第一個任務可能在線程A中執行,當它完成后,第二個任務可能在線程B中執行,以此類推。 但在每個給定的時刻,只有一個線程訪問房間數據。 如果許多Fiber試圖訪問相同的數據,我們就使用鎖。

要從插件中獲取房間Fiber。

IPluginFiber fiber = this.PluginHost.GetRoomFiber();

Back To Top

Enqueue

你可以用以下方法向房間的Fiber發出一個動作的queue。

PluginHost.Enqueue(someAction);

或者你可以使用上面得到的Fiber:

int r = fiber.Enqueue(someAction);
if (r == 0)
{
   // success
}
else
{
   // error
}

Back To Top

計時器

IPluginFiberIPluginHost共享相同的方法,只是它不需要ICallInfo,可以在鉤子上下文之外以線程安全方式執行:

object timer = fiber.CreateOneTimeTimer(someAction, executeAfterMs);
\\ ...
fiber.StopTimer(timer); // in case you want to abort before delay is expired and someAction is executed
object timer = fiber.CreateTimer(someAction, firstExecutionAfterMs, repeatEachMs);
fiber.StopTimer(timer);

Back To Top

IFactoryHost

還有一個新的支持房間外的無鎖處理,可用於整個應用的服務(即anticheat服務,廣播等)。 這種處理方式完全使用 "IPluginFiber "機制建立。 插件Fiber是類似於房間Fiber,但在房間外操作。 您可以從任何地方 "Enqueue "到它們,操作是基於伺服器時間執行的。 要獲得一個 "IPluginFiber",您需要一個 "IFactoryHost "的實例,它是由 "IFactoryPlugin2 "提供。 IFactoryPlugin2是一個擴展標准IPluginFactory的接口,有一個方法:

void SetFactoryHost(IFactoryHost factoryHost, FactoryParams factoryParams);

這個方法在插件的生命周期內被調用一次,就在插件被實例化之后,並提供一個`IFactoryHost'實例,它提供了插件Fiber和插件記錄器:

IPluginFiber CreateFiber();

IPluginLogger CreateLogger(string loggerName);

由 "FactoryHost "創建的Fibers可以在房間外(即在創建新房間時)和房間內使用,以便在房間上下文外進行enqueue或定時操作。 同樣的,由FactoryHost創建的記錄器也要在這些操作裡面使用。

Back To Top

新的同步行為

對於 "同步HTTP請求"(HttpRequesthttpRequest.Async = false的情況下發送),我們現在暫停房間Fiber(即沒有房間信息將被處理),直您得到響應或超時。

舊的IPluginHost.HttpRequest方法是會被阻塞的:它不會返回,直到收到HTTP響應或超時。 這種方法的主要問題是,線程被阻塞,回調的執行可以被推到這個線程的隊列中。 所以,這個回調不能被執行,因為線程被阻塞了。 結果,我們得到了一個超時。 新方法的主要區別是新的IPluginHost.HttpRequest方法的非阻塞性質。 它將永遠不會阻塞。 這意味著用戶的代碼應該被重寫--調用的插件鉤子和使用的HTTP請求回調都需要被更新。 插件鉤子應該知道,新的IPluginHost.HttpRequest調用將在把請求推送到HTTP隊列後立即返回。 在回調中,如果還沒有完成,ICallInfo對象應該被處理(調用callInfo.ContinueFailCancel來繼續或中止操作)。 現在,我們不是阻塞線程,而是阻塞房間Fiber。 所以,沒有新的任務會被處理,直到我們得到一個對我們請求的回應或超時。

Back To Top

全局禁止

當你通過新的RemoveActorReason.GlobalBanned(2)時,主服務器將在內存中保留UserId,最多為認証令牌到期時間的兩倍(默認為2*1小時),所以全局禁止的用戶在這段時間內不能加入任何新房間。 你可以在之後的自定義認証中拒絕該用戶。 此外,這對當前房間的影響與RemoveActorReason.Banned相同:UserId將被添加到房間狀態的ExcludedUsers中,用戶不能重新加入。

Back To Top

更新AuthCookie

現在你可以用這個方法更新每個角色的安全認証cookie。

void IActor.UpdateSecure(string key, object value)

Back To Top

支持所有的緩存操作

你現在可以執行所有的緩存操作了。 對於所有不涉及向客戶端發送事件的緩存操作,使用新方法:

bool IPluginHost.ExecuteCacheOperation(CacheOp operation, out string errorMsg)

Back To Top

更加靈活和強大的HTTP取得API

  • 增加了對PUT方法的支持。 其他HTTP也應該被支持。
  • 你可以使用HttpRequest屬性(Accept, ContentType)設置一些限制性的HTTP獲得標頭檔。
  • 你可以使用IHttpResponse.Headers獲得HTTP響應標頭檔信息。

To Document Top