@quenty/rogue-properties 11.25.1 → 11.25.2-canary.63ba0e0.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,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
+ ## [11.25.2-canary.63ba0e0.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/rogue-properties@11.25.1...@quenty/rogue-properties@11.25.2-canary.63ba0e0.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
+ * Fix parenting and application on client ([ff2f125](https://github.com/Quenty/NevermoreEngine/commit/ff2f1254f45289537444190bc0596f9f519c592e))
13
+ * Fix stuff ([63ba0e0](https://github.com/Quenty/NevermoreEngine/commit/63ba0e0c4b5c8bdaeb452f0b3d742b7b8bc8a7ab))
14
+ * Style stuff ([ff3c47a](https://github.com/Quenty/NevermoreEngine/commit/ff3c47af4e5b23f912aa09a0784849751d5736e1))
15
+
16
+
17
+ ### Features
18
+
19
+ * Move the rogue properties over to attributes as required ([4aa23b0](https://github.com/Quenty/NevermoreEngine/commit/4aa23b0092ba07490b615f1d8ab5920f86b78756))
20
+
21
+
22
+
23
+
24
+
6
25
  ## [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
26
 
8
27
  **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.63ba0e0.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.63ba0e0.0",
28
+ "@quenty/attributeutils": "14.19.1-canary.63ba0e0.0",
29
+ "@quenty/baseobject": "10.9.0",
30
+ "@quenty/binder": "14.22.1-canary.63ba0e0.0",
31
+ "@quenty/brio": "14.19.1-canary.63ba0e0.0",
32
+ "@quenty/defaultvalueutils": "1.2.2",
33
+ "@quenty/ducktype": "5.9.0",
34
+ "@quenty/instanceutils": "13.19.1-canary.63ba0e0.0",
35
+ "@quenty/jsonutils": "10.12.0",
36
+ "@quenty/linkutils": "13.19.1-canary.63ba0e0.0",
37
+ "@quenty/loader": "10.9.0",
38
+ "@quenty/maid": "3.5.0",
39
+ "@quenty/observablecollection": "12.22.2-canary.63ba0e0.0",
40
+ "@quenty/remoting": "12.20.1-canary.63ba0e0.0",
41
+ "@quenty/rx": "13.19.1-canary.63ba0e0.0",
42
+ "@quenty/rxbinderutils": "14.22.1-canary.63ba0e0.0",
43
+ "@quenty/rxsignal": "7.19.1-canary.63ba0e0.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.63ba0e0.0",
50
+ "@quenty/valuebaseutils": "13.19.1-canary.63ba0e0.0",
51
+ "@quenty/valueobject": "13.19.1-canary.63ba0e0.0",
50
52
  "@quentystudios/t": "^3.0.0"
51
53
  },
52
54
  "publishConfig": {
53
55
  "access": "public"
54
56
  },
55
- "gitHead": "697bad47e4b554e9f674e1aebefc65a99b84e41d"
57
+ "gitHead": "63ba0e0c4b5c8bdaeb452f0b3d742b7b8bc8a7ab"
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,59 +230,39 @@ 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(serviceBag: ServiceBag.ServiceBag, adornee: Instance)
236
+ assert(serviceBag, "No serviceBag")
231
237
  assert(typeof(adornee) == "Instance", "Bad adornee")
232
- assert(type(canInitialize) == "boolean", "Bad canInitialize")
233
-
234
- -- TODO: Optimize so we aren't duplcating this logic each time we index a property
235
- self:GetContainer(adornee, canInitialize)
236
-
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
238
+
239
+ local found = self:Get(serviceBag, adornee)
240
+
241
+ -- TODO: caninitialize is broken
242
+
243
+ return found:ObserveContainerBrio()
247
244
  end
248
245
 
249
246
  --[=[
250
247
  Gets the current container for the given adornee.
251
- @param adornee Instance
252
- @param canInitialize boolean
253
248
  @return Folder?
254
249
  ]=]
255
- function RoguePropertyTableDefinition:GetContainer(adornee: Instance, canInitialize): Folder?
250
+ function RoguePropertyTableDefinition:GetContainer(serviceBag: ServiceBag.ServiceBag, adornee: Instance): Folder?
251
+ assert(serviceBag, "No serviceBag")
256
252
  assert(typeof(adornee) == "Instance", "Bad adornee")
257
- assert(type(canInitialize) == "boolean", "Bad canInitialize")
258
253
 
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
254
+ local found = self:Get(serviceBag, adornee)
266
255
 
267
- if not parent then
268
- return nil
269
- end
256
+ return found:GetContainer()
257
+ end
270
258
 
271
- if canInitialize then
272
- return self:GetOrCreateInstance(parent)
273
- else
274
- return parent:FindFirstChild(self:GetName())
275
- end
259
+ function RoguePropertyTableDefinition:FindInstance(parent): Instance?
260
+ assert(typeof(parent) == "Instance", "Bad parent")
261
+
262
+ return parent:FindFirstChild(self:GetName())
276
263
  end
277
264
 
278
- function RoguePropertyTableDefinition:GetOrCreateInstance(parent)
265
+ function RoguePropertyTableDefinition:GetOrCreateInstance(parent): Folder
279
266
  assert(typeof(parent) == "Instance", "Bad parent")
280
267
 
281
268
  local existing = parent:FindFirstChild(self:GetName())
@@ -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,18 +199,130 @@ 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:PromiseBaseValue()
265
+ return Rx.toPromise(self:_observeBaseValueBrio():Pipe({
266
+ RxBrioUtils.flattenToValueAndNil,
267
+ Rx.where(function(value)
268
+ return value ~= nil
269
+ end),
270
+ }))
271
+ end
272
+
273
+ function RogueProperty:_observeModifierContainersBrio()
274
+ local name = self:_getLocalModifierParentName()
275
+
276
+ return self:_observeParentBrio():Pipe({
277
+ RxBrioUtils.switchMapBrio(function(parent)
278
+ return Rx.merge({
279
+ -- The main container
280
+ RxAttributeUtils.observeAttributeBrio(parent, self._definition:GetName(), function(attribute)
281
+ return attribute == RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE
282
+ end):Pipe({
283
+ Rx.switchMap(function()
284
+ return self:_observeBaseValueBrio()
285
+ end),
286
+ }),
287
+
288
+ -- The modifier parent
289
+ RxInstanceUtils.observeChildrenBrio(parent, function(child)
290
+ return child:IsA(LOCAL_MODIFIER_CONTAINER_CLASS_NAME) and child.Name == name
291
+ end),
292
+ })
293
+ end),
294
+ })
295
+ end
296
+
132
297
  function RogueProperty:SetValue(value)
133
298
  assert(self._definition:CanAssign(value, false)) -- This has a good error message
134
299
 
135
- local baseValue = self:GetBaseValueObject()
300
+ local baseValue = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
136
301
  if not baseValue then
137
- warn(
302
+ local warningText = debug.traceback(
138
303
  string.format(
139
304
  "[RogueProperty.SetValue] - Failed to get the baseValue for %q on %q",
140
305
  self._definition:GetFullName(),
141
306
  self._adornee:GetFullName()
142
307
  )
143
308
  )
309
+
310
+ local warnTask = task.delay(5, function()
311
+ warn(warningText)
312
+ end)
313
+
314
+ self:PromiseBaseValue():Then(function(thisBaseValue)
315
+ local current = value
316
+
317
+ local modifiers = self:GetRogueModifiers()
318
+ for i = #modifiers, 1, -1 do
319
+ current = modifiers[i]:GetInvertedVersion(current, value)
320
+ end
321
+
322
+ thisBaseValue.Value = self:_encodeValue(current)
323
+ task.cancel(warnTask)
324
+ end)
325
+
144
326
  return
145
327
  end
146
328
 
@@ -155,7 +337,7 @@ function RogueProperty:SetValue(value)
155
337
  end
156
338
 
157
339
  function RogueProperty:GetBaseValue()
158
- local baseValue = self:GetBaseValueObject()
340
+ local baseValue = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
159
341
  if baseValue then
160
342
  return self:_decodeValue(baseValue.Value)
161
343
  else
@@ -164,7 +346,7 @@ function RogueProperty:GetBaseValue()
164
346
  end
165
347
 
166
348
  function RogueProperty:GetValue()
167
- local propObj = self:GetBaseValueObject()
349
+ local propObj = self:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
168
350
  if not propObj then
169
351
  return self._definition:GetDefaultValue()
170
352
  end
@@ -183,63 +365,99 @@ function RogueProperty:GetDefinition()
183
365
  end
184
366
 
185
367
  function RogueProperty:GetRogueModifiers()
186
- local propObj = self:GetBaseValueObject()
187
- if not propObj then
188
- return {}
368
+ local modifierList = {}
369
+
370
+ for _, parent in self:_getModifierParentContainerList() do
371
+ for _, modifier in RogueModifierInterface:GetChildren(parent, self._tieRealmService:GetTieRealm()) do
372
+ table.insert(modifierList, modifier)
373
+ end
189
374
  end
190
375
 
191
- local found = RogueModifierInterface:GetChildren(propObj)
376
+ if not next(modifierList) then
377
+ return modifierList
378
+ end
192
379
 
193
- local orders = {}
194
- for _, item in found do
195
- orders[item] = item.Order.Value
380
+ local orderMap = {}
381
+ for _, item in modifierList do
382
+ orderMap[item] = item.Order.Value
196
383
  end
197
- table.sort(found, function(a, b)
198
- return orders[a] < orders[b]
384
+ table.sort(modifierList, function(a, b)
385
+ return orderMap[a] < orderMap[b]
199
386
  end)
200
387
 
201
- return found
388
+ return modifierList
202
389
  end
203
390
 
204
391
  function RogueProperty:_observeModifierSortedList()
205
- return Observable.new(function(sub)
392
+ local cache = rawget(self, "_observeModifierSortedListCache")
393
+ if cache then
394
+ return cache
395
+ end
396
+
397
+ cache = Observable.new(function(sub)
206
398
  local topMaid = Maid.new()
207
399
 
208
400
  local sortedList = topMaid:Add(ObservableSortedList.new())
209
401
 
210
- topMaid:GiveTask(self:_observeBaseValueBrio()
402
+ topMaid:GiveTask(self:_observeModifierContainersBrio()
211
403
  :Pipe({
212
404
  RxBrioUtils.flatMapBrio(function(baseValue)
213
- return RogueModifierInterface:ObserveChildrenBrio(baseValue)
405
+ return RogueModifierInterface:ObserveChildrenBrio(baseValue, self._tieRealmService:GetTieRealm())
214
406
  end),
215
407
  })
216
408
  :Subscribe(function(brio)
217
409
  if brio:IsDead() then
218
410
  return
219
411
  end
412
+
220
413
  local maid, rogueModifier = brio:ToMaidAndValue()
221
414
  maid:GiveTask(sortedList:Add(rogueModifier, rogueModifier.Order:Observe()))
222
415
  end))
223
416
 
417
+ debug.profilebegin("sorted_list_add")
224
418
  sub:Fire(sortedList)
419
+ debug.profileend()
225
420
 
226
421
  return topMaid
227
422
  end):Pipe({
228
423
  Rx.cache(),
229
424
  })
425
+
426
+ rawset(self, "_observeModifierSortedListCache", cache)
427
+ return cache
230
428
  end
231
429
 
232
430
  function RogueProperty:Observe()
233
- local observeInitialValue = self:_observeBaseValueBrio():Pipe({
234
- RxBrioUtils.switchMapBrio(function(baseValue)
235
- return RxInstanceUtils.observeProperty(baseValue, "Value")
431
+ local cache = rawget(self, "_observeCache")
432
+ if cache then
433
+ return cache
434
+ end
435
+
436
+ local observeInitialValue = self:_observeParentBrio():Pipe({
437
+ RxBrioUtils.switchMapBrio(function(parent)
438
+ return RxAttributeUtils.observeAttribute(parent, self._definition:GetName())
439
+ end),
440
+ RxBrioUtils.switchMapBrio(function(attribute)
441
+ if attribute == RoguePropertyConstants.INSTANCE_ATTRIBUTE_VALUE then
442
+ return self:_observeBaseValueBrio():Pipe({
443
+ RxBrioUtils.switchMapBrio(function(baseValue)
444
+ return RxInstanceUtils.observeProperty(baseValue, "Value")
445
+ end),
446
+ })
447
+ end
448
+ local decoded = self:_decodeValue(attribute)
449
+ if decoded == nil then
450
+ return Rx.of(self._definition:GetDefaultValue())
451
+ else
452
+ return Rx.of(decoded)
453
+ end
236
454
  end),
237
455
  RxBrioUtils.emitOnDeath(self._definition:GetDefaultValue()),
238
456
  Rx.defaultsTo(self._definition:GetDefaultValue()),
239
457
  Rx.distinct(),
240
458
  })
241
459
 
242
- return self:_observeModifierSortedList():Pipe({
460
+ cache = self:_observeModifierSortedList():Pipe({
243
461
  Rx.switchMap(function(sortedList)
244
462
  return sortedList:Observe()
245
463
  end),
@@ -250,7 +468,10 @@ function RogueProperty:Observe()
250
468
  end
251
469
  return current
252
470
  end),
471
+ Rx.cache(),
253
472
  })
473
+ rawset(self, "_observeCache", cache)
474
+ return cache
254
475
  end
255
476
 
256
477
  function RogueProperty:ObserveBrio(predicate)
@@ -259,20 +480,9 @@ function RogueProperty:ObserveBrio(predicate)
259
480
  })
260
481
  end
261
482
 
262
- function RogueProperty:CreateMultiplier(amount, source)
483
+ function RogueProperty:CreateMultiplier(amount: number, source)
263
484
  assert(type(amount) == "number", "Bad amount")
264
485
 
265
- local baseValue = self:GetBaseValueObject()
266
- if not baseValue then
267
- warn(
268
- string.format(
269
- "[RogueProperty.CreateMultiplier] - Failed to get the baseValue for %q on %q",
270
- self._definition:GetFullName(),
271
- self._adornee:GetFullName()
272
- )
273
- )
274
- end
275
-
276
486
  local className = ValueBaseUtils.getClassNameFromType(typeof(amount))
277
487
  if not className then
278
488
  error(string.format("[RogueProperty.CreateMultiplier] - Can't set to type %q", typeof(amount)))
@@ -289,7 +499,7 @@ function RogueProperty:CreateMultiplier(amount, source)
289
499
 
290
500
  RogueMultiplier:Tag(multiplier)
291
501
 
292
- multiplier.Parent = baseValue
502
+ self:_parentModifier(multiplier)
293
503
 
294
504
  return multiplier
295
505
  end
@@ -297,18 +507,6 @@ end
297
507
  function RogueProperty:CreateAdditive(amount: number, source)
298
508
  assert(type(amount) == "number", "Bad amount")
299
509
 
300
- local baseValue = self:GetBaseValueObject()
301
- if not baseValue then
302
- warn(
303
- string.format(
304
- "[RogueProperty.CreateAdditive] - Failed to get the baseValue for %q on %q",
305
- self._definition:GetFullName(),
306
- self._adornee:GetFullName()
307
- )
308
- )
309
- return nil
310
- end
311
-
312
510
  local className = ValueBaseUtils.getClassNameFromType(typeof(amount))
313
511
  if not className then
314
512
  error(string.format("[RogueProperty.CreateAdditive] - Can't set to type %q", typeof(amount)))
@@ -325,19 +523,22 @@ function RogueProperty:CreateAdditive(amount: number, source)
325
523
 
326
524
  RogueAdditive:Tag(additive)
327
525
 
328
- additive.Parent = baseValue
526
+ self:_parentModifier(additive)
329
527
 
330
528
  return additive
331
529
  end
332
530
 
333
531
  function RogueProperty:GetNamedAdditive(name, source)
334
- local baseValue = self:GetBaseValueObject()
335
- if not baseValue then
532
+ local modifierParent = self:_getModifierParentContainerForNewModifier()
533
+ if not modifierParent then
534
+ -- TODO: Handle this parenting scenario appropriately
336
535
  warn(
337
- string.format(
338
- "[RogueProperty.GetNamedAdditive] - Failed to get the baseValue for %q on %q",
339
- self._definition:GetFullName(),
340
- self._adornee:GetFullName()
536
+ debug.traceback(
537
+ string.format(
538
+ "[RogueProperty.GetNamedAdditive] - Failed to get the modifierParent for %q on %q",
539
+ self._definition:GetFullName(),
540
+ self._adornee:GetFullName()
541
+ )
341
542
  )
342
543
  )
343
544
  return nil
@@ -345,7 +546,7 @@ function RogueProperty:GetNamedAdditive(name, source)
345
546
 
346
547
  local searchName = name .. "Additive"
347
548
 
348
- local found = baseValue:FindFirstChild(searchName)
549
+ local found = modifierParent:FindFirstChild(searchName)
349
550
  if found then
350
551
  return found
351
552
  end
@@ -356,18 +557,6 @@ function RogueProperty:GetNamedAdditive(name, source)
356
557
  end
357
558
 
358
559
  function RogueProperty:CreateSetter(value, source)
359
- local baseValue = self:GetBaseValueObject()
360
- if not baseValue then
361
- warn(
362
- string.format(
363
- "[RogueProperty.CreateSetter] - Failed to get the baseValue for %q on %q",
364
- self._definition:GetFullName(),
365
- self._adornee:GetFullName()
366
- )
367
- )
368
- return nil
369
- end
370
-
371
560
  local className = ValueBaseUtils.getClassNameFromType(typeof(value))
372
561
  if not className then
373
562
  error(string.format("[RogueProperty.CreateSetter] - Can't set to type %q", typeof(value)))
@@ -384,18 +573,61 @@ function RogueProperty:CreateSetter(value, source)
384
573
 
385
574
  RogueSetter:Tag(setter)
386
575
 
387
- setter.Parent = baseValue
576
+ self:_parentModifier(setter)
388
577
 
389
578
  return setter
390
579
  end
391
580
 
581
+ function RogueProperty:_parentModifier(modifier: Instance)
582
+ local modifierParent = self:_getModifierParentContainerForNewModifier()
583
+ if modifierParent then
584
+ modifier.Parent = modifierParent
585
+
586
+ return
587
+ end
588
+
589
+ local maid = Maid.new()
590
+
591
+ local warningText = debug.traceback(
592
+ string.format(
593
+ "[RogueProperty._parentModifier] - Failed to get the modifierParent for %q on %q",
594
+ self._definition:GetFullName(),
595
+ self._adornee:GetFullName()
596
+ )
597
+ )
598
+
599
+ maid._warning = task.delay(5, function()
600
+ warn(warningText)
601
+ end)
602
+
603
+ maid:GivePromise(self:PromiseBaseValue()):Then(function()
604
+ local newParent = self:_getModifierParentContainerForNewModifier()
605
+ if not newParent then
606
+ warn(
607
+ "[RogueProperty:_parentModifier] - Failed to retrieve modifier parent after load, will never modify value"
608
+ )
609
+
610
+ return
611
+ end
612
+
613
+ maid._warning = nil
614
+ modifier.Parent = newParent
615
+ end)
616
+
617
+ maid:GiveTask(modifier.Destroying:Connect(function()
618
+ maid:DoCleaning()
619
+ end))
620
+
621
+ return
622
+ end
623
+
392
624
  function RogueProperty:__index(index)
393
- if index == "Value" then
625
+ if RogueProperty[index] then
626
+ return RogueProperty[index]
627
+ elseif index == "Value" then
394
628
  return self:GetValue()
395
629
  elseif index == "Changed" then
396
630
  return self:GetChangedEvent()
397
- elseif RogueProperty[index] then
398
- return RogueProperty[index]
399
631
  else
400
632
  error(string.format("Bad index %q", tostring(index)))
401
633
  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)
@@ -144,11 +149,18 @@ function RoguePropertyArrayHelper:SetArrayData(arrayData)
144
149
 
145
150
  for index, definition in definitions do
146
151
  if available[index] and available[index]:GetDefinition():GetValueType() == definition:GetValueType() then
152
+ -- TODO: Replication is cursed here, and won't be consistent between client vs. server
153
+ available[index]:SetCanInitialize(true)
147
154
  available[index]:SetValue(definition:GetDefaultValue())
155
+ available[index]:SetCanInitialize(false)
148
156
  else
149
157
  -- Cleanup this old one and setup a new one
150
158
  if available[index] then
151
- available[index]:GetBaseValueObject():Destroy()
159
+ local found = available[index]:GetBaseValueObject(RoguePropertyBaseValueTypes.ANY)
160
+ if typeof(found) == "Instance" then
161
+ found:Destroy()
162
+ return
163
+ end
152
164
  end
153
165
 
154
166
  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,81 @@ 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
+
58
+ if self:CanInitialize() then
59
+ parentTable:GetContainer()
60
+ end
61
+
62
+ cache = parentTable:ObserveContainerBrio():Pipe({
63
+ RxBrioUtils.switchMapBrio(function(parent)
64
+ return RxInstanceUtils.observeLastNamedChildBrio(parent, "Folder", self._definition:GetName())
65
+ end),
66
+ Rx.cache(),
67
+ })
68
+ else
69
+ cache = RxInstanceUtils.observeLastNamedChildBrio(self._adornee, "Folder", self._definition:GetName()):Pipe({
70
+ Rx.cache(),
71
+ })
72
+ end
73
+
74
+ cache = cache
75
+ rawset(self, "_observeContainerCache", cache)
76
+ return cache
46
77
  end
47
78
 
48
- function RoguePropertyTable:GetContainer(): Instance
79
+ function RoguePropertyTable:GetContainer(): Instance?
49
80
  local cached = rawget(self, "_containerCache")
50
- if cached and cached:IsDescendantOf(self._adornee) then
51
- return cached
81
+ if cached then
82
+ if cached:IsDescendantOf(self._adornee) then
83
+ return cached
84
+ else
85
+ rawset(self, "_containerCache", nil)
86
+ end
87
+ end
88
+
89
+ local parent
90
+ local parentDefinition = self._definition:GetParentPropertyDefinition()
91
+ if parentDefinition then
92
+ local parentTable = parentDefinition:Get(self._serviceBag, self._adornee)
93
+ parent = parentTable:GetContainer()
94
+ else
95
+ parent = self._adornee
96
+ end
97
+
98
+ if not parent then
99
+ return nil
100
+ end
101
+
102
+ local container
103
+ if self:CanInitialize() then
104
+ container = self._definition:GetOrCreateInstance(parent)
105
+ else
106
+ container = self._definition:FindInstance(parent)
52
107
  end
53
108
 
54
- local container = self._definition:GetContainer(self._adornee, self:CanInitialize())
55
109
  rawset(self, "_containerCache", container)
56
110
  return container
57
111
  end
@@ -167,6 +221,10 @@ end
167
221
 
168
222
  function RoguePropertyTable:_observeDictionary()
169
223
  -- ok, this is definitely slow
224
+ local cache = rawget(self, "_observeDictionaryCache")
225
+ if cache then
226
+ return cache
227
+ end
170
228
 
171
229
  local toObserve = {}
172
230
 
@@ -180,10 +238,16 @@ function RoguePropertyTable:_observeDictionary()
180
238
  end
181
239
 
182
240
  if next(toObserve) == nil then
183
- return Rx.of({})
241
+ cache = Rx.of({})
242
+ else
243
+ cache = Rx.combineLatest(toObserve):Pipe({
244
+ Rx.cache(),
245
+ })
184
246
  end
185
247
 
186
- return Rx.combineLatest(toObserve)
248
+ rawset(self, "_observeDictionaryCache", cache)
249
+
250
+ return cache
187
251
  end
188
252
 
189
253
  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
+ })