@flamework-experimental/networking 2.0.0-alpha.0 → 2.0.0-alpha.2

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.
Files changed (44) hide show
  1. package/README.md +6 -0
  2. package/flamework.build +1 -1
  3. package/out/event/createEvent.d.ts +22 -4
  4. package/out/event/createEvent.luau +47 -49
  5. package/out/events/createClientMethod.luau +7 -3
  6. package/out/events/createGenericHandler.luau +6 -3
  7. package/out/events/createNetworkingEvent.luau +7 -5
  8. package/out/events/createServerMethod.d.ts +1 -1
  9. package/out/events/createServerMethod.luau +8 -3
  10. package/out/events/types.d.ts +4 -3
  11. package/out/function/createFunctionReceiver.d.ts +10 -9
  12. package/out/function/createFunctionReceiver.luau +53 -55
  13. package/out/function/createFunctionSender.d.ts +3 -2
  14. package/out/function/createFunctionSender.luau +118 -40
  15. package/out/functions/createClientMethod.d.ts +1 -1
  16. package/out/functions/createClientMethod.luau +9 -8
  17. package/out/functions/createGenericHandler.luau +8 -12
  18. package/out/functions/createNetworkingFunction.luau +7 -5
  19. package/out/functions/createServerMethod.d.ts +1 -1
  20. package/out/functions/createServerMethod.luau +9 -8
  21. package/out/functions/types.d.ts +10 -12
  22. package/out/handlers.d.ts +35 -36
  23. package/out/index.d.ts +6 -0
  24. package/out/init.luau +6 -0
  25. package/out/middleware/createGuards.d.ts +10 -0
  26. package/out/middleware/createGuards.luau +32 -0
  27. package/out/middleware/processor.d.ts +62 -0
  28. package/out/middleware/processor.luau +214 -0
  29. package/out/middleware/types.d.ts +26 -15
  30. package/out/util/createOnce.d.ts +14 -0
  31. package/out/util/createOnce.luau +66 -0
  32. package/out/util/createSignalContainer.d.ts +2 -1
  33. package/out/util/createSignalContainer.luau +2 -2
  34. package/out/util/signal.d.ts +27 -0
  35. package/out/util/signal.luau +135 -0
  36. package/out/util/trimArguments.d.ts +12 -0
  37. package/out/util/trimArguments.luau +48 -0
  38. package/package.json +2 -3
  39. package/out/middleware/createGuardMiddleware.d.ts +0 -6
  40. package/out/middleware/createGuardMiddleware.luau +0 -44
  41. package/out/middleware/createMiddlewareProcessor.d.ts +0 -3
  42. package/out/middleware/createMiddlewareProcessor.luau +0 -24
  43. package/out/util/timeoutPromise.d.ts +0 -1
  44. package/out/util/timeoutPromise.luau +0 -10
@@ -3,14 +3,13 @@ local TS = _G[script]
3
3
  local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
4
4
  local Players = _services.Players
5
5
  local RunService = _services.RunService
6
- local _createEvent = TS.import(script, script.Parent.Parent, "event", "createEvent")
7
- local createEvent = _createEvent.createEvent
8
- local decodeArguments = _createEvent.decodeArguments
6
+ local createEvent = TS.import(script, script.Parent.Parent, "event", "createEvent").createEvent
7
+ local decode = TS.import(script, script.Parent.Parent, "middleware", "processor").decode
9
8
  local _errors = TS.import(script, script.Parent, "errors")
10
9
  local NetworkingFunctionError = _errors.NetworkingFunctionError
11
10
  local getFunctionError = _errors.getFunctionError
12
11
  local t = TS.import(script, TS.getModule(script, "@rbxts", "t").lib.ts).t
13
- local createRequestInfo
12
+ local createRequestInfo, hasLeft
14
13
  local function createFunctionSender(options)
15
14
  local event = createEvent({
16
15
  namespace = options.namespace,
@@ -18,8 +17,8 @@ local function createFunctionSender(options)
18
17
  id = options.id,
19
18
  networkInfo = options.networkInfo,
20
19
  })
21
- local processResponse = function(player, requestInfo, id, processResult, ...)
22
- local response = { ... }
20
+ --* A response is `(id, processResult, value)`, or `(id, true, payload, blobs?)` with a result decoder.
21
+ local processResponse = function(player, requestInfo, id, processResult, value, blobs)
23
22
  if not t.number(id) then
