Category: Cookbooks

Object composition is a design pattern, not syntactic sugar. Implementing a basic pattern of object composition in Lua is as trivial as it is powerful. It’s one of my preferred methods of applying general interfaces to stateful objects.

local function CreateObject(obj)
    obj.Clone = function(self,target)
        for k,v in pairs(self) do
            target[k] = v
        end
        return target
    end
    return obj
end
local function CreatePositionableObject(obj)
    obj = CreateObject(obj)
    local attrs = {"Position","Orientation"}
    for _,a in pairs(attrs) do
        obj["Set"..a] = function (self,x)
            self[string.lower(a)] = x
        end 
        obj["Get"..a] = function (self,x)
            return self[string.lower(a)]
        end 
    end
    obj.MoveTo = function(self,x,y,z)
        print("Moving",self:GetName(),"to",x,y,z)
        self:SetPosition({x,y,z})
    end
    return obj
end
local function CreatePlayer(obj)
    obj = CreatePositionableObject(obj)
    local attrs = {"Name","HitPoints","ID","Class","Team"}
    for _,a in pairs(attrs) do
        obj["Set"..a] = function (self,x)
            self[string.lower(a)] = x
        end 
        obj["Get"..a] = function (self,x)
            return self[string.lower(a)]
        end 
    end
    obj.FireAt = function(self,target)
        print(self:GetName(),"firing at",target:GetName())
    end
    return obj
end
local function CreateFakePlayer(obj)
    obj = CreatePlayer(obj)
    obj.MoveTo = function(self,x,y,z)
        print("Fake players can't actually move...")
    end
    obj.FireAt = function(self,target)
        print(self:GetName(),"can't shoot at",target:GetName())
    end
    return obj
end

local p1 = CreatePlayer({})
local p2 = CreateFakePlayer({})

p1:SetName("Player 1")
p2:SetName("Player 2")
p1:MoveTo(1,2,3)
p2:MoveTo(3,2,1)
p1:FireAt(p2)
p2:FireAt(p1)
print(p1:GetName() .. "'s position",p1:GetPosition()[1],p1:GetPosition()[2],p1:GetPosition()[3])
local p3 = p1:Clone({})
p3:SetName("Player 3")
p3:FireAt(p1)

This works for the simple reason that tables in Lua are object references, so changing them inside of a function has side effects. Because of this, it is always a good idea to only change tables being used as object via Getter/Setter functions so that it is easier to debug.

The above code creates a Player and a FakePlayer, the equivalent inheritance path would be Object > PositionableObject > Player > FakePlayer.

Note that self is a convention, not a keyword!

A significant hurdle to testing code, and managing a game in Roblox is the tightly coupled nature of the DataStore. A good idea for dealing with the added complexity is abstracting away the idea of storing information to a swapable or customizable memory backing system.

Unifying Read/Write to a DataStore

The DataStore for Roblox is really just a glorified table that is written to a database. More than likely this is backed by Redis, information that is not really important here.

When you think about data you think about 4 essential operations: Loading, Reading, Writing and Saving.

We can create a module that has a swapable interface, and then in our code use that. During testing we can then back all of the game state with an in-memory database, and when it’s time to deploy the game, we can swap out the Loading and Saving functions to actually use the real datastore.

Logging Module

We should always connect things up with a logger, though in this example we don’t use it yet, it’s still good practice. Here’s my generic logging module:

local m = {}
      m.Level = 3 -- all messages, include info
      m.id ="Logger".. tostring(math.random(1000,9999))
      math.randomseed(math.random(9999999))
local function printable(msg)
    return {
        Print=function()
            print(msg)
        end,
        ToString=function()
            return msg
        end     
    }
end
local function table_print(tt, indent, done)
    if type(tt) == "string" then
        return tt
    end
    if type(tt) == "number" then
        return tostring(tt)
    end
    done = done or {}
    indent = indent or 0
    if type(tt) == "table" then
        local sb = {}
        for key, value in pairs (tt) do
            table.insert(sb, string.rep (" ", indent)) -- indent it
            if type (value) == "table" and not done [value] then
                done [value] = true
                table.insert(sb, "{[" .. key .. "] = ");
                table.insert(sb, table_print (value, indent + 2, done))
                table.insert(sb, string.rep (" ", indent)) -- indent it
                table.insert(sb, "}");
            elseif "number" == type(key) then
                table.insert(sb, string.format("\"%s\"\n", tostring(value)))
            else
                table.insert(sb, string.format("%s = \"%s\"\n", tostring (key), tostring(value)))
            end
        end
        return table.concat(sb)
    else
        return tostring(tt) .. "\n"
    end
