Skip to content
TheFlyingFiddle edited this page Aug 22, 2015 · 18 revisions

This tutorial show how to encode tuple of values in the TIER format. The mapping we will be focusing on in this tutorial is standard.tuple which is located in the tier.standard module.

In TIER a tuple is a collection of one or more items each with an individual type. A tuple mapping is an aggregate mapping that uses one or more sub mappings. The sub mappings can be any sort of mapping including other tuple mappings. Tuples are used to give structure to lua tables enabling a user to encode tables that have different values in different fields. There exists two kinds of tuples, named tuples and anonymous tuples. Named tuples as the name suggests have names associated with types for each sub mapping. Anonymous tuples do not have names associated with sub mappings. Instead values in a tuple is accessed via integer indices, 1 .. n where n is the number of items in the tuple.

Creating a tuple is a little more complex than the other mappings we have seen so far. It is created via a table of tables that contain information on how to map each individual field.

Creation

--Creates a tuple mapping.
--members is a lua sequence of the sub mappings
--        that should go into the tuple  
--        aswell as how these mappings are
--        taken from a lua table. 
standard.tuple(members)

Named Tuple Usage Examples

local tier  = require"tier"
local primitive = tier.primitive
local standard  = tier.standard

--A standard vector2 mapping. 
local vector_mapping = standard.tuple
{
   { key = "x", mapping = primitive.float },
   { key = "y", mapping = primitive.float }
}

--A mapping from a Lua table representing a monster to an encoded monster.
--It's important to note that the fields will be encoded in the order,
--that they are laid out within the tuple. Thus it will not
--continue to work if the order of the fields are changed.
local monster_mapping = standard.tuple
{
   { key = "name",         mapping = primitive.string },
   { key = "health",       mapping = primitive.uint16 },
   { key = "mana",         mapping = primitive.uint16 },
   { key = "position",     mapping = vector_mapping }, 
   { key = "friendly",     mapping = primitive.boolean } 
}

local monster_data = 
{
   name     = "Imp Wizard", 
   health   = 200,
   mana     = 175,
   position = { x = 10.0, y = 35.0 },
   friendly = false
}

local output = io.open("NamedTuples.dat", "wb")

--Encode the monster!
--The monster_mapping looks at all the fields in the monster_data
--and encodes them one after the other.
tier.encode(output, monster_data, monster_mapping) 
output:close()

--We read back the monster data.
local input = io.open("NamedTuples.dat", "rb")
local monster = tier.decode(input, monster_mapping);
input:close()

Anonymous Tuple Usage

local tier  = require"tier"
local primitive = tier.primitive
local standard  = tier.standard

local point_mapping = standard.tuple
{
   { mapping = primitive.float },
   { mapping = primitive.float }
}

local output = io.open("AnonymousTuples.dat", "wb")
tier.encode(output, { 14, -12 }, point_mapping)
tier.encode(output, { 21, 24}, primitive.dynamic, point_mapping)
output:close()

local input = io.open("AnonymousTuples.dat", "rb")
local point_a = tier.decode(input, point_mapping)
local point_b = tier.decode(input, standard.dynamic)
input:close()

--The values in the points are accessed with the integers 
-- 1 .. n where n is the number of items in the tuple.
assert(point_a[1] == 14)
assert(point_a[2] == -12)

Named Tuple Limitations

Lets look at the last thing we did in the _named tuple usage example. We read back the monster tuple from a file using the monster_mapping. In previous tutorials we have always used a mapping to decode objects but in those cases we actually didn't need to do that. If we didn't include a mapping the api would simply look at the encoded metadata figure out what mapping was needed and construct one on the spot (more on this in Encoding dynamic values). However the metadata for tuples do not store the names of the fields in the tuple. So if we did not provide the monster_mapping it would still be able to read the value but the names of the fields would be forgotten. The following example illustrates this.

local tier  = require"tier"
local standard  = tier.standard
local primitive = tier.primitive

local tuple = standard.tuple
{
    { key = "a_int", mapping = primitive.int32 },
    { key = "a_float", mapping = primitive.float } 
}

local output = io.open("NamedTupleLimitations.dat", "wb")
tier.encode(output, { a_int = 13, a_float = 1.0 }, tuple)
tier.encode(output, { a_int = 54, a_float = 16.0 }, primitive.dynamic, tuple)
output:close()

local input = io.open("NamedTupleLimitations.dat", "rb")
local with_mapping = tier.decode(input, tuple)
local without_mapping = tier.decode(input, standard.dynamic)
input:close()

--The fields are there as expected and they still have the same values.
assert(with_mapping.a_int == 13)
assert(with_mapping.a_float == 1.0)

--The fields are missing instead the data can be accessed using 
--the indices 1 and 2. In general the values will be placed in 
--1 .. n where n is the number of elements in the tuple.
assert(without_mapping[1] == 54)
assert(without_mapping[2] == 16.0)

This marks the end of this tutorial. In the next tutorial Encoding Unions we will see how we can encode values that can be of varying type.

Clone this wiki locally