Difference between revisions of "Server Plugins"

From Legends of Aria Admin and Modding Wiki
Jump to: navigation, search
m (async threading cs code)
 
(10 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
== Introduction ==
 
It is possible to write plugins in C# and use these from Lua.
 
It is possible to write plugins in C# and use these from Lua.
  
Line 12: Line 13:
 
  \build\base\plugins
 
  \build\base\plugins
  
If you use <tt>LuaTableProxy</tt> types to return Lua tables your plugin also must be linked against Shardsserver.exe, which means you probably have to update it a lot.  
+
== API Overview ==
 +
=== Synchronous Requests ===
 +
Server Plugins can take requests from Lua and answer them directly, like any other synchronous function.
 +
Your Lua script will wait until the call returns. The engine function for that is
  
Tutorial Download Link:
+
Lua Code:
 +
SendRequestToPlugin("TutorialPlugin", "GimmeFive")
  
https://www.legendsofaria.com/downloads/u3d2bdea/LoA_TutorialPlugin.zip
+
Where "TutorialPlugin" is the name of the plugin as returned by the function <b><tt>string GetPluginName();</tt></b>
 +
 
 +
in the IPlugin interface you implemented and "GimmeFive" is just a piece of data.
 +
 
 +
You can add any number or type of arguments to the request after specifying the plugin name.
 +
 
 +
These arguments find their way into the IPlugin function:
 +
object[] DirectRequestFromLua(object[] _requestData);
 +
Your arguments end up as elements of the <tt>_requestData</tt> array. In our example the string "GimmeFive" ends up in <tt>_requestData[0]</tt>
 +
 
 +
var gimme = new object[5];
 +
gimme[0] = 1; // These are all elements in a LuaArray once they arrive at the Lua Side
 +
gimme[1] = 4;
 +
gimme[2] = 9;
 +
gimme[3] = 16;
 +
gimme[4] = 25;
 +
return gimme; // This is what's being returned to Lua.
 +
 
 +
// Returned always is an array with a list of data members, where index 0 in C# translates to index 1 in Lua, since LuaTables begin with element 1.
 +
// So - in the example Lua will receive a table: {1, 4, 9, 16, 25} as result of the function call
 +
// Synchronous calls with "SendRequestToPlugin" always return a table if there are multiple return values.
 +
// Return value MUST be an array of type object which gets converted into a LuaTable.
 +
// If the returned array has only one member, a single value is returned, which is NOT wrapped into a Lua table !!!
 +
 
 +
var result = new object[1];
 +
result0] = "I am the result";
 +
return result;
 +
 
 +
// in this example the result returned to Lua would be the string "I am the result" and NOT a table like {"I am the result"}
 +
 
 +
=== Asynchronous Requests ===
 +
Asynchronous requests send a message to the plugin along with some data (or not)
 +
 
 +
Important: Beware to use thread when using async method. Else your simulation will be stuck when you call a blocking function.
 +
 
 +
Lua Code
 +
SendMessageToPlugin("TutorialPlugin", "GieffTable", this.Id, "TableHandler")
 +
 
 +
In this example a Message is sent to the "TutorialPlugin".
 +
 
 +
The message is received on the plugin side by
 +
void MessageFromLua(LuaRequest _request);
 +
The parameter <tt>_request</tt> is of type <tt>LuaRequest</tt>
 +
public class LuaRequest
 +
  {
 +
    public string EventId;
 +
    public object[] Data;
 +
  }
 +
The first parameter in the Lua call above: "GieffTable" goes into the EventId: _request.EventId = "GieffTable"
 +
 
 +
All the other parameters: this.Id and "TableHandler" go into _request.Data[]. _request.Data[0] = this.Id and _request.Data[1] = "TableHandler".
 +
 
 +
Inside the plugin <tt>void MessageFromLua(LuaRequest _request);</tt> uses that data to form its response:
 +
ulong  myResponseTargetId = (ulong)(double) _request.Data[0];
 +
string myResponseEventId  =        (string) _request.Data[1];
 +
Here the plugin extracts the additional data to use it for its response to Lua. Mark the double conversion of the Object Id <tt>(ulong)(double)</tt> !
 +
 
 +
Later the plugin creates a table:
 +
// prepare table
 +
LuaTableProxy mTable = new LuaTableProxy();
 +
// Add to the HashTable Part
 +
mTable.TableData.Add(LuaTableItem.Create("TheAnswer"),  LuaTableItem.Create(42));
 +
mTable.TableData.Add(LuaTableItem.Create("TheQuestion"), LuaTableItem.Create("What was it again ... ?"));
 +
