@quenty/tie 2.1.0 → 2.2.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,17 @@
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
+ # [2.2.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/tie@2.1.0...@quenty/tie@2.2.0) (2022-07-31)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add Tie bindings in the opposite direction allowing out-of-oop systems to bind to tie ([6da4426](https://github.com/Quenty/NevermoreEngine/commit/6da4426bb47c9b88e099c461258163f8b26ee4b3))
12
+
13
+
14
+
15
+
16
+
6
17
  # [2.1.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/tie@2.0.0...@quenty/tie@2.1.0) (2022-06-21)
7
18
 
8
19
  **Note:** Version bump only for package @quenty/tie
package/README.md CHANGED
@@ -21,4 +21,14 @@ npm install @quenty/tie --save
21
21
  ```
22
22
 
23
23
  ## Design philosophy
24
- This package does two things. First of all, it basically automates the creation of interfaced definitions, that is, tying a Lua object to BindableEvent/BindableFunction definitions. Second of all, it lets objects be centralized as an interface definition.
24
+ This package does two things. First of all, it basically automates the creation of interfaced definitions, that is, tying a Lua object to BindableEvent/BindableFunction definitions. Second of all, it lets objects be centralized as an interface definition.
25
+
26
+ ## Changes to make
27
+
28
+ 1. Ensure tie properties can be attributes
29
+ 1. Allow tie functions instead of methods
30
+ 1. Add tie library as a primary interface (less OOP)
31
+ 1. Ensure you can ad-hoc create a tie (low-level scripting)
32
+ 1. Support tagged ties with
33
+ 1. Ensure ties can be queried via CollectionService
34
+ 1. Allow client implementation of server-based ties
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/tie",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Tie allows interfaces to be defined between Lua OOP and Roblox objects.",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -28,23 +28,25 @@
28
28
  "Quenty"
29
29
  ],
30
30
  "dependencies": {
31
- "@quenty/baseobject": "^5.0.0",
32
- "@quenty/brio": "^6.1.0",
33
- "@quenty/instanceutils": "^5.1.0",
31
+ "@quenty/attributeutils": "^6.2.0",
32
+ "@quenty/baseobject": "^5.1.0",
33
+ "@quenty/brio": "^6.2.0",
34
+ "@quenty/instanceutils": "^5.2.0",
34
35
  "@quenty/loader": "^5.0.0",
35
- "@quenty/maid": "^2.3.0",
36
- "@quenty/rx": "^5.1.0",
36
+ "@quenty/maid": "^2.4.0",
37
+ "@quenty/rx": "^5.2.0",
37
38
  "@quenty/string": "^2.3.0",
38
39
  "@quenty/symbol": "^2.1.0",
39
40
  "@quenty/table": "^3.1.0",
40
- "@quenty/valueobject": "^5.1.0"
41
+ "@quenty/valuebaseutils": "^5.2.0",
42
+ "@quenty/valueobject": "^5.2.0"
41
43
  },
42
44
  "devDependencies": {
43
- "@quenty/promise": "^5.0.0",
45
+ "@quenty/promise": "^5.1.0",
44
46
  "@quenty/signal": "^2.2.0"
45
47
  },
46
48
  "publishConfig": {
47
49
  "access": "public"
48
50
  },
49
- "gitHead": "c8732cc5dea767b3ff362db43137e2a16da7bc0d"
51
+ "gitHead": "e31b3a35aa475bb5699a24898a8639e107165b36"
50
52
  }
@@ -29,35 +29,57 @@ TieDefinition.ClassName = "TieDefinition"
29
29
  TieDefinition.__index = TieDefinition
30
30
 
31
31
  TieDefinition.Types = Table.readonly({
32
- METHOD = "method";
33
- SIGNAL = "signal";
34
- PROPERTY = "valueobject";
32
+ METHOD = Symbol.named("method");
33
+ SIGNAL = Symbol.named("signal");
34
+ PROPERTY = Symbol.named("property"); -- will default to nil
35
35
  })
36
36
 
37
- function TieDefinition.new(definitionName, methods)
37
+ function TieDefinition.new(definitionName, members, isSharedDefinition)
38
38
  local self = setmetatable({}, TieDefinition)
39
39
 
40
40
  self._definitionName = assert(definitionName, "No definitionName")
41
41
  self._memberMap = {}
42
42
 
43
- for memberName, memberType in pairs(methods) do
43
+ self._isSharedDefinition = isSharedDefinition
44
+
45
+ for memberName, memberTypeOrDefaultValue in pairs(members) do
44
46
  assert(type(memberName) == "string", "Bad memberName")
45
47
 
46
- if memberType == TieDefinition.Types.METHOD then
48
+ if memberTypeOrDefaultValue == TieDefinition.Types.METHOD then
47
49
  self._memberMap[memberName] = TieMethodDefinition.new(self, memberName)
48
- elseif memberType == TieDefinition.Types.SIGNAL then
50
+ elseif memberTypeOrDefaultValue == TieDefinition.Types.SIGNAL then
49
51
  self._memberMap[memberName] = TieSignalDefinition.new(self, memberName)
50
- elseif memberType == TieDefinition.Types.PROPERTY then
51
- self._memberMap[memberName] = TiePropertyDefinition.new(self, memberName)
52
+ elseif memberTypeOrDefaultValue == TieDefinition.Types.PROPERTY then
53
+ self._memberMap[memberName] = TiePropertyDefinition.new(self, memberName, nil)
52
54
  else
53
- error(("Bad memberType %q for member %q for %q"):format(
54
- tostring(memberType), tostring(memberName), self._definitionName))
55
+ self._memberMap[memberName] = TiePropertyDefinition.new(self, memberName, memberTypeOrDefaultValue)
55
56
  end
56
57
  end
57
58
 
58
59
  return self
59
60
  end
60
61
 
62
+ --[=[
63
+ Gets all valid interfaces for this adornee
64
+ @param adornee Instance
65
+ @return { TieInterface }
66
+ ]=]
67
+ function TieDefinition:GetImplementations(adornee: Instance)
68
+ assert(typeof(adornee) == "Instance", "Bad adornee")
69
+
70
+ local implementations = {}
71
+
72
+ for _, item in pairs(adornee:GetChildren()) do
73
+ if item.Name == self:GetContainerName() then
74
+ if self:IsImplementation(item) then
75
+ table.insert(implementations, TieInterface.new(self, item, nil))
76
+ end
77
+ end
78
+ end
79
+
80
+ return implementations
81
+ end
82
+
61
83
  --[=[
62
84
  Returns true if the adornee implements the interface, and false otherwise.
63
85
  @param adornee Instance
@@ -71,7 +93,7 @@ function TieDefinition:HasImplementation(adornee: Instance)
71
93
  return false
72
94
  end
73
95
 
74
- return self:_checkImplementation(folder)
96
+ return self:IsImplementation(folder)
75
97
  end
76
98
 
77
99
  --[=[
@@ -82,7 +104,24 @@ end
82
104
  function TieDefinition:ObserveIsImplemented(adornee: Instance): boolean
83
105
  assert(typeof(adornee) == "Instance", "Bad adornee")
84
106
 
85
- return self:ObserveImplementationBrio(adornee)
107
+ return self:ObserveLastImplementationBrio(adornee)
108
+ :Pipe({
109
+ RxBrioUtils.map(function(result)
110
+ return result and true or false
111
+ end);
112
+ RxBrioUtils.emitOnDeath(false);
113
+ Rx.defaultsTo(false);
114
+ Rx.distinct();
115
+ })
116
+ end
117
+
118
+ --[=[
119
+ Observes whether the folder is a valid implementation
120
+ @param folder Instance
121
+ @return Observable<boolean>>
122
+ ]=]
123
+ function TieDefinition:ObserveIsImplementation(folder: Folder)
124
+ return self:_observeImplementation(folder)
86
125
  :Pipe({
87
126
  RxBrioUtils.map(function(result)
88
127
  return result and true or false
@@ -93,78 +132,133 @@ function TieDefinition:ObserveIsImplemented(adornee: Instance): boolean
93
132
  })
94
133
  end
95
134
 
135
+ --[=[
136
+ Observes whether the folder is a valid implementation on the given adornee
137
+ @param folder Instance
138
+ @param adornee Instance
139
+ @return Observable<boolean>>
140
+ ]=]
141
+ function TieDefinition:ObserveIsImplementedOn(folder: Folder, adornee: Instance)
142
+ assert(typeof(folder) == "Instance", "Bad folder")
143
+ assert(typeof(adornee) == "Instance", "Bad adornee")
144
+
145
+ return RxInstanceUtils.observePropertyBrio(folder, "Parent", function(parent)
146
+ return parent == adornee
147
+ end):Pipe({
148
+ RxBrioUtils.switchMapBrio(function()
149
+ return self:_observeImplementation(folder)
150
+ end);
151
+ RxBrioUtils.map(function(result)
152
+ return result and true or false
153
+ end);
154
+ RxBrioUtils.emitOnDeath(false);
155
+ Rx.defaultsTo(false);
156
+ Rx.distinct();
157
+ })
158
+ end
159
+
96
160
  --[=[
97
161
  Observes a valid implementation wrapped in a brio if it exists.
98
162
  @param adornee Instance
99
163
  @return Observable<Brio<TieImplementation<T>>>
100
164
  ]=]
101
- function TieDefinition:ObserveImplementationBrio(adornee: Instance)
165
+ function TieDefinition:ObserveLastImplementationBrio(adornee: Instance)
102
166
  assert(typeof(adornee) == "Instance", "Bad adornee")
103
167
 
104
168
  return RxInstanceUtils.observeLastNamedChildBrio(adornee, "Folder", self:GetContainerName())
105
169
  :Pipe({
106
170
  RxBrioUtils.switchMapBrio(function(folder)
107
- return Observable.new(function(sub)
108
- -- Bind to all children, instead of individually. This is a
109
- -- performance gain.
110
-
111
- local maid = Maid.new()
112
-
113
- local update
114
- do
115
- local isImplemented = ValueObject.new(UNSET_VALUE)
116
- maid:GiveTask(isImplemented)
117
-
118
- maid:GiveTask(isImplemented.Changed:Connect(function()
119
- maid._brio = nil
120
-
121
- if isImplemented.Value then
122
- local brio = Brio.new(self:Get(adornee))
123
- sub:Fire(brio)
124
- maid._brio = brio
125
- else
126
- maid._brio = nil
127
- end
128
- end))
129
-
130
- function update()
131
- isImplemented.Value = self:_checkImplementation(folder)
132
- end
133
- end
134
-
135
- maid:GiveTask(folder.ChildAdded:Connect(function(child)
136
- maid[child] = child:GetPropertyChangedSignal("Name"):Connect(update)
137
- update()
138
- end))
139
-
140
- maid:GiveTask(folder.ChildRemoved:Connect(function(child)
141
- maid[child] = nil
142
- update()
143
- end))
144
-
145
- for _, child in pairs(folder:GetChildren()) do
146
- maid[child] = child:GetPropertyChangedSignal("Name"):Connect(update)
147
- end
148
-
149
- update()
150
-
151
- return maid
152
- end)
171
+ return self:_observeImplementation(folder)
153
172
  end)
154
173
  })
155
174
  end
156
175
 
176
+ --[=[
177
+ Observes valid implementations wrapped in a brio if it exists.
178
+ @param adornee Instance
179
+ @return Observable<Brio<TieImplementation<T>>>
180
+ ]=]
181
+ function TieDefinition:ObserveImplementationsBrio(adornee: Instance)
182
+ assert(typeof(adornee) == "Instance", "Bad adornee")
183
+
184
+ return RxInstanceUtils.observeChildrenOfNameBrio(adornee, "Folder", self:GetContainerName())
185
+ :Pipe({
186
+ RxBrioUtils.flatMapBrio(function(folder)
187
+ return self:_observeImplementation(folder)
188
+ end)
189
+ })
190
+ end
191
+
192
+ function TieDefinition:_observeImplementation(folder)
193
+ return Observable.new(function(sub)
194
+ -- Bind to all children, instead of individually. This is a
195
+ -- performance gain.
196
+
197
+ local maid = Maid.new()
198
+
199
+ local update
200
+ do
201
+ local isImplemented = ValueObject.new(UNSET_VALUE)
202
+ maid:GiveTask(isImplemented)
203
+
204
+ maid:GiveTask(isImplemented.Changed:Connect(function()
205
+ maid._brio = nil
206
+
207
+ if isImplemented.Value then
208
+ local brio = Brio.new(TieInterface.new(self, folder, nil))
209
+ sub:Fire(brio)
210
+ maid._brio = brio
211
+ else
212
+ maid._brio = nil
213
+ end
214
+ end))
215
+
216
+ function update()
217
+ isImplemented.Value = self:IsImplementation(folder)
218
+ end
219
+ end
220
+
221
+ maid:GiveTask(folder.ChildAdded:Connect(function(child)
222
+ maid[child] = child:GetPropertyChangedSignal("Name"):Connect(update)
223
+ update()
224
+ end))
225
+
226
+ for memberName, member in pairs(self._memberMap) do
227
+ if member.ClassName == "TiePropertyDefinition" then
228
+ maid:GiveTask(folder:GetAttributeChangedSignal(memberName):Connect(update))
229
+ end
230
+ end
231
+
232
+ maid:GiveTask(folder.ChildRemoved:Connect(function(child)
233
+ maid[child] = nil
234
+ update()
235
+ end))
236
+
237
+ for _, child in pairs(folder:GetChildren()) do
238
+ maid[child] = child:GetPropertyChangedSignal("Name"):Connect(update)
239
+ end
240
+
241
+ update()
242
+
243
+ return maid
244
+ end)
245
+ end
246
+
157
247
  --[=[
158
248
  Ensures implementation of the object, binding table values and Lua OOP objects
159
249
  to Roblox objects that can be invoked generally.
160
250
 
251
+ ```lua
252
+
253
+ ```
254
+
161
255
  @param adornee Instance -- Adornee to implement interface on
162
- @param implementer table -- Table with all interface values
256
+ @param implementer table? -- Table with all interface values or nil
163
257
  @return TieImplementation<T>
164
258
  ]=]
165
259
  function TieDefinition:Implement(adornee: Instance, implementer)
166
260
  assert(typeof(adornee) == "Instance", "Bad adornee")
167
- assert(type(implementer) == "table", "Bad implementer")
261
+ assert(type(implementer) == "table" or implementer == nil, "Bad implementer")
168
262
 
169
263
  return TieImplementation.new(self, adornee, implementer)
170
264
  end
@@ -181,7 +275,7 @@ end
181
275
  function TieDefinition:Get(adornee: Instance)
182
276
  assert(typeof(adornee) == "Instance", "Bad adornee")
183
277
 
184
- return TieInterface.new(self, adornee)
278
+ return TieInterface.new(self, nil, adornee)
185
279
  end
186
280
 
187
281
  --[=[
@@ -193,7 +287,7 @@ function TieDefinition:GetName(): string
193
287
  end
194
288
 
195
289
  function TieDefinition:GetContainerName(): string
196
- if RunService:IsClient() then
290
+ if RunService:IsClient() and not self._isSharedDefinition then
197
291
  return self._definitionName .. "Client"
198
292
  else
199
293
  return self._definitionName
@@ -204,7 +298,8 @@ function TieDefinition:GetMemberMap()
204
298
  return self._memberMap
205
299
  end
206
300
 
207
- function TieDefinition:_checkImplementation(folder)
301
+ function TieDefinition:IsImplementation(folder)
302
+ local attributes = folder:GetAttributes()
208
303
  local children = {}
209
304
  for _, item in pairs(folder:GetChildren()) do
210
305
  children[item.Name] = item
@@ -213,6 +308,14 @@ function TieDefinition:_checkImplementation(folder)
213
308
  for memberName, member in pairs(self._memberMap) do
214
309
  local found = children[memberName]
215
310
  if not found then
311
+ if member.ClassName == "TiePropertyDefinition" then
312
+ if attributes[memberName] == nil then
313
+ return false
314
+ else
315
+ continue
316
+ end
317
+ end
318
+
216
319
  return false
217
320
  end
218
321
 
@@ -4,6 +4,9 @@
4
4
 
5
5
  local require = require(script.Parent.loader).load(script)
6
6
 
7
+ local TieMethodImplementation = require("TieMethodImplementation")
8
+ local TieMethodInterfaceUtils = require("TieMethodInterfaceUtils")
9
+
7
10
  local TieMethodDefinition = {}
8
11
  TieMethodDefinition.ClassName = "TieMethodDefinition"
9
12
  TieMethodDefinition.__index = TieMethodDefinition
@@ -17,6 +20,21 @@ function TieMethodDefinition.new(tieDefinition, methodName)
17
20
  return self
18
21
  end
19
22
 
23
+ function TieMethodDefinition:Implement(folder, initialValue, actualSelf)
24
+ assert(typeof(folder) == "Instance", "Bad folder")
25
+ assert(actualSelf, "No actualSelf")
26
+
27
+ return TieMethodImplementation.new(self, folder, initialValue, actualSelf)
28
+ end
29
+
30
+ function TieMethodDefinition:GetInterface(folder: Folder, aliasSelf)
31
+ assert(typeof(folder) == "Instance", "Bad folder")
32
+ assert(aliasSelf, "No aliasSelf")
33
+
34
+ return TieMethodInterfaceUtils.get(aliasSelf, self._tieDefinition, self, folder, nil)
35
+ end
36
+
37
+
20
38
  function TieMethodDefinition:GetTieDefinition()
21
39
  return self._tieDefinition
22
40
  end
@@ -5,16 +5,19 @@
5
5
  local require = require(script.Parent.loader).load(script)
6
6
 
7
7
  local BaseObject = require("BaseObject")
8
+ local TiePropertyImplementation = require("TiePropertyImplementation")
9
+ local TiePropertyInterface = require("TiePropertyInterface")
8
10
 
9
11
  local TiePropertyDefinition = setmetatable({}, BaseObject)
10
12
  TiePropertyDefinition.ClassName = "TiePropertyDefinition"
11
13
  TiePropertyDefinition.__index = TiePropertyDefinition
12
14
 
13
- function TiePropertyDefinition.new(tieDefinition, propertyName: string)
15
+ function TiePropertyDefinition.new(tieDefinition, propertyName: string, defaultValue: any)
14
16
  local self = setmetatable(BaseObject.new(), TiePropertyDefinition)
15
17
 
16
18
  self._tieDefinition = assert(tieDefinition, "No tieDefinition")
17
19
  self._propertyName = assert(propertyName, "No propertyName")
20
+ self._defaultValue = defaultValue
18
21
 
19
22
  return self
20
23
  end
@@ -23,6 +26,26 @@ function TiePropertyDefinition:GetTieDefinition()
23
26
  return self._tieDefinition
24
27
  end
25
28
 
29
+ function TiePropertyDefinition:IsAttribute()
30
+ return self._isAttribute
31
+ end
32
+
33
+ function TiePropertyDefinition:GetDefaultValue()
34
+ return self._defaultValue
35
+ end
36
+
37
+ function TiePropertyDefinition:Implement(folder: Folder, initialValue)
38
+ assert(typeof(folder) == "Instance", "Bad folder")
39
+
40
+ return TiePropertyImplementation.new(self, folder, initialValue)
41
+ end
42
+
43
+ function TiePropertyDefinition:GetInterface(folder: Folder)
44
+ assert(typeof(folder) == "Instance", "Bad folder")
45
+
46
+ return TiePropertyInterface.new(folder, nil, self)
47
+ end
48
+
26
49
  function TiePropertyDefinition:GetMemberName(): string
27
50
  return self._propertyName
28
51
  end
@@ -4,6 +4,9 @@
4
4
 
5
5
  local require = require(script.Parent.loader).load(script)
6
6
 
7
+ local TieSignalImplementation = require("TieSignalImplementation")
8
+ local TieSignalInterface = require("TieSignalInterface")
9
+
7
10
  local TieSignalDefinition = {}
8
11
  TieSignalDefinition.ClassName = "TieSignalDefinition"
9
12
  TieSignalDefinition.__index = TieSignalDefinition
@@ -21,6 +24,18 @@ function TieSignalDefinition:GetTieDefinition()
21
24
  return self._tieDefinition
22
25
  end
23
26
 
27
+ function TieSignalDefinition:Implement(folder, initialValue)
28
+ assert(typeof(folder) == "Instance", "Bad folder")
29
+
30
+ return TieSignalImplementation.new(self, folder, initialValue)
31
+ end
32
+
33
+ function TieSignalDefinition:GetInterface(folder: Folder)
34
+ assert(typeof(folder) == "Instance", "Bad folder")
35
+
36
+ return TieSignalInterface.new(folder, nil, self)
37
+ end
38
+
24
39
  function TieSignalDefinition:GetMemberName()
25
40
  return self._signalName
26
41
  end
@@ -2,8 +2,6 @@
2
2
  @class TieUtils
3
3
  ]=]
4
4
 
5
- local require = require(script.Parent.loader).load(script)
6
-
7
5
  local TieUtils = {}
8
6
 
9
7
  --[=[