@flamework-experimental/networking 2.0.0-alpha.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/README.md +66 -0
- package/flamework.build +7 -0
- package/out/event/createEvent.d.ts +59 -0
- package/out/event/createEvent.luau +114 -0
- package/out/event/createRemoteInstance.d.ts +1 -0
- package/out/event/createRemoteInstance.luau +58 -0
- package/out/events/createClientMethod.d.ts +5 -0
- package/out/events/createClientMethod.luau +30 -0
- package/out/events/createGenericHandler.d.ts +5 -0
- package/out/events/createGenericHandler.luau +102 -0
- package/out/events/createNetworkingEvent.d.ts +2 -0
- package/out/events/createNetworkingEvent.luau +60 -0
- package/out/events/createServerMethod.d.ts +13 -0
- package/out/events/createServerMethod.luau +63 -0
- package/out/events/types.d.ts +220 -0
- package/out/function/createFunctionReceiver.d.ts +52 -0
- package/out/function/createFunctionReceiver.luau +118 -0
- package/out/function/createFunctionSender.d.ts +45 -0
- package/out/function/createFunctionSender.luau +135 -0
- package/out/function/errors.d.ts +10 -0
- package/out/function/errors.luau +25 -0
- package/out/functions/createClientMethod.d.ts +15 -0
- package/out/functions/createClientMethod.luau +52 -0
- package/out/functions/createGenericHandler.d.ts +7 -0
- package/out/functions/createGenericHandler.luau +140 -0
- package/out/functions/createNetworkingFunction.d.ts +2 -0
- package/out/functions/createNetworkingFunction.luau +68 -0
- package/out/functions/createServerMethod.d.ts +15 -0
- package/out/functions/createServerMethod.luau +52 -0
- package/out/functions/types.d.ts +289 -0
- package/out/handlers.d.ts +36 -0
- package/out/index.d.ts +55 -0
- package/out/init.luau +78 -0
- package/out/middleware/createGuardMiddleware.d.ts +6 -0
- package/out/middleware/createGuardMiddleware.luau +44 -0
- package/out/middleware/createMiddlewareProcessor.d.ts +3 -0
- package/out/middleware/createMiddlewareProcessor.luau +24 -0
- package/out/middleware/skip.d.ts +8 -0
- package/out/middleware/skip.luau +22 -0
- package/out/middleware/types.d.ts +15 -0
- package/out/types.d.ts +78 -0
- package/out/util/createSignalContainer.d.ts +5 -0
- package/out/util/createSignalContainer.luau +30 -0
- package/out/util/getNamespaceConfig.d.ts +10 -0
- package/out/util/getNamespaceConfig.luau +15 -0
- package/out/util/timeoutPromise.d.ts +1 -0
- package/out/util/timeoutPromise.luau +10 -0
- package/package.json +32 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FunctionParameters,
|
|
3
|
+
IntrinsicTupleGuards,
|
|
4
|
+
IntrinsicNetworkDecoder,
|
|
5
|
+
IntrinsicObfuscate,
|
|
6
|
+
NetworkingObfuscationMarker,
|
|
7
|
+
NetworkRaw,
|
|
8
|
+
NetworkUnreliable,
|
|
9
|
+
ObfuscateNames,
|
|
10
|
+
} from "../types";
|
|
11
|
+
import { EventNetworkingEvents } from "../handlers";
|
|
12
|
+
import { EventMiddleware } from "../middleware/types";
|
|
13
|
+
import { Modding } from "@flamework-experimental/core";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A sender declared `Networking.RawReliable` / `RawUnreliable`: its arguments travel as they are.
|
|
17
|
+
* Without the hidden marker below, no call site packs them and the peer runs no decoder.
|
|
18
|
+
*/
|
|
19
|
+
export interface RawServerSender<I extends unknown[]> {
|
|
20
|
+
(player: Player | Player[], ...args: I): void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sends this request to the specified player(s).
|
|
24
|
+
* @param players The player(s) that will receive this event
|
|
25
|
+
*/
|
|
26
|
+
fire(players: Player | Player[], ...args: I): void;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sends this request to all players, excluding the specified player(s).
|
|
30
|
+
* @param players The player(s) that will not receive this event
|
|
31
|
+
*/
|
|
32
|
+
except(players: Player | Player[], ...args: I): void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sends this request to all connected players.
|
|
36
|
+
*/
|
|
37
|
+
broadcast(...args: I): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ServerSender<I extends unknown[]> extends RawServerSender<I> {
|
|
41
|
+
/** @hidden Marks a sender for the transformer, which packs its arguments at each call site. */
|
|
42
|
+
readonly _flamework_send?: I;
|
|
43
|
+
|
|
44
|
+
/** @hidden Sends an argument list the transformer already packed; nothing when the list carries nothing. */
|
|
45
|
+
_fire(players: Player | Player[], payload?: buffer, blobs?: Array<defined>): void;
|
|
46
|
+
|
|
47
|
+
/** @hidden Sends an argument list the transformer already packed; nothing when the list carries nothing. */
|
|
48
|
+
_except(players: Player | Player[], payload?: buffer, blobs?: Array<defined>): void;
|
|
49
|
+
|
|
50
|
+
/** @hidden Sends an argument list the transformer already packed; nothing when the list carries nothing. */
|
|
51
|
+
_broadcast(payload?: buffer, blobs?: Array<defined>): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RawServerReceiver<I extends unknown[]> {
|
|
55
|
+
/**
|
|
56
|
+
* Connect to this networking event.
|
|
57
|
+
* @param callback The callback that will be fired
|
|
58
|
+
*/
|
|
59
|
+
connect(cb: (player: Player, ...args: I) => void): RBXScriptConnection;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fires a server event using player as the sender.
|
|
63
|
+
*/
|
|
64
|
+
predict(player: Player, ...args: I): void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ServerReceiver<I extends unknown[]> extends RawServerReceiver<I> {
|
|
68
|
+
/** @hidden Marks a receiver for the transformer. */
|
|
69
|
+
readonly _flamework_receive?: I;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RawClientSender<I extends unknown[]> {
|
|
73
|
+
(...args: I): void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sends this request to the server.
|
|
77
|
+
*/
|
|
78
|
+
fire(...args: I): void;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ClientSender<I extends unknown[]> extends RawClientSender<I> {
|
|
82
|
+
/** @hidden Marks a sender for the transformer, which packs its arguments at each call site. */
|
|
83
|
+
readonly _flamework_send?: I;
|
|
84
|
+
|
|
85
|
+
/** @hidden Sends an argument list the transformer already packed; nothing when the list carries nothing. */
|
|
86
|
+
_fire(payload?: buffer, blobs?: Array<defined>): void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface RawClientReceiver<I extends unknown[]> {
|
|
90
|
+
/**
|
|
91
|
+
* Connect to this networking event.
|
|
92
|
+
* @param callback The callback that will be fired
|
|
93
|
+
*/
|
|
94
|
+
connect(cb: (...args: I) => void): RBXScriptConnection;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Fires a client event.
|
|
98
|
+
*/
|
|
99
|
+
predict(...args: I): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ClientReceiver<I extends unknown[]> extends RawClientReceiver<I> {
|
|
103
|
+
/** @hidden Marks a receiver for the transformer. */
|
|
104
|
+
readonly _flamework_receive?: I;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type ServerHandler<E, R> = NetworkingObfuscationMarker & {
|
|
108
|
+
[k in keyof Events<E>]: E[k] extends NetworkRaw<unknown>
|
|
109
|
+
? RawServerSender<FunctionParameters<E[k]>>
|
|
110
|
+
: ServerSender<FunctionParameters<E[k]>>;
|
|
111
|
+
} & {
|
|
112
|
+
[k in keyof Events<R>]: R[k] extends NetworkRaw<unknown>
|
|
113
|
+
? RawServerReceiver<FunctionParameters<R[k]>>
|
|
114
|
+
: ServerReceiver<FunctionParameters<R[k]>>;
|
|
115
|
+
} & {
|
|
116
|
+
[k in keyof EventNamespaces<E>]: ServerHandler<E[k], k extends keyof R ? R[k] : {}>;
|
|
117
|
+
} & {
|
|
118
|
+
[k in keyof EventNamespaces<R>]: ServerHandler<k extends keyof E ? E[k] : {}, R[k]>;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type ClientHandler<E, R> = NetworkingObfuscationMarker & {
|
|
122
|
+
[k in keyof Events<E>]: E[k] extends NetworkRaw<unknown>
|
|
123
|
+
? RawClientSender<FunctionParameters<E[k]>>
|
|
124
|
+
: ClientSender<FunctionParameters<E[k]>>;
|
|
125
|
+
} & {
|
|
126
|
+
[k in keyof Events<R>]: R[k] extends NetworkRaw<unknown>
|
|
127
|
+
? RawClientReceiver<FunctionParameters<R[k]>>
|
|
128
|
+
: ClientReceiver<FunctionParameters<R[k]>>;
|
|
129
|
+
} & {
|
|
130
|
+
[k in keyof EventNamespaces<E>]: ClientHandler<E[k], k extends keyof R ? R[k] : {}>;
|
|
131
|
+
} & {
|
|
132
|
+
[k in keyof EventNamespaces<R>]: ClientHandler<k extends keyof E ? E[k] : {}, R[k]>;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export interface EventCreateConfiguration<T> {
|
|
136
|
+
/**
|
|
137
|
+
* Disables input validation, allowing any value to pass.
|
|
138
|
+
* Defaults to `false`
|
|
139
|
+
*/
|
|
140
|
+
disableIncomingGuards: boolean;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Emit a warning whenever a guard fails.
|
|
144
|
+
* Defaults to `RunService.IsStudio()`
|
|
145
|
+
*/
|
|
146
|
+
warnOnInvalidGuards: boolean;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The middleware for each event.
|
|
150
|
+
*/
|
|
151
|
+
middleware: EventMiddlewareList<T>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface GlobalEvent<S, C> {
|
|
155
|
+
/**
|
|
156
|
+
* This is the server implementation of the network and does not exist on the client.
|
|
157
|
+
*
|
|
158
|
+
* @metadata macro {@link config intrinsic-const} {@link config intrinsic-middleware}
|
|
159
|
+
*/
|
|
160
|
+
createServer(config: Partial<EventCreateConfiguration<S>>, meta?: NamespaceMetadata<S, C>): ServerHandler<C, S>;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* This is the client implementation of the network and does not exist on the server.
|
|
164
|
+
*
|
|
165
|
+
* @metadata macro {@link config intrinsic-const} {@link config intrinsic-middleware}
|
|
166
|
+
*/
|
|
167
|
+
createClient(config: Partial<EventCreateConfiguration<C>>, meta?: NamespaceMetadata<C, S>): ClientHandler<S, C>;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Registers a networking event handler.
|
|
171
|
+
* @param key The name of the event
|
|
172
|
+
* @param callback The handler you wish to attach
|
|
173
|
+
*/
|
|
174
|
+
registerHandler<K extends keyof EventNetworkingEvents>(
|
|
175
|
+
key: K,
|
|
176
|
+
callback: EventNetworkingEvents[K],
|
|
177
|
+
): RBXScriptConnection;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export type EventNamespaces<T> = ExcludeMembers<T, Callback>;
|
|
181
|
+
export type Events<T> = ExtractMembers<T, Callback>;
|
|
182
|
+
|
|
183
|
+
export type NamespaceMetadata<R, S> = Modding.Emit<{
|
|
184
|
+
incomingIds: ObfuscateNames<keyof Events<R>>;
|
|
185
|
+
incoming: IntrinsicObfuscate<{ [k in keyof Events<R>]: IntrinsicTupleGuards<Parameters<Events<R>[k]>> }>;
|
|
186
|
+
incomingUnreliable: IntrinsicObfuscate<{
|
|
187
|
+
[k in keyof Events<R>]: R[k] extends NetworkUnreliable<unknown> ? true : undefined;
|
|
188
|
+
}>;
|
|
189
|
+
|
|
190
|
+
outgoingIds: ObfuscateNames<keyof Events<S>>;
|
|
191
|
+
outgoingUnreliable: IntrinsicObfuscate<{
|
|
192
|
+
[k in keyof Events<S>]: S[k] extends NetworkUnreliable<unknown> ? true : undefined;
|
|
193
|
+
}>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Decoders for each incoming event's argument list, present only with `networking.serialization`
|
|
197
|
+
* on, and absent for raw events and for lists that carry nothing. Outgoing lists are packed
|
|
198
|
+
* inline where they are fired; nothing here can encode.
|
|
199
|
+
*/
|
|
200
|
+
incomingSerializers: IntrinsicObfuscate<{
|
|
201
|
+
[k in keyof Events<R>]: R[k] extends NetworkRaw<unknown>
|
|
202
|
+
? undefined
|
|
203
|
+
: IntrinsicNetworkDecoder<Parameters<Events<R>[k]>>;
|
|
204
|
+
}>;
|
|
205
|
+
|
|
206
|
+
namespaceIds: ObfuscateNames<keyof EventNamespaces<R> | keyof EventNamespaces<S>>;
|
|
207
|
+
namespaces: IntrinsicObfuscate<
|
|
208
|
+
{
|
|
209
|
+
[k in keyof EventNamespaces<R>]: NamespaceMetadata<R[k], k extends keyof S ? S[k] : {}>;
|
|
210
|
+
} & {
|
|
211
|
+
[k in keyof EventNamespaces<S>]: NamespaceMetadata<k extends keyof R ? R[k] : {}, S[k]>;
|
|
212
|
+
}
|
|
213
|
+
>;
|
|
214
|
+
}>;
|
|
215
|
+
|
|
216
|
+
export type EventMiddlewareList<T> = {
|
|
217
|
+
readonly [k in keyof Events<T>]?: T[k] extends (...args: infer I) => void ? [...EventMiddleware<I>[]] : never;
|
|
218
|
+
} & {
|
|
219
|
+
readonly [k in keyof EventNamespaces<T>]?: EventMiddlewareList<T[k]>;
|
|
220
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Serialization } from "@flamework-experimental/core";
|
|
2
|
+
import { NetworkInfo } from "../types";
|
|
3
|
+
import { NetworkingFunctionError } from "./errors";
|
|
4
|
+
import { MiddlewareFactory } from "../middleware/types";
|
|
5
|
+
export interface CreateFunctionReceiverOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The namespace this event should be created in.
|
|
8
|
+
*/
|
|
9
|
+
namespace: string;
|
|
10
|
+
/**
|
|
11
|
+
* The name of the remote instance, not necessarily unique and used for debugging purposes.
|
|
12
|
+
*/
|
|
13
|
+
debugName: string;
|
|
14
|
+
/**
|
|
15
|
+
* The remote's ID which must be unique.
|
|
16
|
+
*/
|
|
17
|
+
id: string;
|
|
18
|
+
/**
|
|
19
|
+
* Information about the network, which includes some of the above.
|
|
20
|
+
*
|
|
21
|
+
* Passed to the middleware.
|
|
22
|
+
*/
|
|
23
|
+
networkInfo: NetworkInfo;
|
|
24
|
+
/**
|
|
25
|
+
* This function will be called when we receive a response, and can be used to resolve or reject values.
|
|
26
|
+
*/
|
|
27
|
+
incomingMiddleware?: MiddlewareFactory<any[], any>[];
|
|
28
|
+
/**
|
|
29
|
+
* Unpacks the request's argument list. Absent when the project does not enable serialization.
|
|
30
|
+
*/
|
|
31
|
+
argsDecoder?: Serialization.Decoder;
|
|
32
|
+
/**
|
|
33
|
+
* Unpacks the results a packed callback returns, so that `predict` resolves with the value itself.
|
|
34
|
+
* Absent when the project does not enable serialization.
|
|
35
|
+
*/
|
|
36
|
+
resultDecoder?: Serialization.Decoder;
|
|
37
|
+
/**
|
|
38
|
+
* Called when a request cannot be decoded; the caller receives `BadRequest`.
|
|
39
|
+
*/
|
|
40
|
+
onMalformed?: (player: Player | undefined, message: string) => void;
|
|
41
|
+
}
|
|
42
|
+
export interface RequestInfo {
|
|
43
|
+
nextId: number;
|
|
44
|
+
requests: Map<number, (value: unknown, rejection?: NetworkingFunctionError) => void>;
|
|
45
|
+
}
|
|
46
|
+
export interface FunctionReceiverInterface {
|
|
47
|
+
/** `packed`: the callback returns successful results as `[payload, blobs?]`, packed by the transformer. */
|
|
48
|
+
setServerCallback(callback: (player: Player, ...args: unknown[]) => unknown, packed?: boolean): void;
|
|
49
|
+
setClientCallback(callback: (...args: unknown[]) => unknown, packed?: boolean): void;
|
|
50
|
+
invoke(player: Player | undefined, ...args: unknown[]): Promise<unknown>;
|
|
51
|
+
}
|
|
52
|
+
export declare function createFunctionReceiver(options: CreateFunctionReceiverOptions): FunctionReceiverInterface;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
|
|
4
|
+
local _createEvent = TS.import(script, script.Parent.Parent, "event", "createEvent")
|
|
5
|
+
local createEvent = _createEvent.createEvent
|
|
6
|
+
local decodeArguments = _createEvent.decodeArguments
|
|
7
|
+
local NetworkingFunctionError = TS.import(script, script.Parent, "errors").NetworkingFunctionError
|
|
8
|
+
local createMiddlewareProcessor = TS.import(script, script.Parent.Parent, "middleware", "createMiddlewareProcessor").createMiddlewareProcessor
|
|
9
|
+
local _skip = TS.import(script, script.Parent.Parent, "middleware", "skip")
|
|
10
|
+
local Skip = _skip.Skip
|
|
11
|
+
local SkipBadRequest = _skip.SkipBadRequest
|
|
12
|
+
local getProcessResult
|
|
13
|
+
local function createFunctionReceiver(options)
|
|
14
|
+
local event = createEvent({
|
|
15
|
+
namespace = options.namespace,
|
|
16
|
+
debugName = options.debugName,
|
|
17
|
+
id = options.id,
|
|
18
|
+
networkInfo = options.networkInfo,
|
|
19
|
+
})
|
|
20
|
+
local callback
|
|
21
|
+
local packedResults = false
|
|
22
|
+
local setCallback = function(newCallback, packed)
|
|
23
|
+
packedResults = packed
|
|
24
|
+
callback = createMiddlewareProcessor(options.incomingMiddleware, options.networkInfo, function(player, ...)
|
|
25
|
+
local args = { ... }
|
|
26
|
+
if RunService:IsServer() then
|
|
27
|
+
return newCallback(player, unpack(args))
|
|
28
|
+
else
|
|
29
|
+
return newCallback(unpack(args))
|
|
30
|
+
end
|
|
31
|
+
end)
|
|
32
|
+
end
|
|
33
|
+
--[[
|
|
34
|
+
*
|
|
35
|
+
* A packed callback's successful value is `[payload, blobs?]`, or nothing at all when the result
|
|
36
|
+
* type carries nothing (`void`); errors always go back as they are.
|
|
37
|
+
|
|
38
|
+
]]
|
|
39
|
+
local respond = function(player, id, processResult, value)
|
|
40
|
+
if processResult == true and packedResults and value ~= nil then
|
|
41
|
+
local _binding = value
|
|
42
|
+
local payload = _binding[1]
|
|
43
|
+
local blobs = _binding[2]
|
|
44
|
+
event:fireEither(player, id, processResult, payload, blobs)
|
|
45
|
+
else
|
|
46
|
+
event:fireEither(player, id, processResult, value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
local processRequest = function(player, id, ...)
|
|
50
|
+
local args = { ... }
|
|
51
|
+
if not callback then
|
|
52
|
+
return event:fireEither(player, id, NetworkingFunctionError.Unprocessed)
|
|
53
|
+
end
|
|
54
|
+
local decoded = decodeArguments(options.argsDecoder, player, args, options.onMalformed)
|
|
55
|
+
if not decoded then
|
|
56
|
+
return event:fireEither(player, id, NetworkingFunctionError.BadRequest)
|
|
57
|
+
end
|
|
58
|
+
callback(player, unpack(decoded)):andThen(function(value)
|
|
59
|
+
return respond(player, id, getProcessResult(value), value)
|
|
60
|
+
end):catch(function(reason)
|
|
61
|
+
warn(`Failed to process request to '{options.debugName}'`)
|
|
62
|
+
warn(reason)
|
|
63
|
+
event:fireEither(player, id, false)
|
|
64
|
+
end)
|
|
65
|
+
end
|
|
66
|
+
if RunService:IsServer() then
|
|
67
|
+
event:connectServer(function(player, id, ...)
|
|
68
|
+
local args = { ... }
|
|
69
|
+
return processRequest(player, id, unpack(args))
|
|
70
|
+
end)
|
|
71
|
+
else
|
|
72
|
+
event:connectClient(function(id, ...)
|
|
73
|
+
local args = { ... }
|
|
74
|
+
return processRequest(nil, id, unpack(args))
|
|
75
|
+
end)
|
|
76
|
+
end
|
|
77
|
+
return {
|
|
78
|
+
setServerCallback = function(self, callback, packed)
|
|
79
|
+
if packed == nil then
|
|
80
|
+
packed = false
|
|
81
|
+
end
|
|
82
|
+
setCallback(callback, packed)
|
|
83
|
+
end,
|
|
84
|
+
setClientCallback = function(self, callback, packed)
|
|
85
|
+
if packed == nil then
|
|
86
|
+
packed = false
|
|
87
|
+
end
|
|
88
|
+
setCallback(callback, packed)
|
|
89
|
+
end,
|
|
90
|
+
invoke = function(self, player, ...)
|
|
91
|
+
local args = { ... }
|
|
92
|
+
if not callback then
|
|
93
|
+
return TS.Promise.reject(NetworkingFunctionError.Unprocessed)
|
|
94
|
+
end
|
|
95
|
+
return callback(player, unpack(args)):andThen(function(value)
|
|
96
|
+
local processResult = getProcessResult(value)
|
|
97
|
+
if processResult ~= true then
|
|
98
|
+
return TS.Promise.reject(processResult)
|
|
99
|
+
end
|
|
100
|
+
-- A packed callback answered with bytes; a local caller wants the value.
|
|
101
|
+
local decoder = options.resultDecoder
|
|
102
|
+
if not packedResults or not decoder then
|
|
103
|
+
return value
|
|
104
|
+
end
|
|
105
|
+
local _binding = value
|
|
106
|
+
local payload = _binding[1]
|
|
107
|
+
local blobs = _binding[2]
|
|
108
|
+
return decoder(payload, blobs or {})[1]
|
|
109
|
+
end)
|
|
110
|
+
end,
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
function getProcessResult(value)
|
|
114
|
+
return if value == Skip then NetworkingFunctionError.Cancelled elseif value == SkipBadRequest then NetworkingFunctionError.BadRequest else true
|
|
115
|
+
end
|
|
116
|
+
return {
|
|
117
|
+
createFunctionReceiver = createFunctionReceiver,
|
|
118
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Serialization } from "@flamework-experimental/core";
|
|
2
|
+
import { NetworkInfo } from "../types";
|
|
3
|
+
import { NetworkingFunctionError } from "./errors";
|
|
4
|
+
export interface CreateFunctionSenderOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The namespace this event should be created in.
|
|
7
|
+
*/
|
|
8
|
+
namespace: string;
|
|
9
|
+
/**
|
|
10
|
+
* The name of the remote instance, not necessarily unique and used for debugging purposes.
|
|
11
|
+
*/
|
|
12
|
+
debugName: string;
|
|
13
|
+
/**
|
|
14
|
+
* The remote's ID which must be unique.
|
|
15
|
+
*/
|
|
16
|
+
id: string;
|
|
17
|
+
/**
|
|
18
|
+
* Information about the network, which includes some of the above.
|
|
19
|
+
*
|
|
20
|
+
* Passed to the middleware.
|
|
21
|
+
*/
|
|
22
|
+
networkInfo: NetworkInfo;
|
|
23
|
+
/**
|
|
24
|
+
* This function will be called when we receive a response, and can be used to resolve or reject values.
|
|
25
|
+
*/
|
|
26
|
+
responseMiddleware?: (player: Player | undefined, value: unknown, resolve: (value: unknown) => void, reject: (value: unknown) => void) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Unpacks a successful response's value, carried as a one-element list. Absent when the project
|
|
29
|
+
* does not enable serialization. Requests reach `invoke*` already packed by the transformer.
|
|
30
|
+
*/
|
|
31
|
+
resultDecoder?: Serialization.Decoder;
|
|
32
|
+
/**
|
|
33
|
+
* Called when a response cannot be decoded; the request is rejected with `InvalidResult`.
|
|
34
|
+
*/
|
|
35
|
+
onMalformed?: (player: Player | undefined, message: string) => void;
|
|
36
|
+
}
|
|
37
|
+
export interface RequestInfo {
|
|
38
|
+
nextId: number;
|
|
39
|
+
requests: Map<number, (value: unknown, rejection?: NetworkingFunctionError) => void>;
|
|
40
|
+
}
|
|
41
|
+
export interface FunctionSenderInterface {
|
|
42
|
+
invokeServer(...args: unknown[]): Promise<unknown>;
|
|
43
|
+
invokeClient(player: Player, ...args: unknown[]): Promise<unknown>;
|
|
44
|
+
}
|
|
45
|
+
export declare function createFunctionSender(options: CreateFunctionSenderOptions): FunctionSenderInterface;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
|
|
4
|
+
local Players = _services.Players
|
|
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
|
|
9
|
+
local _errors = TS.import(script, script.Parent, "errors")
|
|
10
|
+
local NetworkingFunctionError = _errors.NetworkingFunctionError
|
|
11
|
+
local getFunctionError = _errors.getFunctionError
|
|
12
|
+
local t = TS.import(script, TS.getModule(script, "@rbxts", "t").lib.ts).t
|
|
13
|
+
local createRequestInfo
|
|
14
|
+
local function createFunctionSender(options)
|
|
15
|
+
local event = createEvent({
|
|
16
|
+
namespace = options.namespace,
|
|
17
|
+
debugName = options.debugName,
|
|
18
|
+
id = options.id,
|
|
19
|
+
networkInfo = options.networkInfo,
|
|
20
|
+
})
|
|
21
|
+
local processResponse = function(player, requestInfo, id, processResult, ...)
|
|
22
|
+
local response = { ... }
|
|
23
|
+
if not t.number(id) then
|
|
24
|
+
return nil
|
|
25
|
+
end
|
|
26
|
+
local _requests = requestInfo.requests
|
|
27
|
+
local _id = id
|
|
28
|
+
local request = _requests[_id]
|
|
29
|
+
local _requests_1 = requestInfo.requests
|
|
30
|
+
local _id_1 = id
|
|
31
|
+
_requests_1[_id_1] = nil
|
|
32
|
+
if not request then
|
|
33
|
+
return nil
|
|
34
|
+
end
|
|
35
|
+
local rejection = getFunctionError(processResult)
|
|
36
|
+
if rejection ~= nil or not options.resultDecoder then
|
|
37
|
+
request(response[1], rejection)
|
|
38
|
+
return nil
|
|
39
|
+
end
|
|
40
|
+
-- A successful response carries the packed value: `(buffer, blobs?)`.
|
|
41
|
+
local decoded = decodeArguments(options.resultDecoder, player, response, options.onMalformed)
|
|
42
|
+
if not decoded then
|
|
43
|
+
request(nil, NetworkingFunctionError.InvalidResult)
|
|
44
|
+
return nil
|
|
45
|
+
end
|
|
46
|
+
request(decoded[1], nil)
|
|
47
|
+
end
|
|
48
|
+
-- We don't need to defer here because we only accept responses to our explicit invocations.
|
|
49
|
+
local requestInfoServer = {}
|
|
50
|
+
local requestInfoClient = createRequestInfo()
|
|
51
|
+
if RunService:IsServer() then
|
|
52
|
+
event:connectServer(function(player, id, processResult, ...)
|
|
53
|
+
local response = { ... }
|
|
54
|
+
local _player = player
|
|
55
|
+
local requestInfo = requestInfoServer[_player]
|
|
56
|
+
if not requestInfo then
|
|
57
|
+
return nil
|
|
58
|
+
end
|
|
59
|
+
processResponse(player, requestInfo, id, processResult, unpack(response))
|
|
60
|
+
end)
|
|
61
|
+
Players.PlayerRemoving:Connect(function(player)
|
|
62
|
+
local _player = player
|
|
63
|
+
local requestInfo = requestInfoServer[_player]
|
|
64
|
+
local _player_1 = player
|
|
65
|
+
requestInfoServer[_player_1] = nil
|
|
66
|
+
if requestInfo then
|
|
67
|
+
-- Cancel all existing requests from this player.
|
|
68
|
+
for _, request in requestInfo.requests do
|
|
69
|
+
request(nil, NetworkingFunctionError.Cancelled)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end)
|
|
73
|
+
else
|
|
74
|
+
event:connectClient(function(id, processResult, ...)
|
|
75
|
+
local response = { ... }
|
|
76
|
+
processResponse(nil, requestInfoClient, id, processResult, unpack(response))
|
|
77
|
+
end)
|
|
78
|
+
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)
|
|
84
|
+
if rejection then
|
|
85
|
+
return reject(rejection)
|
|
86
|
+
end
|
|
87
|
+
if options.responseMiddleware then
|
|
88
|
+
options.responseMiddleware(player, value, resolve, reject)
|
|
89
|
+
else
|
|
90
|
+
resolve(value)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
onCancel(function()
|
|
94
|
+
local _requests_1 = requestInfo.requests
|
|
95
|
+
local _id_1 = id
|
|
96
|
+
_requests_1[_id_1] = nil
|
|
97
|
+
end)
|
|
98
|
+
end)
|
|
99
|
+
end
|
|
100
|
+
return {
|
|
101
|
+
invokeServer = function(self, ...)
|
|
102
|
+
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)
|
|
108
|
+
end,
|
|
109
|
+
invokeClient = function(self, player, ...)
|
|
110
|
+
local args = { ... }
|
|
111
|
+
local _player = player
|
|
112
|
+
local requestInfo = requestInfoServer[_player]
|
|
113
|
+
if not requestInfo then
|
|
114
|
+
local _exp = player
|
|
115
|
+
requestInfo = createRequestInfo()
|
|
116
|
+
local _requestInfo = requestInfo
|
|
117
|
+
requestInfoServer[_exp] = _requestInfo
|
|
118
|
+
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)
|
|
124
|
+
end,
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
function createRequestInfo()
|
|
128
|
+
return {
|
|
129
|
+
nextId = 0,
|
|
130
|
+
requests = {},
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
return {
|
|
134
|
+
createFunctionSender = createFunctionSender,
|
|
135
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { t as t } from "@rbxts/t";
|
|
2
|
+
export declare const isNetworkingFunctionError: t.check<NetworkingFunctionError>;
|
|
3
|
+
export declare enum NetworkingFunctionError {
|
|
4
|
+
Timeout = "Timeout",
|
|
5
|
+
Cancelled = "Cancelled",
|
|
6
|
+
BadRequest = "BadRequest",
|
|
7
|
+
Unprocessed = "Unprocessed",
|
|
8
|
+
InvalidResult = "InvalidResult"
|
|
9
|
+
}
|
|
10
|
+
export declare function getFunctionError(value: unknown): NetworkingFunctionError | undefined;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local t = TS.import(script, TS.getModule(script, "@rbxts", "t").lib.ts).t
|
|
4
|
+
local Flamework = TS.import(script, TS.getModule(script, "@flamework-experimental", "core").out).Flamework
|
|
5
|
+
local isNetworkingFunctionError = Flamework.createGuard(t.literalList({ "Timeout", "Cancelled", "BadRequest", "Unprocessed", "InvalidResult" }))
|
|
6
|
+
local NetworkingFunctionError = {
|
|
7
|
+
Timeout = "Timeout",
|
|
8
|
+
Cancelled = "Cancelled",
|
|
9
|
+
BadRequest = "BadRequest",
|
|
10
|
+
Unprocessed = "Unprocessed",
|
|
11
|
+
InvalidResult = "InvalidResult",
|
|
12
|
+
}
|
|
13
|
+
local function getFunctionError(value)
|
|
14
|
+
local _value = value
|
|
15
|
+
if type(_value) == "boolean" then
|
|
16
|
+
return if value == false then NetworkingFunctionError.Unprocessed else nil
|
|
17
|
+
elseif isNetworkingFunctionError(value) then
|
|
18
|
+
return value
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
return {
|
|
22
|
+
getFunctionError = getFunctionError,
|
|
23
|
+
isNetworkingFunctionError = isNetworkingFunctionError,
|
|
24
|
+
NetworkingFunctionError = NetworkingFunctionError,
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FunctionReceiverInterface } from "../function/createFunctionReceiver";
|
|
2
|
+
import { FunctionSenderInterface } from "../function/createFunctionSender";
|
|
3
|
+
import { FunctionCreateConfiguration } from "./types";
|
|
4
|
+
export declare function createClientMethod(config: FunctionCreateConfiguration<unknown>, receiver?: FunctionReceiverInterface, sender?: FunctionSenderInterface): {
|
|
5
|
+
readonly _flamework_send?: unknown[] | undefined;
|
|
6
|
+
_invoke: (payload?: buffer, blobs?: Array<defined>) => Promise<unknown>;
|
|
7
|
+
_invokeWithTimeout: (timeout: number, payload?: buffer, blobs?: Array<defined>) => Promise<unknown>;
|
|
8
|
+
invoke: (...args: unknown[]) => Promise<unknown>;
|
|
9
|
+
invokeWithTimeout: (timeout: number, ...args: unknown[]) => Promise<unknown>;
|
|
10
|
+
readonly _flamework_receive?: unknown[] | undefined;
|
|
11
|
+
readonly _flamework_fn?: unknown;
|
|
12
|
+
_setCallback: (callback: (...args: never[]) => unknown) => void;
|
|
13
|
+
setCallback: (callback: (...args: unknown[]) => unknown | Promise<unknown>) => void;
|
|
14
|
+
predict: (...args: unknown[]) => Promise<unknown>;
|
|
15
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
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
|
|
5
|
+
local function createClientMethod(config, receiver, sender)
|
|
6
|
+
local method = {
|
|
7
|
+
invoke = function(self, ...)
|
|
8
|
+
local args = { ... }
|
|
9
|
+
return self:invokeWithTimeout(config.defaultTimeout, unpack(args))
|
|
10
|
+
end,
|
|
11
|
+
invokeWithTimeout = function(self, timeout, ...)
|
|
12
|
+
local args = { ... }
|
|
13
|
+
local _sender = sender
|
|
14
|
+
assert(_sender, "This is not a sender remote.")
|
|
15
|
+
return TS.Promise.race({ timeoutPromise(timeout, NetworkingFunctionError.Timeout), sender:invokeServer(unpack(args)) })
|
|
16
|
+
end,
|
|
17
|
+
_invoke = function(self, ...)
|
|
18
|
+
local packed = { ... }
|
|
19
|
+
return self:invokeWithTimeout(config.defaultTimeout, unpack(packed))
|
|
20
|
+
end,
|
|
21
|
+
_invokeWithTimeout = function(self, timeout, ...)
|
|
22
|
+
local packed = { ... }
|
|
23
|
+
return self:invokeWithTimeout(timeout, unpack(packed))
|
|
24
|
+
end,
|
|
25
|
+
setCallback = function(self, callback)
|
|
26
|
+
local _receiver = receiver
|
|
27
|
+
assert(_receiver, "This is not a receiver remote.")
|
|
28
|
+
receiver:setClientCallback(callback)
|
|
29
|
+
end,
|
|
30
|
+
_setCallback = function(self, callback)
|
|
31
|
+
local _receiver = receiver
|
|
32
|
+
assert(_receiver, "This is not a receiver remote.")
|
|
33
|
+
receiver:setClientCallback(callback, true)
|
|
34
|
+
end,
|
|
35
|
+
predict = function(self, ...)
|
|
36
|
+
local args = { ... }
|
|
37
|
+
local _receiver = receiver
|
|
38
|
+
assert(_receiver, "This is not a receiver remote.")
|
|
39
|
+
return receiver:invoke(nil, unpack(args))
|
|
40
|
+
end,
|
|
41
|
+
}
|
|
42
|
+
setmetatable(method, {
|
|
43
|
+
__call = function(method, ...)
|
|
44
|
+
local args = { ... }
|
|
45
|
+
return method:invoke(unpack(args))
|
|
46
|
+
end,
|
|
47
|
+
})
|
|
48
|
+
return method
|
|
49
|
+
end
|
|
50
|
+
return {
|
|
51
|
+
createClientMethod = createClientMethod,
|
|
52
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FunctionNetworkingEvents } from "../handlers";
|
|
2
|
+
import { ClientHandler, FunctionCreateConfiguration, NamespaceMetadata, ServerHandler } from "./types";
|
|
3
|
+
import { SignalContainer } from "../util/createSignalContainer";
|
|
4
|
+
import { FunctionReceiverInterface } from "../function/createFunctionReceiver";
|
|
5
|
+
import { FunctionSenderInterface } from "../function/createFunctionSender";
|
|
6
|
+
export type MethodCreator = (config: FunctionCreateConfiguration<unknown>, receiver?: FunctionReceiverInterface, sender?: FunctionSenderInterface) => unknown;
|
|
7
|
+
export declare function createGenericHandler<T extends ClientHandler<S, R> | ServerHandler<S, R>, S, R>(globalName: string, namespaceName: string | undefined, receiverPrefix: string, senderPrefix: string, metadata: NamespaceMetadata<R, S>, config: FunctionCreateConfiguration<R>, signals: SignalContainer<FunctionNetworkingEvents>, createMethod: MethodCreator): T;
|