// Add to the Array Part
 +
mTable.TableData.Add(LuaTableItem.Create(1), LuaTableItem.Create("First  Array Element"));
 +
mTable.TableData.Add(LuaTableItem.Create(2), LuaTableItem.Create("Second Array Element"));
 +
mTable.TableData.Add(LuaTableItem.Create(3), LuaTableItem.Create("Frederick the Great"));
 +
Every time you want to return a table to Lua you MUST use a <tt>LuaTableProxy</tt> type. This has nothing to do with the automated packing of multiple return values into a table in synchronous calls as shown above. If you want to build your own LuaTables as return values, you must use the LuaTableProxy type. The code above demonstrates how to add stuff to it.
 +
 
 +
Note that <tt>LuaTableItem.Create</tt> can be used to create keys, values and idices for tables which are put into the table with <tt>LuaTableProxy.TableData.Add</tt> as key|index - value pairs.
 +
 
 +
Back to Lua we send a
 +
public class LuaResponse
 +
  {
 +
    public string EventId;
 +
    public ulong DestinationObjectId;
 +
    public object[] Data;
 +
  }
 +
 
 +
// This what we will send back to Lua
 +
LuaResponse tableResponse = new LuaResponse();
 +
tableResponse.EventId = myResponseEventId; // this is the string used to mark the Message Event that is arriving on the Lua side, so it finds it's corresponding EventHandler.
 +
tableResponse.DestinationObjectId = myResponseTargetId; // This is the converted "this.Id" from above which tells which object will receive the message we are going to send
 +
// Response payload
 +
tableResponse.Data = new object[1];
 +
tableResponse.Data[0] = mTable; // The table we created above
 +
writeQueue.Enqueue(tableResponse);  //This sends the message with message EventId tableResponse.EventId to object tableResponse.DestinationObjectId with payload tableResponse.Data[].
 +
 
 +
Note, that the writeQueue concurrent Queue is received as an argument in the Init function of the IPlugin Interface. We store it there for later use, like here for the asynchronous response.
 +
 
 +
On the Lua side this response arrives in a Message Event which gets processed in the following EventHandler:
 +
 
 +
Lua Code
 +
RegisterEventHandler(EventType.Message, "TableHandler", function(resultTable)
 +
    print("Table Handler: ")
 +
    print(DumpTable(resultTable)) -- prints the table we created in the plugin and which we sent back here to Lua
 +
    print("--- Done ---")
 +
end)
 +
 
 +
If you want to create a thread so your simulation won't be stuck when using blocking functions:
 +
C# Code
 +
public void MessageFromLua(LuaRequest _request)
 +
        {
 +
            Thread t = new Thread(async () =>
 +
            {
 +
                await MessageFromLuaAsync(_request);
 +
            });
 +
            t.Name = "MessageFromLua";
 +
            t.Start();
 +
        }
 +
 
 +
=== Returning Lua Tables ===
 +
If you use <tt>LuaTableProxy</tt> types to return Lua tables your plugin also must be linked against Shardsserver.exe, which means you probably have to update it a lot.
 +
 
 +
=== Helper Functions ===
 +
TBD
 +
 
 +
== IPlugin ==
 +
Every plugin must implement this interface.
 +
 
 +
Check out the Tutorial Plugin to see how it is done.
 +
<tt>
 +
using System.Collections.Concurrent;
 +
namespace Gameplay.Plugin
 +
{
 +
    public class LuaRequest
 +
    {
 +
        public string EventId;
 +
        public object[] Data;
 +
    }
 +
 +
    public class LuaResponse
 +
    {
 +
        public string EventId;
 +
        public ulong DestinationObjectId;
 +
        public object[] Data;
 +
    }
 +
 +
    public interface IPlugin
 +
    {
 +
        void Init(ConcurrentQueue<LuaResponse> _responseQueue);
 +
        void Start();
 +
        bool IsReadyForServerStartup();
 +
        void Shutdown();
 +
        bool IsShutDown();
 +
        void MessageFromLua(LuaRequest _request);
 +
        object[] DirectRequestFromLua(object[] _requestData);
 +
        string GetPluginName();
 +
    }
 +
}
 +
</tt>
 +
 
 +
== Related Engine Functions ==
 +
These are the engine functions which can be used to interact with your plugin from Lua.
 +
 
 +
There is example Lua code in the Tutorial plugin you can use.
 +
<tt>
 +
SendMessageToPlugin(...)
 +
Description: Sends a message to a C# gameplay plugin.
 +
