Skip to content

Commit 09b9f0a

Browse files
znotfiremandgxo
andauthored
[Tag] (#423)
Co-authored-by: Dog <[email protected]>
1 parent b75ead1 commit 09b9f0a

File tree

6 files changed

+129
-4
lines changed

6 files changed

+129
-4
lines changed

aftman.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
# To add a new tool, add an entry to this table.
55
[tools]
66
rojo = "rojo-rbx/[email protected]"
7-
selene = "Kampfkarren/[email protected]"
7+
selene = "Kampfkarren/[email protected]"
8+
luau-lsp = "JohnnyMorganz/[email protected]"

src/Instances/Tag.luau

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
--!strict
2+
--!nolint LocalUnused
3+
--!nolint LocalShadow
4+
local task = nil -- Disable usage of Roblox's task scheduler
5+
6+
--[[
7+
A special key for property tables, which allows users to apply custom
8+
CollectionService tags to instances
9+
]]
10+
11+
local Package = script.Parent.Parent
12+
local Types = require(Package.Types)
13+
-- Memory
14+
local checkLifetime = require(Package.Memory.checkLifetime)
15+
-- Graph
16+
local Observer = require(Package.Graph.Observer)
17+
-- State
18+
local castToState = require(Package.State.castToState)
19+
local peek = require(Package.State.peek)
20+
21+
local keyCache: { [string]: Types.SpecialKey } = {}
22+
23+
-- TODO: should this accept tagName: UsedAs<string>?
24+
local function Tag(tagName: string): Types.SpecialKey
25+
local key = keyCache[tagName]
26+
if key == nil then
27+
key = {
28+
type = "SpecialKey",
29+
kind = "Tag",
30+
stage = "self",
31+
apply = function(self: Types.SpecialKey, scope: Types.Scope<unknown>, value: unknown, applyTo: Instance)
32+
if castToState(value) then
33+
local value = value :: Types.StateObject<unknown>
34+
checkLifetime.bOutlivesA(
35+
scope,
36+
applyTo,
37+
value.scope,
38+
value.oldestTask,
39+
checkLifetime.formatters.boundTag,
40+
tagName
41+
)
42+
Observer(scope, value :: any):onBind(function()
43+
if peek(value) == true then
44+
applyTo:AddTag(tagName)
45+
elseif applyTo:HasTag(tagName) then
46+
applyTo:RemoveTag(tagName)
47+
end
48+
end)
49+
else
50+
if value == true then
51+
applyTo:AddTag(tagName)
52+
end
53+
end
54+
end,
55+
}
56+
keyCache[tagName] = key
57+
end
58+
return key
59+
end
60+
61+
return Tag

src/Memory/checkLifetime.luau

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ function checkLifetime.formatters.boundAttribute(
4747
return `The {boundName} (bound to the {attribute} attribute)`, `the {selfName} instance`
4848
end
4949

50+
function checkLifetime.formatters.boundTag(
51+
instance: Instance,
52+
bound: unknown,
53+
tag: string
54+
): (string, string)
55+
local selfName = instance.Name
56+
local boundName = nameOf(bound, "value")
57+
return `The {boundName} (bound to the {tag} CollectionService tag)`, `the {selfName} instance`
58+
end
59+
5060
function checkLifetime.formatters.propertyOutputsTo(
5161
instance: Instance,
5262
bound: unknown,

src/Types.luau

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ export type Fusion = {
276276
Attribute: (attributeName: string) -> SpecialKey,
277277
AttributeChange: (attributeName: string) -> SpecialKey,
278278
AttributeOut: (attributeName: string) -> SpecialKey,
279+
Tag: (tagName: string) -> SpecialKey,
279280
}
280281

281282
export type ExternalProvider = {

src/init.luau

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ do
3838
External.setExternalProvider(RobloxExternal)
3939
end
4040

41-
local Fusion: Fusion = table.freeze {
41+
local Fusion: Fusion = table.freeze({
4242
-- General
43-
version = {major = 0, minor = 4, isRelease = false},
43+
version = { major = 0, minor = 4, isRelease = false },
4444
Contextual = require(script.Utility.Contextual),
4545
Safe = require(script.Utility.Safe),
4646

@@ -73,10 +73,11 @@ local Fusion: Fusion = table.freeze {
7373
OnChange = require(script.Instances.OnChange),
7474
OnEvent = require(script.Instances.OnEvent),
7575
Out = require(script.Instances.Out),
76+
Tag = require(script.Instances.Tag),
7677

7778
-- Animation
7879
Tween = require(script.Animation.Tween),
7980
Spring = require(script.Animation.Spring),
80-
}
81+
})
8182

8283
return Fusion

test/Spec/Instances/Tag.spec.luau

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--!strict
2+
--!nolint LocalUnused
3+
local task = nil -- Disable usage of Roblox's task scheduler
4+
5+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
6+
local Fusion = ReplicatedStorage.Fusion
7+
8+
local New = require(Fusion.Instances.New)
9+
local Tag = require(Fusion.Instances.Tag)
10+
local Value = require(Fusion.State.Value)
11+
local doCleanup = require(Fusion.Memory.doCleanup)
12+
13+
return function()
14+
local it = getfenv().it
15+
16+
it("adds tags (constant)", function()
17+
local expect = getfenv().expect
18+
19+
local scope = {}
20+
local child = New(scope, "Folder") {
21+
[Tag "Foo"] = true
22+
}
23+
expect(child:HasTag("Foo")).to.equal(true)
24+
doCleanup(scope)
25+
end)
26+
27+
it("adds tags (state)", function()
28+
local expect = getfenv().expect
29+
30+
local scope = {}
31+
local addTag = Value(scope, true)
32+
local child = New(scope, "Folder") {
33+
[Tag "Foo"] = addTag
34+
}
35+
expect(child:HasTag("Foo")).to.equal(true)
36+
end)
37+
38+
it("adds/removes tag when state objects are updated", function()
39+
local expect = getfenv().expect
40+
41+
local scope = {}
42+
local tagExists = Value(scope, true)
43+
local child = New(scope, "Folder") {
44+
[Tag "Foo"] = tagExists
45+
}
46+
expect(child:HasTag("Foo")).to.equal(true)
47+
tagExists:set(false)
48+
expect(child:HasTag("Foo")).to.equal(false)
49+
doCleanup(scope)
50+
end)
51+
end

0 commit comments

Comments
 (0)