end
function m.Info(msg)
    if m.Level < 3 then
        return
    end
    return printable("[Info]: " .. table_print(msg))
end
function m.Warn(msg)
    if m.Level < 2 then
        return
    end
    return printable("[Warn]: " .. table_print(msg))
end
function m.Error(msg)
    if m.Level < 1 then
        return
    end
    print("m.Error: " .. msg)
    return printable("[Error]: " .. table_print(msg))
end
m.Printable = printable
m.FormatValue = table_print
m.CloneInto = function (id,loc)
    for name,func in pairs(m) do
        if type(func) == "function" then
            loc[name] = function (msgx)
                local tmp = m.FormatValue(msgx)
                tmp = id .. " " .. tmp
                return m[name](tmp)
            end
        else
            loc[name] = func
        end
    end 
    return loc
end
return m

Notice that the Logger functions Info, Warn and Error return an object with a Print and a ToString function. This is a handy convention for when you want to collect up output and consolidate it. You would call it directly like so: logger.Info("Something to note").Print()

This is really just for forward compatibility, it serves no purpose beyond that.

The MemDB module

We only need a dead simple Reader/Writer for the memory backing, with blank Save and Load functions.

We want to be able to store and access tables, and they might be non-trivial in complexity. For instance, perhaps we'll want to use Memory backing for some things, and only save or load certain bits of the table to the DataStore.