24
23
  return nil
25
24
  end
@@ -34,12 +33,16 @@ local function createFunctionSender(options)
34
33
  end
35
34
  local rejection = getFunctionError(processResult)
36
35
  if rejection ~= nil or not options.resultDecoder then
37
- request(response[1], rejection)
36
+ request(value, rejection)
38
37
  return nil
39
38
  end
40
39
  -- A successful response carries the packed value: `(buffer, blobs?)`.
41
- local decoded = decodeArguments(options.resultDecoder, player, response, options.onMalformed)
42
- if not decoded then
40
+ local ok, decoded = decode(options.resultDecoder, value, blobs)
41
+ if not ok then
42
+ local _result = options.onMalformed
43
+ if _result ~= nil then
44
+ _result(player, decoded)
45
+ end
43
46
  request(nil, NetworkingFunctionError.InvalidResult)
44
47
  return nil
45
48
  end
@@ -48,21 +51,27 @@ local function createFunctionSender(options)
48
51
  -- We don't need to defer here because we only accept responses to our explicit invocations.
49
52
  local requestInfoServer = {}
50
53
  local requestInfoClient = createRequestInfo()
54
+ -- A player who has left never answers: `FireClient` drops the request without a word, and only a
55
+ -- finite timeout would ever settle it. Weak, so that remembering them does not keep them alive.
56
+ local departedPlayers = setmetatable({}, {
57
+ __mode = "k",
58
+ })
51
59
  if RunService:IsServer() then
