/
RxAttributeUtils.lua
167 lines (134 loc) · 4.16 KB
/
RxAttributeUtils.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
--[=[
Utility functions involving attributes.
@class RxAttributeUtils
]=]
local require = require(script.Parent.loader).load(script)
local Brio = require("Brio")
local Maid = require("Maid")
local Observable = require("Observable")
local Symbol = require("Symbol")
local UNSET_VALUE = Symbol.named("unsetValue")
local RxAttributeUtils = {}
--[=[
Observes an attribute on an instance.
@param instance Instance
@param attributeName string
@param defaultValue any?
@return Observable<any>
]=]
function RxAttributeUtils.observeAttribute(instance, attributeName, defaultValue)
assert(typeof(instance) == "Instance", "Bad instance")
assert(type(attributeName) == "string", "Bad attributeName")
return Observable.new(function(sub)
local maid = Maid.new()
local function handleAttributeChanged()
local attributeValue = instance:GetAttribute(attributeName)
if attributeValue == nil then
sub:Fire(defaultValue)
else
sub:Fire(attributeValue)
end
end
maid:GiveTask(instance:GetAttributeChangedSignal(attributeName):Connect(handleAttributeChanged))
handleAttributeChanged()
return maid
end)
end
--[=[
Observes all the attribute keys that
@param instance Instance
@return Observable<Brio<string>>
]=]
function RxAttributeUtils.observeAttributeKeysBrio(instance)
assert(typeof(instance) == "Instance", "Bad instance")
return Observable.new(function(sub)
local maid = Maid.new()
local attributeNameToBrio = {}
local function handleAttributeChanged(attributeName, attributeValue)
if attributeValue == nil then
local brio = attributeNameToBrio[attributeName]
if brio then
attributeNameToBrio[attributeName] = nil
maid[brio] = nil
end
else
if not attributeNameToBrio[attributeName] then
local brio = Brio.new(attributeName)
attributeNameToBrio[attributeName] = brio
maid[brio] = brio
sub:Fire(brio)
end
end
end
maid:GiveTask(instance.AttributeChanged:Connect(function(attributeName)
handleAttributeChanged(attributeName, instance:GetAttribute(attributeName))
end))
for attributeName, attributeValue in pairs(instance:GetAttributes()) do
if not sub:IsPending() then
break
end
-- TODO: Maybe we technically need to requery here but it's expensive
handleAttributeChanged(attributeName, attributeValue)
end
return maid
end)
end
--[=[
Observes all the attribute keys for an instance
@param instance Instance
@return Observable<string>
]=]
function RxAttributeUtils.observeAttributeKeys(instance)
assert(typeof(instance) == "Instance", "Bad instance")
return Observable.new(function(sub)
local maid = Maid.new()
maid:GiveTask(instance.AttributeChanged:Connect(function(attribute)
sub:Fire(attribute)
end))
for attribute, _ in pairs(instance:GetAttributes()) do
if not sub:IsPending() then
break
end
sub:Fire(attribute)
end
return maid
end)
end
--[=[
Observes an attribute on an instance with a conditional statement.
@param instance Instance
@param attributeName string
@param condition function | nil
@return Observable<Brio<any>>
]=]
function RxAttributeUtils.observeAttributeBrio(instance, attributeName, condition)
assert(typeof(instance) == "Instance", "Bad instance")
assert(type(attributeName) == "string", "Bad attributeName")
return Observable.new(function(sub)
local maid = Maid.new()
local lastValue = UNSET_VALUE
local function handleAttributeChanged()
local attributeValue = instance:GetAttribute(attributeName)
-- Deferred events can cause multiple values to be queued at once
-- but we operate at this post-deferred layer, so lets only output
-- reflected values.
if lastValue ~= attributeValue then
lastValue = attributeValue
if not condition or condition(attributeValue) then
local brio = Brio.new(attributeValue)
maid._lastBrio = brio
-- The above line can cause us to be overwritten so make sure before firing.
if maid._lastBrio == brio then
sub:Fire(brio)
end
else
maid._lastBrio = nil
end
end
end
maid:GiveTask(instance:GetAttributeChangedSignal(attributeName):Connect(handleAttributeChanged))
handleAttributeChanged()
return maid
end)
end
return RxAttributeUtils