Class Library

From TrainzOnline
Revision as of 17:42, 14 August 2014 by Tonyhilliam (Talk | contribs)

Jump to: navigation, search

Contents

Introduction

TrainzScript Class Library organizes code modules that can be used by other Trainz assets, avoiding duplication of source files and standardizing certain extended features of Trainz as it has evolved.

Script Heirarchy


  • A class that provides the interface for a script Library asset.
  • A library is a self-contained code module that can be accessed by the script of any other asset.
  • Library assets allow content creators to create reusable code repositories that any script can access without having to include a duplicate source file into the asset folder.
  • A Library class is the only way you can get an OnlineAccess object to access Planet Auran services from a Trainz script.


Methods

GetOnlineAccess

native OnlineAccess GetOnlineAccess(void)
Parameters
  • None
Returned Value
  • An OnlineAccess reference allowing access to Planet Auran services when online.
Syntax
OnlineAccess channel = GetOnlineAccess();
Notes
  • This method is the mechanism used to access Planet Auran via an OnlineAccess object from a script.
  • Script programmers should be careful not to allow other classes access to the OnlineAccess structure, either directly or indirectly, in order to prevent asset spoofing.
  • It is only through this private method that you can obtain an OnlineAccess object.


GetProperties

public Soup GetProperties(void)
Parameters
  • None
Returned Value
  • The properties of this Library object in Soup format.
Notes


LibraryCall

public string LibraryCall(string function, string[ ] Strings, GSObject[ ] Objects)
Parameters
  • function = A string identifier defining the Library function to be called.
  • Strings = An array of strings to be used as determined by the script programmer.
  • Objects = An array of GSObjects to be used as determined by the script programmer.
Returned Value
  • A string to be interpreted as determined by the script programmer.
Syntax
string result = Codelib.LibraryCall("AFunction",Strings,Objects);
Notes
  • This method provides a virtual function call.
  • It is through this method that all library calls are processed and it must be overridden if you want your library to do anything.
  • How the arguments are used and processed is left to the library author to decide.
  • This means that any external code using your library would need to use the function arguments as your implementation requires.
  • If your library is going to be available for use by others, then you would need to specify and document the parameters and how to interpret any returned values.
  • The name of the library call is specified in the function argument while parameters for the library calls are provided for through the string and GSObject arrays.
  • Some guidelines to follow if you want your library to be robust and bullet proof:
    • Never assume the arguments are valid.
    • When processing the arguments, make sure they are not null before using them, there is no need to check an argument you don't use.
    • Make sure the elements in the parameter arrays exist before trying to use them. This can be done by verifying the size of array and making sure the element that you are about to process is not null.
    • If using the GSObject array, cast the array member to the type you are expecting and verify the cast, there are no guarantees of what the object might be.
    • A cast will be needed to access the objects in any event as GSObject has no methods or data members.
    • If the cast fails, the object the cast was assigned to will be null.
  • LibraryCall() may not be the most efficient way to handle a library function call and may seem rather crude, but this is a temporary measure that will be replaced with a more elegant direct-call interface in a future version of Trainz.
  • The current library call mechanism will still remain supported so existing library assets are not broken.


SetProperties

public void SetProperties(Soup soup)
Parameters
  • Soup database to initialise the Library's properties.
Returned Value
  • None
Notes


Configuration & Examples

Library Configuration

  • A Library asset is defined by creating a new asset folder with a config.txt file containing these tags:
kind          "Library"
script        ScriptFile    // the name of the *.gs file containing the library class declaration
class         ScriptClass   // the name of the library class itself
  • Other than the standard asset tags such as kuid and username, these are the only tags required.
  • The library script and config.txt are the only files necessary but the asset can contain other files and folders


Client Configuration


  • To make use of a Library asset the calling asset must have a script of its own.
  • The asset script will need a kuid-table reference to the Library script asset. These tags will therefore be required:
script        assetScriptFile
class         assetScriptClass
kuid-table {
   codelib    <kuid:1234:5678>
}
  • The asset script will need a variable to hold its reference to the Library and it will need to make a call to World.GetLibrary() to initialise this variable.
  • In summary these are the calls required in the calling asset script to initiate Library access:
class assetScriptClass isclass whatever {

   // a variable for the library reference
   Library Codelib;        

   // initialise library access
   public void Init(Asset) {
      Codelib = World.GetLibrary(GetAsset().LookupKUIDTable("codelib"));  
   }

};
  • The Library is loaded into Trainz and initialised when the first asset calls World.GetLibrary()
  • Subsequent calls return a reference to the Library but only one instance is ever created.
  • This makes it possible for a Library to provide access to global data which can be shared between multiple objects.
  • There is only one function available to access the script in a Library, LibraryCall(), any use of which returns a string result.
  • An assignment to the GSObject[] or string[] remains valid until a new assignment is made, either by the Library or by the caller.
  • An assignment made in the calling script becomes available to the Library on the next Library Call.
  • An assigment made by the Library script becomes available to the Caller when the current LibraryCall returns.


Library Script Capabilities


  • Reference to the included methods and Library class inheritance will show that:
    • Libraries have an Init() function which can contain code to be executed on initialisation.
    • Libraries can create their own variables and can save them to the Session.
    • Libraries can send and receive messages and run their own threads.
    • Libraries can act as repositories for sound and html resources, including text via a StringTable in the Library asset config.


