@quenty/valueobject 13.7.0 → 13.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [13.8.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/valueobject@13.8.0...@quenty/valueobject@13.8.1) (2024-10-04)
7
+
8
+ **Note:** Version bump only for package @quenty/valueobject
9
+
10
+
11
+
12
+
13
+
14
+ # [13.8.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/valueobject@13.7.0...@quenty/valueobject@13.8.0) (2024-10-04)
15
+
16
+
17
+ ### Performance Improvements
18
+
19
+ * ValueObject uses no Maid, reducing table construction. Also defers construction of GoodSignal until needed ([5701538](https://github.com/Quenty/NevermoreEngine/commit/5701538fbc8c8be5dc4dad94023acb6b7be5477e))
20
+
21
+
22
+
23
+
24
+
6
25
  # [13.7.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/valueobject@13.6.0...@quenty/valueobject@13.7.0) (2024-09-25)
7
26
 
8
27
  **Note:** Version bump only for package @quenty/valueobject
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/valueobject",
3
- "version": "13.7.0",
3
+ "version": "13.8.1",
4
4
  "description": "To work like value objects in Roblox and track a single item with .Changed events",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -26,16 +26,16 @@
26
26
  "Quenty"
27
27
  ],
28
28
  "dependencies": {
29
- "@quenty/brio": "^14.7.0",
30
- "@quenty/ducktype": "^5.5.0",
31
- "@quenty/loader": "^10.5.0",
32
- "@quenty/maid": "^3.3.0",
33
- "@quenty/rx": "^13.7.0",
34
- "@quenty/signal": "^7.6.0",
35
- "@quenty/valuebaseutils": "^13.7.0"
29
+ "@quenty/brio": "^14.8.1",
30
+ "@quenty/ducktype": "^5.6.0",
31
+ "@quenty/loader": "^10.6.0",
32
+ "@quenty/maid": "^3.4.0",
33
+ "@quenty/rx": "^13.8.0",
34
+ "@quenty/signal": "^7.7.0",
35
+ "@quenty/valuebaseutils": "^13.8.1"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
39
39
  },
40
- "gitHead": "9b17fe79cddd071f0f06a9d35184e76b44bd6fe6"
40
+ "gitHead": "539802fea720a92f81ad48d6d5579605d8844d0a"
41
41
  }
@@ -6,13 +6,14 @@
6
6
 
7
7
  local require = require(script.Parent.loader).load(script)
8
8
 
9
- local GoodSignal = require("GoodSignal")
9
+ local Brio = require("Brio")
10
+ local DuckTypeUtils = require("DuckTypeUtils")
10
11
  local Maid = require("Maid")
12
+ local MaidTaskUtils = require("MaidTaskUtils")
11
13
  local Observable = require("Observable")
12
- local ValueBaseUtils = require("ValueBaseUtils")
13
14
  local RxValueBaseUtils = require("RxValueBaseUtils")
14
- local Brio = require("Brio")
15
- local DuckTypeUtils = require("DuckTypeUtils")
15
+ local Signal = require("Signal")
16
+ local ValueBaseUtils = require("ValueBaseUtils")
16
17
 
17
18
  local EMPTY_FUNCTION = function() end
18
19
 
@@ -22,35 +23,36 @@ ValueObject.ClassName = "ValueObject"
22
23
  --[=[
23
24
  Constructs a new value object
24
25
  @param baseValue T
25
- @param checkType string | nil
26
+ @param checkType string | nil | (value: T) -> (boolean, string)
26
27
  @return ValueObject
27
28
  ]=]
28
29
  function ValueObject.new(baseValue, checkType)
29
- local self = {
30
+ local self = setmetatable({
30
31
  _value = baseValue;
31
32
  _checkType = checkType;
32
- _maid = Maid.new();
33
- }
33
+ }, ValueObject)
34
34
 
35
- if checkType and typeof(baseValue) ~= checkType then
36
- error(string.format("Expected value of type %q, got %q instead", checkType, typeof(baseValue)))
35
+ if type(checkType) == "stirng" then
36
+ if typeof(baseValue) ~= checkType then
37
+ error(string.format("Expected value of type %q, got %q instead", checkType, typeof(baseValue)))
38
+ end
39
+ elseif type(checkType) == "function" then
40
+ assert(checkType(baseValue))
37
41
  end
38
42
 
43
+ return self
44
+ end
45
+
39
46
  --[=[
40
47
  Event fires when the value's object value change
41
48
  @prop Changed Signal<T> -- fires with oldValue, newValue, ...
42
49
  @within ValueObject
43
50
  ]=]
44
- self.Changed = self._maid:Add(GoodSignal.new()) -- :Fire(newValue, oldValue, ...)
45
-
46
- return setmetatable(self, ValueObject)
47
- end
48
-
49
51
 
50
52
  --[=[
51
53
  Returns the current check type, if any
52
54
 
53
- @return string | nil
55
+ @return string | nil | (value: T) -> (boolean, string)
54
56
  ]=]
55
57
  function ValueObject:GetCheckType()
56
58
  return rawget(self, "_checkType")
@@ -96,6 +98,7 @@ function ValueObject:_toMountableObservable(value)
96
98
 
97
99
  return nil
98
100
  end