52
- event:connectServer(function(player, id, processResult, ...)
53
- local response = { ... }
60
+ event:connectServer(function(player, id, processResult, value, blobs)
54
61
  local _player = player
55
62
  local requestInfo = requestInfoServer[_player]
56
63
  if not requestInfo then
57
64
  return nil
58
65
  end
59
- processResponse(player, requestInfo, id, processResult, unpack(response))
66
+ processResponse(player, requestInfo, id, processResult, value, blobs)
60
67
  end)
61
68
  Players.PlayerRemoving:Connect(function(player)
62
69
  local _player = player
63
- local requestInfo = requestInfoServer[_player]
70
+ departedPlayers[_player] = true
64
71
  local _player_1 = player
65
- requestInfoServer[_player_1] = nil
72
+ local requestInfo = requestInfoServer[_player_1]
73
+ local _player_2 = player
74
+ requestInfoServer[_player_2] = nil
66
75
  if requestInfo then
67
76
  -- Cancel all existing requests from this player.
68
77
  for _, request in requestInfo.requests do
@@ -71,16 +80,48 @@ local function createFunctionSender(options)
71
80
  end
72
81
  end)
73
82
  else
74
- event:connectClient(function(id, processResult, ...)
75
- local response = { ... }
76
- processResponse(nil, requestInfoClient, id, processResult, unpack(response))
83
+ event:connectClient(function(id, processResult, value, blobs)
84
+ processResponse(nil, requestInfoClient, id, processResult, value, blobs)
77
85
  end)
78
86
  end
79
- local createInvocation = function(player, id, requestInfo)
80
- return TS.Promise.new(function(resolve, reject, onCancel)
81
- local _requests = requestInfo.requests
82
- local _id = id
83
- _requests[_id] = function(value, rejection)
87
+ --[[
88
+ *
89
+ * The one Promise of a request, which its executor registers and sends; nothing else is made for
90
+ * it. The timeout is a `task.delay`, cancelled when the answer arrives (none at all for
91
+ * `math.huge`), and cancelling the Promise drops both the request and the timer.
92
+ *
93
+ * A send that raises (an argument the engine cannot carry) raises to the caller, as it did when
94
+ * the request was sent ahead of its Promise, and leaves nothing pending.
95
+
96
+ ]]
97
+ local createInvocation = function(player, requestInfo, timeout, send)
98
+ local _original = requestInfoClient.nextId
99
+ requestInfoClient.nextId += 1
100
+ local id = _original
101
+ local failed = false
102
+ local failure
103
+ local invocation = TS.Promise.new(function(resolve, reject, onCancel)
104
+ local timer
105
+ -- A player's entry lives only while requests to them are in flight, so a request that is
106
+ -- never answered holds the Player only until it times out or is cancelled.
107
+ local settle = function()
108
+ requestInfo.requests[id] = nil
109
+ local _condition = player and next(requestInfo.requests) == nil
110
+ if _condition then
111
+ local _player = player
112
+ _condition = requestInfoServer[_player] == requestInfo
113
+ end
114
+ if _condition then
115
+ local _player = player
116
+ requestInfoServer[_player] = nil
117
+ end
118
+ if timer ~= nil then
119
+ task.cancel(timer)
120
+ timer = nil
121
+ end
122
+ end
123
+ requestInfo.requests[id] = function(value, rejection)
124
+ settle()
84
125
  if rejection then
85
126
  return reject(rejection)
86
127
  end
@@ -90,40 +131,77 @@ local function createFunctionSender(options)
90
131
  resolve(value)
91
132
  end
92
133
  end
93
- onCancel(function()
94
- local _requests_1 = requestInfo.requests
95
- local _id_1 = id
96
- _requests_1[_id_1] = nil
97
- end)
134
+ if timeout ~= math.huge then
135
+ timer = task.delay(timeout, function()
136
+ -- Running now: the timer is spent, and a thread cannot cancel itself.
137
+ timer = nil
138
+ settle()
139
+ reject(NetworkingFunctionError.Timeout)
140
+ end)
141
+ end
142
+ onCancel(settle)
143
+ local sent, reason = pcall(send, id)
144
+ if not sent then
145
+ settle()
146
+ failed = true
147
+ failure = reason
148
+ end
98
149
  end)
150
+ if failed then
151
+ -- Never settled, so cancelling it is silent; the raise is the caller's.
152
+ invocation:cancel()
153
+ error(failure, 0)
154
+ end
155
+ return invocation
99
156
  end
100
157
  return {
101
- invokeServer = function(self, ...)
158
+ invokeServer = function(self, timeout, ...)
102
159
  local args = { ... }
103
- local _original = requestInfoClient.nextId
104
- requestInfoClient.nextId += 1
105
- local id = _original
106
- event:fireServer(id, unpack(args))
107
- return createInvocation(nil, id, requestInfoClient)
160
+ return createInvocation(nil, requestInfoClient, timeout, function(id)
161
+ return event:fireServer(id, unpack(args))
162
+ end)
108
163
  end,
109
- invokeClient = function(self, player, ...)
164
+ invokeClient = function(self, player, timeout, ...)
110
165
  local args = { ... }
111
166
  local _player = player
112
- local requestInfo = requestInfoServer[_player]
167
+ local _condition = departedPlayers[_player] ~= nil
168
+ if not _condition then
169
+ _condition = hasLeft(player)
170
+ end
171
+ if _condition then
172
+ return TS.Promise.reject(NetworkingFunctionError.Cancelled)
173
+ end
174
+ local _player_1 = player
175
+ local requestInfo = requestInfoServer[_player_1]
113
176
  if not requestInfo then
114
177
  local _exp = player
115
178
  requestInfo = createRequestInfo()
116
179
  local _requestInfo = requestInfo
117
180
  requestInfoServer[_exp] = _requestInfo
118
181
  end
119
- local _original = requestInfoClient.nextId
120
- requestInfoClient.nextId += 1
121
- local id = _original
122
- event:fireClient(player, id, unpack(args))
123
- return createInvocation(player, id, requestInfo)
182
+ return createInvocation(player, requestInfo, timeout, function(id)
183
+ return event:fireClient(player, id, unpack(args))
184
+ end)
124
185
  end,
125
186
  }
126
187
  end
188
+ --[[
189
+ *
190
+ * A player who left before this sender existed, whom its `PlayerRemoving` never saw: a player in the
191
+ * game is parented to `Players`. Inside `PlayerRemoving` the leaving player may still read as
192
+ * parented (Immediate signals), which is what `departedPlayers` is for. A Player that never was
193
+ * parented there (the Lune harness's) counts as present while `Players` lists it.
194
+
195
+ ]]
196
+ function hasLeft(player)
197
+ local _condition = player.Parent ~= Players
198
+ if _condition then
199
+ local _exp = Players:GetPlayers()
200
+ local _player = player
201
+ _condition = not (table.find(_exp, _player) ~= nil)
202
+ end
203
+ return _condition
204
+ end
127
205
  function createRequestInfo()
128
206
  return {
129
207
  nextId = 0,
@@ -9,7 +9,7 @@ export declare function createClientMethod(config: FunctionCreateConfiguration<u
9
9
  invokeWithTimeout: (timeout: number, ...args: unknown[]) => Promise<unknown>;
10
10
  readonly _flamework_receive?: unknown[] | undefined;
11
11
  readonly _flamework_fn?: unknown;
12
- _setCallback: (callback: (...args: never[]) => unknown) => void;
12
+ _setCallback: (callback: (...args: never[]) => unknown, pack: (value: unknown) => unknown) => void;
13
13
  setCallback: (callback: (...args: unknown[]) => unknown | Promise<unknown>) => void;
14
14
  predict: (...args: unknown[]) => Promise<unknown>;
15
15
  };
@@ -1,18 +1,19 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
- local NetworkingFunctionError = TS.import(script, script.Parent.Parent, "function", "errors").NetworkingFunctionError
4
- local timeoutPromise = TS.import(script, script.Parent.Parent, "util", "timeoutPromise").timeoutPromise
3
+ local trimArguments = TS.import(script, script.Parent.Parent, "util", "trimArguments").trimArguments
5
4
  local function createClientMethod(config, receiver, sender)
5
+ -- A method that takes an argument list trims it before spreading it, or an explicit trailing
6
+ -- `undefined` would lose the arguments after a gap (see `trimArguments`).
6
7
  local method = {
7
8
  invoke = function(self, ...)
8
9
  local args = { ... }
9
- return self:invokeWithTimeout(config.defaultTimeout, unpack(args))
10
+ return self:invokeWithTimeout(config.defaultTimeout, unpack(trimArguments(args)))
10
11
  end,
11
12
  invokeWithTimeout = function(self, timeout, ...)
12
13
  local args = { ... }
13
14
  local _sender = sender
14
15
  assert(_sender, "This is not a sender remote.")
15
- return TS.Promise.race({ timeoutPromise(timeout, NetworkingFunctionError.Timeout), sender:invokeServer(unpack(args)) })
16
+ return sender:invokeServer(timeout, unpack(trimArguments(args)))
16
17
  end,
17
18
  _invoke = function(self, ...)
18
19
  local packed = { ... }
@@ -27,22 +28,22 @@ local function createClientMethod(config, receiver, sender)
27
28
  assert(_receiver, "This is not a receiver remote.")
28
29
  receiver:setClientCallback(callback)
29
30
  end,
30
- _setCallback = function(self, callback)
31
+ _setCallback = function(self, callback, pack)
31
32
  local _receiver = receiver
32
33
  assert(_receiver, "This is not a receiver remote.")
33
- receiver:setClientCallback(callback, true)
34
+ receiver:setClientCallback(callback, pack)
34
35
  end,
35
36
  predict = function(self, ...)
36
37
  local args = { ... }
37
38
  local _receiver = receiver
38
39
  assert(_receiver, "This is not a receiver remote.")
39
- return receiver:invoke(nil, unpack(args))
40
+ return receiver:invoke(nil, unpack(trimArguments(args)))
40
41
  end,
41
42
  }
42
43
  setmetatable(method, {
43
44
  __call = function(method, ...)
44
45
  local args = { ... }
45
- return method:invoke(unpack(args))
46
+ return method:invoke(unpack(trimArguments(args)))
46
47
  end,
47
48
  })
48
49
  return method
@@ -1,10 +1,9 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
3
  local NetworkingFunctionError = TS.import(script, script.Parent.Parent, "function", "errors").NetworkingFunctionError
4
- local SkipBadRequest = TS.import(script, script.Parent.Parent, "middleware", "skip").SkipBadRequest
5
4
  local createFunctionReceiver = TS.import(script, script.Parent.Parent, "function", "createFunctionReceiver").createFunctionReceiver
6
5
  local createFunctionSender = TS.import(script, script.Parent.Parent, "function", "createFunctionSender").createFunctionSender
7
- local createGuardMiddleware = TS.import(script, script.Parent.Parent, "middleware", "createGuardMiddleware").createGuardMiddleware
6
+ local createGuards = TS.import(script, script.Parent.Parent, "middleware", "createGuards").createGuards
8
7
  local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Players
9
8
  local getNamespaceConfig = TS.import(script, script.Parent.Parent, "util", "getNamespaceConfig").getNamespaceConfig
10
9
  local function createGenericHandler(globalName, namespaceName, receiverPrefix, senderPrefix, metadata, config, signals, createMethod)
@@ -42,11 +41,13 @@ local function createGenericHandler(globalName, namespaceName, receiverPrefix, s
42
41
  name = effectiveName,
43
42
  globalName = globalName,
44
43
  }
44
+ -- The generated guards run ahead of all user middleware; a request that fails them is answered
45
+ -- `BadRequest`.
46
+ local incomingGuards
45
47
  if not config.disableIncomingGuards and isReceiver then
46
48
  local guards = metadata.incoming[name]
47
49
  assert(guards)
48
- local _arg0 = createGuardMiddleware(name, guards[1], guards[2], networkInfo, config.warnOnInvalidGuards, signals, SkipBadRequest)
49
- table.insert(incomingMiddleware, 1, _arg0)
50
+ incomingGuards = createGuards(name, guards[1], guards[2], networkInfo, config.warnOnInvalidGuards, signals)
50
51
  end
51
52
  -- A malformed serialized payload is reported like a failed guard, with no argument index.
52
53
  local onMalformed = function(player, message)
@@ -65,9 +66,10 @@ local function createGenericHandler(globalName, namespaceName, receiverPrefix, s
65
66
  local _object = {
66
67
  namespace = globalName,
67
68
  debugName = name,
68
- id = if isSender then `{receiverPrefix}{effectiveName}` else effectiveName,
69
+ id = `{receiverPrefix}{effectiveName}`,
69
70
  networkInfo = networkInfo,
70
71
  incomingMiddleware = incomingMiddleware,
72
+ incomingGuards = incomingGuards,
71
73
  }
72
74
  local _left = "argsDecoder"
73
75
  local _result_1 = metadata.incomingSerializers
@@ -75,12 +77,6 @@ local function createGenericHandler(globalName, namespaceName, receiverPrefix, s
75
77
  _result_1 = _result_1[name]
76
78
  end
77
79
  _object[_left] = _result_1
78
- local _left_1 = "resultDecoder"
79
- local _result_2 = metadata.incomingResults
80
- if _result_2 ~= nil then
81
- _result_2 = _result_2[name]
82
- end
83
- _object[_left_1] = _result_2
84
80
  _object.onMalformed = onMalformed
85
81
  _result = createFunctionReceiver(_object)
86
82
  else
@@ -92,7 +88,7 @@ local function createGenericHandler(globalName, namespaceName, receiverPrefix, s
92
88
  local _object = {
93
89
  namespace = globalName,
94
90
  debugName = name,
95
- id = if isReceiver then `{senderPrefix}{effectiveName}` else effectiveName,
91
+ id = `{senderPrefix}{effectiveName}`,
96
92
  networkInfo = networkInfo,
97
93
  }
98
94
  local _left = "resultDecoder"
@@ -5,6 +5,7 @@ local createSignalContainer = TS.import(script, script.Parent.Parent, "util", "c
5
5
  local createGenericHandler = TS.import(script, script.Parent, "createGenericHandler").createGenericHandler
6
6
  local createServerMethod = TS.import(script, script.Parent, "createServerMethod").createServerMethod
7
7
  local createClientMethod = TS.import(script, script.Parent, "createClientMethod").createClientMethod
8
+ local createOnce = TS.import(script, script.Parent.Parent, "util", "createOnce").createOnce
8
9
  local SERVER_PREFIX = "$"
9
10
  local CLIENT_PREFIX = "@"
10
11
  local function getDefaultConfiguration(config)
@@ -38,7 +39,9 @@ end
38
39
  local function createNetworkingFunction(globalName)
39
40
  local signals = createSignalContainer()
40
41
  local server
41
- local client
42
+ -- Built once: building it waits for the server's remotes, and a thread that asks meanwhile gets the
43
+ -- same handler rather than a second one wired to the same remotes.
44
+ local client = createOnce()
42
45
  return {
43
46
  createServer = function(self, config, meta)
44
47
  if RunService:IsRunning() and not RunService:IsServer() then
@@ -53,10 +56,9 @@ local function createNetworkingFunction(globalName)
53
56
  if RunService:IsRunning() and not RunService:IsClient() then
54
57
  return nil
55
58
  end
56
- if client == nil then
57
- client = createGenericHandler(globalName, nil, CLIENT_PREFIX, SERVER_PREFIX, meta, getDefaultConfiguration(config), signals, createClientMethod)
58
- end
59
- return client
59
+ return client(function()
60
+ return createGenericHandler(globalName, nil, CLIENT_PREFIX, SERVER_PREFIX, meta, getDefaultConfiguration(config), signals, createClientMethod)
61
+ end)
60
62
  end,
61
63
  registerHandler = function(self, key, callback)
62
64
  return signals:connect(key, callback)
@@ -9,7 +9,7 @@ export declare function createServerMethod(config: FunctionCreateConfiguration<u
9
9
  invokeWithTimeout: (player: Player, timeout: number, ...args: unknown[]) => Promise<unknown>;
10
10
  readonly _flamework_receive?: unknown[] | undefined;
11
11
  readonly _flamework_fn?: unknown;
12
- _setCallback: (callback: (player: Player, ...args: never[]) => unknown) => void;
12
+ _setCallback: (callback: (player: Player, ...args: never[]) => unknown, pack: (value: unknown) => unknown) => void;
13
13
  setCallback: (callback: (player: Player, ...args: unknown[]) => unknown | Promise<unknown>) => void;
14
14
  predict: (player: Player, ...args: unknown[]) => Promise<unknown>;
15
15
  };
@@ -1,18 +1,19 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
- local NetworkingFunctionError = TS.import(script, script.Parent.Parent, "function", "errors").NetworkingFunctionError
4
- local timeoutPromise = TS.import(script, script.Parent.Parent, "util", "timeoutPromise").timeoutPromise
3
+ local trimArguments = TS.import(script, script.Parent.Parent, "util", "trimArguments").trimArguments
5
4
  local function createServerMethod(config, receiver, sender)
5
+ -- A method that takes an argument list trims it before spreading it, or an explicit trailing
6
+ -- `undefined` would lose the arguments after a gap (see `trimArguments`).
6
7
  local method = {
7
8
  invoke = function(self, player, ...)
8
9
  local args = { ... }
9
- return self:invokeWithTimeout(player, config.defaultTimeout, unpack(args))
10
+ return self:invokeWithTimeout(player, config.defaultTimeout, unpack(trimArguments(args)))
10
11
  end,
11
12
  invokeWithTimeout = function(self, player, timeout, ...)
12
13
  local args = { ... }
13
14
  local _sender = sender
14
15
  assert(_sender, "This is not a sender remote.")
15
- return TS.Promise.race({ timeoutPromise(timeout, NetworkingFunctionError.Timeout), sender:invokeClient(player, unpack(args)) })
16
+ return sender:invokeClient(player, timeout, unpack(trimArguments(args)))
16
17
  end,
17
18
  _invoke = function(self, player, ...)
18
19
  local packed = { ... }
@@ -27,22 +28,22 @@ local function createServerMethod(config, receiver, sender)
27
28
  assert(_receiver, "This is not a receiver remote.")
28
29
  receiver:setServerCallback(callback)
29
30
  end,
30
- _setCallback = function(self, callback)
31
+ _setCallback = function(self, callback, pack)
31
32
  local _receiver = receiver
32
33
  assert(_receiver, "This is not a receiver remote.")
33
- receiver:setServerCallback(callback, true)
34
+ receiver:setServerCallback(callback, pack)
34
35
  end,
35
36
  predict = function(self, player, ...)
36
37
  local args = { ... }
37
38
  local _receiver = receiver
38
39
  assert(_receiver, "This is not a receiver remote.")
39
- return receiver:invoke(player, unpack(args))
40
+ return receiver:invoke(player, unpack(trimArguments(args)))
40
41
  end,
41
42
  }
42
43
  setmetatable(method, {
43
44
  __call = function(method, player, ...)
44
45
  local args = { ... }
45
- return method:invoke(player, unpack(args))
46
+ return method:invoke(player, unpack(trimArguments(args)))
46
47
  end,
47
48
  })
48
49
  return method
@@ -12,6 +12,7 @@ import {
12
12
  } from "../types";
13
13
  import { FunctionNetworkingEvents } from "../handlers";
14
14
  import { FunctionMiddleware } from "../middleware/types";
15
+ import { SignalConnection } from "../util/signal";
15
16
  import { Modding } from "@flamework-experimental/core";
16
17
 
17
18
  /**
@@ -68,8 +69,8 @@ export interface ServerReceiver<I extends unknown[], O, F = unknown> extends Raw
68
69
  /** @hidden The declared function type; its return type is what the transformer packs. */
69
70
  readonly _flamework_fn?: F;
70
71
 
71
- /** @hidden Registers a callback whose successful results are already packed as `[payload, blobs?]`. */
72
- _setCallback(callback: (player: Player, ...args: never[]) => unknown): void;
72
+ /** @hidden Registers a callback with `pack`, which turns a successful result into `[payload, blobs?]`. */
73
+ _setCallback(callback: (player: Player, ...args: never[]) => unknown, pack: (value: unknown) => unknown): void;
73
74
  }
74
75
 
75
76
  export interface RawClientSender<I extends unknown[], O> {
@@ -119,8 +120,8 @@ export interface ClientReceiver<I extends unknown[], O, F = unknown> extends Raw
119
120
  /** @hidden The declared function type; its return type is what the transformer packs. */
120
121
  readonly _flamework_fn?: F;
121
122
 
122
- /** @hidden Registers a callback whose successful results are already packed as `[payload, blobs?]`. */
123
- _setCallback(callback: (...args: never[]) => unknown): void;
123
+ /** @hidden Registers a callback with `pack`, which turns a successful result into `[payload, blobs?]`. */
124
+ _setCallback(callback: (...args: never[]) => unknown, pack: (value: unknown) => unknown): void;
124
125
  }
125
126
 
126
127
  export type ServerHandler<E, R> = NetworkingObfuscationMarker & {
@@ -199,7 +200,7 @@ export interface GlobalFunction<S, C> {
199
200
  registerHandler<K extends keyof FunctionNetworkingEvents>(
200
201
  key: K,
201
202
  callback: FunctionNetworkingEvents[K],
202
- ): RBXScriptConnection;
203
+ ): SignalConnection;
203
204
  }
204
205
 
205
206
  export interface FunctionConfiguration {
@@ -250,22 +251,19 @@ export type NamespaceMetadata<R, S> = Modding.Emit<{
250
251
  incoming: IntrinsicObfuscate<{ [k in keyof Functions<R>]: IntrinsicTupleGuards<Parameters<R[k]>> }>;
251
252
 
252
253
  outgoingIds: ObfuscateNames<keyof Functions<S>>;
253
- outgoing: IntrinsicObfuscate<{ [k in keyof Functions<S>]: Modding.Target.Guard<ReturnType<S[k]>> }>;
254
+ // A response carries the resolved value: a `Promise<T>` result is checked as `T`.
255
+ outgoing: IntrinsicObfuscate<{ [k in keyof Functions<S>]: Modding.Target.Guard<Awaited<ReturnType<S[k]>>> }>;
254
256
 
255
257
  /**
256
258
  * Decoders, present only with `networking.serialization` on and absent for raw functions: the
257
- * argument lists of requests this realm receives, the results its callbacks return (so `predict`
258
- * can unpack them) and the responses to requests it sends. Requests and results are packed inline
259
- * where they are produced.
259
+ * argument lists of requests this realm receives and the responses to requests it sends. Requests
260
+ * and results are packed inline where they are produced.
260
261
  */
261
262
  incomingSerializers: IntrinsicObfuscate<{
262
263
  [k in keyof Functions<R>]: R[k] extends NetworkRaw<unknown>
263
264
  ? undefined
264
265
  : IntrinsicNetworkDecoder<Parameters<R[k]>>;
265
266
  }>;
266
- incomingResults: IntrinsicObfuscate<{
267
- [k in keyof Functions<R>]: R[k] extends NetworkRaw<unknown> ? undefined : IntrinsicNetworkResultDecoder<R[k]>;
268
- }>;
269
267
  outgoingResults: IntrinsicObfuscate<{
270
268
  [k in keyof Functions<S>]: S[k] extends NetworkRaw<unknown> ? undefined : IntrinsicNetworkResultDecoder<S[k]>;
271
269
  }>;
package/out/handlers.d.ts CHANGED
@@ -1,36 +1,35 @@
1
- import Signal from "@rbxts/signal";
2
- import { NetworkInfo } from "./types";
3
-
4
- interface BaseEvent {
5
- /**
6
- * The event or function that was fired.
7
- */
8
- networkInfo: NetworkInfo;
9
- }
10
-
11
- interface BadRequestData extends BaseEvent {
12
- /**
13
- * The index of the argument that was incorrect, or -1 when a serialized payload could not be decoded.
14
- */
15
- argIndex: number;
16
-
17
- /**
18
- * The value of the argument that was incorrect, or the decoding error for a malformed payload.
19
- */
20
- argValue: unknown;
21
- }
22
-
23
- interface BadResponseData extends BaseEvent {
24
- /**
25
- * The index of the argument that was incorrect.
26
- */
27
- value: unknown;
28
- }
29
-
30
- export interface EventNetworkingEvents {
31
- onBadRequest: (player: Player, data: BadRequestData) => void;
32
- }
33
-
34
- export interface FunctionNetworkingEvents extends EventNetworkingEvents {
35
- onBadResponse: (player: Player, data: BadResponseData) => void;
36
- }
1
+ import { NetworkInfo } from "./types";
2
+
3
+ interface BaseEvent {
4
+ /**
5
+ * The event or function that was fired.
6
+ */
7
+ networkInfo: NetworkInfo;
8
+ }
9
+
10
+ interface BadRequestData extends BaseEvent {
11
+ /**
12
+ * The index of the argument that was incorrect, or -1 when a serialized payload could not be decoded.
13
+ */
14
+ argIndex: number;
15
+
16
+ /**
17
+ * The value of the argument that was incorrect, or the decoding error for a malformed payload.
18
+ */
19
+ argValue: unknown;
20
+ }
21
+
22
+ interface BadResponseData extends BaseEvent {
23
+ /**
24
+ * The index of the argument that was incorrect.
25
+ */
26
+ value: unknown;
27
+ }
28
+
29
+ export interface EventNetworkingEvents {
30
+ onBadRequest: (player: Player, data: BadRequestData) => void;
31
+ }
32
+
33
+ export interface FunctionNetworkingEvents extends EventNetworkingEvents {
34
+ onBadResponse: (player: Player, data: BadResponseData) => void;
35
+ }
package/out/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { GlobalFunction } from "./functions/types";
3
3
  import { Skip as NetworkingSkip } from "./middleware/skip";
4
4
  import { NetworkingFunctionError } from "./function/errors";
5
5
  import { EventMiddleware as _EventMiddleware, FunctionMiddleware as _FunctionMiddleware } from "./middleware/types";
6
+ import { SignalConnection as _SignalConnection } from "./util/signal";
6
7
  import { NetworkRaw, NetworkUnreliable } from "./types";
7
8
  import type { Modding } from "@flamework-experimental/core";
8
9
  export declare namespace Networking {
@@ -51,5 +52,10 @@ export declare namespace Networking {
51
52
  * A function that generates an event middleware.
52
53
  */
53
54
  type FunctionMiddleware<I extends readonly unknown[] = unknown[], O = void> = _FunctionMiddleware<I, O>;
55
+ /**
56
+ * What `connect` and `registerHandler` return: `Connected`, `Disconnect()`, and `Destroy()` for
57
+ * maids and janitors. It is networking's own, not an engine `RBXScriptConnection`.
58
+ */
59
+ type Connection = _SignalConnection;
54
60
  }
55
61
  export { NetworkingFunctionError };
package/out/init.luau CHANGED
@@ -71,6 +71,12 @@ do
71
71
  * A function that generates an event middleware.
72
72
 
73
73
  ]]
74
+ --[[
75
+ *
76
+ * What `connect` and `registerHandler` return: `Connected`, `Disconnect()`, and `Destroy()` for
77
+ * maids and janitors. It is networking's own, not an engine `RBXScriptConnection`.
78
+
79
+ ]]
74
80
  end
75
81
  return {
76
82
  Networking = Networking,
@@ -0,0 +1,10 @@
1
+ import { t } from "@rbxts/t";
2
+ import { SignalContainer } from "../util/createSignalContainer";
3
+ import { EventNetworkingEvents } from "../handlers";
4
+ import { NetworkInfo } from "../types";
5
+ import { Guards } from "./processor";
6
+ /**
7
+ * The generated argument checks of one event or function, for the receive pipeline, which runs them
8
+ * ahead of all user middleware. A failure warns (with `warnOnInvalid`) and fires `onBadRequest`.
9
+ */
10
+ export declare function createGuards(name: string, fixedParameters: t.check<unknown>[], restParameter: t.check<unknown> | undefined, networkInfo: NetworkInfo, warnOnInvalid: boolean, signals: SignalContainer<EventNetworkingEvents>): Guards;