Skip to content

Instantly share code, notes, and snippets.

@metatablecat
Created February 6, 2024 18:53
Show Gist options
  • Save metatablecat/e44a2099aef5ad4133ae76e901bb6088 to your computer and use it in GitHub Desktop.
Save metatablecat/e44a2099aef5ad4133ae76e901bb6088 to your computer and use it in GitHub Desktop.
A tiny tree object creator dervived from Create.lua syntax, RBXCreate is provided as an example.
local Create = {}
local function fastMixedTableSplit(t)
local l = #t
if l == 0 then return {} end
local out = table.move(t, 1, l, 1, {})
table.move({}, 1, l, 1, t)
return out
end
local function componentProcessor(autoMapChildren, processorFunc)
return function(props)
local children = fastMixedTableSplit(props)
local com = processorFunc(props, children)
if autoMapChildren then
for _, v in children do
v.Parent = com
end
end
return com
end
end
function Create.component<A, R>(
autoMapChildren: boolean?,
processor: (props: A, children: {any}) -> R
): (A) -> R
-- splits numeric indexes from string indexes, then passes both tables into the processor
-- returns a callback to spawn this behaviour (for generic typing)
return componentProcessor(autoMapChildren, processor)
end
setmetatable(Create, {
__tostring = function() return "Module<Create>" end
})
table.freeze(Create)
return Create
-- Roblox component creator for Create
local RBXCreate = {}
local Create = require(script.Parent.Create)
local SYMBOL_HEADER = newproxy(false)
local function extract(t,k)
local v = t[k]
t[k] = nil
return v
end
type Symbol = {id: string, name: string}
local function Symbol(id): (string) -> Symbol
return function(name)
return {
[SYMBOL_HEADER] = true,
id = id,
name = name
}
end
end
local function mapAttributes(obj, attributes)
for k, v in attributes do
obj:SetAttribute(k, v)
end
end
type EventParams<A> = {
Sender: A,
Connection: RBXScriptConnection
}
local function createEventHandler(sender: Instance, event: RBXScriptSignal, callback, stack)
local eventParams = {
Sender = sender,
Connection = nil,
}
eventParams.Connection = event:Connect(function(...)
if stack then
callback(eventParams, stack, ...)
else
callback(eventParams, ...)
end
end)
return eventParams.Connection
end
RBXCreate.AttributeChange = Symbol("Attribute")
RBXCreate.Change = Symbol("Change")
local function ChangeHandler(eParams, stack)
local i = eParams.Sender
local name = stack.Name
local signal = stack.Signal
local symId = stack.ID
local value = if symId == "Attribute" then i:GetAttribute(name) else i[name]
signal(i, name, value)
end
-- exports for **better**, singleton based, change signals
-- here to stop the typing engine dying
function RBXCreate:ConnectAttributeChange<A>(
obj: A,
attribName: string,
callback: (senderParams: EventParams<A>, attribName: string, attribVal: any) -> ()
): RBXScriptConnection
return createEventHandler(obj, obj:GetAttributeChangedSignal(attribName), ChangeHandler, {
Name = attribName,
Signal = callback,
ID = "Attribute"
})
end
function RBXCreate:ConnectPropertyChange<A>(
obj: A,
propName: string,
callback: (senderParams: EventParams<A>, propName: string, propVal: any) -> ()
): RBXScriptConnection
return createEventHandler(obj, obj:GetPropertyChangedSignal(propName), ChangeHandler, {
Name = propName,
Signal = callback,
ID = "Change"
})
end
local function handleSymbolicArg(symbol: Symbol, signal: any, obj: Instance)
local symId = symbol.id
local name = symbol.name
local f = if symId == "Attribute" then RBXCreate.AttributeChangeSignal else RBXCreate.PropertyChangeSignal
f(RBXCreate, obj, name, signal)
end
local rbxCreateInstanceObj = Create.component(true, function(props)
local className = extract(props, "ClassName")
local parent = extract(props, "Parent")
local attribs = extract(props, "Attributes")
local tags = extract(props, "Tags")
local obj = Instance.new(className)
for k, v in props do
if type(k) == "table" and k[SYMBOL_HEADER] then
handleSymbolicArg(k, v, obj)
continue
end
local propVal = obj[k]
if typeof(propVal) == "RBXScriptSignal" then
createEventHandler(obj, propVal, v)
continue
end
obj[k] = v
end
if attribs then mapAttributes(obj, attribs) end
if tags then for _, v in tags do obj:AddTag(v) end end
if parent then obj.Parent = parent end
return obj
end)
setmetatable(RBXCreate, {
__index = function(self, k)
return function(props)
props.ClassName = k -- storing the class name here prevents pollution of components
-- and allows a single component instead
return rbxCreateInstanceObj(props)
end
end,
})
return RBXCreate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment