Skip to content

Latest commit

 

History

History
151 lines (119 loc) · 6.34 KB

README.md

File metadata and controls

151 lines (119 loc) · 6.34 KB

build

Game State Encoder Library

This library will encode and decode data as per the specification documented here: https://github.com/fluffy/draft-jennings-game-state-over-rtp. (This code is against commit 1955e14ab4557045608efd7564ea08c3984ca86a, dated 2021-12-23.)

Building

cmake -S . -B build ; cmake --build build --parallel

Integrating In Other Projects

The following is an example CMakeLists.txt file that can be used to include this library in other software package builds.

# Enable fetching content
include(FetchContent)

# Fetch the Game State Encoder Library
FetchContent_Declare(gse
    GIT_REPOSITORY  https://github.com/cisco/gse.git
    GIT_TAG         main)

# Make the library available
FetchContent_MakeAvailable(gse)

C++ Interface

The core of the library is written in C++ and a C interface exists (discussed below) to facilitate using the library with other languages.

The two main C++ objects are:

  • gs::Encoder
  • gs::Decoder

These objects only need to be instantiated once. They are stateless and thread-safe, as long as no two threads write or read from the same DataBuffer simultaneously.

The objects to encode are defined in the header file gs_types.h and should align with the Internet Draft referenced above. For example, there is a structure called gs::Hand1. To encode a gs::Hand1 object, one would populate the structure with the desired values and then call gs::Encoder's Encode() function, passing it a DataBuffer object into which the object should be serialized. The DataBuffer can allocate a buffer or accept a user-provided buffer (and length) when it is instantiated. The total length of the encoded object(s) can be determined by calling DataBuffer's GetDataLength() function.

Multiple objects can be encoded into the same DataBuffer. Each call to Encode() will result in the next object being appended to previously serialized objects in the DataBuffer.

Likewise, multiple objects may be deserialized from the same DataBuffer. To decode a buffer full of objects received over a network, for example, one would create a DataBuffer object having a pointer to the start of the encoded data. Then, gs::Decoder's Decode() function would be called. The Decode() function accepts a variant gs::GSObject (and called repetitively until there are no further objects) or a vector of variants gs::GSObjects (requiring a single call to decode the entire buffer) as the second parameter into which the decoded object(s) will be written.

Note that the objects gs::Serializer and gs::Deserializer exist to facilitate serialization and deserialization of various simpler data types into and out of the DataBuffer. One does not use those object directly. Rather, they are used indirectly by gs::Encoder and gs::Decoder.

In the event of an error, the gs::Encoder may throw an exception of type gs::EncoderException if an attempt is made to encode an invalid object or DataBufferException if there is a problem with the DataBuffer, though an effort was made to detect such issues to avoid such exceptions from being thrown. Likewise, gs::Decoder may throw an exception of the type DataBufferException or gs::DecoderException if there is an error reading from the data before or decoding the data buffer.

For examples of how to use these objects, see the unit test code in test/test_gs_encoder and test/test_gs_decoder or the C API code.

C Interface

The C interface has the following encoder (serialization) functions:

  • GSEncoderInit()
  • GSEncoderSetBuffer()
  • GSEncoderResetBuffer()
  • GSEncoderDataLength()
  • GSEncodeObject()
  • GetEncoderError()
  • GSEncoderDestroy()

One would call GSEncoderInit() with, optionally, a pointer to the raw buffer and a buffer length. That will create a context that is used with other calls. A buffer may also be passed in via GSEncoderSetBuffer(), allowing re-use of the context created via GSEncoderInit(). It's also possible to reset the existing buffer with a call to GSEncoderResetBuffer(), which effectively clears whatever is in the buffer previously assigned to the context.

A GS_Object is populated and passed into GSEncodeObject(), which will return 1 on success, 0 if the buffer cannot hold more data, or -1 on error. On error, one can check the error string. Calling the GSEncoderDataLength() will return the number of octets serialized into the buffer. GSEncodeObject() may be called repeatedly until there is no more room in the buffer.

GSEEncoderDestroy() will destroy the encoder context and any associated data, so be sure to call GSEncoderDataLength() before GSEEncoderDestroy().

The C interface has the following decoder (deserialization) functions:

  • GSDecoderInit()
  • GSDecoderSetBuffer()
  • GSDecoderResetBuffer()
  • GSDecodeObject()
  • GetDecoderError()
  • GSDecoderDestroy()

One would call GSDecoderInit() with, optionally, a pointer to the buffer holding the object(s) to deserialize and a buffer length. That will create a context that is used with other calls. A buffer may also be passed in via GSDeccoderSetBuffer(), allowing re-use of the context created via GSEncoderInit(). It's also possible to reset the existing buffer with a call to GSDecoderResetBuffer(), which effectively sets the reading position back to the start of the buffer that was previously provided and assigns a new buffer length value.

One then calls GSDecodeObject() with, a pointer to a GS_Object type. The structured will be zero-initialized by this API call, so the caller need not initialize it. The result result will be 1 if successful, 0 if there are no more objects to deserialize from the buffer, or -1 if there is an error. On error, one can check the error string. GSDecodeObject() may be called repeatedly until there are no more objects to decode.

As a part of the decode process, some data may be dynamically allocated on the heap with pointers assigned inside the GS_Object. Pointers to any such data are stored with the decoder context and will be freed when GSEDecoderDestroy() is called. One may assume pointers remain valid until GSEDecoderDestroy() is called.

GSEDecoderDestroy() will destroy the decoder context and any associated data.