@rbxts/tether 1.1.6 → 1.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/README.md +81 -14
- package/out/builtin-middlewares.d.ts +20 -9
- package/out/builtin-middlewares.luau +51 -51
- package/out/message-emitter.d.ts +87 -38
- package/out/message-emitter.luau +304 -144
- package/out/middleware.d.ts +12 -7
- package/out/structs.d.ts +12 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Tether
|
|
2
|
-
A message-based networking solution for Roblox with automatic binary serialization.
|
|
2
|
+
A message-based networking solution for Roblox with automatic binary serialization and type validation.
|
|
3
3
|
|
|
4
4
|
> [!CAUTION]
|
|
5
5
|
> Depends on `rbxts-transformer-flamework`!
|
|
@@ -14,7 +14,8 @@ import { MessageEmitter } from "@rbxts/tether";
|
|
|
14
14
|
export const messaging = MessageEmitter.create<MessageData>();
|
|
15
15
|
|
|
16
16
|
export const enum Message {
|
|
17
|
-
Test
|
|
17
|
+
Test,
|
|
18
|
+
Packed
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export interface MessageData {
|
|
@@ -22,6 +23,16 @@ export interface MessageData {
|
|
|
22
23
|
readonly foo: string;
|
|
23
24
|
readonly n: DataType.u8;
|
|
24
25
|
};
|
|
26
|
+
[Message.Packed]: DataType.Packed<{
|
|
27
|
+
boolean1: boolean;
|
|
28
|
+
boolean2: boolean;
|
|
29
|
+
boolean3: boolean;
|
|
30
|
+
boolean4: boolean;
|
|
31
|
+
boolean5: boolean;
|
|
32
|
+
boolean6: boolean;
|
|
33
|
+
boolean7: boolean;
|
|
34
|
+
boolean8: boolean;
|
|
35
|
+
}>;
|
|
25
36
|
}
|
|
26
37
|
```
|
|
27
38
|
|
|
@@ -32,7 +43,7 @@ export interface MessageData {
|
|
|
32
43
|
```ts
|
|
33
44
|
import { Message, messaging } from "shared/messaging";
|
|
34
45
|
|
|
35
|
-
messaging.
|
|
46
|
+
messaging.server.on(Message.Test, (player, data) => {
|
|
36
47
|
print(player, "sent data:", data);
|
|
37
48
|
});
|
|
38
49
|
```
|
|
@@ -41,12 +52,59 @@ messaging.onServerMessage(Message.Test, (player, data) => {
|
|
|
41
52
|
```ts
|
|
42
53
|
import { Message, messaging } from "shared/messaging";
|
|
43
54
|
|
|
44
|
-
messaging.
|
|
55
|
+
messaging.server.emit(Message.Test, {
|
|
45
56
|
foo: "bar",
|
|
46
57
|
n: 69
|
|
47
58
|
});
|
|
48
59
|
```
|
|
49
60
|
|
|
61
|
+
## Simulated Remote Functions
|
|
62
|
+
Tether does not directly use RemoteFunctions since it's based on the MessageEmitter structure. However I have created a small framework to simulate remote functions, as shown below.
|
|
63
|
+
|
|
64
|
+
For each function you will need two messages. One to invoke the function, and one to send the return value back (which is done automatically).
|
|
65
|
+
|
|
66
|
+
### In `shared/messaging.ts`
|
|
67
|
+
```ts
|
|
68
|
+
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
69
|
+
import { MessageEmitter } from "@rbxts/tether";
|
|
70
|
+
|
|
71
|
+
export const messaging = MessageEmitter.create<MessageData>();
|
|
72
|
+
|
|
73
|
+
export const enum Message {
|
|
74
|
+
Increment,
|
|
75
|
+
IncrementReturn
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface MessageData {
|
|
79
|
+
[Message.Increment]: DataType.u8;
|
|
80
|
+
[Message.IncrementReturn]: DataType.u8;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Server
|
|
85
|
+
```ts
|
|
86
|
+
import { Message, messaging } from "shared/messaging";
|
|
87
|
+
|
|
88
|
+
messaging.server.setCallback(Message.Increment, Message.IncrementReturn, (_, n) => n + 1);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Client
|
|
92
|
+
```ts
|
|
93
|
+
import { Message, messaging } from "shared/messaging";
|
|
94
|
+
|
|
95
|
+
messaging.server
|
|
96
|
+
.invoke(Message.Increment, Message.IncrementReturn, 69)
|
|
97
|
+
.then(print); // 70 - incremented by the server
|
|
98
|
+
|
|
99
|
+
// or use await style
|
|
100
|
+
async function main(): Promise<void> {
|
|
101
|
+
const value = await messaging.server.invoke(Message.Increment, Message.IncrementReturn, 69);
|
|
102
|
+
print(value) // 70
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main();
|
|
106
|
+
```
|
|
107
|
+
|
|
50
108
|
## Middleware
|
|
51
109
|
Drop, delay, or modify requests
|
|
52
110
|
|
|
@@ -107,19 +165,18 @@ export const messaging = MessageEmitter.create<MessageData>();
|
|
|
107
165
|
messaging.middleware
|
|
108
166
|
// only allows requests to the server every 5 seconds,
|
|
109
167
|
// drops any requests that occur within 5 seconds of each other
|
|
110
|
-
.useServer(Message.Test,
|
|
111
|
-
|
|
112
|
-
// the data associated with the message at runtime using type guards
|
|
113
|
-
.useServer(Message.Test, [BuiltinMiddlewares.validate()])
|
|
168
|
+
.useServer(Message.Test, BuiltinMiddlewares.rateLimit(5))
|
|
169
|
+
.useShared(Message.Packed, () => (_, __, getRawData) => print("Packed object size:", buffer.len(getRawData()))); // will be just one byte!
|
|
114
170
|
// logs every message fired
|
|
115
|
-
.useServerGlobal(
|
|
116
|
-
.useClientGlobal(
|
|
117
|
-
.useSharedGlobal(
|
|
118
|
-
.useServer(Message.Test,
|
|
119
|
-
.useServerGlobal(
|
|
171
|
+
.useServerGlobal(logServer())
|
|
172
|
+
.useClientGlobal(logClient())
|
|
173
|
+
.useSharedGlobal(BuiltinMiddlewares.debug()); // verbosely logs every packet sent
|
|
174
|
+
.useServer(Message.Test, incrementNumberData()) // error! - data for Message.Test is not a number
|
|
175
|
+
.useServerGlobal(incrementNumberData()); // error! - global data type is always 'unknown', we cannot guarantee a number
|
|
120
176
|
|
|
121
177
|
export const enum Message {
|
|
122
|
-
Test
|
|
178
|
+
Test,
|
|
179
|
+
Packed
|
|
123
180
|
}
|
|
124
181
|
|
|
125
182
|
export interface MessageData {
|
|
@@ -127,5 +184,15 @@ export interface MessageData {
|
|
|
127
184
|
readonly foo: string;
|
|
128
185
|
readonly n: DataType.u8;
|
|
129
186
|
};
|
|
187
|
+
[Message.Packed]: DataType.Packed<{
|
|
188
|
+
boolean1: boolean;
|
|
189
|
+
boolean2: boolean;
|
|
190
|
+
boolean3: boolean;
|
|
191
|
+
boolean4: boolean;
|
|
192
|
+
boolean5: boolean;
|
|
193
|
+
boolean6: boolean;
|
|
194
|
+
boolean7: boolean;
|
|
195
|
+
boolean8: boolean;
|
|
196
|
+
}>;
|
|
130
197
|
}
|
|
131
198
|
```
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { Modding } from "@flamework/core";
|
|
2
|
+
import type { SerializerMetadata } from "@rbxts/flamework-binary-serializer";
|
|
2
3
|
import { type SharedMiddleware } from "./middleware";
|
|
3
|
-
type
|
|
4
|
+
import type { TetherPacket } from "./structs";
|
|
5
|
+
import { Any } from "ts-toolbelt";
|
|
4
6
|
export declare namespace BuiltinMiddlewares {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a shared middleware that will simulate a ping of the given amount when a message is sent
|
|
9
|
+
*
|
|
10
|
+
* @param pingInMs The amount of time in milliseconds that the middleware should wait
|
|
11
|
+
* @returns A shared middleware that will simulate a ping
|
|
12
|
+
*/
|
|
13
|
+
function simulatePing(pingInMs: number): SharedMiddleware;
|
|
5
14
|
/**
|
|
6
15
|
* Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
|
|
7
16
|
*
|
|
@@ -10,15 +19,17 @@ export declare namespace BuiltinMiddlewares {
|
|
|
10
19
|
*/
|
|
11
20
|
function rateLimit(interval: number): SharedMiddleware;
|
|
12
21
|
/**
|
|
13
|
-
* Creates a shared middleware that
|
|
14
|
-
*
|
|
15
|
-
*
|
|
22
|
+
* Creates a shared middleware that will log a message whenever a message is sent, containing the following information:
|
|
23
|
+
* - The message kind
|
|
24
|
+
* - The data associated with the message
|
|
25
|
+
* - The raw data (buffer and blobs) associated with the message
|
|
26
|
+
* - The size of the packet (in bytes)
|
|
27
|
+
* - The size of the buffer (in bytes)
|
|
28
|
+
* - The size of the blobs (in bytes)
|
|
29
|
+
* - The schema string associated with the message (if it can be deduced)
|
|
16
30
|
*
|
|
17
|
-
* @
|
|
18
|
-
* @returns A shared middleware that validates the data with the given guard.
|
|
31
|
+
* @returns A shared middleware that will log a message whenever a message is sent.
|
|
19
32
|
* @metadata macro
|
|
20
33
|
*/
|
|
21
|
-
function
|
|
22
|
-
function debug(): SharedMiddleware;
|
|
34
|
+
function debug<T>(schema?: Modding.Many<Any.Equals<T, unknown> extends 1 ? undefined : SerializerMetadata<TetherPacket<T>>>): SharedMiddleware<T>;
|
|
23
35
|
}
|
|
24
|
-
export {};
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
|
-
local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
|
|
4
3
|
local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
|
|
5
|
-
local
|
|
6
|
-
|
|
7
|
-
end
|
|
8
|
-
local validationGuardGenerationFailed = function()
|
|
9
|
-
return `[@rbxts/tether]: Failed to generate guard for validate<T>() builtin middleware - skipping validation`
|
|
10
|
-
end
|
|
11
|
-
local guardFailed = function(message)
|
|
12
|
-
return `[@rbxts/tether]: Type validation guard failed for message '{tostring(message)}' - check your sent data`
|
|
13
|
-
end
|
|
4
|
+
local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out)
|
|
5
|
+
local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
|
|
14
6
|
local BLOB_SIZE = 5
|
|
15
7
|
local BuiltinMiddlewares = {}
|
|
16
8
|
do
|
|
17
9
|
local _container = BuiltinMiddlewares
|
|
10
|
+
--[[
|
|
11
|
+
*
|
|
12
|
+
* Creates a shared middleware that will simulate a ping of the given amount when a message is sent
|
|
13
|
+
*
|
|
14
|
+
* @param pingInMs The amount of time in milliseconds that the middleware should wait
|
|
15
|
+
* @returns A shared middleware that will simulate a ping
|
|
16
|
+
|
|
17
|
+
]]
|
|
18
|
+
local function simulatePing(pingInMs)
|
|
19
|
+
return function()
|
|
20
|
+
return function()
|
|
21
|
+
task.wait(pingInMs / 1000)
|
|
22
|
+
return nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
_container.simulatePing = simulatePing
|
|
18
27
|
--[[
|
|
19
28
|
*
|
|
20
29
|
* Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
|
|
@@ -35,34 +44,7 @@ do
|
|
|
35
44
|
end
|
|
36
45
|
end
|
|
37
46
|
_container.rateLimit = rateLimit
|
|
38
|
-
|
|
39
|
-
*
|
|
40
|
-
* Creates a shared middleware that validates the data with the given guard (or a generated guard if none was provided)
|
|
41
|
-
*
|
|
42
|
-
* If the guard fails, the middleware will drop the message
|
|
43
|
-
*
|
|
44
|
-
* @param guard The guard to use to validate the data.
|
|
45
|
-
* @returns A shared middleware that validates the data with the given guard.
|
|
46
|
-
* @metadata macro
|
|
47
|
-
|
|
48
|
-
]]
|
|
49
|
-
local function validate(guard)
|
|
50
|
-
if guard == nil then
|
|
51
|
-
warn(validationGuardGenerationFailed())
|
|
52
|
-
return noOp
|
|
53
|
-
end
|
|
54
|
-
return function(message)
|
|
55
|
-
return function(data)
|
|
56
|
-
if guard(data) then
|
|
57
|
-
return nil
|
|
58
|
-
end
|
|
59
|
-
warn(guardFailed(message))
|
|
60
|
-
return DropRequest
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
_container.validate = validate
|
|
65
|
-
local function toStringBuffer(buf)
|
|
47
|
+
local function bufferToString(buf)
|
|
66
48
|
local s = { "{ " }
|
|
67
49
|
do
|
|
68
50
|
local i = 0
|
|
@@ -87,24 +69,42 @@ do
|
|
|
87
69
|
table.insert(s, " }")
|
|
88
70
|
return table.concat(s, "")
|
|
89
71
|
end
|
|
90
|
-
local
|
|
91
|
-
|
|
72
|
+
local horizontalLine = "------------------------------------"
|
|
73
|
+
--[[
|
|
74
|
+
*
|
|
75
|
+
* Creates a shared middleware that will log a message whenever a message is sent, containing the following information:
|
|
76
|
+
* - The message kind
|
|
77
|
+
* - The data associated with the message
|
|
78
|
+
* - The raw data (buffer and blobs) associated with the message
|
|
79
|
+
* - The size of the packet (in bytes)
|
|
80
|
+
* - The size of the buffer (in bytes)
|
|
81
|
+
* - The size of the blobs (in bytes)
|
|
82
|
+
* - The schema string associated with the message (if it can be deduced)
|
|
83
|
+
*
|
|
84
|
+
* @returns A shared middleware that will log a message whenever a message is sent.
|
|
85
|
+
* @metadata macro
|
|
86
|
+
|
|
87
|
+
]]
|
|
88
|
+
local function debug(schema)
|
|
92
89
|
return function(message)
|
|
93
90
|
return function(data, _, getRawData)
|
|
94
91
|
local rawData = getRawData()
|
|
95
92
|
local bufferSize = buffer.len(rawData.buffer)
|
|
96
93
|
local blobsSize = #rawData.blobs * BLOB_SIZE
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
local schemaString = if schema ~= nil then " " .. table.concat(string.split(repr(schema[2], {
|
|
95
|
+
pretty = true,
|
|
96
|
+
}), "\n"), "\n ") else "unknown"
|
|
97
|
+
local text = { "\n", horizontalLine, "\n", "Packet sent to ", (if RunService:IsServer() then "client" else "server"), "!\n", " - Message: ", message, "\n", " - Data: ", if data == nil then "undefined" else data, "\n", " - Raw data:\n", " - Buffer: ", bufferToString(rawData.buffer), "\n", " - Blobs: ", repr(rawData.blobs, {
|
|
98
|
+
pretty = false,
|
|
99
|
+
robloxClassName = true,
|
|
100
|
+
}), "\n", " - Packet size: ", bufferSize + blobsSize, " bytes\n", " - Buffer: ", bufferSize, " bytes\n", " - Blobs: ", blobsSize, " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
|
|
101
|
+
-- ▼ ReadonlyArray.join ▼
|
|
102
|
+
local _result = table.create(#text)
|
|
103
|
+
for _k, _v in text do
|
|
104
|
+
_result[_k] = tostring(_v)
|
|
105
|
+
end
|
|
106
|
+
-- ▲ ReadonlyArray.join ▲
|
|
107
|
+
print(table.concat(_result, ""))
|
|
108
108
|
end
|
|
109
109
|
end
|
|
110
110
|
end
|
package/out/message-emitter.d.ts
CHANGED
|
@@ -1,54 +1,103 @@
|
|
|
1
1
|
import { Modding } from "@flamework/core";
|
|
2
|
-
import { type SerializerMetadata } from "@rbxts/flamework-binary-serializer";
|
|
3
2
|
import Destroyable from "@rbxts/destroyable";
|
|
4
3
|
import { MiddlewareProvider } from "./middleware";
|
|
5
|
-
import type {
|
|
4
|
+
import type { ClientMessageCallback, ServerMessageCallback, BaseMessage, MessageEmitterMetadata, ClientMessageFunctionCallback, ServerMessageFunctionCallback } from "./structs";
|
|
6
5
|
export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
7
6
|
readonly middleware: MiddlewareProvider<MessageData>;
|
|
8
7
|
private readonly clientCallbacks;
|
|
8
|
+
private readonly clientFunctions;
|
|
9
9
|
private readonly serverCallbacks;
|
|
10
|
+
private readonly serverFunctions;
|
|
11
|
+
private readonly guards;
|
|
10
12
|
private serializers;
|
|
11
13
|
private serverEvents;
|
|
12
14
|
private clientEvents;
|
|
13
15
|
/** @metadata macro */
|
|
14
|
-
static create<MessageData>(
|
|
15
|
-
[Kind in keyof MessageData]: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<TetherPacket<MessageData[Kind]>>>;
|
|
16
|
-
}>): MessageEmitter<MessageData>;
|
|
16
|
+
static create<MessageData>(meta?: Modding.Many<MessageEmitterMetadata<MessageData>>): MessageEmitter<MessageData>;
|
|
17
17
|
private constructor();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
18
|
+
readonly server: {
|
|
19
|
+
/**
|
|
20
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
21
|
+
*/
|
|
22
|
+
on: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ServerMessageCallback<MessageData[Kind]>) => () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Disconnects the callback as soon as it is called for the first time
|
|
25
|
+
*
|
|
26
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
27
|
+
*/
|
|
28
|
+
once: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ServerMessageCallback<MessageData[Kind]>) => () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Emits a message to the server
|
|
31
|
+
*
|
|
32
|
+
* @param message The message kind to be sent
|
|
33
|
+
* @param data The data associated with the message
|
|
34
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
35
|
+
*/
|
|
36
|
+
emit: <Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Simulates a remote function invocation.
|
|
39
|
+
*
|
|
40
|
+
* @param message The message kind to be sent
|
|
41
|
+
* @param data The data associated with the message
|
|
42
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
43
|
+
*/
|
|
44
|
+
invoke: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => Promise<MessageData[ReturnKind]>;
|
|
45
|
+
/**
|
|
46
|
+
* Sets a callback for a simulated remote function
|
|
47
|
+
*
|
|
48
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
49
|
+
*/
|
|
50
|
+
setCallback: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, callback: ServerMessageFunctionCallback<MessageData[Kind], MessageData[ReturnKind]>) => () => void;
|
|
51
|
+
};
|
|
52
|
+
readonly client: {
|
|
53
|
+
/**
|
|
54
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
55
|
+
*/
|
|
56
|
+
on: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ClientMessageCallback<MessageData[Kind]>) => () => void;
|
|
57
|
+
/**
|
|
58
|
+
* Disconnects the callback as soon as it is called for the first time
|
|
59
|
+
*
|
|
60
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
61
|
+
*/
|
|
62
|
+
once: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ClientMessageCallback<MessageData[Kind]>) => () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Emits a message to a specific client
|
|
65
|
+
*
|
|
66
|
+
* @param player The player to whom the message is sent
|
|
67
|
+
* @param message The message kind to be sent
|
|
68
|
+
* @param data The data associated with the message
|
|
69
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
70
|
+
*/
|
|
71
|
+
emit: <Kind extends keyof MessageData>(player: Player | Player[], message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Emits a message to all connected clients
|
|
74
|
+
*
|
|
75
|
+
* @param message The message kind to be sent
|
|
76
|
+
* @param data The data associated with the message
|
|
77
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
78
|
+
*/
|
|
79
|
+
emitAll: <Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
|
|
80
|
+
/**
|
|
81
|
+
* Simulates a remote function invocation.
|
|
82
|
+
*
|
|
83
|
+
* @param message The message kind to be sent
|
|
84
|
+
* @param data The data associated with the message
|
|
85
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
86
|
+
*/
|
|
87
|
+
invoke: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, player: Player, data?: MessageData[Kind], unreliable?: boolean) => Promise<MessageData[ReturnKind]>;
|
|
88
|
+
/**
|
|
89
|
+
* Sets a callback for a simulated remote function
|
|
90
|
+
*
|
|
91
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
92
|
+
*/
|
|
93
|
+
setCallback: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, callback: ClientMessageFunctionCallback<MessageData[Kind], MessageData[ReturnKind]>) => () => void;
|
|
94
|
+
};
|
|
95
|
+
private validateData;
|
|
51
96
|
private initialize;
|
|
97
|
+
private readMessageFromPacket;
|
|
98
|
+
private executeFunctions;
|
|
99
|
+
private executeEventCallbacks;
|
|
100
|
+
private once;
|
|
52
101
|
private on;
|
|
53
102
|
private getPacket;
|
|
54
103
|
/** @metadata macro */
|
package/out/message-emitter.luau
CHANGED
|
@@ -10,7 +10,12 @@ local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyabl
|
|
|
10
10
|
local _middleware = TS.import(script, script.Parent, "middleware")
|
|
11
11
|
local DropRequest = _middleware.DropRequest
|
|
12
12
|
local MiddlewareProvider = _middleware.MiddlewareProvider
|
|
13
|
-
|
|
13
|
+
-- TODO: error when trying to do something like server.emit() from the server
|
|
14
|
+
local remotes = Networking.createEvent("@rbxts/tether:message-emitter@remotes")
|
|
15
|
+
local metaGenerationFailed = "[@rbxts/tether]: Failed to generate message metadata - make sure you are using Flamework macro-friendly types in your schemas"
|
|
16
|
+
local guardFailed = function(message)
|
|
17
|
+
return `[@rbxts/tether]: Type validation guard failed for message '{message}' - check your sent data`
|
|
18
|
+
end
|
|
14
19
|
local MessageEmitter
|
|
15
20
|
do
|
|
16
21
|
local super = Destroyable
|
|
@@ -29,8 +34,213 @@ do
|
|
|
29
34
|
super.constructor(self)
|
|
30
35
|
self.middleware = MiddlewareProvider.new()
|
|
31
36
|
self.clientCallbacks = {}
|
|
37
|
+
self.clientFunctions = {}
|
|
32
38
|
self.serverCallbacks = {}
|
|
39
|
+
self.serverFunctions = {}
|
|
40
|
+
self.guards = {}
|
|
33
41
|
self.serializers = {}
|
|
42
|
+
self.server = {
|
|
43
|
+
on = function(message, callback)
|
|
44
|
+
return self:on(message, callback, self.serverCallbacks)
|
|
45
|
+
end,
|
|
46
|
+
once = function(message, callback)
|
|
47
|
+
return self:once(message, callback, self.serverCallbacks)
|
|
48
|
+
end,
|
|
49
|
+
emit = function(message, data, unreliable)
|
|
50
|
+
if unreliable == nil then
|
|
51
|
+
unreliable = false
|
|
52
|
+
end
|
|
53
|
+
local updateData = function(newData)
|
|
54
|
+
data = newData
|
|
55
|
+
return nil
|
|
56
|
+
end
|
|
57
|
+
local getPacket = function()
|
|
58
|
+
return self:getPacket(message, data)
|
|
59
|
+
end
|
|
60
|
+
if not self:validateData(message, data) then
|
|
61
|
+
return nil
|
|
62
|
+
end
|
|
63
|
+
task.spawn(function()
|
|
64
|
+
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
65
|
+
if not self:validateData(message, data) then
|
|
66
|
+
return nil
|
|
67
|
+
end
|
|
68
|
+
local result = globalMiddleware(message)(data, updateData, getPacket)
|
|
69
|
+
if result == DropRequest then
|
|
70
|
+
return nil
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
for _, middleware in self.middleware:getServer(message) do
|
|
74
|
+
if not self:validateData(message, data) then
|
|
75
|
+
return nil
|
|
76
|
+
end
|
|
77
|
+
local result = middleware(message)(data, updateData, getPacket)
|
|
78
|
+
if result == DropRequest then
|
|
79
|
+
return nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
if not self:validateData(message, data) then
|
|
83
|
+
return nil
|
|
84
|
+
end
|
|
85
|
+
local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
|
|
86
|
+
send(getPacket())
|
|
87
|
+
end)
|
|
88
|
+
end,
|
|
89
|
+
invoke = TS.async(function(message, returnMessage, data, unreliable)
|
|
90
|
+
if unreliable == nil then
|
|
91
|
+
unreliable = false
|
|
92
|
+
end
|
|
93
|
+
local _clientFunctions = self.clientFunctions
|
|
94
|
+
local _returnMessage = returnMessage
|
|
95
|
+
if not (_clientFunctions[_returnMessage] ~= nil) then
|
|
96
|
+
local _clientFunctions_1 = self.clientFunctions
|
|
97
|
+
local _returnMessage_1 = returnMessage
|
|
98
|
+
_clientFunctions_1[_returnMessage_1] = {}
|
|
99
|
+
end
|
|
100
|
+
local _clientFunctions_1 = self.clientFunctions
|
|
101
|
+
local _returnMessage_1 = returnMessage
|
|
102
|
+
local functions = _clientFunctions_1[_returnMessage_1]
|
|
103
|
+
local returnValue
|
|
104
|
+
functions[function(data)
|
|
105
|
+
returnValue = data
|
|
106
|
+
return returnValue
|
|
107
|
+
end] = true
|
|
108
|
+
self.server.emit(message, data, unreliable)
|
|
109
|
+
while returnValue == nil do
|
|
110
|
+
RunService.Heartbeat:Wait()
|
|
111
|
+
end
|
|
112
|
+
return returnValue
|
|
113
|
+
end),
|
|
114
|
+
setCallback = function(message, returnMessage, callback)
|
|
115
|
+
return self.server.on(message, function(player, data)
|
|
116
|
+
local returnValue = callback(player, data)
|
|
117
|
+
self.client.emit(player, returnMessage, returnValue)
|
|
118
|
+
end)
|
|
119
|
+
end,
|
|
120
|
+
}
|
|
121
|
+
self.client = {
|
|
122
|
+
on = function(message, callback)
|
|
123
|
+
return self:on(message, callback, self.clientCallbacks)
|
|
124
|
+
end,
|
|
125
|
+
once = function(message, callback)
|
|
126
|
+
return self:once(message, callback, self.clientCallbacks)
|
|
127
|
+
end,
|
|
128
|
+
emit = function(player, message, data, unreliable)
|
|
129
|
+
if unreliable == nil then
|
|
130
|
+
unreliable = false
|
|
131
|
+
end
|
|
132
|
+
local updateData = function(newData)
|
|
133
|
+
data = newData
|
|
134
|
+
return nil
|
|
135
|
+
end
|
|
136
|
+
local getPacket = function()
|
|
137
|
+
return self:getPacket(message, data)
|
|
138
|
+
end
|
|
139
|
+
if not self:validateData(message, data) then
|
|
140
|
+
return nil
|
|
141
|
+
end
|
|
142
|
+
task.spawn(function()
|
|
143
|
+
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
144
|
+
if not self:validateData(message, data) then
|
|
145
|
+
return nil
|
|
146
|
+
end
|
|
147
|
+
local result = globalMiddleware(message)(player, data, updateData, getPacket)
|
|
148
|
+
if result == DropRequest then
|
|
149
|
+
return nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
for _, middleware in self.middleware:getClient(message) do
|
|
153
|
+
if not self:validateData(message, data) then
|
|
154
|
+
return nil
|
|
155
|
+
end
|
|
156
|
+
local result = middleware(message)(player, data, updateData, getPacket)
|
|
157
|
+
if result == DropRequest then
|
|
158
|
+
return nil
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
if not self:validateData(message, data) then
|
|
162
|
+
return nil
|
|
163
|
+
end
|
|
164
|
+
local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
165
|
+
send(player, getPacket())
|
|
166
|
+
end)
|
|
167
|
+
end,
|
|
168
|
+
emitAll = function(message, data, unreliable)
|
|
169
|
+
if unreliable == nil then
|
|
170
|
+
unreliable = false
|
|
171
|
+
end
|
|
172
|
+
local updateData = function(newData)
|
|
173
|
+
data = newData
|
|
174
|
+
return nil
|
|
175
|
+
end
|
|
176
|
+
local getPacket = function()
|
|
177
|
+
return self:getPacket(message, data)
|
|
178
|
+
end
|
|
179
|
+
if not self:validateData(message, data) then
|
|
180
|
+
return nil
|
|
181
|
+
end
|
|
182
|
+
task.spawn(function()
|
|
183
|
+
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
184
|
+
for _1, player in Players:GetPlayers() do
|
|
185
|
+
if not self:validateData(message, data) then
|
|
186
|
+
return nil
|
|
187
|
+
end
|
|
188
|
+
local result = globalMiddleware(message)(player, data, updateData, getPacket)
|
|
189
|
+
if result == DropRequest then
|
|
190
|
+
return nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
for _, middleware in self.middleware:getClient(message) do
|
|
195
|
+
for _1, player in Players:GetPlayers() do
|
|
196
|
+
if not self:validateData(message, data) then
|
|
197
|
+
return nil
|
|
198
|
+
end
|
|
199
|
+
local result = middleware(message)(player, data, updateData, getPacket)
|
|
200
|
+
if result == DropRequest then
|
|
201
|
+
return nil
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
if not self:validateData(message, data) then
|
|
206
|
+
return nil
|
|
207
|
+
end
|
|
208
|
+
local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
209
|
+
send:broadcast(getPacket())
|
|
210
|
+
end)
|
|
211
|
+
end,
|
|
212
|
+
invoke = TS.async(function(message, returnMessage, player, data, unreliable)
|
|
213
|
+
if unreliable == nil then
|
|
214
|
+
unreliable = false
|
|
215
|
+
end
|
|
216
|
+
local _serverFunctions = self.serverFunctions
|
|
217
|
+
local _returnMessage = returnMessage
|
|
218
|
+
if not (_serverFunctions[_returnMessage] ~= nil) then
|
|
219
|
+
local _serverFunctions_1 = self.serverFunctions
|
|
220
|
+
local _returnMessage_1 = returnMessage
|
|
221
|
+
_serverFunctions_1[_returnMessage_1] = {}
|
|
222
|
+
end
|
|
223
|
+
local _serverFunctions_1 = self.serverFunctions
|
|
224
|
+
local _returnMessage_1 = returnMessage
|
|
225
|
+
local functions = _serverFunctions_1[_returnMessage_1]
|
|
226
|
+
local returnValue
|
|
227
|
+
functions[function(data)
|
|
228
|
+
returnValue = data
|
|
229
|
+
return returnValue
|
|
230
|
+
end] = true
|
|
231
|
+
self.client.emit(player, message, data, unreliable)
|
|
232
|
+
while returnValue == nil do
|
|
233
|
+
RunService.Heartbeat:Wait()
|
|
234
|
+
end
|
|
235
|
+
return returnValue
|
|
236
|
+
end),
|
|
237
|
+
setCallback = function(message, returnMessage, callback)
|
|
238
|
+
return self.client.on(message, function(data)
|
|
239
|
+
local returnValue = callback(data)
|
|
240
|
+
self.server.emit(returnMessage, returnValue)
|
|
241
|
+
end)
|
|
242
|
+
end,
|
|
243
|
+
}
|
|
34
244
|
self.janitor:Add(function()
|
|
35
245
|
table.clear(self.clientCallbacks)
|
|
36
246
|
table.clear(self.serverCallbacks)
|
|
@@ -39,7 +249,7 @@ do
|
|
|
39
249
|
self.clientEvents = nil
|
|
40
250
|
end)
|
|
41
251
|
if RunService:IsServer() then
|
|
42
|
-
self.serverEvents =
|
|
252
|
+
self.serverEvents = remotes:createServer({}, {
|
|
43
253
|
incomingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
|
|
44
254
|
incoming = {
|
|
45
255
|
sendServerMessage = { { t.interface({
|
|
@@ -62,7 +272,7 @@ do
|
|
|
62
272
|
namespaces = {},
|
|
63
273
|
})
|
|
64
274
|
else
|
|
65
|
-
self.clientEvents =
|
|
275
|
+
self.clientEvents = remotes:createClient({}, {
|
|
66
276
|
incomingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
|
|
67
277
|
incoming = {
|
|
68
278
|
sendClientMessage = { { t.interface({
|
|
@@ -86,180 +296,130 @@ do
|
|
|
86
296
|
})
|
|
87
297
|
end
|
|
88
298
|
end
|
|
89
|
-
function MessageEmitter:create(
|
|
299
|
+
function MessageEmitter:create(meta)
|
|
90
300
|
local emitter = MessageEmitter.new()
|
|
91
|
-
if
|
|
92
|
-
warn(
|
|
301
|
+
if meta == nil then
|
|
302
|
+
warn(metaGenerationFailed)
|
|
93
303
|
return emitter:initialize()
|
|
94
304
|
end
|
|
95
|
-
for kind,
|
|
96
|
-
|
|
305
|
+
for kind, _binding in pairs(meta) do
|
|
306
|
+
local guard = _binding.guard
|
|
307
|
+
local serializerMetadata = _binding.serializerMetadata
|
|
308
|
+
local numberKind = tonumber(kind)
|
|
309
|
+
emitter.guards[numberKind] = guard
|
|
310
|
+
if serializerMetadata == nil then
|
|
97
311
|
continue
|
|
98
312
|
end
|
|
99
|
-
emitter:addSerializer(
|
|
313
|
+
emitter:addSerializer(numberKind, serializerMetadata)
|
|
100
314
|
end
|
|
101
315
|
return emitter:initialize()
|
|
102
316
|
end
|
|
103
|
-
function MessageEmitter:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if unreliable == nil then
|
|
111
|
-
unreliable = false
|
|
112
|
-
end
|
|
113
|
-
local updateData = function(newData)
|
|
114
|
-
data = newData
|
|
115
|
-
return nil
|
|
116
|
-
end
|
|
117
|
-
local getPacket = function()
|
|
118
|
-
return self:getPacket(message, data)
|
|
119
|
-
end
|
|
120
|
-
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
121
|
-
local result = globalMiddleware(message)(data, updateData, getPacket)
|
|
122
|
-
if result == DropRequest then
|
|
123
|
-
return nil
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
for _, middleware in self.middleware:getServer(message) do
|
|
127
|
-
local result = middleware(message)(data, updateData, getPacket)
|
|
128
|
-
if result == DropRequest then
|
|
129
|
-
return nil
|
|
130
|
-
end
|
|
317
|
+
function MessageEmitter:validateData(message, data)
|
|
318
|
+
local _guards = self.guards
|
|
319
|
+
local _message = message
|
|
320
|
+
local guard = _guards[_message]
|
|
321
|
+
local guardPassed = guard(data)
|
|
322
|
+
if not guardPassed then
|
|
323
|
+
warn(guardFailed(message))
|
|
131
324
|
end
|
|
132
|
-
|
|
133
|
-
send(getPacket())
|
|
325
|
+
return guardPassed
|
|
134
326
|
end
|
|
135
|
-
function MessageEmitter:
|
|
136
|
-
if
|
|
137
|
-
|
|
327
|
+
function MessageEmitter:initialize()
|
|
328
|
+
if RunService:IsClient() then
|
|
329
|
+
self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
|
|
330
|
+
local sentMessage = self:readMessageFromPacket(serializedPacket)
|
|
331
|
+
self:executeEventCallbacks(sentMessage, serializedPacket)
|
|
332
|
+
self:executeFunctions(sentMessage, serializedPacket)
|
|
333
|
+
end))
|
|
334
|
+
else
|
|
335
|
+
self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, serializedPacket)
|
|
336
|
+
local sentMessage = self:readMessageFromPacket(serializedPacket)
|
|
337
|
+
self:executeEventCallbacks(sentMessage, serializedPacket, player)
|
|
338
|
+
self:executeFunctions(sentMessage, serializedPacket)
|
|
339
|
+
end))
|
|
138
340
|
end
|
|
139
|
-
|
|
140
|
-
|
|
341
|
+
return self
|
|
342
|
+
end
|
|
343
|
+
function MessageEmitter:readMessageFromPacket(serializedPacket)
|
|
344
|
+
return buffer.readu8(serializedPacket.buffer, 0)
|
|
345
|
+
end
|
|
346
|
+
function MessageEmitter:executeFunctions(message, serializedPacket)
|
|
347
|
+
local isServer = RunService:IsServer()
|
|
348
|
+
local functionsMap = if isServer then self.serverFunctions else self.clientFunctions
|
|
349
|
+
local _message = message
|
|
350
|
+
local functions = functionsMap[_message]
|
|
351
|
+
if functions == nil then
|
|
141
352
|
return nil
|
|
142
353
|
end
|
|
143
|
-
local
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
local result = globalMiddleware(message)(player, data, updateData, getPacket)
|
|
148
|
-
if result == DropRequest then
|
|
149
|
-
return nil
|
|
150
|
-
end
|
|
354
|
+
local serializer = self:getSerializer(message)
|
|
355
|
+
local _packet = serializer
|
|
356
|
+
if _packet ~= nil then
|
|
357
|
+
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
151
358
|
end
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
359
|
+
local packet = _packet
|
|
360
|
+
for callback in functions do
|
|
361
|
+
local _result = packet
|
|
362
|
+
if _result ~= nil then
|
|
363
|
+
_result = _result.data
|
|
156
364
|
end
|
|
365
|
+
callback(_result)
|
|
157
366
|
end
|
|
158
|
-
local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
159
|
-
send(player, getPacket())
|
|
160
367
|
end
|
|
161
|
-
function MessageEmitter:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
local
|
|
166
|
-
|
|
368
|
+
function MessageEmitter:executeEventCallbacks(message, serializedPacket, player)
|
|
369
|
+
local isServer = RunService:IsServer()
|
|
370
|
+
local callbacksMap = if isServer then self.serverCallbacks else self.clientCallbacks
|
|
371
|
+
local _message = message
|
|
372
|
+
local callbacks = callbacksMap[_message]
|
|
373
|
+
if callbacks == nil then
|
|
167
374
|
return nil
|
|
168
375
|
end
|
|
169
|
-
local
|
|
170
|
-
|
|
376
|
+
local serializer = self:getSerializer(message)
|
|
377
|
+
local _packet = serializer
|
|
378
|
+
if _packet ~= nil then
|
|
379
|
+
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
171
380
|
end
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
381
|
+
local packet = _packet
|
|
382
|
+
for callback in callbacks do
|
|
383
|
+
if isServer then
|
|
384
|
+
local _exp = player
|
|
385
|
+
local _result = packet
|
|
386
|
+
if _result ~= nil then
|
|
387
|
+
_result = _result.data
|
|
177
388
|
end
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if result == DropRequest then
|
|
184
|
-
return nil
|
|
389
|
+
callback(_exp, _result)
|
|
390
|
+
else
|
|
391
|
+
local _result = packet
|
|
392
|
+
if _result ~= nil then
|
|
393
|
+
_result = _result.data
|
|
185
394
|
end
|
|
395
|
+
callback(_result)
|
|
186
396
|
end
|
|
187
397
|
end
|
|
188
|
-
local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
189
|
-
send:broadcast(self:getPacket(message, data))
|
|
190
398
|
end
|
|
191
|
-
function MessageEmitter:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
for _ in messageCallbacks do
|
|
199
|
-
_size += 1
|
|
200
|
-
end
|
|
201
|
-
-- ▲ ReadonlySet.size ▲
|
|
202
|
-
if _size == 0 then
|
|
203
|
-
return nil
|
|
204
|
-
end
|
|
205
|
-
local serializer = self:getSerializer(sentMessage)
|
|
206
|
-
local _packet = serializer
|
|
207
|
-
if _packet ~= nil then
|
|
208
|
-
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
209
|
-
end
|
|
210
|
-
local packet = _packet
|
|
211
|
-
for callback in messageCallbacks do
|
|
212
|
-
local _result = packet
|
|
213
|
-
if _result ~= nil then
|
|
214
|
-
_result = _result.data
|
|
215
|
-
end
|
|
216
|
-
callback(_result)
|
|
217
|
-
end
|
|
218
|
-
end))
|
|
219
|
-
else
|
|
220
|
-
self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, serializedPacket)
|
|
221
|
-
local sentMessage = buffer.readu8(serializedPacket.buffer, 0)
|
|
222
|
-
local messageCallbacks = self.serverCallbacks[sentMessage] or {}
|
|
223
|
-
-- ▼ ReadonlySet.size ▼
|
|
224
|
-
local _size = 0
|
|
225
|
-
for _ in messageCallbacks do
|
|
226
|
-
_size += 1
|
|
227
|
-
end
|
|
228
|
-
-- ▲ ReadonlySet.size ▲
|
|
229
|
-
if _size == 0 then
|
|
230
|
-
return nil
|
|
231
|
-
end
|
|
232
|
-
local serializer = self:getSerializer(sentMessage)
|
|
233
|
-
local _packet = serializer
|
|
234
|
-
if _packet ~= nil then
|
|
235
|
-
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
236
|
-
end
|
|
237
|
-
local packet = _packet
|
|
238
|
-
for callback in messageCallbacks do
|
|
239
|
-
local _exp = player
|
|
240
|
-
local _result = packet
|
|
241
|
-
if _result ~= nil then
|
|
242
|
-
_result = _result.data
|
|
243
|
-
end
|
|
244
|
-
callback(_exp, _result)
|
|
245
|
-
end
|
|
246
|
-
end))
|
|
247
|
-
end
|
|
248
|
-
return self
|
|
399
|
+
function MessageEmitter:once(message, callback, callbacksMap)
|
|
400
|
+
local destructor
|
|
401
|
+
destructor = self:on(message, function(player, data)
|
|
402
|
+
destructor()
|
|
403
|
+
callback(player, data)
|
|
404
|
+
end, callbacksMap)
|
|
405
|
+
return destructor
|
|
249
406
|
end
|
|
250
|
-
function MessageEmitter:on(message, callback)
|
|
251
|
-
local
|
|
407
|
+
function MessageEmitter:on(message, callback, callbacksMap)
|
|
408
|
+
local _callbacksMap = callbacksMap
|
|
252
409
|
local _message = message
|
|
253
|
-
if not (
|
|
410
|
+
if not (_callbacksMap[_message] ~= nil) then
|
|
411
|
+
local _callbacksMap_1 = callbacksMap
|
|
254
412
|
local _message_1 = message
|
|
255
|
-
|
|
413
|
+
_callbacksMap_1[_message_1] = {}
|
|
256
414
|
end
|
|
415
|
+
local _callbacksMap_1 = callbacksMap
|
|
257
416
|
local _message_1 = message
|
|
258
|
-
local callbacks =
|
|
417
|
+
local callbacks = _callbacksMap_1[_message_1]
|
|
259
418
|
local _callback = callback
|
|
260
419
|
callbacks[_callback] = true
|
|
420
|
+
local _callbacksMap_2 = callbacksMap
|
|
261
421
|
local _message_2 = message
|
|
262
|
-
|
|
422
|
+
_callbacksMap_2[_message_2] = callbacks
|
|
263
423
|
return function()
|
|
264
424
|
local _callback_1 = callback
|
|
265
425
|
-- ▼ Set.delete ▼
|
package/out/middleware.d.ts
CHANGED
|
@@ -4,10 +4,15 @@ type DropRequestSymbol = symbol & {
|
|
|
4
4
|
};
|
|
5
5
|
export declare const DropRequest: DropRequestSymbol;
|
|
6
6
|
type UpdateDataFn<T> = (newData: T) => void;
|
|
7
|
-
|
|
8
|
-
export type
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
type GetRawDataFn = () => SerializedPacket;
|
|
8
|
+
export type ClientMiddleware<Data = unknown> = {
|
|
9
|
+
_client?: void;
|
|
10
|
+
} & ((message: BaseMessage) => (player: Player | Player[], data: Readonly<Data>, updateData: UpdateDataFn<Data>, getRawData: GetRawDataFn) => DropRequestSymbol | void);
|
|
11
|
+
export type ServerMiddleware<Data = unknown> = SharedMiddleware<Data> & {
|
|
12
|
+
_server?: void;
|
|
13
|
+
};
|
|
14
|
+
export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (data: Readonly<Data>, updateData: UpdateDataFn<Data>, getRawData: GetRawDataFn) => DropRequestSymbol | void;
|
|
15
|
+
export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data> & SharedMiddleware<Data>;
|
|
11
16
|
export declare class MiddlewareProvider<MessageData> {
|
|
12
17
|
private readonly clientGlobalMiddlewares;
|
|
13
18
|
private readonly serverGlobalMiddlewares;
|
|
@@ -21,9 +26,9 @@ export declare class MiddlewareProvider<MessageData> {
|
|
|
21
26
|
getClientGlobal<Data>(): ClientMiddleware<Data>[];
|
|
22
27
|
/** @hidden */
|
|
23
28
|
getServerGlobal<Data>(): ServerMiddleware<Data>[];
|
|
24
|
-
useClient<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ClientMiddleware<MessageData[Kind]> | readonly ClientMiddleware<MessageData[Kind]>[], order?: number): this;
|
|
25
|
-
useServer<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ServerMiddleware<MessageData[Kind]> | readonly ServerMiddleware<MessageData[Kind]>[], order?: number): this;
|
|
26
|
-
useShared<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
|
|
29
|
+
useClient<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ClientMiddleware<MessageData[Kind]> | readonly ClientMiddleware<MessageData[Kind]>[] | ClientMiddleware | readonly ClientMiddleware[], order?: number): this;
|
|
30
|
+
useServer<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ServerMiddleware<MessageData[Kind]> | readonly ServerMiddleware<MessageData[Kind]>[] | ServerMiddleware | readonly ServerMiddleware[], order?: number): this;
|
|
31
|
+
useShared<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: SharedMiddleware<MessageData[Kind]> | readonly SharedMiddleware<MessageData[Kind]>[] | SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
|
|
27
32
|
useClientGlobal(middlewares: ClientMiddleware | readonly ClientMiddleware[], order?: number): this;
|
|
28
33
|
useServerGlobal(middlewares: ServerMiddleware | readonly ServerMiddleware[], order?: number): this;
|
|
29
34
|
useSharedGlobal(middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
|
package/out/structs.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { Modding } from "@flamework/core";
|
|
1
2
|
import type { Networking } from "@flamework/networking";
|
|
2
|
-
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
3
|
+
import type { DataType, SerializerMetadata } from "@rbxts/flamework-binary-serializer";
|
|
3
4
|
export type MessageCallback<T = unknown> = ServerMessageCallback<T> | ClientMessageCallback<T>;
|
|
4
5
|
export type ClientMessageCallback<T = unknown> = (data: T) => void;
|
|
6
|
+
export type ClientMessageFunctionCallback<T = unknown, R = unknown> = (data: T) => R;
|
|
5
7
|
export type ServerMessageCallback<T = unknown> = (player: Player, data: T) => void;
|
|
8
|
+
export type ServerMessageFunctionCallback<T = unknown, R = unknown> = (player: Player, data: T) => R;
|
|
6
9
|
export type BaseMessage = number;
|
|
7
10
|
export interface SerializedPacket {
|
|
8
11
|
readonly buffer: buffer;
|
|
@@ -22,3 +25,11 @@ export interface ClientEvents {
|
|
|
22
25
|
sendClientMessage: MessageEvent;
|
|
23
26
|
sendUnreliableClientMessage: UnreliableMessageEvent;
|
|
24
27
|
}
|
|
28
|
+
export interface MessageMetadata<MessageData, Kind extends keyof MessageData> {
|
|
29
|
+
readonly guard: Modding.Generic<MessageData[Kind], "guard">;
|
|
30
|
+
readonly serializerMetadata: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<TetherPacket<MessageData[Kind]>>>;
|
|
31
|
+
}
|
|
32
|
+
export type Guard<T = unknown> = (value: unknown) => value is T;
|
|
33
|
+
export type MessageEmitterMetadata<MessageData> = {
|
|
34
|
+
[Kind in keyof MessageData]: MessageMetadata<MessageData, Kind>;
|
|
35
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/tether",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"main": "out/init.lua",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "rbxtsc",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"author": "runicly",
|
|
22
22
|
"license": "ISC",
|
|
23
|
-
"description": "A message-based networking solution for Roblox with automatic binary serialization",
|
|
23
|
+
"description": "A message-based networking solution for Roblox with automatic binary serialization and type validation",
|
|
24
24
|
"types": "out/index.d.ts",
|
|
25
25
|
"files": [
|
|
26
26
|
"out",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@rbxts/destroyable": "^1.0.1",
|
|
44
44
|
"@rbxts/flamework-binary-serializer": "^0.6.0",
|
|
45
45
|
"@rbxts/flamework-meta-utils": "^1.0.4",
|
|
46
|
+
"@rbxts/repr": "^1.0.1",
|
|
46
47
|
"@rbxts/services": "^1.5.5"
|
|
47
48
|
}
|
|
48
49
|
}
|