Optimising
Suppose we want to animate something on a pixel-based canvas made of parts.
With no optimisation, it could look like this.
local e = Feather.createElement
return function(props)
local children = {}
local pixelSize = props.Width/props.Res
for i=1, props.Res do
for j=1, props.Res do
local key = (j-1) * props.Res + i
children[key] = e("Part", {
Color = props.Grid[key] or Color3.new(0,0,0),
Size = Vector3.new(pixelSize, 1, pixelSize),
CFrame = CFrame.new((i - 0.5)* pixelSize, 0.5, (j-0.5) * pixelSize),
Material = Enum.Material.Neon,
TopSurface = Enum.SurfaceType.Smooth,
BottomSurface = Enum.SurfaceType.Smooth,
Anchored = true,
})
end
end
return e("Model", {
[Feather.Children] = children
})
end
The problem with this component is that every time we call update (maybe every Heartbeat), Feather will update 7 properties of every single pixel part, even if none have changed.
OldProps + DeltaChildren
Function components can take a second argument, to which Feather passes the stored props of the last update to that component. We can use this to check if the color of a particular pixel has changed or not.
Then how do we tell Feather which children to update?
Instead of passing the table of all children to the Feather.Children, we can pass the table of changed-children to Feather.DeltaChildren. Existing children that are missing from this table will not be destroyed as instances (like they would with Feather.Children).
Our improved component looks like this.
local e = Feather.createElement
return function(props, oldProps)
local deltaChildren = {}
local pixelSize = props.Width/props.Res
local oldGrid = oldProps.Grid
local updateAll =
not oldGrid
or props.Width ~= oldProps.Width
or props.Res ~= oldProps.Res
for i=1, props.Res do
for j=1, props.Res do
local key = (j-1) * props.Res + i
if not oldGrid or props.Grid[key] ~= oldProps.Grid[key] then
deltaChildren[key] = e("Part", {
Color = props.Grid[key] or Color3.new(0,0,0),
Size = Vector3.new(pixelSize, 1, pixelSize),
CFrame = CFrame.new((i - 0.5)* pixelSize, 0.5, (j-0.5) * pixelSize),
Material = Enum.Material.Neon,
TopSurface = Enum.SurfaceType.Smooth,
BottomSurface = Enum.SurfaceType.Smooth,
Anchored = true,
})
end
end
end
return e("Model", {
[Feather.DeltaChildren] = deltaChildren
})
end
It is still possible to remove children when using Feather.DeltaChildren. Just set their value to the special symbol Feather.SubtractChild in the delta children table.
HostInitProps
There are often properties of hosts that never change with any update, and only need to be set when the instance is first created. While it's possible to deduce whether a host already exists via oldProps, it's much easier to use the special key Feather.HostInitProps to gather any props that should only be set when the instance is first created.
local e = Feather.createElement
local partInitProps = {
Material = Enum.Material.Neon,
TopSurface = Enum.SurfaceType.Smooth,
BottomSurface = Enum.SurfaceType.Smooth,
Anchored = true,
}
return function(props, oldProps)
local deltaChildren = {}
local pixelSize = props.Width / props.Res
local oldGrid = oldProps.Grid
local updateAll =
not oldGrid
or props.Width ~= oldProps.Width
or props.Res ~= oldProps.Res
for i=1, props.Res do
for j=1, props.Res do
local key = (j-1) * props.Res + i
if updateAll or props.Grid[key] ~= oldProps.Grid[key] then
deltaChildren[key] = e("Part", {
Color = props.Grid[key] or Color3.new(0,0,0),
Size = Vector3.new(pixelSize, 1, pixelSize),
CFrame = CFrame.new((i - 0.5)* pixelSize, 0.5, (j-0.5) * pixelSize),
[Feather.HostInitProps] = partInitProps,
})
end
end
end
return e("Model", {
[Feather.DeltaChildren] = deltaChildren
})
end
BulkMoveCFrame
If you are moving lots of BaseParts, you can use the special key Feather.BulkMoveCFrame, instead of CFrame
for better performance.
local function sphere(props)
return e("Part", {
[Feather.BulkMoveCFrame] = CFrame.new(props.Position),
Size = Vector3.new(props.Diameter, props.Diameter, props.Diameter),
Color = props.Color,
Anchored = true,
})
end
When running Feather.update or Feather.mount, Feather will use WorldRoot:BulkMoveTo() on all of the parts with this prop, instead of individually cframing every part during the update.
WorldRoot:BulkMoveTo() is called with Enum.BulkMoveTo.FireCFrameChanged
, so Position/Orientation Changed events will not fire.