Sending Events to a Storyboard Application

Storyboard events contain string based names and a variable data field.  For this reason the event data must be serialized into a buffer for communication.  The Storyboard IO API provides the functions needed to both serialize your data and send the event.  The event you wish to send must first be serialized via a call to gre_io_serialize().  This will allocated a serialized data buffer for your event.  The event can then be sent via the gre_io_send() function.  Once the event has been sent the buffer can be reused or freed via a call to gre_io_free_buffer().

Note

Serialized buffers can be reused multiple times. The gre_io_serialize_buffer() function will resize or reallocate the buffer if the data being serialized is larger than the existing buffer. This is designed to cut down on repetitive memory allocation and deallocation churn.

Data parameters must be sent in order of descending alignment requirements. Example: 4u1 4u1 2u1 1s0 is good, 2u1 4u1 4u1 1s0 is not


gre_io_t                    *send_handle;
gre_io_serialized_data_t    *nbuffer = NULL;
const char                  *event_data = "my event data"

/*
 * Connect to a channel to send messages.
 */
send_handle = gre_io_open("my_channel", GRE_IO_TYPE_WRONLY);
if(send_handle == NULL) {
    printf("Can't open send handle [%s]\n", argv[1]);
    return 0;
}

/*
 * Send a named event containing no data payload
 */
nbuffer = gre_io_serialize(nbuffer, NULL,
             "my_event_name",
             NULL,
             NULL,
             0);

gre_io_send(send_handle, nbuffer);

/*
 * Send a named event with an additional string payload
 */
nbuffer = gre_io_serialize(nbuffer, NULL,
             "my_event_name",
             "1s0 data",
             event_data,
             strlen(event_data)+1);

gre_io_send(send_handle, nbuffer);
               

Event Naming Conventions

New events can be readily defined and are not required to contain a data payload. In this case their format string and data payload will be empty values.  When creating new events, it is appropriate to namespace the event definitions so that the names of events do not collide.  For example, the Storyboard framework reserves the name prefix of gre. for user interface events, and the timer functions all generate events that are prefixed with timer.

The use of events is closely coupled with the declaration and operation of actions. An action can only be invoked when an event matching the action definition is received. This results in a common design pattern where an action will perform sophisticated logic in an external script or program and then signal a completion action to run once the script work is complete.

Event >Action (script) >Work >Trigger Event >Action (completion)

Serialized Data and the Event Format String

Any data associated with an event as its payload must be a linearly contiguous block of memory. In order for clients receiving the event to decode the event data memory block back into structured content a key must be provided. The format string that is provided as part of the event is this decoding key.

The format string describes how the individual bytes of event data are to be grouped together as specific data types. For example the Lua script plugin can use the format string to convert the event data memory block into Lua variables that conform to Lua's type system. Once converted, the symbolic name for the data, provided as part of the format string, can be used to reference that particular information. Other clients, such as C or C++ programs, may not need to interpret the data symbolically but may use a language specific mechanism to convert the memory block.

The format string is relatively straightforward to create and is a series of entries formatted as [numbytes][signed/unsigned][numelements][ ][name]. For the standard C data types the number formatting would look like:

C/C++ TypeFormat StringData Size
int8_t1s11 byte
uint8_t1u11 byte
int16_t2s12 bytes
uint16_t2u12 bytes
int32_t4s14 bytes
uint32_t4u14 bytes
int64_t8s18 bytes
uint64_t8u18 bytes
float (IEEE754 float)4f14 bytes
char *1s0Length of string including nul terminator

So, if you were transmitting the following C/C++ structure you would presume that the bytewise memory layout would be:

You would use a format string of 4s1 a 2u1 b to describe the event.  

The symbolic field descriptions a and b are optional but highly recommended. They are used to give the data symbolic representation for clients that can't access the memory bytes directly (such as Lua). These symbolic field descriptions do not need to match the names of the structure member variables so an equally valid format string for the above structure might have been 4s1 angle 2u1 magnitude if angle and magnitude were better symbolic names for what the data represents.

The format string provided describes the linear memory layout of the event data. Consequently it is important that the format string take into consideration any alignment or padding inserted when the memory block is created. Consider changing the order of the members in the sample structure:

without any additional guidance to tell it otherwise the C/C++ compiler is going to create storage for the structure such that members are aligned to boundaries that match their data types (ie 4 byte types are aligned on 4 byte boundaries). This can create holes in the memory layout.

Here the 32 bit/4 byte member of the structure a comes after b but there are two additional bytes of padding inserted to ensure a starts on a 4 byte memory boundary. Since the format string must describe the linear memory layout for clients, we would have to change the format string to accomodate the extra padding inserted for alignment and the format string would be 2u1 b 2u1 pad 4s1 a. It is always good practice to avoid wasting extra bytes on padding alignment, but Storyboard does not perform any sort of interpretation. In fact providing a format string that mis-aligns data can result in unpredictable behaviour.

Event data frequently will contain string information. Strings are simply an array of one byte values with a nul terminating character, often represented as a pointer to this memory (i.echar *). All text in Storyboard is encoded using UTF-8 so this statement applies regardless of the text values being represented. If an event's data payload is composed of a single string, then the bytes of that string can be used directly as the block of memory:

char * event_data = "Crank";

The event_data variable, as a pointer to memory, can be used directly and the format string used to represent it would be 1s0 msg, where msg can be whatever symbolic name makes sense.

It is not possible to send C/C++ structures that contain strings as members if those variables are declared as pointers because the memory of the structure (including the string) is not linear and the event data must be a linearly contiguous block of memory.

However it is possible to include strings within structures by either fixing their size which will force their storage to be included as part of a structure block, i.e char msg[20], or if only a single string is being sent then the C/C++ idom of overallocating the size of a structure can be used to force a linear memory layout:

The C/C++ code technique for using this would look something like:

struct event_data {
    int      a;  //Assume 32 bit integers
    char     b[1];
};    

struct event_data *ed;

//Allocate the memory for the base structure and the string to follow it
ed = malloc(sizeof(*ed) + strlen("Crank"));

//Assign the values to the allocated structure
ed->a = 2018;
strcpy(ed->b, "Crank");          //nul character is accounted for by b[1]

In this case the data can now be described with the format string 4s1 a 1s0 b where the 1s0 is shorthand for nul terminated strings and would be equivalent to saying 1s6 where 6 is the number of bytes in the string "Crank" plus the nul terminating character.