수정중인 페이지 입니다.
server | v5 switch to v4  

What's New

목차

New Separate Configuration File

Plugins are now configured separately on their own file. The file is "plugin.config" in the same folder as the previously used configuration file. The configuration syntax is still the same though.

메인 화면으로

New Logging Interface

We have added new improved logging interface accessible from plugins.

Old Deprecated Way

In v4 we used to call the IPluginHost logging methods directly.

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");

메인 화면으로

New Way

Now instead of calling the IPluginHost logging methods, you should create a new IPluginLogger object per plugin instance and use it to log everything from your plugin:

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");

When setting the name of the logger (passed to IPluginHost.CreateLogger(loggerName)), we prepend the logger name with Plugin.. For example, if you set MyPlugin.MyClass, it will be logged as Plugin.MyPlugin.MyClass. The code snippet above will generate these log entries in case the log level is set to maximum level:

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

메인 화면으로

Improved Plugin Hook Processing

The ICallInfo is the argument we get inside the plugin hook methods which represents the context of the method call. This context object needs to be processed within a limited time after the hook call to guarantee the normal workflow inside the room.

메인 화면으로

Anonymous Functions

If you were using anonymous functions (lambda expression or anonymous method) as callbacks for HTTP requests (see examples below), you can keep doing so.

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),

It is recommended however to replace obsolete HTTP method calls:

PluginHost.HttpRequest(request);

with the new overload method that takes two arguments:

PluginHost.HttpRequest(request, callInfo);

메인 화면으로

Old Way

Previously we had to use a workaround to get the ICallInfo object inside the HTTP response callback. We used the custom optional HttpRequest.UserState to store the ICallInfo object.

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);

메인 화면으로

New Way

We now pass the plugin hook context (ICallInfo) inside a new PluginHost.HttpRequest overload method. Then we can retrieve it from the 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);

메인 화면으로

New Async Behaviour

Old Deprecated Way

In v4, you had to call ICallInfo.Defer for asynchronous HTTP requests or timers when you want to delay processing for a plugin hook.

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();
}

메인 화면으로

New Way

Now ICallInfo.Defer is removed and instead we internally defer the hook context call for you automatically if:

  • you are sending a HttpRequest with httpRequest.Async = true
  • you are creating timers

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);
}

메인 화면으로

Fibers Access

A fiber is a list of tasks which are executed one by one sequentially in FIFO manner. This does not mean that they are executed in one thread. Actually, they are executed in many threads, but one by one. So the first task may be executed in thread A, when it finishes, the second task may be executed in thread B and so on. But at every given moment just one thread accesses room data. If many fibers try to access the same data we use locks.

To get a room fiber from plugins:

IPluginFiber fiber = this.PluginHost.GetRoomFiber();

메인 화면으로

Enqueue

You could enqueue an action to the room's fiber using:

PluginHost.Enqueue(someAction);

Or you could use the fiber you got above:

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

메인 화면으로

Timers

IPluginFiber share same methods as IPluginHost except it does not require ICallInfo and could be executed with thread safety outside hooks context:

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);

메인 화면으로

IFactoryHost

There is also a new support for lock-less processing outside rooms, that can be used for application-wide services (ie. anticheat service, broadcasting, etc.). This processing is built fully using IPluginFiber mechanism. Plugin fibers are fibers similar to room fibers, but operating outside rooms. You can Enqueue to them from anywhere and operations are executed based on a server time. To get a IPluginFiber, you need an instance of IFactoryHost, which is provided by IFactoryPlugin2. IFactoryPlugin2 in an interface extending standard IPluginFactory with one method:

void SetFactoryHost(IFactoryHost factoryHost, FactoryParams factoryParams);

This method is called once during the plugin lifetime, right after the plugin factory is instantiated and provides an IFactoryHost instance, which provides plugin fibers and plugin logger:

IPluginFiber CreateFiber();

IPluginLogger CreateLogger(string loggerName);

Fibers created by FactoryHost can be used both outside rooms (i.e. when creating a new room) and inside rooms to enqueue or timer operations outside of a room context. Similarly, loggers created by FactoryHost are to be used inside these operations.

메인 화면으로

New Sync Behaviour

For "synchronous HTTP requests" (HttpRequest sent with httpRequest.Async = false) we now pause the room fiber (i.e. no room message will be processed) until you get the response or a timeout.

The old IPluginHost.HttpRequest method is blocking: it does not return until the HTTP response is received or a timeout. The main issue with this approach is that the thread is blocked and the callback execution can be pushed to the queue of this thread. So, this callback cannot be executed, because the thread is blocked.. As a result we get a timeout. The main difference with the new approach is the non-blocking nature of the new IPluginHost.HttpRequest method. It will never block.. This means that the user code should be rewritten - both the plugin hook where the call is made and the HTTP request callback used need to be updated. The plugin hook should be aware that the new IPluginHost.HttpRequest call will return right after pushing request to HTTP queue. Inside the callback the ICallInfo object should be processed (call callInfo.Continue or Fail or Cancel to continue or abort operation) if not done yet. Now instead of blocking thread we block the room fiber. So, no new tasks will be processed until we get a response to our request or a timeout.

메인 화면으로

New Kick Reason

When you pass the new RemoveActorReason.GlobalBanned (2), the removed user will not be able to join any room until the authentication token expires and the user authenticates again. You can reject the user in custom authentication afterwards.

메인 화면으로

Updating AuthCookie

You can now update the secure authentication cookie per actor using this method:

void IActor.UpdateSecure(string key, object value)

메인 화면으로

Support For All Cache Operations

You can now execure all cache operations. For all cache operations that do not involve sending events to clients use the new methods:

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

메인 화면으로

More Flexible & Powerful HTTP Requests API

  • Added support for PUT Method.
  • You can pass custom certificates using HttpRequet.ClientCertificate.
  • You can set some restricted HTTP request headers using HttpRequest properties (Accept, ContentType).
  • You can get HTTP response headers using IHttpResponse.Headers.

기술문서 TOP으로 돌아가기