Datatypes

LSL integer

Used as a boolean value, with constants TRUE and FALSE is an SLua type boolean, with values true and false.

Used as an integer is an SLua type number (which has integer and float values all in the same datatype).

// integers (LSL)

integer isOk = TRUE;
integer count = 0;
-- integers (SLua)

local isOk = true
local count = 0

The constants TRUE and FALSE doesn’t exist in SLua. Remember to not use them!

LSL float

It is an SLua type number (floats and integers all go in the datatype number). SLua numbers are stored as 64 bit floats.

// floats (LSL)

float height = 1.65;
-- floats (SLua)

local height = 1.65

LSL string

It is an SLua type string.

// strings (LSL)

string message = "hello";
-- strings (SLua)

local message = "hello"

More about strings here: Strings.

LSL key

SLua adds the type uuid, which is the same than the LSL key.The change of name is to avoid confusion with the key of a table.

  • We get a uuid using myId = uuid("0f16c0e1-384e-4b5f-b7ce-886dda3bce41").
  • Or with myId = uuid.create("0f16c0e1-384e-4b5f-b7ce-886dda3bce41").
  • Or with myId = touuid("0f16c0e1-384e-4b5f-b7ce-886dda3bce41").

uuid(), uuid.create() and touuid() are the same, we can use any of them.

// keys (LSL)

key myId = "0f16c0e1-384e-4b5f-b7ce-886dda3bce41";
-- uuids (SLua)

local myId = uuid("0f16c0e1-384e-4b5f-b7ce-886dda3bce41")

They return nil if the string has not a valid uuid format.

This is different to LSL, where we can store any string in a variable of type key.
More info on the use of uuid’s in linked messages here: Linked messages and uuid’s.

They can take a buffer of 16 or more bytes and get the uuid in numeric format from the first 16 bytes.

The variables that contain an uuid have these properties:

  • istruthy : returns true if the variable has an uuid, false if it is “” or NULL_KEY. It’s mostly used with an if command:
    • if someUuid.istruthy then
  • bytes : returns a string with the uuid in numeric format (16 characters):
    • print(someUuid.bytes) -- > ??8NK_?Έm?;?A

Example converting an uuid to numeric format and back. Useful to store them in 16 bytes instead of 36:

-- uuid's to string16

local me = ll.GetOwner()

