Skip to content

API: Definitions, Sources and Factories

Craig Minihan edited this page Nov 3, 2016 · 28 revisions

#Overview Scriptable objects only exist at runtime. They are not C++ data types and may not be directly declared and initialized in your client code. Instead we create an object source and pass that to the object factory which returns us the object.

All object sources are derived from ScriptObjectDefinition and ScriptObjectSource, these two base classes allow the object factory to query the number of fields, their names and types and ultimately create the new object.

##Object Definitions ScriptObjectDefinition exposes the fundamental methods to determine the number of fields, their names and their types in the source object. The class does not expose object instance values, it is used solely to infer the object signature based on its name and type components.

In addition the class can calculate a hash based on the field names and types which can be used by libscriptobject to look up metadata associated with the object (e.g. common keys).

The class is defined as follows:

class ScriptObjectDefinition {
public:
    virtual unsigned count() const = 0;
    virtual ScriptObjectType type(int index) const = 0;
    virtual const char* name(int index) const = 0;    
    virtual unsigned length(int index) const = 0;
    
    void CalculateHash(ScriptObjectHash digest) const;
};
  • count returns the number of fields in the object.
  • type returns the type of the field at the supplied index.
  • name returns the null terminated name of the field and length the number of characters occupied by the name.

##Object Sources ScriptObjectSource, derived from ScriptObjectDefinition, provides methods to query the source field values which will ultimately be assigned to the new object in the factory.

The class is defined as follows:

class ScriptObjectSource : public ScriptObjectDefinition {
public:
    virtual bool getBoolean(int index) const = 0;
    virtual std::int32_t getInt32(int index) const = 0;
    virtual std::uint32_t getUInt32(int index) const = 0;
    virtual std::int64_t getInt64(int index) const = 0;
    virtual std::uint64_t getUInt64(int index) const = 0;
    virtual double getDouble(int index) const = 0;
    virtual const char* getString(int index) const = 0;
    virtual int getStringLength(int index) const = 0;
    virtual const ScriptObjectPtr getObject(int index) const = 0;
    virtual const ScriptArrayPtr getArray(int index) const = 0;
};

The methods in this class exclusively return values associated with fields in the source object. When an object derived from ScriptObjectSource is passed to the object factory the new object is created as follows:

  • the factory asks for the number of fields in the source object
  • the factory enumerates each field, asking for its type, name and value
  • the factory adds the field to the new script object
  • when all fields have been added the factory returns the new object to the caller

When child objects are encountered in the object definition they are recursively passed to the factory which assigns the new child object instance on return. The same mechanism is used for arrays.

##Object Factories The only mechanism offered by libscriptobject to create scriptable objects is the object factory. It is defined as follows:

class ScriptObjectFactory final {
public:
    static ScriptObjectPtr CreateObject(const ScriptObjectSource& source, bool useKeyCache = true);
    static unsigned getCount();    
    static unsigned long getTotalBytesAllocated();
};

The CreateObject method takes a source parameter and creates the new object instance, it is that simple, now go script.

Ok, so not so fast. libscriptobject really begins at CreateObject, we don't give you a re-usable source class since that defeats the purpose of the factory. You define the source and we create the object, that is contract. In that way the object source can be anything you like, the factory does not care.

To make your life easier we provide a JSON source and a msgpack source. You could easily add a custom Protocol Buffers source and the factory will create the object instances you need. If you link libscriptobject to a scripting engine you can consume all your objects regardless of source - and that really is the end goal of this library.

Here is a (non-functional) source example just to illustrate what is going on here:

class MySource : public ScriptObjectSource {

  // the ScriptObjectDefinition overrides
  unsigned count() const override { return 1; }
  ScriptObjectType type(int index) const override { return ScriptObjectType ::Int32; }
  const char* name(int index) const override { return "the_answer"; }
  unsigned length(int index) const override { return 10; }

  // the ScriptObjectSource overrides (all other gets omitted since they aren't used)
  std::int32_t getInt32(int index) const override { return 42; }
}

This source describes the following JavaScript object:

{ "the_answer": 42 }

Creating a scriptable object instance is now simple:

MySource source;
auto obj = ScriptObjectFactory::CreateObject(source);

// output the field value to stdout (getInt32 is a member of ScriptObject
// don't confuse this with ScriptObjectSource)
std::cout << obj->getInt32("the_answer") << std::endl;

For a better insight into object sources check out the JSON (h and cpp) and msgpack (h and cpp) source code.