Savarese Software Research Corporation
Lua/Perl/Python/Ruby API Differences

The Lua, Perl, Python, and Ruby bindings use the same naming scheme as the C++ API in almost every case.

No attempt has been made to customize the API to the idioms of the respective scripting languages. This allows you to use the C++ API documentation for all environments. However, a few differences are forced upon the API by the languages. See the unit tests in tests/swig/lua, tests/swig/perl, tests/swig/python, and tests/swig/ruby for usage examples.

Lua Differences

  • #include <ssrc/spread.h> becomes require("ssrc.spread")
  • ssrc::spread::foo becomes ssrc.spread.foo
  • Static constants and enums that are class members cannot be accessed via the dot operator. Instead they are mapped to the class name followed by an underscore followed by the constant name. For example, ssrc::spread::BaseMessage::SelfDiscard becomes ssrc.spread.BaseMessage_SelfDiscard.
  • When you reference class constants, you must reference the class in which they are defined. For example, use ssrc.spread.BaseMessage_SelfDiscard instead of ssrc.spread.Message_SelfDiscard
  • There are no bitwise logical operators in Lua. Therefore, you cannot use bitmasks. Instead of ssrc::spread::BaseMessage::Reliable | ssrc::spread::BaseMessage::SelfDiscard use ssrc.spread.BaseMessage_ReliableSelfDiscard.
  • Error and BufferSizeError cannot be caught in Lua, but you can prevent your program from exiting by using pcall. For example, the Lua unit tests do the following:
      function test_error()
        local succeeded, message = pcall(mbox.join, mbox, "####");
        assert(not succeeded)
      end
    

Perl Differences

  • #include <ssrc/spread.h> becomes use ssrc::spread;
  • ssrc::spread::foo becomes ssrc::spread::foo
  • split_private_group() is not implemented
  • When you reference class constants, you must reference the class in which they are defined. For example, use $ssrc::spread::BaseMessage::SelfDiscard instead of $ssrc::spread::Message::SelfDiscard
  • Error and BufferSizeError can be caught in Perl only via the use of eval, which makes the exception available via the $@ special variable. For example, the Perl unit tests do the following:
      sub test_error {
        eval { $mbox->join("####"); };
        is($@->error(), $ssrc::spread::Error::IllegalGroup);
      }
    

Python Differences

Ruby Differences

  • #include <ssrc/spread.h> becomes require 'ssrc/spread'
  • ssrc::spread::foo becomes Ssrc::Spread::foo

Shared Differences

  • The Spread C API is not available in the Spread namespace.
  • split_private_group() (not implemented in Perl) expects only the private group name as a parameter and returns an array (Ruby) or tuple (Python). For example:
      private_name, proc_name = ssrc.spread.split_private_group("#foo#bar")
      private_name == "foo" # This expression evaluates to True
      proc_name == "bar" # This expression evaluates to True
    
    In Lua split_private_group() returns a C++-style pair with .first and .second members, just like the C++ API.
    You don't really need split_private_group() in the scripting environment because the same result can be obtained via string manipulation functions or regular expressions.
  • operator=() becomes copy()
    For example:
      g1 = ssrc.spread.GroupList()
      g2 = ssrc.spread.GroupList()
      g1.add("foo")
      g2.copy(g1)
      g2.group(0) == "foo" # This expression evaluates to True.
    
  • operator[] is unavailable. Use the corresponding accessor method instead. For example, use GroupList::group.
  • Messages are treated as strings. If there is user demand, functions for creating raw memory buffers can be exported into the scripting environment. But for now, we feel it is best to serialize script objects into strings (e.g., with Python pickling) or create script bindings of your C++ data types that are sent as messages instead of trying to manipulate memory allocation in the scripting environment.
  • Message::read returns a string and Message::write expects a string parameter. Message::read expects the maximum number of bytes to read as a parameter. For example:
      m = ssrc.spread.Message()
      m.write("foobar")
      m.rewind()
      s = m.read(m.size())
      "foobar" == s # This expression evaluates to True.
    
  • The C++ versions of Message::read and Message::write are available as readn and writen. Note, that readn is dangerous (e.g., strings are supposed to be immutable in Python) and should be avoided unless you know exactly what you're doing.
  • ScatterMessage::add(const void *, unsigned int) and Mailbox::add_message_part(const void *, unsigned int) expect only a string as a parameter (same as the Message::write mapping). You should avoid using this form of the method for receives. Instead, use Message parts for receives, or you may overwrite script data structures in unpredictable ways.

Additional Considerations

The Mailbox class uses the C++ idiom of resource acquisition is initialization. Therefore, no disconnect method is exposed and the disconnect happens in the destructor. In a garbage-collected scripting language, this means the disconnect may not happen at a predictable point when you stop using the object. You can invoke Mailbox::kill to free the file descriptor immediately, but don't continue to use the object afterward.

In order to specify a connection timeout, you may use the Timeout class or you may specify a timeout in seconds with a single integer. For example, to specify a connection timeout of 3 seconds in Python, you could use:

mbox = ssrc.spread.Mailbox("4803@localhost", "", True, ssrc.spread.Timeout(3));

or:

mbox = ssrc.spread.Mailbox("4803@localhost", "", True, 3);

You can't access the contents of a Message directly via &Message[0] as in C++. Instead, you always have to read and write the contents using Message::read and Message::write. Because messages are treated as strings in the script environment, your data shouldn't contain null/0 characters. For example, if you write two strings to a message in a row and read them back into one string, you'll find that the result is treated as only the first string because of the null termination. For example:

m = ssrc.spread.Message()
m.write("foo")
m.write("bar")
m.rewind()
s = m.read(m.size())
"foo" == s # This expression evaluates to True.
"foobar" == s # This expression evaluates to False.
m.rewind()
s1 = m.read(4)
s2 = m.read(4)
"foo" == s1 # This expression evaluates to True.
"bar" == s2 # This expression evaluates to True.

As long as you use the script environment's object serialization mechanisms, you shouldn't run into any problems in this regard.

Don't add raw data parts (things that are not of type Message) to ScatterMessage::add or Mailbox::add_message_part if they will be garbage-collected before a send. For example:

s = "foo"
mbox.add_message_part(s)
s = None # Don't do this before the send or you may segfault!
mbox.send()

Savarese Software Research Corporation
Copyright © 2006-2015 Savarese Software Research Corporation. All rights reserved.