Instances
ShotInstance, the base of every object a program touches, with its tree, signals, attributes, tags, Destroy, Instance.new and Clone.
Instance.new("Folder")Clone()ParentChangedDestroy()In System and Generic projects, everything a program touches is a ShotInstance: its tree's folders, values and scripts, the services and their handles (an open file, a timer). They work like Roblox's instances, in Roblox's words.
Each example replaces the Main.shot of shot new Demo.ssp (shot write Main.shot --project Demo.ssp, the text on stdin), and shot run Demo.ssp prints what follows it. shot check --project Demo.ssp passes unless an error is shown.
What every instance has
Name,Parent(a ShotInstance or nil) andClassName(read only).- Roblox's tree methods:
GetChildren,GetDescendants,FindFirstChild,FindFirstAncestor,IsA, ... - Attributes and tags:
SetAttribute,GetAttribute,GetAttributes,AddTag,HasTag, ... - Events:
Changed,ChildAdded,ChildRemoved,AncestryChanged,AttributeChanged,Destroying. Destroy()andClone().
The class pages list every member.
local list = Instance.new("Folder")
list.Name = "List"
local first = Instance.new("StringValue", list)
first.Name = "First"
first.Value = "one"
print(list.ClassName .. " " .. tostring(list.Parent) .. " " .. first:GetFullName() .. " " .. tostring(first))
print(first)
print(typeof(first) .. " " .. tostring(first:IsA("ValueBase")) .. " " .. tostring(first:IsA("ShotInstance")))
print(tostring(list:FindFirstChild("Second")) .. " " .. #list:GetChildren())
local found = list:FindFirstChild("First") :: StringValue?
print("found: " .. (if found then found.Value else "none"))prints:
Folder nil List.First First
First
userdata true true
nil 1
found: oneprint and tostring give the Name. IsA is true for the class, each class above it, "ShotInstance" and "Instance". Three traps:
typeofis"userdata"for every class: useIsA.IsAdoesn't narrow the type forshot check: afterfound:IsA("StringValue"),found.Valueis stillTypeError: Key 'Value' not found in external type 'ShotInstance'. Cast, as above::: StringValue?.- A property or method hides a child of the same name (Roblox's rule): reach a child called
NamewithFindFirstChild("Name").
Properties and the value classes
BoolValue, IntValue, NumberValue, StringValue, ObjectValue, Color3Value and Vector3Value have one property, Value. Wrong types, read-only properties and missing members error in Roblox's words; :: any gets past shot check, which catches them first:
local count = Instance.new("IntValue")
count.Value = 3
print(select(2, pcall(function() (count :: any).Value = "three" end)))
print(select(2, pcall(function() (count :: any).ClassName = "Counter" end)))
print(select(2, pcall(function() return (count :: any).Nope end)))
local pointer = Instance.new("ObjectValue")
pointer.Value = count
print(tostring(pointer.Value == count) .. " " .. count.Value)prints:
Main.shot:3: Unable to assign property Value. number expected, got string
Main.shot:4: Unable to assign property ClassName. Property is read only
Main.shot:5: Nope is not a valid member of IntValue "IntValue"
true 3A caught error starts with its file and line.
Roblox's datatypes keep Roblox's spelling: Vector2, Vector3, CFrame, Color3 (new, fromRGB, fromHex, fromHSV), UDim, UDim2 (new, fromScale, fromOffset), Rect, NumberRange, BrickColor.
local tint = Instance.new("Color3Value")
tint.Value = Color3.fromRGB(255, 128, 0)
print(tint.Value)prints:
<Color3(1, 0.5019608, 0)>TweenInfo, Ray and Region3 stay Roblox's:
local info = TweenInfo.new(1)
print(info)shot check prints:
Demo.ssp/Main.shot:1:14 TweenInfo.new is Roblox's: a System project has no TweenInfo; keep its numbers in a tableEvents and signals
use TimeService
local box = Instance.new("Folder")
box.Name = "Box"
local item = Instance.new("StringValue", box)
item.Name = "Item"
local shelf = Instance.new("Folder")
shelf.Name = "Shelf"
box.ChildRemoved:Connect(function(child : ShotInstance) print("Box lost " .. child.Name) end)
shelf.ChildAdded:Connect(function(child : ShotInstance) print("Shelf got " .. child.Name) end)
item:GetPropertyChangedSignal("Parent"):Connect(function() print("Item's Parent signal") end)
item.AncestryChanged:Connect(function(moved : ShotInstance, parent : ShotInstance?)
print("AncestryChanged: " .. moved.Name .. " is now in " .. (if parent then parent.Name else "nothing"))
end)
local heard = item.Changed:Connect(function(value : string) print("Item's Changed: " .. value) end)
shelf.Changed:Once(function(property : string) print("Shelf's Changed: " .. property) end)
item.Parent = shelf
item.Value = "apple"
item.Value = "apple"
shelf.Name = "Crate"
shelf.Name = "Bin"
heard:Disconnect()
item.Value = "pear"
print("connected: " .. tostring(heard.Connected))
task.spawn(function()
TimeService:WaitAsync(0.1)
item.Value = "plum"
end)
print("WaitAsync gave " .. tostring(item.Changed:WaitAsync(1)) .. ", then " .. tostring(item.Changed:WaitAsync(0.1)))prints:
Box lost Item
Shelf got Item
Item's Parent signal
AncestryChanged: Item is now in Shelf
Item's Changed: apple
Shelf's Changed: Name
connected: false
WaitAsync gave plum, then nilOne parenting fires, in order:
ChildRemovedon the old parent,ChildAddedon the new;- the
Parentproperty signal, thenChanged("Parent")whereChangednames properties; AncestryChanged(moved, parent)on the moved instance and each descendant.
A value class's Changed fires only for Value, with the new value; other classes pass the property's name. A set that changes nothing fires nothing. Callbacks run in connect order, each in its own thread.
A signal has:
Connect(callback): returns a connection withConnectedandDisconnect().Once(callback): the next fire only.WaitAsync(timeout?): the next fire's values, or nil after the timeout, a Destroy or the run's end. A thread waiting with no timeout keeps the run going.
Services' and handles' signals (a started program's Line, RunService.Stopping) are the same.
Attributes and tags
local row = Instance.new("Folder")
row.AttributeChanged:Connect(function(name : string) print("AttributeChanged " .. name) end)
row:SetAttribute("Index", 1)
row:SetAttribute("Index", 1)
row:SetAttribute("Label", "first")
row:SetAttribute("Label", nil)
local all = row:GetAttributes()
all.Index = 99
print(tostring(row:GetAttribute("Index")) .. " " .. tostring(row:GetAttribute("Label")))
print(select(2, pcall(function() row:SetAttribute("bad name", 1) end)))
print(select(2, pcall(function() row:SetAttribute("List", { 1, 2 }) end)))
row:AddTag("Done")
row:AddTag("Done")
print(tostring(row:HasTag("Done")) .. " " .. #row:GetTags())prints:
AttributeChanged Index
AttributeChanged Label
AttributeChanged Label
1 nil
Main.shot:10: "bad name" is not a valid attribute name (letters, digits and _, at most 100, not starting RBX)
Main.shot:11: SetAttribute List: a table can't be an attribute
true 1- Setting the value it has fires nothing; nil removes it.
GetAttributes()is a copy.- No tables or functions, as in Roblox.
- Tags are a set.
Destroy
Destroy() is the one way to end an instance:
Destroyingfires first, with everything still there.- Then its descendants are destroyed, its Parent becomes nil and locked, its connections are cut and every
WaitAsyncon its signals gets nil. - A second
Destroy()does nothing; any other use errors.
Parent = nil alone keeps an instance alive, to be put back.
use TimeService
local shelf = Instance.new("Folder")
shelf.Name = "Shelf"
local item = Instance.new("StringValue", shelf)
item.Name = "Item"
shelf.Destroying:Connect(function() print("Shelf is being destroyed; it still has " .. #shelf:GetChildren() .. " child") end)
item.Destroying:Connect(function() print("Item is being destroyed") end)
local listening = item.Changed:Connect(function() end)
task.spawn(function() print("WaitAsync resumed with " .. tostring(item.Changed:WaitAsync())) end)
shelf:Destroy()
shelf:Destroy()
TimeService:WaitAsync(0)
print("connected: " .. tostring(listening.Connected))
print(select(2, pcall(function() return item.Name end)))
print(select(2, pcall(function() item.Parent = Instance.new("Folder") end)))prints:
Shelf is being destroyed; it still has 1 child
Item is being destroyed
WaitAsync resumed with nil
connected: false
Main.shot:15: StringValue "Item" was destroyed
Main.shot:16: StringValue "Item" was destroyedMaking instances
Instance.new(className, parent?) makes an instance, parented when parent is given. A run makes Folder and the value classes; shot check types the result by the name (Instance.new("StringValue") is a StringValue).
shot check also accepts the 48 classes declared for windows, images and more (Window, Frame, BlurEffect, ..., all on the class pages). A run doesn't make them yet, so each gets a note, not an error:
local window = Instance.new("Window")
print(window)shot check prints:
Demo.ssp/Main.shot:1:16 note: Instance.new("Window") works on the native runtime; today's shot run stops with: Unable to create an Instance of type "Window"
0 errors (1 files checked, 0.3 s)and shot run:
error: Main.shot:1: Unable to create an Instance of type "Window"
at Main.shot:1shot check refuses any other name with how to get one; a typo (Foldr) or a near name (TextButton) gets the close class first:
local service = Instance.new("FileService")
local runner = Instance.new("RunService")
local typo = Instance.new("Foldr")
print(service, runner, typo)shot check prints:
Demo.ssp/Main.shot:1:17 FileService can't be made with Instance.new: write use FileService
Demo.ssp/Main.shot:2:16 RunService can't be made with Instance.new: write use RunService
Demo.ssp/Main.shot:3:14 Foldr can't be made with Instance.new: write Instance.new("Folder"); it makes Folder, StringValue, NumberValue, IntValue, BoolValue, ObjectValue, Color3Value, Vector3Value, 18 UI classes (Window, Dialog, Frame, TextLabel, ImageLabel, Button ...), 15 image effects (BlurEffect ...) and 15 more (shot docs Instances lists every one)
3 errors in 1 file (1 files checked, 0.3 s)A name known only at run time is refused by the run:
for _, className in { "FileService", "FileHandler", "PathHandler", "Timer", "Script", "ModuleScript", "Window", "Foldr" } do
print(select(2, pcall(function() return Instance.new(className :: any) end)))
endprints:
Main.shot:2: FileService can't be made with Instance.new: write use FileService
Main.shot:2: FileHandler can't be made with Instance.new: PathHandler:OpenAsync() gives one
Main.shot:2: PathHandler can't be made with Instance.new: FileService:GetPathHandler(path) gives one
Main.shot:2: Timer can't be made with Instance.new: TimeService:CreateTimer(interval), TimeService:CreateAlarm(at), TimeService:After(seconds, fn) or TimeService:Every(seconds, fn) gives one
Main.shot:2: Script can't be made with Instance.new: a script comes from the project's tree: add it in the editor and reach it through script or root
Main.shot:2: ModuleScript can't be made with Instance.new: a script comes from the project's tree: add it in the editor and reach it through script or root
Main.shot:2: Unable to create an Instance of type "Window"
Main.shot:2: Unable to create an Instance of type "Foldr"Clone
Clone() copies an instance with its properties, attributes, tags and descendants, Parent nil. A copied ObjectValue pointing inside the original points at the copy's match; one pointing outside keeps its target. Services and handles such as an open file or a timer can't be cloned. A template in the tree becomes many rows:
Template/Title.instance
StringValue
Value = "row"Main.shot
use FileService
local template = script.Parent.Template
local list = Instance.new("Folder", script.Parent)
list.Name = "List"
for k = 1, 3 do
local row = template:Clone()
row.Name = "Row" .. k
row:SetAttribute("Index", k)
row.Parent = list
end
local second = list:FindFirstChild("Row2")
local title = if second then second:FindFirstChild("Title") :: StringValue? else nil
print(#list:GetChildren() .. " rows; Row2's Title: " .. (if title then title.Value else "none"))
local typed = template:Clone() :: typeof(template)
print("typed copy: " .. typed.Title.Value .. ", Parent " .. tostring(typed.Parent))
print(script.Name .. " " .. script.ClassName .. " " .. tostring(script:IsA("ModuleScript")))
print(select(2, pcall(function() return FileService:Clone() end)))prints:
3 rows; Row2's Title: row
typed copy: row, Parent nil
Main Script true
Main.shot:18: FileService can't be clonedClone() returns a plain ShotInstance, so shot check refuses a child of an uncast copy:
local copy = script.Parent.Template:Clone()
print(copy.Title.Value)shot check prints:
Demo.ssp/Main.shot:2:7 TypeError: Key 'Title' not found in external type 'ShotInstance'Cast it (:: typeof(template)) or use FindFirstChild.
The run's tree
A program runs on its own copy of the tree (One-file projects, ROOT AND SCRIPT), which is never saved: after the Clone example, shot tree --project Demo.ssp shows no List:
Demo.ssp
./ (the project)
Main.shot
Template/
Title (StringValue)Only files the program writes outlive the run (FileService). A .shot file's ClassName is Script, a plain .luau file's Luau; both are IsA("ModuleScript").
Services and handles
Services and their handles are instances too. Reaching a service, ending a handle with Destroy() and what a call gives back: see Services.
