Hello, My first Community Resource. A RBLX Shadow Table.
I’ve always liked the idea when it comes to roblox and editing tables from meta tables, So I present to you a shadow table. This shadow-table is stack safe so your errors will be fine with the events. But handling a table index may cause an error. I’ve tried to make it as basic as possible.
So, What is a shadow table?
A shadow table is a table with certain methods that allow you to collect/handle information with ease.
For example,
shadowTable.example = "Hello, World!" -- Our normal index
shadowTableChnagedEvent = shadowTable.example.Changed.Event:Connect(function(oldValue, newValue)
print("oldValue: " .. tostring(oldValue)) -- Before changed
print("newValue: " .. tostring(newValue)) -- After changed
end)
The reason this might be useful. Is due to handling values such as statistical values inside of a module.
For most developers, They use TableData. Meaning they cache player data inside a table on the server and manage it there.
Using a shadow table opens up your table to multitude of opportunities. This shadow table is basic and easy to understand for scripters who understand a little about meta methods. But even if you don’t, Using it is quite easy.
Code Example
The code example below easily changes an index.
Once this index is destroyed, Which you can just set it to nil. It should deal with the aftermath, Helping to reduce the chance of a memory leak.
local shadowTableModule = require(game:GetService("ReplicatedStorage").ShadowTable)
local shadowTable = shadowTableModule.new()
local shadowTableChnagedEvent
shadowTable.example = "Hello, World!" -- Our normal index
shadowTable.example.Removed.Event:Connect(function()
shadowTableChnagedEvent:Disconnect() -- Once we remove the index, We disconnect the event.
-- NOTE; The bindable destroys itself once destroyed.
end)
print(shadowTable)
shadowTableChnagedEvent = shadowTable:GetElementChangedSignal("example"):Connect(function(oldValue, newValue)
print("oldValue: " .. tostring(oldValue)) -- Before changed
print("newValue: " .. tostring(newValue)) -- After changed
end)
--[[shadowTableChnagedEvent = shadowTable.example.Changed.Event:Connect(function(oldValue, newValue)
print("oldValue: " .. tostring(oldValue)) -- Before changed
print("newValue: " .. tostring(newValue)) -- After changed
end)]]--
shadowTable.example = "Bye, World!" -- Fire the changed event
shadowTable.example = nil -- Fire the removed event.
Module Code
local shadowTable = {}
local tableContent = {}
local tableManipulation = {}
--<<-------------------------------------------------------->>--
tableContent.__metatable = "The metatable is locked."
function tableContent:__index(index)
local content = rawget(self, "content")
local events = rawget(self, "events")
if events[index] then return events[index] end
return content[index]
end
function tableContent:__newindex(index, value)
local content = rawget(self, "content")
content[index] = value
end
function tableContent:__tostring(index)
local content = rawget(self, "content")
return tostring(content)
end
function tableContent:__call(...)
local content = rawget(self, "content")
return content(...)
end
function tableContent:__concat(value)
local content = rawget(self, "content")
return content .. value
end
function tableContent:__unm()
local content = rawget(self, "content")
return -content
end
function tableContent:__add(value)
local content = rawget(self, "content")
return content + value
end
function tableContent:__sub(value)
local content = rawget(self, "content")
return content - value
end
function tableContent:__mul(value)
local content = rawget(self, "content")
return content * value
end
function tableContent:__div(value)
local content = rawget(self, "content")
return content / value
end
function tableContent:__mod(value)
local content = rawget(self, "content")
return content % value
end
function tableContent:__pow(value)
local content = rawget(self, "content")
return content ^ value
end
function tableContent:__eq(value)
local content = rawget(self, "content")
return content == value
end
function tableContent:__lt(value)
local content = rawget(self, "content")
return content < value
end
function tableContent:__le(value)
local content = rawget(self, "content")
return content <= value
end
function tableContent:__len()
local content = rawget(self, "content")
return #content
end
--<<-------------------------------------------------------->>--
function shadowTable:__index(index)
local apiCallback = rawget(self, index)
if apiCallback then
return apiCallback
else
local content = rawget(self, "content")
return content[index]
end
end
function shadowTable:__newindex(index, value)
local content = rawget(self, "content")
if content[index] then
local previousValue = rawget(content[index], "content")
local events = rawget(content[index], "events")
rawset(content[index], "content", value)
if value == nil then
events.Removed:Fire(previousValue, value)
events.Removed:Destroy()
events.Changed:Destroy()
rawset(content, index, nil)
else
events.Changed:Fire(previousValue, value)
end
else
local indexTable = {}
indexTable.events = {Changed = Instance.new("BindableEvent"), Removed = Instance.new("BindableEvent")}
indexTable.content = value
content[index] = setmetatable(indexTable, tableContent)
end
end
--<<-------------------------------------------------------->>--
function tableManipulation:replicateElements(table_1, table_2, setvalueCallback, methodClass)
for index, value in pairs(table_1) do
if setvalueCallback then
setvalueCallback(methodClass, table_2, index, value)
else
table_2[index] = value
end
end
end
function tableManipulation:removeElements(table_1)
local clone = {}
for index, value in pairs(table_1) do
clone[index] = value
table_1[index] = nil
end
return clone
end
--<<-------------------------------------------------------->>--
return {
["new"] = function(previousDataTable)
local self__content = {}
local self__raw = previousDataTable or {}
tableManipulation:replicateElements(self__raw, self__content)
tableManipulation:removeElements(self__raw)
self__raw.content = {}
tableManipulation:replicateElements(self__content, self__raw.content, shadowTable.__index, self__raw)
tableManipulation:removeElements(self__content)
function self__raw:GetElementChangedSignal(elementName)
return self__raw[elementName].Changed.Event
end
setmetatable(self__content, {mode = "k"})
return setmetatable(self__raw, shadowTable)
end
}
Aside from that. I guess this concludes my first community resource.
If you have any problems or suggestions. Feel free to tell me what I can do to improve.