local meStr16 = me.bytes
print(#meStr16)  -- > 16

local meBack = uuid(buffer.fromstring(meStr16))
print(me == meBack)  -- > true

To store in linkset data we need to avoid characters ascii 0 and other special characters. We can store them in 24 byes instead of 36:

-- uuid's to string24 for linkset data

local me = ll.GetOwner()

ll.LinksetDataWrite("test",llbase64.encode(me.bytes))
print(#ll.LinksetDataRead("test")) -- > 24

local meBack = uuid(llbase64.decode(ll.LinksetDataRead("test"), true))
print(me == meBack)  -- > true

LSL vector

It is an SLua type vector. It uses the Luau library vector.

  • We get a vector using myVec = vector(50, 50, 0).
  • It’s not possible to assign a value to a component. We need to create a new vector.
  • We can get a component from the return value of a function, not only from a variable.
  • Components are stored in 32 bits (same as LSL).
// vectors (LSL)

vector myVec = <50, 50, 0>;

myVec.z = 20;

vector pos = llGetPos();  // we need a variable before reading the component
float posZ = pos.z;
-- vectors (SLua)

local myVec = vector(50, 50, 0)

myVec = vector(myVec.x, myVec.y, 20)  -- we can't assign a value to a component


local posZ = ll.GetPos().z 

More about vectors here: Vectors.

LSL rotation

SLua adds the types rotation and quaternion. They are synonims, internally they are the same datatype quaternion.

  • We get a rotation using myRot = rotation(1, 1, 1, 0) or myRot = quaternion(1, 1, 1, 0).
  • It’s not possible to assign a value to a component. We need to create a new rotation.
  • We can get a component from the return value of a function, not only from a variable.
  • Components are stored in 32 bits (same as LSL).
  • Vectors can use uppercase components (X, Y, Z) but rotations can’t. Let’s use always lowercase components.
// rotations (LSL)

rotation myRot = <1, 1, 1, 0>;
quaternion myRot2 = <2, 2, 2, 0>;

myRot.s = -myRot.s;

rotation rot = llGetRot();  // we need a variable before reading the component
float rotS = rot.s;
-- rotations (SLua)

local myRot = rotation(1, 1, 1, 0)
local myRot2 = quaternion(2, 2, 2, 0)

myRot = rotation(myRot.x, myRot.y, myRot.z, -myRot.s)  -- we can't assign a value to a component


local rotS = ll.GetRot().s

More about rotations here: Rotations.

LSL list

Lists are tables.

// lists (LSL)

list fruits = ["apple", "banana", "orange"];

//
-- tables (SLua)

local fruits = {"apple", "banana", "orange"}

-- tables are enclosed in { and }, instead of [ and ]

More about tables here: Tables.

Typecasting

  • to boolean
    • from number or integer: myBool = (myNum ~= 0)
  • to number
    • from string:
      • if the string is fully numeric:
        • myNum = tonumber("123") -- > 123 or myNum = tonumber("1.75") -- > 1.75
        • but tonumber("123abc") -- > nil
      • if the string starts with an integer, to typecast in LSL-style:
        • tonumber(string.match("123abc", "^%s*([-+]?%d+)" )) or 0 -- > 123
        • tonumber(string.match("aaa", "^%s*([-+]?%d+)" )) or 0 -- > 0
    • from boolean:
      • if myBool then 1 else 0
  • to string
    • from any type: myStr = tostring(myVar)
  • to uuid
    • from string: myUuid = uuid("0f16c0e1-384e-4b5f-b7ce-886dda3bce41")
      • or with touuid() that is the same
  • to vector
    • from string: myVec = tovector("<50, 50, 20>")
  • to rotation/quaternion
    • from string:
      • myRot = torotation("<1, 1, 1, 0>")
      • myRot = toquaternion("<1, 1, 1, 0>")

type() and typeof()

type( myVar ) returns the Lua base type of the variable. All the types added by SLua return “userdata”, which is an internal datatype used to define new types in the language itself. We can’t use “userdata”.

typeof( myVar ) returns the type of the variable, including the new types:

  • typeof( vector( 1, 2, 3 ) ) -- > vector
  • typeof( uuid( "0f16c0e1-384e-4b5f-b7ce-886dda3bce41" ) ) -- > uuid
  • typeof ( rotation ( 1, 2, 3, 4) ) -- > quaternion

We have the datatypes rotation and quaternion and the functions torotation() and toquaternion() to cast from string, but internally only exists the type quaternion, rotation is just an alias.

-- typeof() (SLua)

if typeof(myVar) == "quaternion" then  -- NOT "rotation", it would never happen!
	-- do rotations stuff
end

typeof( ZERO_ROTATION ) -- > quaternion. There is no constant ZERO_QUATERNION.

Types in LL constants and functions

In SLua, LL constants, function return values, and the elements of lists returned by LL functions have the type number if their LSL type is integer or float.

For LL function parameters that are integer or float in LSL, SLua accepts both number and integer types.

  • If a number with decimals is passed to a parameter expecting an integer, the decimal part is truncated (not rounded).
  • Many of these functions also accept a boolean type, which is internally cast to an integer.

In SLua, LL constants that contain a uuid have type uuid. In LSL they have type string.

Use of memory

Every variable or literal value, of any type, is stored as a 16 bytes tagged value (TValue) that includes the type identifier.

  • Primitive types (boolean, number, vector and nil) have their value in the TValue.
  • Reference types (string, table, function, thread, buffer and userdata) have a pointer to the heap.

  • SLua vector is derived from Luau vector and is a primitive type.

  • SLua quaternion and uuid are derived from userdata and are reference types.
    • rotation is an alternative name for quaternion, internally there is only the type quaternion.

The format of the TValue is:

  • 8 bytes : value (for primitive types) or pointer (for reference types)
  • 4 bytes : extra (used for Luau vectors to store their 3rd component)
  • 4 bytes : type identifier

When used as a key in a table, it changes to a Tkey, with this format:

  • 8 bytes : value
  • 4 bytes : extra
  • 4 bits : type identifier
  • 28 bits : link to the next node in the table

Each node in a dictionary table has a TKey and a TValue. Array tables are very optimized and in the best case only need to store the TValues (depending on how the elements are added, preallocating the array with table.create() when the number of elements is known gives the best optimization).

Reference types have their data stored in the heap (pointed by the TValue) with a header with internal metadata:

  • string has its length (in bytes) and data for string interning (explained here string interning).
  • table has the length of the array part, a pointer to its metatable, the read-only parameter and data to optimize search.
  • userdata has the length and a pointer to its internal metatable.

Memory used for each datatype:

Datatype Bytes Comments
nil 16
boolean 16
number 16
vector 16 Luau vector
quaternion 48 userdata, rotations are quaternions
string 37 + string length uses string interning
uuid 61 userdata, stored in numeric format
local variable 24 without TValue, has an index to it in the stack (or heap for closures)
table 52 (empty)
function 36 (empty)
buffer 32 + buffer length more exactly: 24 + buffer length with a minimum of 32
thread 1024 (empty) coroutine


Strings and string uuids are stored as UTF-8. The characters ASCII 0-127 use 1 byte (instead of 2 bytes in LSL):

Bytes Unicode Range Character Types
1 U+0000 to U+007F ASCII characters (basic English letters, digits, etc.)
2 U+0080 to U+07FF Extended Latin, Greek, Cyrillic, Hebrew, Arabic
3 U+0800 to U+FFFF Chinese, Japanese, Korean, symbols, most emojis
4 U+10000 to U+10FFFF Supplementary characters, rare scripts, more emojis

Details about memory allocation for tables here: Memory allocation for tables.