Params:
 +
    (string) - Plug-in name to send to.
 +
    (string) - EventId name.
 +
    (...) One or more object arguments to be passed to the plug-in.
 +
Returns:
 +
    None
 +
----------------------------------
 +
 
 +
SendRequestToPlugin(...)
 +
Description: Sends a direct request to a C# gameplay plugin.
 +
Params:
 +
    (string) - Plug-in name to send to.
 +
    (...) One or more object arguments to be passed to the plug-in.
 +
Returns:
 +
    A single value, or an array of values.
 +
----------------------------------
 +
 
 +
IsPluginLoaded(...)
 +
Description: Ask if a c# gameplay plug-in has been loaded or not
 +
Params:
 +
    (string) - Plug-in name to send to.
 +
Returns:
 +
    true or false
 +
----------------------------------
 +
</tt>
 +
 
 +
== Tutorial Plugin Download Link ==
 +
 
 +
https://adminwiki.legendsofaria.com/downloads/u3d2bdea/LoA_TutorialPlugin.zip
  
 
[[Category:Systems]][[Category:Unsupported]][[Category:Drafts]]
 
[[Category:Systems]][[Category:Unsupported]][[Category:Drafts]]

Latest revision as of 18:49, 9 February 2019

Introduction

It is possible to write plugins in C# and use these from Lua.

These plugins must implement a certain Interface to be usable.

In the moment there is no extensive written documentation, but there exists a tutorial plugin which is well commented and which can be used as a starting point.

CAVEAT: 
Server plugins are officially not supported and you can seriously shoot yourself in the foot with them. 
You need to know what you are doing to really use them.

Compiled plugins need to be placed into

\build\base\plugins

API Overview

Synchronous Requests

Server Plugins can take requests from Lua and answer them directly, like any other synchronous function. Your Lua script will wait until the call returns. The engine function for that is

Lua Code:
SendRequestToPlugin("TutorialPlugin", "GimmeFive")

Where "TutorialPlugin" is the name of the plugin as returned by the function string GetPluginName();

in the IPlugin interface you implemented and "GimmeFive" is just a piece of data.

You can add any number or type of arguments to the request after specifying the plugin name.

These arguments find their way into the IPlugin function:

object[] DirectRequestFromLua(object[] _requestData);

Your arguments end up as elements of the _requestData array. In our example the string "GimmeFive" ends up in _requestData[0]

var gimme = new object[5];
gimme[0] = 1; // These are all elements in a LuaArray once they arrive at the Lua Side
gimme[1] = 4;
gimme[2] = 9;
gimme[3] = 16;
gimme[4] = 25;
return gimme; // This is what's being returned to Lua. 
// Returned always is an array with a list of data members, where index 0 in C# translates to index 1 in Lua, since LuaTables begin with element 1.
// So - in the example Lua will receive a table: {1, 4, 9, 16, 25} as result of the function call
// Synchronous calls with "SendRequestToPlugin" always return a table if there are multiple return values.
// Return value MUST be an array of type object which gets converted into a LuaTable.
// If the returned array has only one member, a single value is returned, which is NOT wrapped into a Lua table !!!
var result = new object[1];
result0] = "I am the result";
return result;
// in this example the result returned to Lua would be the string "I am the result" and NOT a table like {"I am the result"}

Asynchronous Requests

Asynchronous requests send a message to the plugin along with some data (or not)

Important: Beware to use thread when using async method. Else your simulation will be stuck when you call a blocking function.

Lua Code
SendMessageToPlugin("TutorialPlugin", "GieffTable", this.Id, "TableHandler")

In this example a Message is sent to the "TutorialPlugin".

The message is received on the plugin side by

void MessageFromLua(LuaRequest _request);

The parameter _request is of type LuaRequest

public class LuaRequest
  {
    public string EventId;
    public object[] Data;
  }

The first parameter in the Lua call above: "GieffTable" goes into the EventId: _request.EventId = "GieffTable"

All the other parameters: this.Id and "TableHandler" go into _request.Data[]. _request.Data[0] = this.Id and _request.Data[1] = "TableHandler".

Inside the plugin void MessageFromLua(LuaRequest _request); uses that data to form its response:

ulong  myResponseTargetId = (ulong)(double) _request.Data[0];
string myResponseEventId  =        (string) _request.Data[1];

Here the plugin extracts the additional data to use it for its response to Lua. Mark the double conversion of the Object Id (ulong)(double) !

Later the plugin creates a table:

// prepare table
LuaTableProxy mTable = new LuaTableProxy();
// Add to the HashTable Part
mTable.TableData.Add(LuaTableItem.Create("TheAnswer"),   LuaTableItem.Create(42));
mTable.TableData.Add(LuaTableItem.Create("TheQuestion"), LuaTableItem.Create("What was it again ... ?"));
// Add to the Array Part
mTable.TableData.Add(LuaTableItem.Create(1), LuaTableItem.Create("First  Array Element"));
mTable.TableData.Add(LuaTableItem.Create(2), LuaTableItem.Create("Second Array Element"));
mTable.TableData.Add(LuaTableItem.Create(3), LuaTableItem.Create("Frederick the Great"));

Every time you want to return a table to Lua you MUST use a LuaTableProxy type. This has nothing to do with the automated packing of multiple return values into a table in synchronous calls as shown above. If you want to build your own LuaTables as return values, you must use the LuaTableProxy type. The code above demonstrates how to add stuff to it.

Note that LuaTableItem.Create can be used to create keys, values and idices for tables which are put into the table with LuaTableProxy.TableData.Add as key|index - value pairs.

Back to Lua we send a

public class LuaResponse
 {
   public string EventId;
   public ulong DestinationObjectId;
   public object[] Data;
 }
// This what we will send back to Lua
LuaResponse tableResponse = new LuaResponse();
tableResponse.EventId = myResponseEventId; // this is the string used to mark the Message Event that is arriving on the Lua side, so it finds it's corresponding EventHandler.
tableResponse.DestinationObjectId = myResponseTargetId; // This is the converted "this.Id" from above which tells which object will receive the message we are going to send
// Response payload
tableResponse.Data = new object[1];
tableResponse.Data[0] = mTable; // The table we created above
writeQueue.Enqueue(tableResponse);  //This sends the message with message EventId tableResponse.EventId to object tableResponse.DestinationObjectId with payload tableResponse.Data[].

Note, that the writeQueue concurrent Queue is received as an argument in the Init function of the IPlugin Interface. We store it there for later use, like here for the asynchronous response.

On the Lua side this response arrives in a Message Event which gets processed in the following EventHandler:

Lua Code
RegisterEventHandler(EventType.Message, "TableHandler", function(resultTable)
   print("Table Handler: ")
   print(DumpTable(resultTable)) -- prints the table we created in the plugin and which we sent back here to Lua
   print("--- Done ---")
end)

If you want to create a thread so your simulation won't be stuck when using blocking functions:

C# Code
public void MessageFromLua(LuaRequest _request)
       {
           Thread t = new Thread(async () =>
           {
               await MessageFromLuaAsync(_request);
           });
           t.Name = "MessageFromLua";
           t.Start();
       }

Returning Lua Tables

If you use LuaTableProxy types to return Lua tables your plugin also must be linked against Shardsserver.exe, which means you probably have to update it a lot.

Helper Functions

TBD

IPlugin

Every plugin must implement this interface.

Check out the Tutorial Plugin to see how it is done.

using System.Collections.Concurrent;
namespace Gameplay.Plugin
{
   public class LuaRequest
   {
       public string EventId;
       public object[] Data;
   }

   public class LuaResponse
   {
       public string EventId;
       public ulong DestinationObjectId;
       public object[] Data;
   }

   public interface IPlugin
   {
       void Init(ConcurrentQueue<LuaResponse> _responseQueue);
       void Start();
       bool IsReadyForServerStartup();
       void Shutdown();
       bool IsShutDown();
       void MessageFromLua(LuaRequest _request);
       object[] DirectRequestFromLua(object[] _requestData);
       string GetPluginName();
   }
}

Related Engine Functions

These are the engine functions which can be used to interact with your plugin from Lua.

There is example Lua code in the Tutorial plugin you can use.

SendMessageToPlugin(...)
Description: Sends a message to a C# gameplay plugin.
Params:
   (string) - Plug-in name to send to.
   (string) - EventId name.
   (...) One or more object arguments to be passed to the plug-in.
Returns:
   None
----------------------------------
SendRequestToPlugin(...)
Description: Sends a direct request to a C# gameplay plugin.
Params:
   (string) - Plug-in name to send to.
   (...) One or more object arguments to be passed to the plug-in.
Returns:
   A single value, or an array of values.
----------------------------------
IsPluginLoaded(...)
Description: Ask if a c# gameplay plug-in has been loaded or not
Params:
   (string) - Plug-in name to send to.
Returns:
   true or false
----------------------------------

Tutorial Plugin Download Link

https://adminwiki.legendsofaria.com/downloads/u3d2bdea/LoA_TutorialPlugin.zip