@quenty/rogue-properties 11.25.1 → 11.25.2-canary.ff3c47a.0

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,23 @@
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
+ ## [11.25.2-canary.ff3c47a.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/rogue-properties@11.25.1...@quenty/rogue-properties@11.25.2-canary.ff3c47a.0) (2025-09-26)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Fix caching performance of the rogue system when multiple things want to observe state ([1a067f7](https://github.com/Quenty/NevermoreEngine/commit/1a067f7dec837a2eb4db399e46bf06566824c9a3))
12
+ * Style stuff ([ff3c47a](https://github.com/Quenty/NevermoreEngine/commit/ff3c47af4e5b23f912aa09a0784849751d5736e1))
13
+
14
+
15
+ ### Features
16
+
17
+ * Move the rogue properties over to attributes as required ([4aa23b0](https://github.com/Quenty/NevermoreEngine/commit/4aa23b0092ba07490b615f1d8ab5920f86b78756))
18
+
19
+
20
+
21
+
22
+
6
23
  ## [11.25.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/rogue-properties@11.25.0...@quenty/rogue-properties@11.25.1) (2025-09-15)
7
24
 
8
25
  **Note:** Version bump only for package @quenty/rogue-properties
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/rogue-properties",
3
- "version": "11.25.1",
3
+ "version": "11.25.2-canary.ff3c47a.0",
4
4
  "description": "Roguelike properties which can be modified by external provides",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -24,33 +24,35 @@
24
24
  "Quenty"
25
25
  ],
26
26
  "dependencies": {
27
- "@quenty/adorneedata": "^7.20.0",
28
- "@quenty/baseobject": "^10.9.0",
29
- "@quenty/binder": "^14.22.0",
30
- "@quenty/brio": "^14.19.0",
31
- "@quenty/defaultvalueutils": "^1.2.2",
32
- "@quenty/ducktype": "^5.9.0",
33
- "@quenty/instanceutils": "^13.19.0",
34
- "@quenty/jsonutils": "^10.12.0",
35
- "@quenty/linkutils": "^13.19.0",
36
- "@quenty/loader": "^10.9.0",
37
- "@quenty/maid": "^3.5.0",
38
- "@quenty/observablecollection": "^12.22.1",
39
- "@quenty/rx": "^13.19.0",
40
- "@quenty/rxbinderutils": "^14.22.0",
41
- "@quenty/rxsignal": "^7.19.0",
42
- "@quenty/servicebag": "^11.13.1",
43
- "@quenty/signal": "^7.11.1",
44
- "@quenty/spring": "^10.9.0",
45
- "@quenty/string": "^3.3.3",
46
- "@quenty/table": "^3.8.0",
47
- "@quenty/tie": "^10.22.0",
48
- "@quenty/valuebaseutils": "^13.19.0",
49
- "@quenty/valueobject": "^13.19.0",
27
+ "@quenty/adorneedata": "7.20.1-canary.ff3c47a.0",
28
+ "@quenty/attributeutils": "14.19.1-canary.ff3c47a.0",
29
+ "@quenty/baseobject": "10.9.0",
30
+ "@quenty/binder": "14.22.1-canary.ff3c47a.0",
31
+ "@quenty/brio": "14.19.1-canary.ff3c47a.0",
32
+ "@quenty/defaultvalueutils": "1.2.2",
33
+ "@quenty/ducktype": "5.9.0",
34
+ "@quenty/instanceutils": "13.19.1-canary.ff3c47a.0",
35
+ "@quenty/jsonutils": "10.12.0",
36
+ "@quenty/linkutils": "13.19.1-canary.ff3c47a.0",
37
+ "@quenty/loader": "10.9.0",
38
+ "@quenty/maid": "3.5.0",
39
+ "@quenty/observablecollection": "12.22.2-canary.ff3c47a.0",
40
+ "@quenty/remoting": "12.20.1-canary.ff3c47a.0",
41
+ "@quenty/rx": "13.19.1-canary.ff3c47a.0",
42
+ "@quenty/rxbinderutils": "14.22.1-canary.ff3c47a.0",
43
+ "@quenty/rxsignal": "7.19.1-canary.ff3c47a.0",
44
+ "@quenty/servicebag": "11.13.1",
45
+ "@quenty/signal": "7.11.1",
46
+ "@quenty/spring": "10.9.0",
47
+ "@quenty/string": "3.3.3",
48
+ "@quenty/table": "3.8.0",
49
+ "@quenty/tie": "10.22.1-canary.ff3c47a.0",
50
+ "@quenty/valuebaseutils": "13.19.1-canary.ff3c47a.0",
51
+ "@quenty/valueobject": "13.19.1-canary.ff3c47a.0",
50
52
  "@quentystudios/t": "^3.0.0"
51
53
  },
52
54
  "publishConfig": {
53
55
  "access": "public"
54
56
  },
55
- "gitHead": "697bad47e4b554e9f674e1aebefc65a99b84e41d"
57
+ "gitHead": "ff3c47af4e5b23f912aa09a0784849751d5736e1"
56
58
  }
@@ -114,6 +114,13 @@ end
114
114
  function RoguePropertyArrayUtils.getDefaultValueMapFromContainer(container: Instance)
115
115
  local value = {}
116
116
 
117
+ -- This is a hack, kinda
118
+ for attributeKey, attributeValue in container:GetAttributes() do
119
+ if attributeKey ~= "HasInitializedArrayComponent" then
120
+ value[attributeKey] = attributeValue
121
+ end
122
+ end
123
+
117
124
  for _, item in container:GetChildren() do
118
125
  local index = RoguePropertyArrayUtils.getIndexFromName(item.Name)
119
126
  if index then
@@ -7,6 +7,7 @@ local require = require(script.Parent.loader).load(script)
7
7
  local DuckTypeUtils = require("DuckTypeUtils")
8
8
  local RogueProperty = require("RogueProperty")
9
9
  local RoguePropertyCacheService = require("RoguePropertyCacheService")
10
+ local RoguePropertyConstants = require("RoguePropertyConstants")
10
11
  local RoguePropertyUtils = require("RoguePropertyUtils")
11
12
  local ServiceBag = require("ServiceBag")
12
13
  local ValueBaseUtils = require("ValueBaseUtils")
@@ -36,6 +37,10 @@ function RoguePropertyDefinition.isRoguePropertyDefinition(value: any): boolean
36
37
  return DuckTypeUtils.isImplementation(RoguePropertyDefinition, value)
37
38
  end
38
39
 
40
+ function RoguePropertyDefinition:HasChildren(): boolean
41
+ return false
42
+ end
43
+
39
44
  --[=[
40
45
  @param serviceBag ServiceBag
41
46
  @param adornee Instance
@@ -61,12 +66,21 @@ end
61
66
  function RoguePropertyDefinition:GetOrCreateInstance(parent: Instance)
62
67
  assert(typeof(parent) == "Instance", "Bad parent")
63
68
 
64
- return ValueBaseUtils.getOrCreateValue(
69
+ -- Note, in forcing the creation, we move to an attribute
70
+ local original = parent:GetAttribute(self:GetName())
71
+ local created = ValueBaseUtils.getOrCreateValue(
65
72
  parent,
66
73
  self:GetStorageInstanceType(),
67
74
  self:GetName(),
68
75
  self:GetEncodedDefaultValue()
69
76
  )
77
+
78
+ if original ~= nil and original ~= RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE then
79
+ created.Value = original
80
+ end
81
+
82
+ parent:SetAttribute(self:GetName(), RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE)
83
+ return created
70
84
  end
71
85
 
72
86
  function RoguePropertyDefinition:SetParentPropertyTableDefinition(parentPropertyTableDefinition)
@@ -12,8 +12,6 @@ local RoguePropertyDefinition = require("RoguePropertyDefinition")
12
12
  local RoguePropertyDefinitionArrayHelper = require("RoguePropertyDefinitionArrayHelper")
13
13
  local RoguePropertyService = require("RoguePropertyService")
14
14
  local RoguePropertyTable = require("RoguePropertyTable")
15
- local RxBrioUtils = require("RxBrioUtils")
16
- local RxInstanceUtils = require("RxInstanceUtils")
17
15
  local ServiceBag = require("ServiceBag")
18
16
  local Set = require("Set")
19
17
  local Table = require("Table")
@@ -133,6 +131,11 @@ function RoguePropertyTableDefinition:CanAssign(mainValue, strict: boolean): (bo
133
131
  return false, string.format("Bad index %q, %q is not an array", tostring(key), self:GetFullName())
134
132
  end
135
133
  else
134
+ if self._arrayDefinitionHelper then
135
+ -- TODO: Maybe this check is actually wrong...? we might need to remove it.
136
+ return false, string.format("Bad index %q, %q is an array", tostring(key), self:GetFullName())
137
+ end
138
+
136
139
  if self._definitionMap[key] then
137
140
  local canAssign, message = self._definitionMap[key]:CanAssign(value, strict)
138
141
  if not canAssign then
@@ -176,6 +179,10 @@ function RoguePropertyTableDefinition:GetDefinitionMap()
176
179
  return self._definitionMap
177
180
  end
178
181
 
182
+ function RoguePropertyTableDefinition:HasChildren()
183
+ return true
184
+ end
185
+
179
186
  --[=[
180
187
  Gets the RoguePropertyDefinition if it exists
181
188
  @param propertyName
@@ -223,27 +230,21 @@ RoguePropertyTableDefinition.GetPropertyTable = RoguePropertyTableDefinition.Get
223
230
  --[=[
224
231
  Observes the current container while it exists for the given adornee.
225
232
 
226
- @param adornee Instance
227
- @param canInitialize boolean
228
233
  @return Observable<Brio<Folder>>
229
234
  ]=]
230
- function RoguePropertyTableDefinition:ObserveContainerBrio(adornee: Instance, canInitialize)
235
+ function RoguePropertyTableDefinition:ObserveContainerBrio(
236
+ serviceBag: ServiceBag.ServiceBag,
237
+ adornee: Instance,
238
+ canInitialize: boolean
239
+ )
231
240
  assert(typeof(adornee) == "Instance", "Bad adornee")
232
241
  assert(type(canInitialize) == "boolean", "Bad canInitialize")
233
242
 
234
- -- TODO: Optimize so we aren't duplcating this logic each time we index a property
235
- self:GetContainer(adornee, canInitialize)
243
+ local found = self:Get(serviceBag, adornee)
236
244
 
237
- local parentDefinition = self:GetParentPropertyDefinition()
238
- if parentDefinition then
239
- return parentDefinition:ObserveContainerBrio(adornee, canInitialize):Pipe({
240
- RxBrioUtils.switchMapBrio(function(parent)
241
- return RxInstanceUtils.observeLastNamedChildBrio(parent, "Folder", self:GetName())
242
- end),
243
- })
244
- else
245
- return RxInstanceUtils.observeLastNamedChildBrio(adornee, "Folder", self:GetName())
246
- end
245
+ -- TODO: caninitialize is broken
246
+
247
+ return found:ObserveContainerBrio(canInitialize)
247
248
  end
248
249
 
249
250
  --[=[
@@ -252,27 +253,17 @@ end
252
253
  @param canInitialize boolean
253
254
  @return Folder?
254
255
  ]=]
255
- function RoguePropertyTableDefinition:GetContainer(adornee: Instance, canInitialize): Folder?
256
+ function RoguePropertyTableDefinition:GetContainer(
257
+ serviceBag: ServiceBag.ServiceBag,
258
+ adornee: Instance,
259
+ canInitialize: boolean
260
+ ): Folder?
256
261
  assert(typeof(adornee) == "Instance", "Bad adornee")
257
262
  assert(type(canInitialize) == "boolean", "Bad canInitialize")
258
263
 
259
- local parent
260
- local parentDefinition = self:GetParentPropertyDefinition()
261
- if parentDefinition then
262
- parent = parentDefinition:GetContainer(adornee, canInitialize)
263
- else
264
- parent = adornee
265
- end
266
-
267
- if not parent then
268
- return nil
269
- end
264
+ local found = self:Get(serviceBag, adornee)
270
265
 
271
- if canInitialize then
272
- return self:GetOrCreateInstance(parent)
273
- else
274
- return parent:FindFirstChild(self:GetName())
275
- end
266
+ return found:GetContainer()
276
267
  end
277
268
 
278
269
  function RoguePropertyTableDefinition:GetOrCreateInstance(parent)
@@ -4,22 +4,31 @@
4
4
 
5
5
  local require = require(script.Parent.loader).load(script)
6
6
 
7
+ local AttributeValue = require("AttributeValue")
7
8
  local Maid = require("Maid")
8
9
  local Observable = require("Observable")
9
10
  local ObservableSortedList = require("ObservableSortedList")
10
11
  local RogueAdditive = require("RogueAdditive")
11
12
  local RogueModifierInterface = require("RogueModifierInterface")
12
13
  local RogueMultiplier = require("RogueMultiplier")
14
+ local RoguePropertyBaseValueTypeUtils = require("RoguePropertyBaseValueTypeUtils")
15
+ local RoguePropertyBaseValueTypes = require("RoguePropertyBaseValueTypes")
16
+ local RoguePropertyConstants = require("RoguePropertyConstants")
13
17
  local RoguePropertyModifierData = require("RoguePropertyModifierData")
14
18
  local RoguePropertyUtils = require("RoguePropertyUtils")
15
19
  local RogueSetter = require("RogueSetter")
16
20
  local Rx = require("Rx")
21
+ local RxAttributeUtils = require("RxAttributeUtils")
17
22
  local RxBrioUtils = require("RxBrioUtils")
18
23
  local RxInstanceUtils = require("RxInstanceUtils")
19
24
  local RxSignal = require("RxSignal")
20
25
  local ServiceBag = require("ServiceBag")
26
+ local TieRealmService = require("TieRealmService")
21
27
  local ValueBaseUtils = require("ValueBaseUtils")
22
28
 
29
+ local ONLY_USE_INSTANCES = false
30
+ local LOCAL_MODIFIER_CONTAINER_CLASS_NAME = "Camera"
31
+
23
32
  local RogueProperty = {}
24
33
  RogueProperty.ClassName = "RogueProperty"
25
34
  RogueProperty.__index = RogueProperty
@@ -28,6 +37,7 @@ function RogueProperty.new(adornee: Instance, serviceBag: ServiceBag.ServiceBag,
28
37
  local self = {}
29
38
 
30
39
  self._serviceBag = assert(serviceBag, "No serviceBag")
40
+ self._tieRealmService = self._serviceBag:GetService(TieRealmService)
31
41
 
32
42
  self._adornee = assert(adornee, "Bad adornee")
33
43
  self._definition = assert(definition, "Bad definition")
@@ -43,7 +53,7 @@ function RogueProperty:SetCanInitialize(canInitialize: boolean)
43
53
  rawset(self, "_canInitialize", canInitialize)
44
54
 
45
55
  if canInitialize then
46
- self:GetBaseValueObject()
56
+ self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
47
57
  end
48
58
  end
49
59
  end
@@ -56,66 +66,126 @@ function RogueProperty:CanInitialize()
56
66
  return rawget(self, "_canInitialize")
57
67
  end
58
68
 
59
- function RogueProperty:GetBaseValueObject()
60
- local cached = rawget(self, "_baseValueCache")
61
- local adornee = rawget(self, "_adornee")
62
- local definition = rawget(self, "_definition")
63
-
64
- if cached and cached:IsDescendantOf(adornee) then
65
- return cached
69
+ function RogueProperty:_getParentContainer()
70
+ local parentDefinition = self._definition:GetParentPropertyDefinition()
71
+ if parentDefinition then
72
+ return parentDefinition:Get(self._serviceBag, self._adornee):GetContainer()
73
+ else
74
+ return self._adornee
66
75
  end
76
+ end
67
77
 
68
- local parent
69
- local parentDefinition = definition:GetParentPropertyDefinition()
78
+ function RogueProperty:GetBaseValueObject(roguePropertyBaseValueType)
79
+ assert(
80
+ RoguePropertyBaseValueTypeUtils.isRoguePropertyBaseValueType(roguePropertyBaseValueType),
81
+ "Bad roguePropertyBaseValueType"
82
+ )
70
83
 
71
- if parentDefinition then
72
- parent = parentDefinition:GetContainer(adornee, self:CanInitialize())
73
- else
74
- parent = adornee
84
+ -- TODO: check this caching!
85
+ local cachedInstance = rawget(self, "_baseValueInstanceCache")
86
+ local adornee = rawget(self, "_adornee")
87
+
88
+ if cachedInstance then
89
+ if cachedInstance:IsDescendantOf(adornee) then
90
+ return cachedInstance
91
+ else
92
+ rawset(self, "_baseValueInstanceCache", nil)
93
+ end
75
94
  end
76
95
 
96
+ local definition = rawget(self, "_definition")
97
+
98
+ local parent = self:_getParentContainer()
77
99
  if not parent then
78
100
  return nil
79
101
  end
80
102
 
103
+ local currentAttribute = parent:GetAttribute(definition:GetName())
104
+ local instanceRequired = roguePropertyBaseValueType == RoguePropertyBaseValueTypes.INSTANCE
105
+ or definition:HasChildren()
106
+ or currentAttribute == RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE
107
+ or ONLY_USE_INSTANCES
108
+
109
+ -- Short circuit querying datamodel
110
+ local hasValidAttribute = currentAttribute ~= nil
111
+ and currentAttribute ~= RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE
112
+ if hasValidAttribute and not instanceRequired then
113
+ -- TODO: Interface/avoid attribute value/cache
114
+ return AttributeValue.new(parent, definition:GetName(), definition:GetEncodedDefaultValue())
115
+ end
116
+
81
117
  local found
82
- if self:CanInitialize() then
118
+ if self:CanInitialize() and instanceRequired then
83
119
  found = definition:GetOrCreateInstance(parent)
84
120
  else
85
121
  found = parent:FindFirstChild(definition:GetName())
86
122
  end
87
123
 
88
- -- Store cache
89
- rawset(self, "_baseValueCache", found)
90
-
91
- return found
124
+ if found then
125
+ rawset(self, "_baseValueInstanceCache", found)
126
+ parent:SetAttribute(definition:GetName(), RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE)
127
+ return found
128
+ elseif not instanceRequired then
129
+ if self:CanInitialize() then
130
+ return AttributeValue.new(parent, definition:GetName(), definition:GetEncodedDefaultValue())
131
+ else
132
+ if currentAttribute ~= nil then
133
+ return AttributeValue.new(parent, definition:GetName(), definition:GetEncodedDefaultValue())
134
+ else
135
+ return nil
136
+ end
137
+ end
138
+ else
139
+ return nil
140
+ end
92
141
  end
93
142
 
94
- function RogueProperty:_observeBaseValueBrio()
143
+ function RogueProperty:_observeParentBrio()
144
+ local cache = rawget(self, "_observeParentBrioCache")
145
+ if cache then
146
+ return cache
147
+ end
148
+
95
149
  local parentDefinition = self._definition:GetParentPropertyDefinition()
96
150
  if parentDefinition then
97
- return parentDefinition:ObserveContainerBrio(self._adornee, self:CanInitialize()):Pipe({
98
- RxBrioUtils.switchMapBrio(function(container)
99
- return RxInstanceUtils.observeLastNamedChildBrio(
100
- container,
101
- self._definition:GetStorageInstanceType(),
102
- self._definition:GetName()
103
- )
104
- end),
105
- })
151
+ local parentTable = parentDefinition:Get(self._serviceBag, self._adornee)
152
+ cache = parentTable:ObserveContainerBrio()
106
153
  else
107
- return RxInstanceUtils.observeLastNamedChildBrio(
108
- self._adornee,
109
- self._definition:GetStorageInstanceType(),
110
- self._definition:GetName()
111
- )
154
+ -- TODO: Performance very sad, unneeded table construction
155
+ cache = RxBrioUtils.of(self._adornee)
112
156
  end
157
+
158
+ rawset(self, "_observeParentBrioCache", cache)
159
+
160
+ return cache
161
+ end
162
+
163
+ function RogueProperty:_observeBaseValueBrio()
164
+ local cache = rawget(self, "_observeBaseValueBrioCache")
165
+ if cache then
166
+ return cache
167
+ end
168
+
169
+ cache = self:_observeParentBrio():Pipe({
170
+ RxBrioUtils.switchMapBrio(function(container)
171
+ return RxInstanceUtils.observeLastNamedChildBrio(
172
+ container,
173
+ self._definition:GetStorageInstanceType(),
174
+ self._definition:GetName()
175
+ )
176
+ end),
177
+ Rx.cache(),
178
+ })
179
+
180
+ rawset(self, "_observeBaseValueBrioCache", cache)
181
+
182
+ return cache
113
183
  end
114
184
 
115
185
  function RogueProperty:SetBaseValue(value)
116
186
  assert(self._definition:CanAssign(value, false)) -- This has a good error message
117
187
 
118
- local baseValue = self:GetBaseValueObject()
188
+ local baseValue = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
119
189
  if baseValue then
120
190
  baseValue.Value = self:_encodeValue(value)
121
191
  else
@@ -129,10 +199,96 @@ function RogueProperty:SetBaseValue(value)
129
199
  end
130
200
  end
131
201
 
202
+ function RogueProperty:_getModifierParentContainerForNewModifier()
203
+ if self:CanInitialize() then
204
+ return self:GetBaseValueObject(RoguePropertyBaseValueTypes.INSTANCE)
205
+ end
206
+
207
+ local found = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
208
+ if found then
209
+ if typeof(found) == "Instance" then
210
+ return found
211
+ end
212
+ end
213
+
214
+ -- else, search for local parent
215
+ local parent = self:_getParentContainer()
216
+ if not parent then
217
+ return nil
218
+ end
219
+
220
+ -- TODO: Maybe we should use this before anything else on the client...?
221
+ -- TODO: Maybe only do this on the client?
222
+ local name = self:_getLocalModifierParentName()
223
+
224
+ found = parent:FindFirstChild(name)
225
+ if found then
226
+ return found
227
+ end
228
+
229
+ local localParent = Instance.new(LOCAL_MODIFIER_CONTAINER_CLASS_NAME)
230
+ localParent.Name = name
231
+ localParent.Archivable = false
232
+ localParent.Parent = parent
233
+
234
+ return localParent
235
+ end
236
+
237
+ function RogueProperty:_getLocalModifierParentName()
238
+ return self._definition:GetName() .. "_LocalModifiers"
239
+ end
240
+
241
+ function RogueProperty:_getModifierParentContainerList()
242
+ local containerList = {}
243
+
244
+ if self:CanInitialize() then
245
+ local found = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
246
+ if typeof(found) == "Instance" then
247
+ table.insert(containerList, found)
248
+ end
249
+ end
250
+
251
+ local parent = self:_getParentContainer()
252
+ if parent then
253
+ local name = self:_getLocalModifierParentName()
254
+
255
+ local localParent = parent:FindFirstChild(name)
256
+ if localParent then
257
+ table.insert(containerList, localParent)
258
+ end
259
+ end
260
+
261
+ return containerList
262
+ end
263
+
264
+ function RogueProperty:_observeModifierContainersBrio()
265
+ local name = self:_getLocalModifierParentName()
266
+
267
+ return self:_observeParentBrio():Pipe({
268
+ RxBrioUtils.switchMapBrio(function(parent)
269
+ return Rx.merge({
270
+ -- The main container
271
+ RxAttributeUtils.observeAttributeBrio(parent, self._definition:GetName(), function(attribute)
272
+ return attribute == RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE
273
+ end):Pipe({
274
+ Rx.switchMap(function()
275
+ return self:_observeBaseValueBrio()
276
+ end),
277
+ }),
278
+
279
+ -- The modifier parent
280
+ RxInstanceUtils.observeChildrenBrio(parent, function(child)
281
+ return child:IsA(LOCAL_MODIFIER_CONTAINER_CLASS_NAME) and child.Name == name
282
+ end),
283
+ })
284
+ end),
285
+ })
286
+ end
287
+
132
288
  function RogueProperty:SetValue(value)
133
289
  assert(self._definition:CanAssign(value, false)) -- This has a good error message
134
290
 
135
- local baseValue = self:GetBaseValueObject()
291
+ local baseValue = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
136
292
  if not baseValue then
137
293
  warn(
138
294
  string.format(
@@ -155,7 +311,7 @@ function RogueProperty:SetValue(value)
155
311
  end
156
312
 
157
313
  function RogueProperty:GetBaseValue()
158
- local baseValue = self:GetBaseValueObject()
314
+ local baseValue = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
159
315
  if baseValue then
160
316
  return self:_decodeValue(baseValue.Value)
161
317
  else
@@ -164,7 +320,7 @@ function RogueProperty:GetBaseValue()
164
320
  end
165
321
 
166
322
  function RogueProperty:GetValue()
167
- local propObj = self:GetBaseValueObject()
323
+ local propObj = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
168
324
  if not propObj then
169
325
  return self._definition:GetDefaultValue()
170
326
  end
@@ -183,63 +339,99 @@ function RogueProperty:GetDefinition()
183
339
  end
184
340
 
185
341
  function RogueProperty:GetRogueModifiers()
186
- local propObj = self:GetBaseValueObject()
187
- if not propObj then
188
- return {}
342
+ local modifierList = {}
343
+
344
+ for _, parent in self:_getModifierParentContainerList() do
345
+ for _, modifier in RogueModifierInterface:GetChildren(parent, self._tieRealmService:GetTieRealm()) do
346
+ table.insert(modifierList, modifier)
347
+ end
189
348
  end
190
349
 
191
- local found = RogueModifierInterface:GetChildren(propObj)
350
+ if not next(modifierList) then
351
+ return modifierList
352
+ end
192
353
 
193
- local orders = {}
194
- for _, item in found do
195
- orders[item] = item.Order.Value
354
+ local orderMap = {}
355
+ for _, item in modifierList do
356
+ orderMap[item] = item.Order.Value
196
357
  end
197
- table.sort(found, function(a, b)
198
- return orders[a] < orders[b]
358
+ table.sort(modifierList, function(a, b)
359
+ return orderMap[a] < orderMap[b]
199
360
  end)
200
361
 
201
- return found
362
+ return modifierList
202
363
  end
203
364
 
204
365
  function RogueProperty:_observeModifierSortedList()
205
- return Observable.new(function(sub)
366
+ local cache = rawget(self, "_observeModifierSortedListCache")
367
+ if cache then
368
+ return cache
369
+ end
370
+
371
+ cache = Observable.new(function(sub)
206
372
  local topMaid = Maid.new()
207
373
 
208
374
  local sortedList = topMaid:Add(ObservableSortedList.new())
209
375
 
210
- topMaid:GiveTask(self:_observeBaseValueBrio()
376
+ topMaid:GiveTask(self:_observeModifierContainersBrio()
211
377
  :Pipe({
212
378
  RxBrioUtils.flatMapBrio(function(baseValue)
213
- return RogueModifierInterface:ObserveChildrenBrio(baseValue)
379
+ return RogueModifierInterface:ObserveChildrenBrio(baseValue, self._tieRealmService:GetTieRealm())
214
380
  end),
215
381
  })
216
382
  :Subscribe(function(brio)
217
383
  if brio:IsDead() then
218
384
  return
219
385
  end
386
+
220
387
  local maid, rogueModifier = brio:ToMaidAndValue()
221
388
  maid:GiveTask(sortedList:Add(rogueModifier, rogueModifier.Order:Observe()))
222
389
  end))
223
390
 
391
+ debug.profilebegin("sorted_list_add")
224
392
  sub:Fire(sortedList)
393
+ debug.profileend()
225
394
 
226
395
  return topMaid
227
396
  end):Pipe({
228
397
  Rx.cache(),
229
398
  })
399
+
400
+ rawset(self, "_observeModifierSortedListCache", cache)
401
+ return cache
230
402
  end
231
403
 
232
404
  function RogueProperty:Observe()
233
- local observeInitialValue = self:_observeBaseValueBrio():Pipe({
234
- RxBrioUtils.switchMapBrio(function(baseValue)
235
- return RxInstanceUtils.observeProperty(baseValue, "Value")
405
+ local cache = rawget(self, "_observeCache")
406
+ if cache then
407
+ return cache
408
+ end
409
+
410
+ local observeInitialValue = self:_observeParentBrio():Pipe({
411
+ RxBrioUtils.switchMapBrio(function(parent)
412
+ return RxAttributeUtils.observeAttribute(parent, self._definition:GetName())
413
+ end),
414
+ RxBrioUtils.switchMapBrio(function(attribute)
415
+ if attribute == RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE then
416
+ return self:_observeBaseValueBrio():Pipe({
417
+ RxBrioUtils.switchMapBrio(function(baseValue)
418
+ return RxInstanceUtils.observeProperty(baseValue, "Value")
419
+ end),
420
+ })
421
+ end
422
+ local decoded = self:_decodeValue(attribute)
423
+ if decoded == nil then
424
+ return Rx.of(self._definition:GetDefaultValue())
425
+ else
426
+ return Rx.of(decoded)
427
+ end
236
428
  end),
237
429
  RxBrioUtils.emitOnDeath(self._definition:GetDefaultValue()),
238
430
  Rx.defaultsTo(self._definition:GetDefaultValue()),
239
431
  Rx.distinct(),
240
432
  })
241
433
 
242
- return self:_observeModifierSortedList():Pipe({
434
+ cache = self:_observeModifierSortedList():Pipe({
243
435
  Rx.switchMap(function(sortedList)
244
436
  return sortedList:Observe()
245
437
  end),
@@ -250,7 +442,10 @@ function RogueProperty:Observe()
250
442
  end
251
443
  return current
252
444
  end),
445
+ Rx.cache(),
253
446
  })
447
+ rawset(self, "_observeCache", cache)
448
+ return cache
254
449
  end
255
450
 
256
451
  function RogueProperty:ObserveBrio(predicate)
@@ -259,14 +454,14 @@ function RogueProperty:ObserveBrio(predicate)
259
454
  })
260
455
  end
261
456
 
262
- function RogueProperty:CreateMultiplier(amount, source)
457
+ function RogueProperty:CreateMultiplier(amount: number, source)
263
458
  assert(type(amount) == "number", "Bad amount")
264
459
 
265
- local baseValue = self:GetBaseValueObject()
266
- if not baseValue then
460
+ local modifierParent = self:_getModifierParentContainerForNewModifier()
461
+ if not modifierParent then
267
462
  warn(
268
463
  string.format(
269
- "[RogueProperty.CreateMultiplier] - Failed to get the baseValue for %q on %q",
464
+ "[RogueProperty.CreateMultiplier] - Failed to get the modifierParent for %q on %q",
270
465
  self._definition:GetFullName(),
271
466
  self._adornee:GetFullName()
272
467
  )
@@ -289,7 +484,7 @@ function RogueProperty:CreateMultiplier(amount, source)
289
484
 
290
485
  RogueMultiplier:Tag(multiplier)
291
486
 
292
- multiplier.Parent = baseValue
487
+ multiplier.Parent = modifierParent
293
488
 
294
489
  return multiplier
295
490
  end
@@ -297,11 +492,11 @@ end
297
492
  function RogueProperty:CreateAdditive(amount: number, source)
298
493
  assert(type(amount) == "number", "Bad amount")
299
494
 
300
- local baseValue = self:GetBaseValueObject()
301
- if not baseValue then
495
+ local modifierParent = self:_getModifierParentContainerForNewModifier()
496
+ if not modifierParent then
302
497
  warn(
303
498
  string.format(
304
- "[RogueProperty.CreateAdditive] - Failed to get the baseValue for %q on %q",
499
+ "[RogueProperty.CreateAdditive] - Failed to get the modifierParent for %q on %q",
305
500
  self._definition:GetFullName(),
306
501
  self._adornee:GetFullName()
307
502
  )
@@ -325,17 +520,17 @@ function RogueProperty:CreateAdditive(amount: number, source)
325
520
 
326
521
  RogueAdditive:Tag(additive)
327
522
 
328
- additive.Parent = baseValue
523
+ additive.Parent = modifierParent
329
524
 
330
525
  return additive
331
526
  end
332
527
 
333
528
  function RogueProperty:GetNamedAdditive(name, source)
334
- local baseValue = self:GetBaseValueObject()
335
- if not baseValue then
529
+ local modifierParent = self:_getModifierParentContainerForNewModifier()
530
+ if not modifierParent then
336
531
  warn(
337
532
  string.format(
338
- "[RogueProperty.GetNamedAdditive] - Failed to get the baseValue for %q on %q",
533
+ "[RogueProperty.GetNamedAdditive] - Failed to get the modifierParent for %q on %q",
339
534
  self._definition:GetFullName(),
340
535
  self._adornee:GetFullName()
341
536
  )
@@ -345,7 +540,7 @@ function RogueProperty:GetNamedAdditive(name, source)
345
540
 
346
541
  local searchName = name .. "Additive"
347
542
 
348
- local found = baseValue:FindFirstChild(searchName)
543
+ local found = modifierParent:FindFirstChild(searchName)
349
544
  if found then
350
545
  return found
351
546
  end
@@ -356,11 +551,11 @@ function RogueProperty:GetNamedAdditive(name, source)
356
551
  end
357
552
 
358
553
  function RogueProperty:CreateSetter(value, source)
359
- local baseValue = self:GetBaseValueObject()
360
- if not baseValue then
554
+ local modifierParent = self:_getModifierParentContainerForNewModifier()
555
+ if not modifierParent then
361
556
  warn(
362
557
  string.format(
363
- "[RogueProperty.CreateSetter] - Failed to get the baseValue for %q on %q",
558
+ "[RogueProperty.CreateSetter] - Failed to get the modifierParent for %q on %q",
364
559
  self._definition:GetFullName(),
365
560
  self._adornee:GetFullName()
366
561
  )
@@ -384,18 +579,18 @@ function RogueProperty:CreateSetter(value, source)
384
579
 
385
580
  RogueSetter:Tag(setter)
386
581
 
387
- setter.Parent = baseValue
582
+ setter.Parent = modifierParent
388
583
 
389
584
  return setter
390
585
  end
391
586
 
392
587
  function RogueProperty:__index(index)
393
- if index == "Value" then
588
+ if RogueProperty[index] then
589
+ return RogueProperty[index]
590
+ elseif index == "Value" then
394
591
  return self:GetValue()
395
592
  elseif index == "Changed" then
396
593
  return self:GetChangedEvent()
397
- elseif RogueProperty[index] then
398
- return RogueProperty[index]
399
594
  else
400
595
  error(string.format("Bad index %q", tostring(index)))
401
596
  end
@@ -6,6 +6,7 @@ local require = require(script.Parent.loader).load(script)
6
6
 
7
7
  local BaseObject = require("BaseObject")
8
8
  local RoguePropertyArrayUtils = require("RoguePropertyArrayUtils")
9
+ local RoguePropertyBaseValueTypes = require("RoguePropertyBaseValueTypes")
9
10
  local Rx = require("Rx")
10
11
 
11
12
  local RoguePropertyArrayHelper = setmetatable({}, BaseObject)
@@ -113,7 +114,11 @@ function RoguePropertyArrayHelper:SetArrayBaseData(arrayData)
113
114
  else
114
115
  -- Cleanup this old one and setup a new one
115
116
  if available[index] then
116
- available[index]:GetBaseValueObject():Destroy()
117
+ local found = available[index]:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
118
+ if typeof(found) == "Instance" then
119
+ found:Destroy()
120
+ return
121
+ end
117
122
  end
118
123
 
119
124
  local property = definition:Get(self._serviceBag, adornee)
@@ -148,7 +153,11 @@ function RoguePropertyArrayHelper:SetArrayData(arrayData)
148
153
  else
149
154
  -- Cleanup this old one and setup a new one
150
155
  if available[index] then
151
- available[index]:GetBaseValueObject():Destroy()
156
+ local found = available[index]:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
157
+ if typeof(found) == "Instance" then
158
+ found:Destroy()
159
+ return
160
+ end
152
161
  end
153
162
 
154
163
  local property = definition:Get(self._serviceBag, adornee)
@@ -7,6 +7,8 @@ local require = require(script.Parent.loader).load(script)
7
7
  local RogueProperty = require("RogueProperty")
8
8
  local RoguePropertyArrayHelper = require("RoguePropertyArrayHelper")
9
9
  local Rx = require("Rx")
10
+ local RxBrioUtils = require("RxBrioUtils")
11
+ local RxInstanceUtils = require("RxInstanceUtils")
10
12
  local ServiceBag = require("ServiceBag")
11
13
 
12
14
  local RoguePropertyTable = {} -- inherits from RogueProperty
@@ -29,29 +31,70 @@ end
29
31
  function RoguePropertyTable:SetCanInitialize(canInitialize: boolean)
30
32
  assert(type(canInitialize) == "boolean", "Bad canInitialize")
31
33
 
32
- RogueProperty.SetCanInitialize(self, canInitialize)
34
+ if self:CanInitialize() ~= canInitialize then
35
+ RogueProperty.SetCanInitialize(self, canInitialize)
33
36
 
34
- for _, property in self:GetRogueProperties() do
35
- property:SetCanInitialize(canInitialize)
36
- end
37
+ for _, property in self:GetRogueProperties() do
38
+ property:SetCanInitialize(canInitialize)
39
+ end
37
40
 
38
- local arrayHelper = rawget(self, "_arrayHelper")
39
- if arrayHelper then
40
- arrayHelper:SetCanInitialize(canInitialize)
41
+ local arrayHelper = rawget(self, "_arrayHelper")
42
+ if arrayHelper then
43
+ arrayHelper:SetCanInitialize(canInitialize)
44
+ end
41
45
  end
42
46
  end
43
47
 
44
48
  function RoguePropertyTable:ObserveContainerBrio()
45
- return self._definition:ObserveContainerBrio(self._adornee, self:CanInitialize())
49
+ local cache = rawget(self, "_observeContainerCache")
50
+ if cache then
51
+ return cache
52
+ end
53
+
54
+ local parentDefinition = self._definition:GetParentPropertyDefinition()
55
+ if parentDefinition then
56
+ local parentTable = parentDefinition:Get(self._serviceBag, self._adornee)
57
+ parentTable:GetContainer()
58
+
59
+ cache = parentTable:ObserveContainerBrio():Pipe({
60
+ RxBrioUtils.switchMapBrio(function(parent)
61
+ return RxInstanceUtils.observeLastNamedChildBrio(parent, "Folder", self._definition:GetName())
62
+ end),
63
+ Rx.cache(),
64
+ })
65
+ else
66
+ cache = RxInstanceUtils.observeLastNamedChildBrio(self._adornee, "Folder", self._definition:GetName()):Pipe({
67
+ Rx.cache(),
68
+ })
69
+ end
70
+
71
+ cache = cache
72
+ rawset(self, "_observeContainerCache", cache)
73
+ return cache
46
74
  end
47
75
 
48
76
  function RoguePropertyTable:GetContainer(): Instance
49
77
  local cached = rawget(self, "_containerCache")
50
- if cached and cached:IsDescendantOf(self._adornee) then
51
- return cached
78
+ if cached then
79
+ if cached:IsDescendantOf(self._adornee) then
80
+ return cached
81
+ else
82
+ rawset(self, "_containerCache", nil)
83
+ end
84
+ end
85
+
86
+ local parent
87
+ local parentDefinition = self._definition:GetParentPropertyDefinition()
88
+ if parentDefinition then
89
+ local parentTable = parentDefinition:Get(self._serviceBag, self._adornee)
90
+ parent = parentTable:GetContainer()
91
+ else
92
+ parent = self._adornee
52
93
  end
53
94
 
54
- local container = self._definition:GetContainer(self._adornee, self:CanInitialize())
95
+ local container = self._definition:GetOrCreateInstance(parent, self:CanInitialize())
96
+ container:AddTag("RoguePropertyTable")
97
+
55
98
  rawset(self, "_containerCache", container)
56
99
  return container
57
100
  end
@@ -167,6 +210,10 @@ end
167
210
 
168
211
  function RoguePropertyTable:_observeDictionary()
169
212
  -- ok, this is definitely slow
213
+ local cache = rawget(self, "_observeDictionaryCache")
214
+ if cache then
215
+ return cache
216
+ end
170
217
 
171
218
  local toObserve = {}
172
219
 
@@ -180,10 +227,16 @@ function RoguePropertyTable:_observeDictionary()
180
227
  end
181
228
 
182
229
  if next(toObserve) == nil then
183
- return Rx.of({})
230
+ cache = Rx.of({})
231
+ else
232
+ cache = Rx.combineLatest(toObserve):Pipe({
233
+ Rx.cache(),
234
+ })
184
235
  end
185
236
 
186
- return Rx.combineLatest(toObserve)
237
+ rawset(self, "_observeDictionaryCache", cache)
238
+
239
+ return cache
187
240
  end
188
241
 
189
242
  function RoguePropertyTable:GetRogueProperty(name: string)
@@ -0,0 +1,15 @@
1
+ --[=[
2
+ @class RoguePropertyBaseValueTypeUtils
3
+ ]=]
4
+
5
+ local require = require(script.Parent.loader).load(script)
6
+
7
+ local RoguePropertyBaseValueTypes = require("RoguePropertyBaseValueTypes")
8
+
9
+ local RoguePropertyBaseValueTypeUtils = {}
10
+
11
+ function RoguePropertyBaseValueTypeUtils.isRoguePropertyBaseValueType(value)
12
+ return value == RoguePropertyBaseValueTypes.INSTANCE or value == RoguePropertyBaseValueTypes.ANY
13
+ end
14
+
15
+ return RoguePropertyBaseValueTypeUtils
@@ -0,0 +1,12 @@
1
+ --[=[
2
+ @class RoguePropertyBaseValueTypes
3
+ ]=]
4
+
5
+ local require = require(script.Parent.loader).load(script)
6
+
7
+ local Table = require("Table")
8
+
9
+ return Table.readonly({
10
+ INSTANCE = "instance",
11
+ ANY = "any",
12
+ })
@@ -0,0 +1,11 @@
1
+ --[=[
2
+ @class RoguePropertyConstants
3
+ ]=]
4
+
5
+ local require = require(script.Parent.loader).load(script)
6
+
7
+ local Table = require("Table")
8
+
9
+ return Table.readonly({
10
+ INSTANCE_ATTRIBUTE_VALUE = "_DATAMODEL_INSTANCE",
11
+ })