Custom Language Wrappers - GimpArm/AntRunner GitHub Wiki

Ant Runner is written in C# .Net and initially intended for Windows .Net developers. To allow other developers to compete a loader and wrapper model was created to act as a bridge. More Loaders and Wrappers can be added for other languages.

Ants in other languages do have a slight disadvantage due to the overhead of serialization and communication. Although it is extremely small, in the neighborhood of ~10ms less processing time. In a properly written ant this should not be a problem. The objects needed for communication are small and you should take care to serialize/deserialize them in the most efficient way possible in .Net and the target language.

Look to the already implemented examples in the source code.

IWrapperLoader

An interface to implement that provides information on the file extension to load and generates information needed to create a new instance of the ant wrapper.

public interface AntRunner.Interface.IWrapperLoader

Properties

Type Name Summary
String Extension Contains a string of the extension this wrapper can load.

Example

public string Extension => "js";

Methods

Type Name Summary
AssemblyLoaderData MakeLoaderData(String filename) Takes in the pull path to the ant file to be loaded and returns AssemblyLoaderData containing information on how to crate a new instance of the Loader.

Example

public AssemblyLoaderData MakeLoaderData(string filename)
{
    return new AssemblyLoaderData
    {
        AssemblyName = Assembly.GetExecutingAssembly().GetName(),
        TypeString = "AntRunner.Wrapper.Js.JsAnt",
        ConstructorParameters = new object[] { filename }
    };
}

AssemblyLoaderData

Object containing the information needed to load this file type.

public class AntRunner.Interface.AssemblyLoaderData

Properties

Type Name Summary
String TypeString String of the full name of the ant class.
AssemblyName AssemblyName AssemblyName information of the assembly containing the ant class.
Object[] ConstructorParameters Object array of the parameters needed for the ant constructor. Leave null for no parameters.

Loading

Wrappers are defined in the AntRunner.exe.config file. Add it to <Wrappers> section with a path that is relative to the Wrappers folder and the type which is full type name of the class which implements IWrapperLoader.

<wrapperLoader>
  <wrappers>
    <add path="php\AntRunner.Wrapper.Php.dll" type="AntRunner.Wrapper.Php.Loader" />
    <add path="js\AntRunner.Wrapper.Js.dll" type="AntRunner.Wrapper.Js.Loader" />
    <add path="python\AntRunner.Wrapper.Python.dll" type="AntRunner.Wrapper.Python.Loader" />
    <add path="ruby\AntRunner.Wrapper.Ruby.dll" type="AntRunner.Wrapper.Ruby.Loader" />
  </wrappers>
</wrapperLoader>

Ant Wrapper

This is just a class inheriting from AntRunner.Interface.Ant which initializes and manages the other language. You can (and probably should) implement IDisposable to clean up any processes which may be left orphaned.

Many scripting languages are intended to start and stop but this poses a problem of saving and restoring state information. Doing so would cause even more overhead and put the ant at a very large disadvantage, not to mention the added complexity. Therefore it is recommended to keep the environment running in a loop and wait for input. There are some languages with a server environment that can be started and communicated with, like Node.js, but most do not. I have had success by creating a process and controlling it via STDIN and STDOUT in a wrapper script.

You should package all required binaries with the wrapper. This includes reference libraries for .Net. The only exceptions are AntRunner.Interface and Newtonsoft.Json which are automatically loaded and should not be included. Some languages like Python and Ruby contain extremely large runtime environments. These should be included but can be compressed and decompressed when run. Although it is possible to require a configuration file with the ant that contains information about the runtime environment that is installed by the user.

Constructor

The constructor should initialize the target environment and keep it running. If there is an error it should throw an exception and clean up after itself. This can take as long as needed and it is better to initialize everything required here as to not cause slowdowns while the ant is running.

Name

After an ant is loaded the first piece of information required is the name. Since Name is a property, this can be requested from the environment on demand.

Flag

The flag needs to be a stream. It is recommended to read the file from the disk and return a MemoryStream to prevent locking which may prevent an ant from loading multiple times.

Initialize

Initialize is called once at the start of the game. The information is basic, 5 integer values, so a simple json array serialization is recommended. No result is needed so simply sending the command is enough.

Example

var initSerialized = $"[{mapWidth},{mapHeight},{(int)antColor},{startX},{startY}]";

Tick

The tick is run every turn and is sensitive to time constraints. It is also responsible For communicating the AntAction and setting it in the wrapper. In this case it is recommended to serialize the GameState as quickly as possible, send it to the environment and wait for a result of the AntAction which can be set to this.Action. The wrapper environment should also be responsible for resetting the Action to AntAction.Wait or 0 in the target environment. This will prevent a process that takes too long from accidentally moving the ant.

Example

var responseSerialized = state.Response == null ? "null" : $"{{\"Distance\":{state.Response.Distance},\"Item\":{(int)state.Response.Item}}}";
var stateSerialized = $"{{\"TickNumber\":{state.TickNumber},\"Response\":{responseSerialized },\"Event\":{(int)state.Event},\"HasFlag\":{(state.HasFlag?"true":"false")},\"FlagX\":{state.FlagX},\"FlagY\":{state.FlagY},\"AntWithFlag\":{(int)state.AntWithFlag}}}";

Communication

Communicating with the target environment can be tricky. Some languages provide a server like Node.js but most do not. The communication protocol you decide on should be simple and quick. There is only a limited amount of data and commands needed so don't over engineer this.

I have settled on a very basic method of sending strings and using the first character to designate the command and all remaining characters to contain serialized data if needed.

Command Meaning Summary
T Tick This is followed by a serialization of the GameState and expects an Int32 response of the AntAction.
I Initialize This is followed by a serialized array of the 5 integers required for the method arguments. No response is expected.
N Name Expects a String response of the ant's name.
P Ping A test command so we know the environment is running even if the ant contained in it is not properly functioning. Expects a string "Ping" as a response.
X Exit When received the environment should shut itself down.

Serialization

There isn't much data that needs to be serialized but this still should be done as quickly as possible since every millisecond lost to overhead is a millisecond not available to the ant. It is temping to just use the Newtonsoft.Json library and forget about it. This will probably work but it is not the most efficient method. You should think about what is the most efficient deserialization method of the target language and whether you can implement this reliably in .Net. If you do choose JSON then it should be done as a quick string interpolation as in the Initialize and Tick examples.

Take PHP for example. JSON is not the fastest serialization available. It is better to use PHP's native serialization which an array looks like a:5:{i:0;i:100;i:1;i:100;i:2;i:1;i:3;i:37;i:4;i:63;}. This has the added benefit of communicating type information so objects can be deserialized directly as their defined class instead of a generic object. It is also easy to create in C# using basic string interpolation since the data being sent is simple and repetitive.

On the other hand, Python has a native serialization (Marshal) but this is a binary format and Python version dependent. This will be difficult for C# to implement and transmit. Python also has a Pick module but again this is a binary format and surprisingly is slower than standard JSON.

⚠️ **GitHub.com Fallback** ⚠️