Library Script Example


  • Although this code will compile it is not guaranteed to work, the intention is to demonstrate the format of a simple library script and some of the techniques that can be used.
  • The code shows a Library script which maintains a list of all the client assets which make calls to it.
  • The list is checked by the Library itself every ten seconds, any objects which no longer exist are removed.
  • Calling scripts use the LibraryCall mechanism to obtain information, based both on the data supplied in the LibraryCall parameters, and on the GameObject list maintained by the Library code.
include "Library.gs"

class Clients isclass Library {

// member & forward method declarations, global scope
// 'Clients' holds a list of gameobjects which have made calls to this library
// if client assets make such a call in their Init() method the list will hold all client objects.
   GameObject[] Clients = new GameObject[0];
   int CountClients(void);

// Init sets up a message handler and posts a 'reminder' message
   public void Init(void) {
      inherited();
      AddHandler(me,"CheckClients","","ClientHandler");
      PostMessage(me,"CheckClients","",10.0);
   }

// ClientHandler reacts to messages by calling CountClients to check the membership 
// of the Clients list and posting a further 'reminder' to repeat the process in 10 seconds time
   void ClientHandler(Message msg) {
      if (msg.major == "CheckClients") CountClients();
      PostMessage(me,"CheckClients","",10.0);
   }

// CountClients checks that all of the members of the Clients array still exist, if
// they have been deleted from the map they are removed from the array
   int CountClients(void) {
      GameObject[] NewClientList = new GameObject[0];
      int n;
      for (n = 0; n < Clients.size(); n++) {
         if (Router.GetGameObject(Clients[n].GetId())) {
            NewClientList[NewClientList.size()] = Clients[n];
         }
      }
      int result = NewClientList.size();
      if (result < Clients.size()) {
         Interface.Print((Clients.size() - result) + " of our Clients have left");
      }
      Clients = NewClientList;
      return result;
   }
	
// Utility method to check whether or not an object is a member of an array
// This method is available throughout the class
   public bool Member(GameObject[] goArray, GameObject go) {
      int n;
      bool result = false;
      if (goArray.size() == 0) {return result;}
      for (n = 0; n < goArray.size(); n++) {
         if (go == goArray[n]) {
            result = true;
            break;
         }
      }
      return result;
   }

// Utility method to remove a member from an array, available throughout the class
   public void Remove(GameObject[] goArray, GameObject go) {
      int n;
      if (goArray.size() == 0) {return;}
      for (n = 0; n < goArray.size(); n++) {
         if (go == goArray[n]) {goArray[n,n+1] = null;}
      }
   }

// This is the LibraryCall method, through which client scripts issue instructions
// and receive information.
   public string LibraryCall(string function, string[] Strings, GSObject[] Objects) {

    // declarations local to LibraryCall
      GameObject client;

    // code to execute on all calls, irrespective of function
    // This adds the Calling object to our 'membership' list
      client = cast<GameObject>Objects[0];
      if (client and !Member(Clients,client)) Clients[Clients.size()] = client;

    // specified library call function methods
    // Return count of members
      if (function == "CountMembers") {
         int count = CountClients();
         return "There are currently " + (string)count + " members";
      }

   // Return 'membership' number of Caller
      if (function == "MembershipNumber") {
         if (client) return "Your membership number is " + (string)client.GetId();
         return "You are not a member";
      }

    // Remove Caller from the list of members, the Caller will 'rejoin' if any
    // further LibraryCalls are made.
      if (function == "Resign") {
         if (client) {
            Remove(Clients,client);
         // Instruct the library to update its list, just in case something went wrong
            PostMessage(me,"CheckClients","",0.0);
            return "Resignation accepted, Please rejoin soon";
         }
         return "Non members cannot resign";
      }

    // code to execute if 'function' doesn't match any definition.
      Interface.Exception("Unknown LibraryCall function: " + function);
      return "";

   }

};
  • To use the Library the calling object's script must include this setup.
  • We are not using the Strings array so it won't need any members, but it must be declared nonetheless
  • You would need several placed instances of the asset to see a meaningful result
class client isclass MapObject {

   Library Codelib;
   string[] Strings = new string[0];
   GSObject[] Objects = new GSObject[1];

   public void Init(void) {
      inherited();
   // Initialise our reference to the Library
      Codelib = World.GetLibrary(GetAsset().LookupKUIDTable("codelib"));
   // assign a reference to ourself to the Objects array
      Objects[0] = me;
   // Call all of the defined methods, printing out the result
      Interface.Print(Codelib.LibraryCall("CountMembers",Strings,Objects));
      Interface.Print(Codelib.LibraryCall("MembershipNumber",Strings,Objects));
      Interface.Print(Codelib.LibraryCall("Resign",Strings,Objects));
   }

};
  • Remember that, once assigned, a member of either of the array parameters will persist until changed.
  • Since there is no practical limit on the size of either array there should be little need to continuously reassign array members prior to making calls.
  • You will need to keep a careful track of the significance of each member of the arrays.


Related Methods


Categories

Personal tools