When you create an instance with
Instance.new, internally it creates an 'Object'. Every instance in the game is an object -
game:GetService("ReplicatedStorage"), or any other instance is an object. There is a cool thing with all of these objects though - they all have the same metatable.
You may be asking - what is a metatable? Fear not, as we will now be diving deep into how metatables work and why they are so important for development with Synapse X.
Metatables serve an extremely important purpose in both Lua and Synapse X - they allow for logic to be put behind regular tables, allowing for powerful programming constructs that are extensively used throughout the game engine.
But for Synapse X, they allow something even more powerful - lets look at an example script and explain the process of how it executes:
We will take a particular look at the blocks highlighted in blue - those are metamethod calls, and Synapse X has a special feature for metamethod calls - we can hook them.
Metamethods are a particular feature of metatables which allow Lua functions to be called when someone tries to do certain operations with our metatable. We will be using that fact to replace the function used for the object metatable.
You may be thinking - "Doesn't
getmetatable block you from getting the metatable with a
__metatable property defined?" The answer for that question is yes, but Synapse X has a trick up its sleeve for that.
The following script is the base script used for metamethod hooking on Synapse X. We will explain this script line by line.
local GameMt = getrawmetatable(game) local OldIndex = GameMt.__index local OldNameCall = GameMt.__namecall setreadonly(GameMt, false) GameMt.__index = newcclosure(function(Self, Key) return OldIndex(Self, Key) end) GameMt.__namecall = newcclosure(function(Self, ...) local NamecallMethod = getnamecallmethod() return OldNameCall(Self, ...) end) setreadonly(GameMt, true)
local GameMt = getrawmetatable(game)
This is the 'trick up our sleeve' - it bypasses any
__metatable property and allows you to directly get the metatable.
local OldIndex = GameMt.__index local OldNameCall = GameMt.__namecall
We need to create backups of the old functions so can call the originals.
This makes the table not read-only, which is required so we can overwrite the metamethods.
GameMt.__index = newcclosure(function(Self, Key) return OldIndex(Self, Key) end)
This has two parts - the
newcclosure call and the actual hook itself. This hooks the
__index metamethod, which is called whenever someone indexes a object. (
game.Workspace for example)
We will be explaining why
newcclosure is needed later on, but you need to call it for any function that will be used within a hook of any kind.
GameMt.__namecall = newcclosure(function(Self, ...) local NamecallMethod = getnamecallmethod() return OldNameCall(Self, ...) end)
This is the same as the above hook, but will replace
__namecall, which is used for calls with self. (
game:GetService("Workspace") for example)
getnamecallmethod call is needed so we can get the function name which we will use later in this tutorial. (in the above case,
NamecallMethod would be
This will make the table read only again. This isn't 100% required, but is good form.
We will now be taking a detour to explain how newcclosure works, which is important to understand for later parts of this tutorial.