161 lines
4.3 KiB
Plaintext
161 lines
4.3 KiB
Plaintext
--!strict
|
|
|
|
--[[
|
|
Applies a table of properties to an instance, including binding to any
|
|
given state objects and applying any special keys.
|
|
|
|
No strong reference is kept by default - special keys should take care not
|
|
to accidentally hold strong references to instances forever.
|
|
|
|
If a key is used twice, an error will be thrown. This is done to avoid
|
|
double assignments or double bindings. However, some special keys may want
|
|
to enable such assignments - in which case unique keys should be used for
|
|
each occurence.
|
|
]]
|
|
|
|
local PubTypes = require "../PubTypes"
|
|
local External = require "../External"
|
|
local cleanup = require "../Utility/cleanup"
|
|
local xtypeof = require "../Utility/xtypeof"
|
|
local logError = require "../Logging/logError"
|
|
local Observer = require "../State/Observer"
|
|
local peek = require "../State/peek"
|
|
local typeof = require "../../../Modules/Polyfill/typeof"
|
|
|
|
local function setProperty_unsafe(
|
|
instance: Instance,
|
|
property: string,
|
|
value: any
|
|
)
|
|
(instance :: any)[property] = value
|
|
end
|
|
|
|
local function testPropertyAssignable(instance: Instance, property: string)
|
|
(instance :: any)[property] = (instance :: any)[property]
|
|
end
|
|
|
|
local function setProperty(instance: Instance, property: string, value: any)
|
|
if not pcall(setProperty_unsafe, instance, property, value) then
|
|
if not pcall(testPropertyAssignable, instance, property) then
|
|
if instance == nil then
|
|
-- reference has been lost
|
|
logError("setPropertyNilRef", nil, property, tostring(value))
|
|
else
|
|
-- property is not assignable
|
|
logError(
|
|
"cannotAssignProperty",
|
|
nil,
|
|
instance.ClassName,
|
|
property
|
|
)
|
|
end
|
|
else
|
|
-- property is assignable, but this specific assignment failed
|
|
-- this typically implies the wrong type was received
|
|
local givenType = typeof(value)
|
|
local expectedType = typeof((instance :: any)[property])
|
|
logError(
|
|
"invalidPropertyType",
|
|
nil,
|
|
instance.ClassName,
|
|
property,
|
|
expectedType,
|
|
givenType
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function bindProperty(
|
|
instance: Instance,
|
|
property: string,
|
|
value: PubTypes.CanBeState<any>,
|
|
cleanupTasks: { PubTypes.Task }
|
|
)
|
|
if xtypeof(value) == "State" then
|
|
-- value is a state object - assign and observe for changes
|
|
local willUpdate = false
|
|
local function updateLater()
|
|
if not willUpdate then
|
|
willUpdate = true
|
|
External.doTaskDeferred(function()
|
|
willUpdate = false
|
|
setProperty(instance, property, peek(value))
|
|
end)
|
|
end
|
|
end
|
|
|
|
setProperty(instance, property, peek(value))
|
|
table.insert(cleanupTasks, Observer(value :: any):onChange(updateLater))
|
|
else
|
|
-- value is a constant - assign once only
|
|
setProperty(instance, property, value)
|
|
end
|
|
end
|
|
|
|
local function applyInstanceProps(
|
|
props: PubTypes.PropertyTable,
|
|
applyTo: Instance
|
|
)
|
|
local specialKeys = {
|
|
self = {} :: { [PubTypes.SpecialKey]: any },
|
|
descendants = {} :: { [PubTypes.SpecialKey]: any },
|
|
ancestor = {} :: { [PubTypes.SpecialKey]: any },
|
|
observer = {} :: { [PubTypes.SpecialKey]: any },
|
|
}
|
|
local cleanupTasks = {}
|
|
|
|
for key, value in pairs(props) do
|
|
local keyType = xtypeof(key)
|
|
|
|
if keyType == "string" then
|
|
if key ~= "Parent" then
|
|
bindProperty(applyTo, key :: string, value, cleanupTasks)
|
|
end
|
|
elseif keyType == "SpecialKey" then
|
|
local stage = (key :: PubTypes.SpecialKey).stage
|
|
local keys = specialKeys[stage]
|
|
if keys == nil then
|
|
logError("unrecognisedPropertyStage", nil, stage)
|
|
else
|
|
keys[key] = value
|
|
end
|
|
else
|
|
-- we don't recognise what this key is supposed to be
|
|
logError("unrecognisedPropertyKey", nil, xtypeof(key))
|
|
end
|
|
end
|
|
|
|
for key, value in pairs(specialKeys.self) do
|
|
key:apply(value, applyTo, cleanupTasks)
|
|
end
|
|
for key, value in pairs(specialKeys.descendants) do
|
|
key:apply(value, applyTo, cleanupTasks)
|
|
end
|
|
|
|
if props.Parent ~= nil then
|
|
bindProperty(applyTo, "Parent", props.Parent, cleanupTasks)
|
|
end
|
|
|
|
for key, value in pairs(specialKeys.ancestor) do
|
|
key:apply(value, applyTo, cleanupTasks)
|
|
end
|
|
for key, value in pairs(specialKeys.observer) do
|
|
key:apply(value, applyTo, cleanupTasks)
|
|
end
|
|
|
|
-- applyTo.Destroying:connect(function()
|
|
-- cleanup(cleanupTasks)
|
|
-- end)
|
|
|
|
if applyTo.Parent then -- close enough?
|
|
game.DescendantRemoving:connect(function(descendant)
|
|
if descendant == applyTo then
|
|
cleanup(cleanupTasks)
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
return applyInstanceProps
|