101
+
99
102
  --[=[
100
103
  Mounts the value to the observable. Overrides the last mount.
101
104
 
@@ -105,29 +108,21 @@ end
105
108
  function ValueObject:Mount(value)
106
109
  local observable = self:_toMountableObservable(value)
107
110
  if observable then
108
- self._maid._mount = nil
109
-
110
- local maid = Maid.new()
111
+ self:_cleanupLastMountedSub()
111
112
 
112
- maid:GiveTask(observable:Subscribe(function(...)
113
+ local sub = observable:Subscribe(function(...)
113
114
  self:SetValue(...)
114
- end))
115
-
116
- maid:GiveTask(function()
117
- if self._maid._mount == maid then
118
- self._maid._mount = nil
119
- end
120
115
  end)
121
116
 
122
- self._maid._mount = maid
117
+ rawset(self, "_lastMountedSub", sub)
123
118
 
124
119
  return function()
125
- if self._maid._mount == maid then
126
- self._maid._mount = nil
120
+ if rawget(self, "_lastMountedSub") == sub then
121
+ self:_cleanupLastMountedSub()
127
122
  end
128
123
  end
129
124
  else
130
- self._maid._mount = nil
125
+ self:_cleanupLastMountedSub()
131
126
 
132
127
  self:SetValue(value)
133
128
 
@@ -135,12 +130,25 @@ function ValueObject:Mount(value)
135
130
  end
136
131
  end
137
132
 
133
+ function ValueObject:_cleanupLastMountedSub()
134
+ local lastSub = rawget(self, "_lastMountedSub")
135
+ if lastSub then
136
+ rawset(self, "_lastMountedSub", nil)
137
+ MaidTaskUtils.doTask(lastSub)
138
+ end
139
+ end
140
+
138
141
  --[=[
139
142
  Observes the current value of the ValueObject
140
143
  @return Observable<T>
141
144
  ]=]
142
145
  function ValueObject:Observe()
143
- return Observable.new(function(sub)
146
+ local found = rawget(self, "_observable")
147
+ if found then
148
+ return found
149
+ end
150
+
151
+ local created = Observable.new(function(sub)
144
152
  if not self.Destroy then
145
153
  warn("[ValueObject.observeValue] - Connecting to dead ValueObject")
146
154
  -- No firing, we're dead
@@ -148,21 +156,24 @@ function ValueObject:Observe()
148
156
  return
149
157
  end
150
158
 
151
- local connection
152
-
153
- connection = self.Changed:Connect(function(newValue, _, ...)
159
+ local connection = self.Changed:Connect(function(newValue, _, ...)
154
160
  sub:Fire(newValue, ...)
155
161
  end)
156
162
 
157
163
  local args = rawget(self, "_lastEventContext")
164
+ local value = rawget(self, "_value")
158
165
  if args then
159
- sub:Fire(self.Value, table.unpack(args, 1, args.n))
166
+ sub:Fire(value, table.unpack(args, 1, args.n))
160
167
  else
161
- sub:Fire(self.Value)
168
+ sub:Fire(value)
162
169
  end
163
170
 
164
171
  return connection
165
172
  end)
173
+
174
+ -- We use a lot of these so let's cache the result which reduces the number of tables we have here
175
+ rawset(self, "_observable", created)
176
+ return created
166
177
  end
167
178
 
168
179
  --[=[
@@ -228,8 +239,12 @@ function ValueObject:SetValue(value, ...)
228
239
  local previous = rawget(self, "_value")
229
240
  local checkType = rawget(self, "_checkType")
230
241
 
231
- if checkType and typeof(value) ~= checkType then
232
- error(string.format("Expected value of type %q, got %q instead", checkType, typeof(value)))
242
+ if type(checkType) == "string" then
243
+ if typeof(value) ~= checkType then
244
+ error(string.format("Expected value of type %q, got %q instead", checkType, typeof(value)))
245
+ end
246
+ elseif typeof(checkType) == "function" then
247
+ assert(checkType(value))
233
248
  end
234
249
 
235
250
  if previous ~= value then
@@ -240,8 +255,10 @@ function ValueObject:SetValue(value, ...)
240
255
  end
241
256
 
242
257
  rawset(self, "_value", value)
243
-
244
- self.Changed:Fire(value, previous, ...)
258
+ local changed = rawget(self, "Changed")
259
+ if changed then
260
+ changed:Fire(value, previous, ...)
261
+ end
245
262
  end
246
263
  end
247
264
 
@@ -251,10 +268,19 @@ end
251
268
  @within ValueObject
252
269
  ]=]
253
270
  function ValueObject:__index(index)
254
- if index == "Value" then
255
- return self._value
256
- elseif ValueObject[index] then
271
+ if ValueObject[index] then
257
272
  return ValueObject[index]
273
+ elseif index == "Value" then
274
+ return self._value
275
+ elseif index == "Changed" then
276
+ -- Defer construction of Changed event until something needs it, since a lot
277
+ -- of times we don't need it
278
+
279
+ local signal = Signal.new() -- :Fire(newValue, oldValue, ...)
280
+
281
+ rawset(self, "Changed", signal)
282
+
283
+ return signal
258
284
  elseif index == "LastEventContext" then
259
285
  local args = rawget(self, "_lastEventContext")
260
286
  if args then
@@ -287,7 +313,15 @@ end
287
313
  ]=]
288
314
  function ValueObject:Destroy()
289
315
  rawset(self, "_value", nil)
290
- self._maid:DoCleaning()
316
+
317
+ self:_cleanupLastMountedSub()
318
+
319
+ -- Avoid using a maid here because we make a LOT of ValueObjects
320
+ local changed = rawget(self, "Changed")
321
+ if changed then
322
+ changed:Destroy()
323
+ end
324
+
291
325
  setmetatable(self, nil)
292
326
  end
293
327