LS2Module:Building data types

From ismods.com: dedicated to promoting white hat, EULA-compliant Inner Space and LavishScript mods
Jump to: navigation, search

This is an example of how to go about adding LavishScript 2 data types in your own module or Inner Space extension.

How Types work

A Type object in LavishScript 2 is something that you request to be created through ILS2StandardEnvironment, and is returned to you in the form of a reference counted ILS2CodeBoxType. You can also retrieve a reference to any other existing Type.

Once you have a reference to a Type, you can add members to it, as well as access, replace, or enumerate existing members. For now we will focus on registering a new type and populating it with members.

A non-static Type will usually expect to operate on a certain type of value, which is represented by a LS2CodeBoxValue. In most cases, custom Types will use one of the templated value types LS2CodeBoxValue_ObjectT<T>, LS2CodeBoxValue_ObjectRefCountedT<T>, or LS2CodeBoxValue_Pointer<T>. So, for example, a System.Int32 will operate on LS2CodeBoxValue_Int32, and each of its members will verify that before attempting to access the data. Your custom members will do the same, for whichever type of value you will operate on.

Conveniently building Type members

Here's a convenient layout for a .cpp file that defines a data type. Extending our data type is a matter of expanding on this basic structure.

#include <ls2module.h>
namespace LavishScript2
{
  namespace Types 
  {
    namespace MyType
    {
       // members like Methods and Properties will go in here
     }
  }
}

bool RegisterMyType()
{
  // register type
  // register interfaces implemented by type
  // register members of type
  return false; // type isn't registered, this isn't even implemented!
}

void UnregisterMyType()
{
  // unregister type...
}

Register your Type

Registration creates a type with a given Fully-Qualified Name, subclassed from a given parent Type (or none). This will typically only fail if a Type with this name already exists.

We first add something like this to store a reference to our type...
LavishScript2::ILS2CodeBoxType *g_pMyType = 0;
In our example, the following goes in RegisterMyType()
// Register the type as Foo.Foo, implying the namespace "Foo" and type name "Foo"
if (!g_pLS2Environment->RegisterType(L"Foo.Foo",0,g_pMyType))
{
  // type registration failed
  return false;
}

Build a Method

Let's start by adding a method. A method in a LavishScript 2 Type will typically wrap an existing function, and often the original function can be mimicked. In fact, it does make it easier if the parameters to your LavishScript 2 method match something you will be calling.

So, first, let's build a "void Start()" member of our hypothetical class.

Here's an example implementation of a Method. In our example, this goes in the LavishScript2::Types::MyType namespace.
static bool __stdcall Start(LavishScript2::LS2CodeBoxValue &subject, LavishScript2::ILS2Array *pInputs, class LavishScript2::LS2Exception **ppException)
{
  // this handy macro will make sure the Value is an Object type and the Object is an instance of our type, or return false with an exception
  // if it succeeds, we have a view of the subject as pMyObject!
  LS2_SUBJECT_AS_OBJECT(g_pMyType,LS2CodeBoxValue_MyType,pMyObject);
   
  pMyObject->Start();
  return true;
}

So there's a few things to learn here:

  • the 'subject' would otherwise be known as the 'this' object.
  • pInputs is technically an array of both inputs and outputs, where the outputs are passed in as Reference objects. If our method had a "return" value, it would be the first input as a Reference.
  • ppException should get populated by instantiating a new LS2Exception, if you mean to throw an exception into LavishScript 2. Return false to indicate the failure.
  • Return true to indicate a successful execution of this method (otherwise return false and provide an exception)
Now inside RegisterMyType()...
g_pMyType->RegisterMethod(L"Start{}",0,&LavishScript2::Types::MyType::Start);

And a few notes on this call:

  • The first parameter is the "name" of the method. It should follow a specific format. Parameters are specified with {} and separated with commas, with no extra spacing. Don't provide parameter names or values, and leave out the return type. Parameters should use the keyword name for types where available, e.g. "string" for System.String. Example: "Start{string,int}". If it's named wrong, a LavishScript 2 program attempting to use the method will express a runtime exception.
  • The second parameter is for input declarations (parameter list). Since this method has a void (no) return type and no parameters, we do not give any input declarations. For methods that will provide input declarations, use the REGISTER_METHOD (or REGISTER_STATIC_METHOD) macros, as described separately below...

Build a Static Method

A static method is pretty much the same as a method, but does not require a subject. Because it is static, and that's what a static method is, is a method that can operate without a subject.

Here's an example implementation of a Static Method. In our example, this goes in the LavishScript2::Types::MyType namespace.
static bool __stdcall MyStaticMethod(LavishScript2::ILS2Array *pInputs, class LavishScript2::LS2Exception **ppException)
{
  *ppException = new LavishScript2::LS2NotImplementedException(L"Static Method example!");
  return false;
}

Build a Property

A Property is somewhat like a Method. In terms of LavishScript 2, a Property is used to get or set a particular value from a given subject. Therefore, instead of an ILS2Array, the functions either take a LavishScript2::LS2CodeBoxValue **ppOutput (get) or LavishScript2::LS2CodeBoxValue &input (set).

Here's an example implementation of a Property. In our example, this goes in the LavishScript2::Types::MyType namespace.
static bool __stdcall MyProperty_get(LavishScript2::LS2CodeBoxValue &subject, LavishScript2::LS2CodeBoxValue **ppOutput, LavishScript2::LS2Exception **ppException)
{
  *ppException = new LavishScript2::LS2NotImplementedException(L"Property get example!");
  return false;
}
static bool __stdcall MyProperty_set(LavishScript2::LS2CodeBoxValue &subject, LavishScript2::LS2CodeBoxValue &input, LavishScript2::LS2Exception **ppException)
{
  *ppException = new LavishScript2::LS2NotImplementedException(L"Property set example!");
  return false;
}

Build a Static Property

A static property is pretty much the same as a property, but does not require a subject.

Here's an example implementation of a Static Property. In our example, this goes in the LavishScript2::Types::MyType namespace.
static bool __stdcall MyStaticProperty_get(LavishScript2::LS2CodeBoxValue **ppOutput, LavishScript2::LS2Exception **ppException)
{
  *ppException = new LavishScript2::LS2NotImplementedException(L"Static Property get example!");
  return false;
}
static bool __stdcall MyStaticProperty_set(LavishScript2::LS2CodeBoxValue &input, LavishScript2::LS2Exception **ppException)
{
  *ppException = new LavishScript2::LS2NotImplementedException(L"Static Property set example!");
  return false;
}

Build a Field

Build a Static Field