memdb.Write = function (key,value)
    local tbl = memdb.DB
    if string.find(key,"/",1,true) then
        local parts = split(key,"/")
        local c_k = nil
        for i = 1,(#parts - 1) do
            c_k = parts[i]
            if not tbl[c_k] then
                tbl[c_k] = {}
            end
            tbl = tbl[c_k]
        end 
        key = parts[#parts]
    end
    tbl[key] = value
end

Here we take advantage of lua's natural representation of tables as references. This works because each time we assign tbl, we get a reference and not a new variable, so we can drill down recursively into a table, using the '/' as a path separator. Essentially, we treat a piece of data as a "Path", so if you wanted to set a flag on a player, that lets your game know the player has the ability to jump higher, or run faster, you might save that information in a path like so: data.Write("Players/" .. player.UserId .. "/JumpPower", 200)

The Read function is almost identical:

memdb.Read = function (key)
    local tbl = memdb.DB
    if string.find(key,"/",1,true) then
        local parts = split(key,"/")
        local c_k = nil
        for i = 1,(#parts - 1) do
            c_k = parts[i]
            if not tbl[c_k] then
                tbl[c_k] = {}
            end
            tbl = tbl[c_k]
        end 
        key = parts[#parts]
    end
    return tbl[key]
end

If tbl[key] is not set, it will be nil, and it's a good idea to use nil sparringly, because it's a good marker for something like key missing, or key not set.

The full code

local function split(str, delim)
    local result,pat,lastPos = {},"(.-)" .. delim .. "()",1
    for part, pos in string.gmatch(str, pat) do
        table.insert(result, part); lastPos = pos
    end
    table.insert(result, string.sub(str, lastPos))
    return result
end
local memdb = {}
local logger = {}
memdb.DB = {}
memdb.Read = function (key)
    local tbl = memdb.DB
    if string.find(key,"/",1,true) then
        local parts = split(key,"/")
        local c_k = nil
        for i = 1,(#parts - 1) do
            c_k = parts[i]
            if not tbl[c_k] then
                tbl[c_k] = {}
            end
            tbl = tbl[c_k]
        end 
        key = parts[#parts]
    end
    return tbl[key]
end
memdb.Write = function (key,value)
    local tbl = memdb.DB
    if string.find(key,"/",1,true) then
        local parts = split(key,"/")
        local c_k = nil
        for i = 1,(#parts - 1) do
            c_k = parts[i]
            if not tbl[c_k] then
                tbl[c_k] = {}
            end
            tbl = tbl[c_k]
        end 
        key = parts[#parts]
    end
    tbl[key] = value
end
memdb.Save = function () 
    return true,nil
end
memdb.Load = function (data)
    memdb.DB = data
end
memdb.Install = function(log)
    logger = log.CloneInto("memdb",logger)
    memdb.Load({})
end
return memdb

Simple test rig

Now let's pull it all together and write a simple unit test for the two use cases:

package.path = './?.lua;' .. package.path
local data = require("memdb")
local logger = require("logger")
data.Install(logger)
data.Write("testKey","Hello from the testKey")
data.Write("this/contains/one","Hello World")
print(data.Read("this/contains/one"))
print(data.Read("testKey"))
logger.Info(data.DB).Print()

Which outputs:

D:\Projects\Roblox>lua test.lua
Hello World
Hello from the testKey
[Info]: testKey = "Hello from the testKey"
{[this] =   {[contains] =     one = "Hello World"
  }}
  1. Design your plugins/modules around Inputs and Outputs
  2. The functions receiving Inputs (and returning Outputs) are your Interface
  3. Obey the Separation of Concerns
  4. Use Dependency Injection of other modules with well defined Interfaces
  5. Never do more than you have to
  6. Never settle for 1 plugin when 2 will be cleaner

Basics of Module/Plugin Design

A module is a black box that has an input and an output (or many) and it produces a result. The user of the box should not really care how it does what it does. Designing a good module is about being very precise on what inputs you’ll get, and what outputs are required.

A module requires some set of functions for receiving inputs (called the interface) and at least one function for installation or attaching. Think of the attach function as a kind of constructor.

local BlackBox = {}
local function greet(name)
    return "Hello " .. name
end 
BlackBox.Install = function(custom_greet)
    if not custom_greet then
        BlackBox.Greet = greet
    else
        BlackBox.Greet = custom_greet
    end
end
return BlackBox

And here is how you would use the plugin:

local blackBox = require(game.ReplicatedStorage.Modules:WaitForChild("BlackBox"))
blackBox.Install()
-- OR
blackBox.Install(function(name)
    return "Hola " .. name
end)
local greeting = blackBox.Greet(player.Name)

This illustrates the basic design of a black box, or a Module. Notice how we don’t print inside of the module. Never do anything you don’t need to do inside the black box. A Module should exist for outputting messages, like logging, errors, and so on. If a Module needs to print anything, it should always accept a Printer. A Printer or Logger should have a standard interface like Log, Info, Warn, Error, Print etc. We call this Dependency Injection. Whenever your module depends on certain functionality that it really shouldn’t provide, then we need to inject that functionality into the plugin.

Separating Concerns

What should your module know about and do? The answer is: AS LITTLE AS POSSIBLE. Programmers on the go, especially new ones, often make the mistake of combining two or three modules into one big monolith that knows too much and goes too far. One example I’ve seen is an in-game currency manager that knows about and delivers assets to players.

This should really be two plugins, if you think about it, your wallet doesn’t care what you buy, or where you buy it, only that you have enough money or not.

This is a continuation of the idea that Plugins or Modules really shouldn’t be printing things, a Printer or Logger should do that, and the facility should be provided to the Module. A wallet doesn’t decide whether the money you spend is for beer or condoms or pet care magazines.

Here is some untested pseudo-code for a wallet module:

-- WalletModule
local Wallet = {}
local balance = 0
local last_error = nil
Logger = {}
Logger.Log = function (msg) return nil end
local function setBalance(amnt)
    balance = amnt
    return true
end
local function getBalance(amnt)
    return balance
end
function canDebit(amnt)
    if balance >= amnt then
        return true
    else
        return false
    end
end
local function credit(amnt)
    balance = balance + amnt
    return true
end
local function debit(amnt)
    if canDebit(amnt) then
        balance = balance - amnt
        return true
    else
        last_error = "Insufficient Balance"
        return false
    end
end
local function getLastError()
    return last_error
end
Wallet.Install = function(logger,save_func)
    Logger = logger
    Wallet.GetLastError = getLastError
    Wallet.CanDebit = canDebit
    Wallet.Debit = debit
    Wallet.Credit = credit
    Wallet.SetBalance = setBalance
    Wallet.GetBalance = getBalance
    if save_func then
        Wallet.Save = save_func
    else
        Wallet.Save = function()
            Logger.Log("Save function not implemented")
        end
    end
end
return Wallet

-- LoggerModule
local Logger = {}

Logger.Log = function(msg)
    print(msg)
end
return Logger

-- Main Server Script, or a Player section of
-- the script
local wallet = require(...)
local logger = require(...)
wallet.Install(logger,function() 
    -- Do something with DataStore here.
end)

I don’t want to get too caught up in the implementation details, suffice it to say, you only want your Modules to do one or two things, and do them well and bug free.

One thing to remember about Lua is that it is NOT Object Oriented, it is a functional programming language. So you should be thinking in terms of inputs and outputs. Now there is a kind of semi-global state to the Module in the form of balance, and the functions have side effects, but only within the module. Try as best as you can to never let a black box have side effects to the global space. If you must have some kind of side effect, make sure it is explicitly set by a function that obviously has side effects, or is marked as having them. So when tits become upwardly inclined, which they will, you can easily find the site of the side effect and make sure it’s not the cause of your sorrows (it usually will be).

Naming and Interfaces

There are many flame wars about function and variable naming, spacing, and interface design. Screw that. Just do it this way, and move on.

Local Variables/Internal Module State

Use snake_case, but spell the words out fully. No abbreviations like msg, or _t etc.

local shekel_balance = 0
local floren_balance = 0

Local/Internal Functions

Use camelCase as well.

local function canDebit()
    return true
end

Function Arguments

Use snake_case. But never use the whole word, make them short but meaningful. _t is a throwback from C, where custom types were often appended with an _t, like int_t or player_t.

local function canDebit(curr_t,amnt)
    if curr_t == "Shekel" then
        if shekel_balance >= amnt then return true end
    elseif curr_t == "Floren" then
        if floren_balance >= amnt then return true end
    else
        last_error_message = "Unknown Currency " .. curr_t
    end
end

Public Interface Functions

Use a CamelCase, with each word capitalized.

Module.CanDebit = canDebit

Getters and Setters

Make sure that you declare all “private” variables as local, and all private functions as local, assign them later to public facing functions.

Use Getters and Setters to allow outside users to manage or affect data. Don’t be stingy either. C++ programmers can really be a headache because they take the Public/Private thing way too far and hamstring their modules. There should be a valid way to change EVERYTHING inside a module. You have to grant an interface for more experienced programmers, otherwise they’ll just hack your plugin up. What’s the point?

I can’t tell you how many times I’ve just re-written or forked and modified a library because the author got cute with public/private for no good reason.

Substrings

In Common Lisp this function is called subseq, in other languages it is usually called substr.

(subseq string start end)

(defvar str "Hello World")
(subseq str 6) 
Output:
"World"
Try:
(subseq str 3 8) 
Output:
"lo Wo"

While you can “overwrite” a subsequence of a string, that string will not be “stretched” to fit your new subsequence should it prove longer.

(setf (subseq str 0 5) "Goodbye Cruel") 
Output:
"Goodb World"

Notice it is not what we expected, i.e. it did not automatically expand the string to “Goobye Cruel World”.

Individual Characters

This function is called char, other languages often supply syntactic sugar that allows you to
reference strings like you would an array with the [] operator.

(char string index)

(defvar str "Hello World")
(char str 0)
(aref str 0)
(elt str 0)
Output:
#\H
#\H
#\H

Because strings are implemented as arrays, it is possible to also use aref and elt.

You can set a character directly with setf.

(setf (char str 0) #\J)

Result:

"Jello World"



Chars are not very surprising in Lisp, you can access UTF-8 Character Codes with code-char

(code-char integer)

(code-char 666)
Result:
#\LATIN_SMALL_LETTER_CLOSED_OPEN_E

A string is treated as a sequence, and so any sequence function can act on it.

(remove char string)

We can remove some arbitrary char, or chars with remove
(defvar str "Hello World")
(remove #\o str) 
Result:
"Hell Wrld"

(remove-if predicate string)

We can remove some arbitrary char, or chars with remove-if by specifying a predicate function to run on each character.
(defvar str "Hello World")
(remove-if #'lower-case-p str)
(remove-if #'upper-case-p str)
(remove-if #'(lambda (x) (if (eq x #\e) t nil)) str)
Result:
"H W" 
"ello orld"
"Hllo World"

(substitute replacement needle haystack)

We can susbstitue some arbitrary char, or chars with substitute
(defvar str "Hello World")
(substitute #\u #\e str)
Result:
"Hullo World"

(substitute-if replacement predicate haystack)

Or we can substitute-if and use a predicate.
(defvar str "Hello World")
(substitute-if #\o #'lower-case-p str)
Result:
"Hoooo Woooo"

(find char str :test function)

We can search for a char in a string with find.
(find #\e "Hello" :test #'equalp)
(find #\z "Hello" :test #'equalp)
Result:
#\e
NIL

Snake Case

We can use these functions to implement snake_case:

(defvar str "Something Wicked This Way Comes")
(substitute #\_ #\Space str)
=> "Something_Wicked_This_way_Comes"

Or we can get a bit more fancy:

(defvar str "Something Wicked This Way Comes")
(defun snake-case (x)
  (string-downcase (substitute #\_ #\Space str)))
(snake-case str)
=> "something_wicked_this_way_comes"

Even that is not good enough to snake-case complex strings, so we can do this:


(defun snake-case (str)
  (let ( ( result "") )
    (loop for i from 0 to (- (length str) 1) do
      (if (>= i (- (length str) 1))
        (setf result (format nil "~a~C" result (char str i) ))
        (let ((nxt (char str (+ 1 i))) (current (char str i)))
          (cond
              ((and
                (not (eq nxt (char-downcase nxt)))
                (not (and (eq current #\Space) (eq current #\_))))
                ; then this is some mark + Capital, insert a space
                (if (eq current #\_)
                  (setf result (format nil "~a~C" result current ))
                  (setf result (format nil "~a~C_" result current ))))
              (t
                (setf result (format nil "~a~C" result current)))))))
  (string-downcase (substitute #\_ #\Space result))))
Try:
(print (snake-case "hello world"))
(print (snake-case "HelloWorld"))
(print (snake-case "_HelloWorldD2"))
Result:
"hello_world"
"hello_world"
"_hello_world_d2"

Split

Users of other programming languages will generally expect a way to split a given string
by another string, not necessarily a single character. There is no standard way to do this,
but here is a function that will do it.

(defun split (delim str &rest values)
  ;(print (format t "SPLIT: ~{ ~a ~} ~C" values #\return))
  ; If it's our first pass, just recurse with default values
  (cond ((eq values nil) (split delim str 0 0 '()))
        ; If we don't have enough characters left in the string,
        ; append what's left and return
        ((> (car values) (- (length str) (length delim)))
          (append
            (caddr values)
            (list (subseq str (cadr values) (length str))) ))
        ; else
        (t
          (let (( part (subseq str (car values) (+ (car values) (length delim))))
                  ( offset (car values))
                  ( from (cadr values))
                  ( results (caddr values) ) )
            ; Is the current part of the string equal to the delim?
            (cond ((equal part delim)
                  (let (( addon (subseq str from offset)))
                  ; Recurse after appending everything up to the delim
                  ; set the offset to offest + delim length
                  ; set the new from to the offset + delim length
                  ; append the results and boom
                  (split
                      delim
                      str
                      (+ offset (length delim))
                      (+ offset (length delim))
                      (append results (list addon) ))))
                  ; else advance one character and try again!
                  (t (split delim str (+ offset 1) from results)))))))

Lua is by far the best scripting language I’ve ever had the pleasure of using. It’s joy to embed, it’s fun to use, and here’s the kicker: It’s more like Lisp/Scheme than you think. I recently saw an absolutely crazy implementation of linked lists, far crazier than the standard Lua implementation. Basically, implementing cons, car, and cdr gets us the linked list for free. The table based implementation may be faster. Note that this idea comes right out of the MIT Scheme lectures, and I believe it’s also in SICP.

function cons(a,b)
    return function (p)
        if p == 1 then
            return a
        end
        if p == 2 then
            return b
        end
    end
end
function car(l)
    return l(1)
end
function cdr(l)
    return l(2)
end
function cdar(l)
    return car(cdr(l))
end
function cddr(l)
    return cdr(cdr(l))
end
function cddar(l)
    return car(cddr(l))
end
local x = cons(1,cons(2,cons(3,nil)))
print(car(x))
print(cdar(x))
print(cddar(x))
local y = x
while y do
    print(car(y))
    y = cdr(y)
end

Consider the same definitions in Scheme:

(define (cons a b)
    (lambda (p) 
        (if (= p 1) a b)))
(define (car x)
    (x 1))
(define (cdr x)
    (x 2))
(define (cdar x)
  (car (cdr x)))
(define (cddr x)
  (cdr (cdr x)))
(define (cddar x)
  (car (cddr x)))
(define x (cons 1 (cons 2 (cons 3 '()))))
(car x)
(cdar x)
(cddar x)
(define (while x)
  (cond
    ((empty? x) (display "Done"))
    (else
     (display (format "~a\n" (car x)))
     (while (cdr x)))))
(while x)

In all fairness though, Lua would allow us to define even the loop the same, such as:

function whilex(x)
    if x then
        print(car(x))
        whilex(cdr(x))
    else
        print("Done")
    end
end
whilex(x)