@quenty/adorneedata 1.0.1-canary.433.80025dc.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 +8 -0
- package/LICENSE.md +21 -0
- package/README.md +101 -0
- package/default.project.json +6 -0
- package/package.json +40 -0
- package/src/Shared/AdorneeData.lua +254 -0
- package/src/Shared/AdorneeDataEntry.lua +41 -0
- package/src/Shared/AdorneeDataValue.lua +152 -0
- package/src/node_modules.project.json +7 -0
- package/test/default.project.json +11 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## 1.0.1-canary.433.80025dc.0 (2023-12-14)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @quenty/adorneedata
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014-2023 James Onnen (Quenty)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
## AdorneeData
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<a href="http://quenty.github.io/NevermoreEngine/">
|
|
5
|
+
<img src="https://github.com/Quenty/NevermoreEngine/actions/workflows/docs.yml/badge.svg" alt="Documentation status" />
|
|
6
|
+
</a>
|
|
7
|
+
<a href="https://discord.gg/mhtGUS8">
|
|
8
|
+
<img src="https://img.shields.io/discord/385151591524597761?color=5865F2&label=discord&logo=discord&logoColor=white" alt="Discord" />
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://github.com/Quenty/NevermoreEngine/actions">
|
|
11
|
+
<img src="https://github.com/Quenty/NevermoreEngine/actions/workflows/build.yml/badge.svg" alt="Build and release status" />
|
|
12
|
+
</a>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
Bridges attributes and serialization
|
|
16
|
+
|
|
17
|
+
<div align="center"><a href="https://quenty.github.io/NevermoreEngine/api/AdorneeData">View docs →</a></div>
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
npm install @quenty/adorneedata --save
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
These are the requirements for this attribute data library.
|
|
27
|
+
|
|
28
|
+
* Not like Tie in that it is focused on serialization/boundary between Roblox and our stuff (tie is interfaces separated by network boundary)
|
|
29
|
+
* Easy to transform between data and attributes
|
|
30
|
+
* No service bag requirements
|
|
31
|
+
|
|
32
|
+
### Centralized definition with no server/client information
|
|
33
|
+
```lua
|
|
34
|
+
return AdorneeData.new({
|
|
35
|
+
CurrencyColor = Color3.new();
|
|
36
|
+
CurrencyNameTranslationKey = "";
|
|
37
|
+
CurrencyFormatTranslationKey = "";
|
|
38
|
+
CurrencyImageId = "";
|
|
39
|
+
CurrencyShowType = CurrencyShowTypes.NONE;
|
|
40
|
+
GivePlayerCurrency = false;
|
|
41
|
+
CurrencySaves = false;
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Easy to write/author
|
|
46
|
+
We should be able to create new data that is validated
|
|
47
|
+
|
|
48
|
+
```lua
|
|
49
|
+
CurrencyDefinitionData:CreateData({
|
|
50
|
+
CurrencyKey = CurrencyDefinitionConstants.DEFAULT_CURRENCY_KEY
|
|
51
|
+
CurrencyColor = Color3.fromRGB(55, 180, 74)
|
|
52
|
+
CurrencyTranslationKey = "currency.default.name"
|
|
53
|
+
CurrencyFormatTranslationKey = "currency.default.format"
|
|
54
|
+
DoesSave = true
|
|
55
|
+
ImageId = "rbxassetid://10049671651"
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
We should be able to create partial data
|
|
60
|
+
|
|
61
|
+
```lua
|
|
62
|
+
CurrencyDefinitionData:CreatePartialData({
|
|
63
|
+
CurrencyKey = CurrencyDefinitionConstants.DEFAULT_CURRENCY_KEY
|
|
64
|
+
CurrencyColor = Color3.fromRGB(55, 180, 74)
|
|
65
|
+
CurrencyTranslationKey = "currency.default.name"
|
|
66
|
+
CurrencyFormatTranslationKey = "currency.default.format"
|
|
67
|
+
DoesSave = true
|
|
68
|
+
ImageId = "rbxassetid://10049671651"
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
#### Additional authorship requirements
|
|
72
|
+
|
|
73
|
+
* Should be able to set optional values
|
|
74
|
+
|
|
75
|
+
### Easy to validate/assert/assign
|
|
76
|
+
|
|
77
|
+
We should be able to check data types
|
|
78
|
+
|
|
79
|
+
```lua
|
|
80
|
+
function CurrencyService:InitCurrencyDefinition(currencyDefinitionData)
|
|
81
|
+
assert(CurrencyDefinitionData:IsData(currencyDefinitionData), "Bad currencyDefinitionData")
|
|
82
|
+
|
|
83
|
+
local currencyDefinition = CurrencyDefinition:Create("Folder")
|
|
84
|
+
CurrencyDefinitionData:SetAttributes(currencyDefinition, currencyDefinitionData)
|
|
85
|
+
|
|
86
|
+
return currencyDefinition
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Replacement files
|
|
91
|
+
|
|
92
|
+
Should replace following files:
|
|
93
|
+
|
|
94
|
+
* CurrencyDefinitionDataUtils - Files with `t` interface asserting type
|
|
95
|
+
* CurrencyDefinitionConstants - Files with constant attribute names
|
|
96
|
+
* CurrencyDefinitionUtils - File that sets certain properties or attributes and do validation
|
|
97
|
+
|
|
98
|
+
### Easy to serialize
|
|
99
|
+
|
|
100
|
+
Should be able to flash to constant
|
|
101
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quenty/adorneedata",
|
|
3
|
+
"version": "1.0.1-canary.433.80025dc.0",
|
|
4
|
+
"description": "Bridges attributes and serialization",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"Roblox",
|
|
7
|
+
"Nevermore",
|
|
8
|
+
"Lua",
|
|
9
|
+
"AdorneeData"
|
|
10
|
+
],
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Quenty/NevermoreEngine/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/Quenty/NevermoreEngine.git",
|
|
17
|
+
"directory": "src/adorneedata/"
|
|
18
|
+
},
|
|
19
|
+
"funding": {
|
|
20
|
+
"type": "patreon",
|
|
21
|
+
"url": "https://www.patreon.com/quenty"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"contributors": [
|
|
25
|
+
"Quenty"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@quenty/attributeutils": "9.1.2-canary.433.80025dc.0",
|
|
29
|
+
"@quenty/ducktype": "2.0.1-canary.433.80025dc.0",
|
|
30
|
+
"@quenty/loader": "7.0.1-canary.433.80025dc.0",
|
|
31
|
+
"@quenty/rx": "8.1.2-canary.433.80025dc.0",
|
|
32
|
+
"@quenty/rxsignal": "2.1.2-canary.433.80025dc.0",
|
|
33
|
+
"@quenty/table": "3.3.1-canary.433.80025dc.0",
|
|
34
|
+
"@quentystudios/t": "^3.0.0"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"gitHead": "80025dcd926765b502d322bb84e013b973409d8c"
|
|
40
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
Bridges attributes and serializable data table. It's typical to need to define data in 3 ways.
|
|
3
|
+
|
|
4
|
+
1. Attributes on an instance for replication
|
|
5
|
+
2. Tables for Lua configuration
|
|
6
|
+
3. Within AttributeValues for writing regular code
|
|
7
|
+
|
|
8
|
+
Providing all 3
|
|
9
|
+
|
|
10
|
+
@class AdorneeData
|
|
11
|
+
]=]
|
|
12
|
+
|
|
13
|
+
local require = require(script.Parent.loader).load(script)
|
|
14
|
+
|
|
15
|
+
local AdorneeDataEntry = require("AdorneeDataEntry")
|
|
16
|
+
local AdorneeDataValue = require("AdorneeDataValue")
|
|
17
|
+
local AttributeUtils = require("AttributeUtils")
|
|
18
|
+
local t = require("t")
|
|
19
|
+
|
|
20
|
+
local AdorneeData = {}
|
|
21
|
+
AdorneeData.ClassName = "AdorneeData"
|
|
22
|
+
AdorneeData.__index = AdorneeData
|
|
23
|
+
|
|
24
|
+
--[=[
|
|
25
|
+
Attribute data specification
|
|
26
|
+
|
|
27
|
+
@param prototype any
|
|
28
|
+
@return AdorneeData<T>
|
|
29
|
+
]=]
|
|
30
|
+
function AdorneeData.new(prototype)
|
|
31
|
+
local self = setmetatable({}, AdorneeData)
|
|
32
|
+
|
|
33
|
+
self._fullPrototype = assert(prototype, "Bad prototype")
|
|
34
|
+
self._attributePrototype = {}
|
|
35
|
+
self._valueObjectPrototype = {}
|
|
36
|
+
|
|
37
|
+
for key, item in pairs(self._fullPrototype) do
|
|
38
|
+
if AdorneeDataEntry.isAdorneeDataEntry(item) then
|
|
39
|
+
self._valueObjectPrototype[key] = item
|
|
40
|
+
else
|
|
41
|
+
self._attributePrototype[key] = item
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
return self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
--[=[
|
|
49
|
+
Returns true if the data is valid data, otherwise returns false and an error.
|
|
50
|
+
|
|
51
|
+
@param data any
|
|
52
|
+
@return boolean
|
|
53
|
+
@return string -- Error message
|
|
54
|
+
]=]
|
|
55
|
+
function AdorneeData:IsData(data)
|
|
56
|
+
return self:GetStrictTInterface()(data)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
--[=[
|
|
60
|
+
Validates and creates a new data table for the data that is readonly and frozen
|
|
61
|
+
|
|
62
|
+
@param data T
|
|
63
|
+
@return T
|
|
64
|
+
]=]
|
|
65
|
+
function AdorneeData:CreateData(data)
|
|
66
|
+
assert(self:IsData(data))
|
|
67
|
+
|
|
68
|
+
return table.freeze(table.clone(data))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
--[=[
|
|
73
|
+
Validates and creates a new data table that is readonly and frozen, but for partial
|
|
74
|
+
data.
|
|
75
|
+
|
|
76
|
+
The partial data can just be part of the attributes.
|
|
77
|
+
|
|
78
|
+
@param partialData TPartial
|
|
79
|
+
@return TPartial
|
|
80
|
+
]=]
|
|
81
|
+
function AdorneeData:CreatePartialData(partialData)
|
|
82
|
+
assert(self:IsPartialData(partialData))
|
|
83
|
+
|
|
84
|
+
return table.freeze(table.clone(partialData))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
--[=[
|
|
89
|
+
Gets attribute table for the data
|
|
90
|
+
|
|
91
|
+
@param adornee Instance
|
|
92
|
+
@return AdorneeDataValue
|
|
93
|
+
]=]
|
|
94
|
+
function AdorneeData:CreateAdorneeDataValue(adornee)
|
|
95
|
+
assert(typeof(adornee) == "Instance", "Bad adornee")
|
|
96
|
+
|
|
97
|
+
local attributeTableValue = AdorneeDataValue.new(adornee, self._fullPrototype)
|
|
98
|
+
|
|
99
|
+
return attributeTableValue
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
--[=[
|
|
103
|
+
Gets the attributes for the adornee
|
|
104
|
+
|
|
105
|
+
@param adornee Instance
|
|
106
|
+
@return T
|
|
107
|
+
]=]
|
|
108
|
+
function AdorneeData:GetAttributes(adornee)
|
|
109
|
+
assert(typeof(adornee) == "Instance", "Bad adornee")
|
|
110
|
+
|
|
111
|
+
local data = {}
|
|
112
|
+
for key, value in pairs(self._attributePrototype) do
|
|
113
|
+
data[key] = adornee:GetAttribute(value)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
-- TODO: Avoid additional allocation
|
|
117
|
+
for key, value in pairs(self._valueObjectPrototype) do
|
|
118
|
+
data[key] = value:CreateValueObject(adornee).Value
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
return self:CreateData(data)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
--[=[
|
|
125
|
+
Sets the attributes for the adornee
|
|
126
|
+
|
|
127
|
+
@param adornee Instance
|
|
128
|
+
@param data T
|
|
129
|
+
]=]
|
|
130
|
+
function AdorneeData:SetAttributes(adornee, data)
|
|
131
|
+
assert(typeof(adornee) == "Instance", "Bad adornee")
|
|
132
|
+
assert(self:IsData(data))
|
|
133
|
+
|
|
134
|
+
for key, _ in pairs(self._attributePrototype) do
|
|
135
|
+
adornee:SetAttribute(key, data[key])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
-- TODO: Avoid additional allocation
|
|
139
|
+
for key, value in pairs(self._valueObjectPrototype) do
|
|
140
|
+
value:CreateValueObject(adornee).Value = data[key]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
--[=[
|
|
145
|
+
Initializes the attributes for the adornee
|
|
146
|
+
|
|
147
|
+
@param adornee Instance
|
|
148
|
+
@param data T
|
|
149
|
+
]=]
|
|
150
|
+
function AdorneeData:InitAttributes(adornee, data)
|
|
151
|
+
assert(typeof(adornee) == "Instance", "Bad adornee")
|
|
152
|
+
assert(self:IsData(data))
|
|
153
|
+
|
|
154
|
+
for key, _ in pairs(self._attributePrototype) do
|
|
155
|
+
if adornee:GetAttribute(key) == nil then
|
|
156
|
+
adornee:SetAttribute(key, data[key])
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
-- TODO: Avoid additional allocation
|
|
161
|
+
for key, value in pairs(self._valueObjectPrototype) do
|
|
162
|
+
local valueObject = value:CreateValueObject(adornee)
|
|
163
|
+
if valueObject == nil then
|
|
164
|
+
valueObject.Value = data[key]
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
--[=[
|
|
170
|
+
Sets partial attributes on the adornee
|
|
171
|
+
|
|
172
|
+
@param adornee Instance
|
|
173
|
+
@param partialData TPartial
|
|
174
|
+
]=]
|
|
175
|
+
function AdorneeData:SetPartialAttributes(adornee, partialData)
|
|
176
|
+
assert(typeof(adornee) == "Instance", "Bad adornee")
|
|
177
|
+
assert(self:IsPartialData(partialData))
|
|
178
|
+
|
|
179
|
+
local attributeTable = self:CreateAdorneeDataValue(adornee)
|
|
180
|
+
for key, value in pairs(partialData) do
|
|
181
|
+
attributeTable[key].Value = value
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
--[=[
|
|
186
|
+
Gets a strict interface which will return true if the value is a partial interface and
|
|
187
|
+
false otherwise.
|
|
188
|
+
|
|
189
|
+
@return function
|
|
190
|
+
]=]
|
|
191
|
+
function AdorneeData:GetStrictTInterface()
|
|
192
|
+
if self._fullInterface then
|
|
193
|
+
return self._fullInterface
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
self._fullInterface = t.strictInterface(self:_getOrCreateTypeInterfaceList())
|
|
197
|
+
return self._fullInterface
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
--[=[
|
|
201
|
+
Gets a [t] interface which will return true if the value is a partial interface, and
|
|
202
|
+
false otherwise.
|
|
203
|
+
|
|
204
|
+
@return function
|
|
205
|
+
]=]
|
|
206
|
+
function AdorneeData:GetPartialTInterface()
|
|
207
|
+
if self._partialInterface then
|
|
208
|
+
return self._partialInterface
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
local interfaceList = {}
|
|
212
|
+
for key, value in pairs(self:_getOrCreateTypeInterfaceList()) do
|
|
213
|
+
interfaceList[key] = t.optional(value)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
self._partialInterface = t.strictInterface(interfaceList)
|
|
217
|
+
return self._partialInterface
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
--[=[
|
|
222
|
+
Returns true if the data is valid partial data, otherwise returns false and an error.
|
|
223
|
+
|
|
224
|
+
@param data any
|
|
225
|
+
@return boolean
|
|
226
|
+
@return string -- Error message
|
|
227
|
+
]=]
|
|
228
|
+
function AdorneeData:IsPartialData(data)
|
|
229
|
+
return self:GetPartialTInterface()(data)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
function AdorneeData:_getOrCreateTypeInterfaceList()
|
|
233
|
+
if self._typeInterfaceList then
|
|
234
|
+
return self._typeInterfaceList
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
local interfaceList = {}
|
|
238
|
+
|
|
239
|
+
for key, value in pairs(self._fullPrototype) do
|
|
240
|
+
if AdorneeDataEntry.isAdorneeDataEntry(value) then
|
|
241
|
+
interfaceList[key] = value:GetStrictInterface()
|
|
242
|
+
else
|
|
243
|
+
local valueType = typeof(value)
|
|
244
|
+
assert(AttributeUtils.isValidAttributeType(valueType), "Not a valid value type")
|
|
245
|
+
|
|
246
|
+
interfaceList[key] = t.typeof(valueType)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
self._typeInterfaceList = interfaceList
|
|
251
|
+
return interfaceList
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
return AdorneeData
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
@class AdorneeDataEntry
|
|
3
|
+
]=]
|
|
4
|
+
|
|
5
|
+
local require = require(script.Parent.loader).load(script)
|
|
6
|
+
|
|
7
|
+
local DuckTypeUtils = require("DuckTypeUtils")
|
|
8
|
+
local t = require("t")
|
|
9
|
+
|
|
10
|
+
local AdorneeDataEntry = {}
|
|
11
|
+
AdorneeDataEntry.ClassName = "AdorneeDataEntry"
|
|
12
|
+
AdorneeDataEntry.__index = AdorneeDataEntry
|
|
13
|
+
|
|
14
|
+
function AdorneeDataEntry.new(dataType, createValueObject)
|
|
15
|
+
assert(type(dataType) == "string", "Bad dataType")
|
|
16
|
+
assert(type(createValueObject) == "function", "Bad createValueObject")
|
|
17
|
+
|
|
18
|
+
local self = setmetatable({}, AdorneeDataEntry)
|
|
19
|
+
|
|
20
|
+
self._dataType = dataType
|
|
21
|
+
self._createValueObject = createValueObject
|
|
22
|
+
self._strictInterface = t.typeof(self._dataType)
|
|
23
|
+
|
|
24
|
+
return self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
function AdorneeDataEntry.isAdorneeDataEntry(data)
|
|
28
|
+
return DuckTypeUtils.isImplementation(AdorneeDataEntry, data)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
function AdorneeDataEntry:CreateValueObject(adornee)
|
|
32
|
+
assert(typeof(adornee) == "Instance", "Bad adornee")
|
|
33
|
+
|
|
34
|
+
return self._createValueObject(adornee)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
function AdorneeDataEntry:GetStrictInterface()
|
|
38
|
+
return self._strictInterface
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
return AdorneeDataEntry
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
Allows access to an attribute like a ValueObject but in table form.
|
|
3
|
+
|
|
4
|
+
```lua
|
|
5
|
+
local data = AdorneeDataValue.new(workspace, {
|
|
6
|
+
Version = "1.0.0";
|
|
7
|
+
CFrame = CFrame.new();
|
|
8
|
+
})
|
|
9
|
+
print(data.Version.Value) --> 1.0.0
|
|
10
|
+
print(workspace:GetAttribute("version")) --> 1.0.0
|
|
11
|
+
|
|
12
|
+
data.Version.Changed:Connect(function()
|
|
13
|
+
print(data.Version.Value)
|
|
14
|
+
end)
|
|
15
|
+
|
|
16
|
+
workspace:SetAttribute("1.1.0") --> 1.1.0
|
|
17
|
+
data.Value = "1.2.0" --> 1.2.0
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
@class AdorneeDataValue
|
|
21
|
+
]=]
|
|
22
|
+
|
|
23
|
+
local require = require(script.Parent.loader).load(script)
|
|
24
|
+
|
|
25
|
+
local AttributeValue = require("AttributeValue")
|
|
26
|
+
local RxSignal = require("RxSignal")
|
|
27
|
+
local Rx = require("Rx")
|
|
28
|
+
local AdorneeDataEntry = require("AdorneeDataEntry")
|
|
29
|
+
|
|
30
|
+
local AdorneeDataValue = {}
|
|
31
|
+
AdorneeDataValue.ClassName = "AdorneeDataValue"
|
|
32
|
+
AdorneeDataValue.__index = AdorneeDataValue
|
|
33
|
+
|
|
34
|
+
--[=[
|
|
35
|
+
Constructs a new AdorneeDataValue
|
|
36
|
+
|
|
37
|
+
@param adornee Instance
|
|
38
|
+
@param prototype table
|
|
39
|
+
@return AdorneeDataValue<T>
|
|
40
|
+
]=]
|
|
41
|
+
function AdorneeDataValue.new(adornee, prototype)
|
|
42
|
+
assert(typeof(adornee) == "Instance", "Bad adornee")
|
|
43
|
+
assert(type(prototype) == "table", "Bad prototype")
|
|
44
|
+
|
|
45
|
+
local self = {
|
|
46
|
+
_adornee = adornee;
|
|
47
|
+
_defaultValues = prototype;
|
|
48
|
+
_valueObjects = {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for key, value in pairs(prototype) do
|
|
52
|
+
if AdorneeDataEntry.isAdorneeDataEntry(value) then
|
|
53
|
+
self._valueObjects[key] = value:CreateValueObject(adornee)
|
|
54
|
+
else
|
|
55
|
+
self._valueObjects[key] = AttributeValue.new(adornee, key, value)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return setmetatable(self, AdorneeDataValue)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
--[=[
|
|
63
|
+
The current property of the Attribute. Can be assigned to to write
|
|
64
|
+
the attribute.
|
|
65
|
+
@prop Value T
|
|
66
|
+
@within AdorneeDataValue
|
|
67
|
+
]=]
|
|
68
|
+
|
|
69
|
+
--[=[
|
|
70
|
+
Signal that fires when the attribute changes
|
|
71
|
+
@readonly
|
|
72
|
+
@prop Changed Signal<()>
|
|
73
|
+
@within AdorneeDataValue
|
|
74
|
+
]=]
|
|
75
|
+
function AdorneeDataValue:__index(index)
|
|
76
|
+
if index == "Value" then
|
|
77
|
+
local result = {}
|
|
78
|
+
for key, valueObject in pairs(self._valueObjects) do
|
|
79
|
+
result[key] = valueObject.Value
|
|
80
|
+
end
|
|
81
|
+
return result
|
|
82
|
+
elseif index == "Changed" then
|
|
83
|
+
return RxSignal.new(self:Observe():Pipe({
|
|
84
|
+
Rx.skip(1);
|
|
85
|
+
}))
|
|
86
|
+
elseif AdorneeDataValue[index] then
|
|
87
|
+
return AdorneeDataValue[index]
|
|
88
|
+
elseif type(index) == "string" then
|
|
89
|
+
local attributeValues = rawget(self, "_valueObjects")
|
|
90
|
+
if attributeValues[index] then
|
|
91
|
+
return attributeValues[index]
|
|
92
|
+
else
|
|
93
|
+
error(string.format("%q is not a member of AdorneeDataValue", tostring(index)))
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
error(string.format("%q is not a member of AdorneeDataValue", tostring(index)))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
--[=[
|
|
101
|
+
Observes an attribute on an instance.
|
|
102
|
+
@return Observable<any>
|
|
103
|
+
]=]
|
|
104
|
+
function AdorneeDataValue:Observe()
|
|
105
|
+
local current = rawget(self, "_defaultObservable")
|
|
106
|
+
if current then
|
|
107
|
+
return current
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
local attributeValues = rawget(self, "_valueObjects")
|
|
111
|
+
|
|
112
|
+
local observables = {}
|
|
113
|
+
for key, valueObject in pairs(attributeValues) do
|
|
114
|
+
observables[key] = valueObject:Observe()
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
local observable = Rx.combineLatest(observables)
|
|
118
|
+
rawset(self, "_defaultObservable", observable)
|
|
119
|
+
return observable
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
function AdorneeDataValue:__newindex(index, newValue)
|
|
123
|
+
if index == "Value" then
|
|
124
|
+
assert(type(newValue) == "table", "Bad newValue")
|
|
125
|
+
local attributeValues = rawget(self, "_valueObjects")
|
|
126
|
+
|
|
127
|
+
for key, value in pairs(newValue) do
|
|
128
|
+
if attributeValues[key] then
|
|
129
|
+
attributeValues[key].Value = value
|
|
130
|
+
else
|
|
131
|
+
error("%s is not a valid member of AttributeValue")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
else
|
|
135
|
+
error(string.format("Use AttributeValue.%q.Value to set an attribute", tostring(index)))
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
function AdorneeDataValue:_getOrCreateAttributeValue(key)
|
|
140
|
+
local attributeValues = rawget(self, "_valueObjects")
|
|
141
|
+
if attributeValues[key] then
|
|
142
|
+
return attributeValues[key]
|
|
143
|
+
else
|
|
144
|
+
local prototype = rawget(self, "_defaultValues")
|
|
145
|
+
local adornee = rawget(self, "_adornee")
|
|
146
|
+
|
|
147
|
+
attributeValues[key] = AttributeValue.new(adornee, key, prototype[key])
|
|
148
|
+
return attributeValues
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
return AdorneeDataValue
|