@rbxts/tether 1.1.6 → 1.2.1
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 +86 -20
- package/out/builtin-middlewares.d.ts +28 -9
- package/out/builtin-middlewares.luau +77 -51
- package/out/message-emitter.d.ts +88 -39
- package/out/message-emitter.luau +320 -145
- package/out/middleware.d.ts +16 -8
- package/out/middleware.luau +4 -4
- 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,31 +23,86 @@ export interface MessageData {
|
|
|
22
23
|
readonly foo: string;
|
|
23
24
|
readonly n: DataType.u8;
|
|
24
25
|
};
|
|
26
|
+
[Message.Packed]: DataType.Packed<{
|
|
27
|
+
readonly boolean1: boolean;
|
|
28
|
+
readonly boolean2: boolean;
|
|
29
|
+
readonly boolean3: boolean;
|
|
30
|
+
readonly boolean4: boolean;
|
|
31
|
+
readonly boolean5: boolean;
|
|
32
|
+
readonly boolean6: boolean;
|
|
33
|
+
readonly boolean7: boolean;
|
|
34
|
+
readonly boolean8: boolean;
|
|
35
|
+
}>;
|
|
25
36
|
}
|
|
26
37
|
```
|
|
27
38
|
|
|
28
39
|
> [!CAUTION]
|
|
29
|
-
> Every single message kind must implement an interface for it's data (in the example that would be the object
|
|
40
|
+
> Every single message kind must implement an interface for it's data (in the example that would be the object types in `MessageData`). Message serialization (as well as your message itself) will not work if you don't do this.
|
|
30
41
|
|
|
31
42
|
### Server
|
|
32
43
|
```ts
|
|
33
44
|
import { Message, messaging } from "shared/messaging";
|
|
34
45
|
|
|
35
|
-
messaging.
|
|
36
|
-
print(player, "sent data:", data);
|
|
37
|
-
});
|
|
46
|
+
messaging.server.on(Message.Test, (player, data) => print(player, "sent data:", data));
|
|
38
47
|
```
|
|
39
48
|
|
|
40
49
|
### Client
|
|
41
50
|
```ts
|
|
42
51
|
import { Message, messaging } from "shared/messaging";
|
|
43
52
|
|
|
44
|
-
messaging.
|
|
53
|
+
messaging.server.emit(Message.Test, {
|
|
45
54
|
foo: "bar",
|
|
46
55
|
n: 69
|
|
47
56
|
});
|
|
48
57
|
```
|
|
49
58
|
|
|
59
|
+
## Simulated Remote Functions
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
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).
|
|
63
|
+
|
|
64
|
+
### In `shared/messaging.ts`
|
|
65
|
+
```ts
|
|
66
|
+
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
67
|
+
import { MessageEmitter } from "@rbxts/tether";
|
|
68
|
+
|
|
69
|
+
export const messaging = MessageEmitter.create<MessageData>();
|
|
70
|
+
|
|
71
|
+
export const enum Message {
|
|
72
|
+
Increment,
|
|
73
|
+
IncrementReturn
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface MessageData {
|
|
77
|
+
[Message.Increment]: DataType.u8;
|
|
78
|
+
[Message.IncrementReturn]: DataType.u8;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Server
|
|
83
|
+
```ts
|
|
84
|
+
import { Message, messaging } from "shared/messaging";
|
|
85
|
+
|
|
86
|
+
messaging.server.setCallback(Message.Increment, Message.IncrementReturn, (_, n) => n + 1);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Client
|
|
90
|
+
```ts
|
|
91
|
+
import { Message, messaging } from "shared/messaging";
|
|
92
|
+
|
|
93
|
+
messaging.server
|
|
94
|
+
.invoke(Message.Increment, Message.IncrementReturn, 69)
|
|
95
|
+
.then(print); // 70 - incremented by the server
|
|
96
|
+
|
|
97
|
+
// or use await style
|
|
98
|
+
async function main(): Promise<void> {
|
|
99
|
+
const value = await messaging.server.invoke(Message.Increment, Message.IncrementReturn, 69);
|
|
100
|
+
print(value) // 70
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
main();
|
|
104
|
+
```
|
|
105
|
+
|
|
50
106
|
## Middleware
|
|
51
107
|
Drop, delay, or modify requests
|
|
52
108
|
|
|
@@ -58,7 +114,7 @@ Drop, delay, or modify requests
|
|
|
58
114
|
import type { ClientMiddleware } from "@rbxts/tether";
|
|
59
115
|
|
|
60
116
|
export function logClient(): ClientMiddleware {
|
|
61
|
-
return message => (player,
|
|
117
|
+
return message => (player, ctx) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, ctx.data);
|
|
62
118
|
}
|
|
63
119
|
```
|
|
64
120
|
|
|
@@ -67,7 +123,7 @@ export function logClient(): ClientMiddleware {
|
|
|
67
123
|
import type { ServerMiddleware } from "@rbxts/tether";
|
|
68
124
|
|
|
69
125
|
export function logServer(): ServerMiddleware {
|
|
70
|
-
return message =>
|
|
126
|
+
return message => ctx => print(`[LOG]: Sent message '${message}' to server with data:`, ctx.data);
|
|
71
127
|
}
|
|
72
128
|
```
|
|
73
129
|
|
|
@@ -94,7 +150,7 @@ import type { ServerMiddleware } from "@rbxts/tether";
|
|
|
94
150
|
|
|
95
151
|
export function incrementNumberData(): ServerMiddleware<number> {
|
|
96
152
|
// sets the data to be used by the any subsequent middlewares as well as sent through the remote
|
|
97
|
-
return () => (data, updateData) => updateData(data + 1);
|
|
153
|
+
return () => ({ data, updateData }) => updateData(data + 1);
|
|
98
154
|
}
|
|
99
155
|
```
|
|
100
156
|
|
|
@@ -107,19 +163,19 @@ export const messaging = MessageEmitter.create<MessageData>();
|
|
|
107
163
|
messaging.middleware
|
|
108
164
|
// only allows requests to the server every 5 seconds,
|
|
109
165
|
// drops any requests that occur within 5 seconds of each other
|
|
110
|
-
.useServer(Message.Test,
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
.useServer(Message.Test, [BuiltinMiddlewares.validate()])
|
|
166
|
+
.useServer(Message.Test, BuiltinMiddlewares.rateLimit(5))
|
|
167
|
+
// will be just one byte!
|
|
168
|
+
.useShared(Message.Packed, () => ctx => print("Packed object size:", buffer.len(ctx.getRawData().buffer)));
|
|
114
169
|
// logs every message fired
|
|
115
|
-
.useServerGlobal(
|
|
116
|
-
.useClientGlobal(
|
|
117
|
-
.useSharedGlobal(
|
|
118
|
-
.useServer(Message.Test,
|
|
119
|
-
.useServerGlobal(
|
|
170
|
+
.useServerGlobal(logServer())
|
|
171
|
+
.useClientGlobal(logClient())
|
|
172
|
+
.useSharedGlobal(BuiltinMiddlewares.debug()); // verbosely logs every packet sent
|
|
173
|
+
.useServer(Message.Test, incrementNumberData()) // error! - data for Message.Test is not a number
|
|
174
|
+
.useServerGlobal(incrementNumberData()); // error! - global data type is always 'unknown', we cannot guarantee a number
|
|
120
175
|
|
|
121
176
|
export const enum Message {
|
|
122
|
-
Test
|
|
177
|
+
Test,
|
|
178
|
+
Packed
|
|
123
179
|
}
|
|
124
180
|
|
|
125
181
|
export interface MessageData {
|
|
@@ -127,5 +183,15 @@ export interface MessageData {
|
|
|
127
183
|
readonly foo: string;
|
|
128
184
|
readonly n: DataType.u8;
|
|
129
185
|
};
|
|
186
|
+
[Message.Packed]: DataType.Packed<{
|
|
187
|
+
readonly boolean1: boolean;
|
|
188
|
+
readonly boolean2: boolean;
|
|
189
|
+
readonly boolean3: boolean;
|
|
190
|
+
readonly boolean4: boolean;
|
|
191
|
+
readonly boolean5: boolean;
|
|
192
|
+
readonly boolean6: boolean;
|
|
193
|
+
readonly boolean7: boolean;
|
|
194
|
+
readonly boolean8: boolean;
|
|
195
|
+
}>;
|
|
130
196
|
}
|
|
131
197
|
```
|
|
@@ -1,7 +1,24 @@
|
|
|
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;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a shared middleware that will check if a message packet exceeds the given maximum size in bytes
|
|
16
|
+
*
|
|
17
|
+
* @param maxBytes The maximum size of the packet in bytes
|
|
18
|
+
* @param throwError Whether the middleware should throw an error if the packet exceeds the maximum size, or simply drop the request
|
|
19
|
+
* @returns A shared middleware that will check if a message packet exceeds the given maximum size
|
|
20
|
+
*/
|
|
21
|
+
function maxPacketSize(maxBytes: number, throwError?: boolean): SharedMiddleware;
|
|
5
22
|
/**
|
|
6
23
|
* Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
|
|
7
24
|
*
|
|
@@ -10,15 +27,17 @@ export declare namespace BuiltinMiddlewares {
|
|
|
10
27
|
*/
|
|
11
28
|
function rateLimit(interval: number): SharedMiddleware;
|
|
12
29
|
/**
|
|
13
|
-
* Creates a shared middleware that
|
|
14
|
-
*
|
|
15
|
-
*
|
|
30
|
+
* Creates a shared middleware that will log a message whenever a message is sent, containing the following information:
|
|
31
|
+
* - The message kind
|
|
32
|
+
* - The data associated with the message
|
|
33
|
+
* - The raw data (buffer and blobs) associated with the message
|
|
34
|
+
* - The size of the packet (in bytes)
|
|
35
|
+
* - The size of the buffer (in bytes)
|
|
36
|
+
* - The size of the blobs (in bytes)
|
|
37
|
+
* - The schema string associated with the message (if it can be deduced)
|
|
16
38
|
*
|
|
17
|
-
* @
|
|
18
|
-
* @returns A shared middleware that validates the data with the given guard.
|
|
39
|
+
* @returns A shared middleware that will log a message whenever a message is sent.
|
|
19
40
|
* @metadata macro
|
|
20
41
|
*/
|
|
21
|
-
function
|
|
22
|
-
function debug(): SharedMiddleware;
|
|
42
|
+
function debug<T>(schema?: Modding.Many<Any.Equals<T, unknown> extends 1 ? undefined : SerializerMetadata<TetherPacket<T>>>): SharedMiddleware<T>;
|
|
23
43
|
}
|
|
24
|
-
export {};
|
|
@@ -1,68 +1,74 @@
|
|
|
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
|
|
18
10
|
--[[
|
|
19
11
|
*
|
|
20
|
-
* Creates a shared middleware that will
|
|
12
|
+
* Creates a shared middleware that will simulate a ping of the given amount when a message is sent
|
|
21
13
|
*
|
|
22
|
-
* @param
|
|
23
|
-
* @returns A middleware that will
|
|
14
|
+
* @param pingInMs The amount of time in milliseconds that the middleware should wait
|
|
15
|
+
* @returns A shared middleware that will simulate a ping
|
|
24
16
|
|
|
25
17
|
]]
|
|
26
|
-
local function
|
|
27
|
-
local lastRequest = 0
|
|
18
|
+
local function simulatePing(pingInMs)
|
|
28
19
|
return function()
|
|
29
20
|
return function()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
lastRequest = os.clock()
|
|
21
|
+
task.wait(pingInMs / 1000)
|
|
22
|
+
return nil
|
|
34
23
|
end
|
|
35
24
|
end
|
|
36
25
|
end
|
|
37
|
-
_container.
|
|
26
|
+
_container.simulatePing = simulatePing
|
|
38
27
|
--[[
|
|
39
28
|
*
|
|
40
|
-
* Creates a shared middleware that
|
|
41
|
-
*
|
|
42
|
-
* If the guard fails, the middleware will drop the message
|
|
29
|
+
* Creates a shared middleware that will check if a message packet exceeds the given maximum size in bytes
|
|
43
30
|
*
|
|
44
|
-
* @param
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
31
|
+
* @param maxBytes The maximum size of the packet in bytes
|
|
32
|
+
* @param throwError Whether the middleware should throw an error if the packet exceeds the maximum size, or simply drop the request
|
|
33
|
+
* @returns A shared middleware that will check if a message packet exceeds the given maximum size
|
|
47
34
|
|
|
48
35
|
]]
|
|
49
|
-
local function
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
return noOp
|
|
36
|
+
local function maxPacketSize(maxBytes, throwError)
|
|
37
|
+
if throwError == nil then
|
|
38
|
+
throwError = true
|
|
53
39
|
end
|
|
54
40
|
return function(message)
|
|
55
|
-
return function(
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
return function(ctx)
|
|
42
|
+
local rawData = ctx.getRawData()
|
|
43
|
+
local totalSize = buffer.len(rawData.buffer) + #rawData.blobs * BLOB_SIZE
|
|
44
|
+
if totalSize > maxBytes then
|
|
45
|
+
return if throwError then error(`[@rbxts/tether]: Message '{message}' exceeded maximum packet size of {maxBytes} bytes`) else DropRequest
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
_container.maxPacketSize = maxPacketSize
|
|
51
|
+
--[[
|
|
52
|
+
*
|
|
53
|
+
* Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
|
|
54
|
+
*
|
|
55
|
+
* @param interval The interval in seconds that the middleware should wait before allowing a new request
|
|
56
|
+
* @returns A middleware that will drop any message that occurs within the given interval
|
|
57
|
+
|
|
58
|
+
]]
|
|
59
|
+
local function rateLimit(interval)
|
|
60
|
+
local lastRequest = 0
|
|
61
|
+
return function()
|
|
62
|
+
return function()
|
|
63
|
+
if os.clock() - lastRequest < interval then
|
|
64
|
+
return DropRequest
|
|
58
65
|
end
|
|
59
|
-
|
|
60
|
-
return DropRequest
|
|
66
|
+
lastRequest = os.clock()
|
|
61
67
|
end
|
|
62
68
|
end
|
|
63
69
|
end
|
|
64
|
-
_container.
|
|
65
|
-
local function
|
|
70
|
+
_container.rateLimit = rateLimit
|
|
71
|
+
local function bufferToString(buf)
|
|
66
72
|
local s = { "{ " }
|
|
67
73
|
do
|
|
68
74
|
local i = 0
|
|
@@ -87,24 +93,44 @@ do
|
|
|
87
93
|
table.insert(s, " }")
|
|
88
94
|
return table.concat(s, "")
|
|
89
95
|
end
|
|
90
|
-
local
|
|
91
|
-
|
|
96
|
+
local horizontalLine = "------------------------------------"
|
|
97
|
+
--[[
|
|
98
|
+
*
|
|
99
|
+
* Creates a shared middleware that will log a message whenever a message is sent, containing the following information:
|
|
100
|
+
* - The message kind
|
|
101
|
+
* - The data associated with the message
|
|
102
|
+
* - The raw data (buffer and blobs) associated with the message
|
|
103
|
+
* - The size of the packet (in bytes)
|
|
104
|
+
* - The size of the buffer (in bytes)
|
|
105
|
+
* - The size of the blobs (in bytes)
|
|
106
|
+
* - The schema string associated with the message (if it can be deduced)
|
|
107
|
+
*
|
|
108
|
+
* @returns A shared middleware that will log a message whenever a message is sent.
|
|
109
|
+
* @metadata macro
|
|
110
|
+
|
|
111
|
+
]]
|
|
112
|
+
local function debug(schema)
|
|
92
113
|
return function(message)
|
|
93
|
-
return function(
|
|
114
|
+
return function(_param)
|
|
115
|
+
local data = _param.data
|
|
116
|
+
local getRawData = _param.getRawData
|
|
94
117
|
local rawData = getRawData()
|
|
95
118
|
local bufferSize = buffer.len(rawData.buffer)
|
|
96
119
|
local blobsSize = #rawData.blobs * BLOB_SIZE
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
local schemaString = if schema ~= nil then " " .. table.concat(string.split(repr(schema[2], {
|
|
121
|
+
pretty = true,
|
|
122
|
+
}), "\n"), "\n ") else "unknown"
|
|
123
|
+
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, {
|
|
124
|
+
pretty = false,
|
|
125
|
+
robloxClassName = true,
|
|
126
|
+
}), "\n", " - Packet size: ", bufferSize + blobsSize, " bytes\n", " - Buffer: ", bufferSize, " bytes\n", " - Blobs: ", blobsSize, " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
|
|
127
|
+
-- ▼ ReadonlyArray.join ▼
|
|
128
|
+
local _result = table.create(#text)
|
|
129
|
+
for _k, _v in text do
|
|
130
|
+
_result[_k] = tostring(_v)
|
|
131
|
+
end
|
|
132
|
+
-- ▲ ReadonlyArray.join ▲
|
|
133
|
+
print(table.concat(_result, ""))
|
|
108
134
|
end
|
|
109
135
|
end
|
|
110
136
|
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
|
|
10
|
+
private readonly serverFunctions;
|
|
11
|
+
private readonly guards;
|
|
12
|
+
private readonly 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,17 +34,237 @@ 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
|
+
local ctx = {
|
|
65
|
+
data = data,
|
|
66
|
+
updateData = updateData,
|
|
67
|
+
getRawData = getPacket,
|
|
68
|
+
}
|
|
69
|
+
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
70
|
+
if not self:validateData(message, data) then
|
|
71
|
+
return nil
|
|
72
|
+
end
|
|
73
|
+
local result = globalMiddleware(message)(ctx)
|
|
74
|
+
if result == DropRequest then
|
|
75
|
+
return nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
for _, middleware in self.middleware:getServer(message) do
|
|
79
|
+
if not self:validateData(message, data) then
|
|
80
|
+
return nil
|
|
81
|
+
end
|
|
82
|
+
local result = middleware(message)(ctx)
|
|
83
|
+
if result == DropRequest then
|
|
84
|
+
return nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
if not self:validateData(message, data) then
|
|
88
|
+
return nil
|
|
89
|
+
end
|
|
90
|
+
local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
|
|
91
|
+
send(getPacket())
|
|
92
|
+
end)
|
|
93
|
+
end,
|
|
94
|
+
invoke = TS.async(function(message, returnMessage, data, unreliable)
|
|
95
|
+
if unreliable == nil then
|
|
96
|
+
unreliable = false
|
|
97
|
+
end
|
|
98
|
+
local _clientFunctions = self.clientFunctions
|
|
99
|
+
local _returnMessage = returnMessage
|
|
100
|
+
if not (_clientFunctions[_returnMessage] ~= nil) then
|
|
101
|
+
local _clientFunctions_1 = self.clientFunctions
|
|
102
|
+
local _returnMessage_1 = returnMessage
|
|
103
|
+
_clientFunctions_1[_returnMessage_1] = {}
|
|
104
|
+
end
|
|
105
|
+
local _clientFunctions_1 = self.clientFunctions
|
|
106
|
+
local _returnMessage_1 = returnMessage
|
|
107
|
+
local functions = _clientFunctions_1[_returnMessage_1]
|
|
108
|
+
local returnValue
|
|
109
|
+
functions[function(data)
|
|
110
|
+
returnValue = data
|
|
111
|
+
return returnValue
|
|
112
|
+
end] = true
|
|
113
|
+
self.server.emit(message, data, unreliable)
|
|
114
|
+
while returnValue == nil do
|
|
115
|
+
RunService.Heartbeat:Wait()
|
|
116
|
+
end
|
|
117
|
+
return returnValue
|
|
118
|
+
end),
|
|
119
|
+
setCallback = function(message, returnMessage, callback)
|
|
120
|
+
return self.server.on(message, function(player, data)
|
|
121
|
+
local returnValue = callback(player, data)
|
|
122
|
+
self.client.emit(player, returnMessage, returnValue)
|
|
123
|
+
end)
|
|
124
|
+
end,
|
|
125
|
+
}
|
|
126
|
+
self.client = {
|
|
127
|
+
on = function(message, callback)
|
|
128
|
+
return self:on(message, callback, self.clientCallbacks)
|
|
129
|
+
end,
|
|
130
|
+
once = function(message, callback)
|
|
131
|
+
return self:once(message, callback, self.clientCallbacks)
|
|
132
|
+
end,
|
|
133
|
+
emit = function(player, message, data, unreliable)
|
|
134
|
+
if unreliable == nil then
|
|
135
|
+
unreliable = false
|
|
136
|
+
end
|
|
137
|
+
local updateData = function(newData)
|
|
138
|
+
data = newData
|
|
139
|
+
return nil
|
|
140
|
+
end
|
|
141
|
+
local getPacket = function()
|
|
142
|
+
return self:getPacket(message, data)
|
|
143
|
+
end
|
|
144
|
+
if not self:validateData(message, data) then
|
|
145
|
+
return nil
|
|
146
|
+
end
|
|
147
|
+
task.spawn(function()
|
|
148
|
+
local ctx = {
|
|
149
|
+
data = data,
|
|
150
|
+
updateData = updateData,
|
|
151
|
+
getRawData = getPacket,
|
|
152
|
+
}
|
|
153
|
+
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
154
|
+
if not self:validateData(message, data) then
|
|
155
|
+
return nil
|
|
156
|
+
end
|
|
157
|
+
local result = globalMiddleware(message)(player, ctx)
|
|
158
|
+
if result == DropRequest then
|
|
159
|
+
return nil
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
for _, middleware in self.middleware:getClient(message) do
|
|
163
|
+
if not self:validateData(message, data) then
|
|
164
|
+
return nil
|
|
165
|
+
end
|
|
166
|
+
local result = middleware(message)(player, ctx)
|
|
167
|
+
if result == DropRequest then
|
|
168
|
+
return nil
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
if not self:validateData(message, data) then
|
|
172
|
+
return nil
|
|
173
|
+
end
|
|
174
|
+
local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
175
|
+
send(player, getPacket())
|
|
176
|
+
end)
|
|
177
|
+
end,
|
|
178
|
+
emitAll = function(message, data, unreliable)
|
|
179
|
+
if unreliable == nil then
|
|
180
|
+
unreliable = false
|
|
181
|
+
end
|
|
182
|
+
local updateData = function(newData)
|
|
183
|
+
data = newData
|
|
184
|
+
return nil
|
|
185
|
+
end
|
|
186
|
+
local getPacket = function()
|
|
187
|
+
return self:getPacket(message, data)
|
|
188
|
+
end
|
|
189
|
+
if not self:validateData(message, data) then
|
|
190
|
+
return nil
|
|
191
|
+
end
|
|
192
|
+
task.spawn(function()
|
|
193
|
+
local ctx = {
|
|
194
|
+
data = data,
|
|
195
|
+
updateData = updateData,
|
|
196
|
+
getRawData = getPacket,
|
|
197
|
+
}
|
|
198
|
+
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
199
|
+
for _1, player in Players:GetPlayers() do
|
|
200
|
+
if not self:validateData(message, data) then
|
|
201
|
+
return nil
|
|
202
|
+
end
|
|
203
|
+
local result = globalMiddleware(message)(player, ctx)
|
|
204
|
+
if result == DropRequest then
|
|
205
|
+
return nil
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
for _, middleware in self.middleware:getClient(message) do
|
|
210
|
+
for _1, player in Players:GetPlayers() do
|
|
211
|
+
if not self:validateData(message, data) then
|
|
212
|
+
return nil
|
|
213
|
+
end
|
|
214
|
+
local result = middleware(message)(player, ctx)
|
|
215
|
+
if result == DropRequest then
|
|
216
|
+
return nil
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
if not self:validateData(message, data) then
|
|
221
|
+
return nil
|
|
222
|
+
end
|
|
223
|
+
local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
224
|
+
send:broadcast(getPacket())
|
|
225
|
+
end)
|
|
226
|
+
end,
|
|
227
|
+
invoke = TS.async(function(message, returnMessage, player, data, unreliable)
|
|
228
|
+
if unreliable == nil then
|
|
229
|
+
unreliable = false
|
|
230
|
+
end
|
|
231
|
+
local _serverFunctions = self.serverFunctions
|
|
232
|
+
local _returnMessage = returnMessage
|
|
233
|
+
if not (_serverFunctions[_returnMessage] ~= nil) then
|
|
234
|
+
local _serverFunctions_1 = self.serverFunctions
|
|
235
|
+
local _returnMessage_1 = returnMessage
|
|
236
|
+
_serverFunctions_1[_returnMessage_1] = {}
|
|
237
|
+
end
|
|
238
|
+
local _serverFunctions_1 = self.serverFunctions
|
|
239
|
+
local _returnMessage_1 = returnMessage
|
|
240
|
+
local functions = _serverFunctions_1[_returnMessage_1]
|
|
241
|
+
local returnValue
|
|
242
|
+
functions[function(data)
|
|
243
|
+
returnValue = data
|
|
244
|
+
return returnValue
|
|
245
|
+
end] = true
|
|
246
|
+
self.client.emit(player, message, data, unreliable)
|
|
247
|
+
while returnValue == nil do
|
|
248
|
+
RunService.Heartbeat:Wait()
|
|
249
|
+
end
|
|
250
|
+
return returnValue
|
|
251
|
+
end),
|
|
252
|
+
setCallback = function(message, returnMessage, callback)
|
|
253
|
+
return self.client.on(message, function(data)
|
|
254
|
+
local returnValue = callback(data)
|
|
255
|
+
self.server.emit(returnMessage, returnValue)
|
|
256
|
+
end)
|
|
257
|
+
end,
|
|
258
|
+
}
|
|
34
259
|
self.janitor:Add(function()
|
|
35
260
|
table.clear(self.clientCallbacks)
|
|
36
261
|
table.clear(self.serverCallbacks)
|
|
37
|
-
self.serializers
|
|
262
|
+
table.clear(self.serializers)
|
|
38
263
|
self.serverEvents = nil
|
|
39
264
|
self.clientEvents = nil
|
|
40
265
|
end)
|
|
41
266
|
if RunService:IsServer() then
|
|
42
|
-
self.serverEvents =
|
|
267
|
+
self.serverEvents = remotes:createServer({}, {
|
|
43
268
|
incomingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
|
|
44
269
|
incoming = {
|
|
45
270
|
sendServerMessage = { { t.interface({
|
|
@@ -62,7 +287,7 @@ do
|
|
|
62
287
|
namespaces = {},
|
|
63
288
|
})
|
|
64
289
|
else
|
|
65
|
-
self.clientEvents =
|
|
290
|
+
self.clientEvents = remotes:createClient({}, {
|
|
66
291
|
incomingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
|
|
67
292
|
incoming = {
|
|
68
293
|
sendClientMessage = { { t.interface({
|
|
@@ -86,180 +311,130 @@ do
|
|
|
86
311
|
})
|
|
87
312
|
end
|
|
88
313
|
end
|
|
89
|
-
function MessageEmitter:create(
|
|
314
|
+
function MessageEmitter:create(meta)
|
|
90
315
|
local emitter = MessageEmitter.new()
|
|
91
|
-
if
|
|
92
|
-
warn(
|
|
316
|
+
if meta == nil then
|
|
317
|
+
warn(metaGenerationFailed)
|
|
93
318
|
return emitter:initialize()
|
|
94
319
|
end
|
|
95
|
-
for kind,
|
|
96
|
-
|
|
320
|
+
for kind, _binding in pairs(meta) do
|
|
321
|
+
local guard = _binding.guard
|
|
322
|
+
local serializerMetadata = _binding.serializerMetadata
|
|
323
|
+
local numberKind = tonumber(kind)
|
|
324
|
+
emitter.guards[numberKind] = guard
|
|
325
|
+
if serializerMetadata == nil then
|
|
97
326
|
continue
|
|
98
327
|
end
|
|
99
|
-
emitter:addSerializer(
|
|
328
|
+
emitter:addSerializer(numberKind, serializerMetadata)
|
|
100
329
|
end
|
|
101
330
|
return emitter:initialize()
|
|
102
331
|
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
|
|
332
|
+
function MessageEmitter:validateData(message, data)
|
|
333
|
+
local _guards = self.guards
|
|
334
|
+
local _message = message
|
|
335
|
+
local guard = _guards[_message]
|
|
336
|
+
local guardPassed = guard(data)
|
|
337
|
+
if not guardPassed then
|
|
338
|
+
warn(guardFailed(message))
|
|
131
339
|
end
|
|
132
|
-
|
|
133
|
-
send(getPacket())
|
|
340
|
+
return guardPassed
|
|
134
341
|
end
|
|
135
|
-
function MessageEmitter:
|
|
136
|
-
if
|
|
137
|
-
|
|
342
|
+
function MessageEmitter:initialize()
|
|
343
|
+
if RunService:IsClient() then
|
|
344
|
+
self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
|
|
345
|
+
local sentMessage = self:readMessageFromPacket(serializedPacket)
|
|
346
|
+
self:executeEventCallbacks(sentMessage, serializedPacket)
|
|
347
|
+
self:executeFunctions(sentMessage, serializedPacket)
|
|
348
|
+
end))
|
|
349
|
+
else
|
|
350
|
+
self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, serializedPacket)
|
|
351
|
+
local sentMessage = self:readMessageFromPacket(serializedPacket)
|
|
352
|
+
self:executeEventCallbacks(sentMessage, serializedPacket, player)
|
|
353
|
+
self:executeFunctions(sentMessage, serializedPacket)
|
|
354
|
+
end))
|
|
138
355
|
end
|
|
139
|
-
|
|
140
|
-
|
|
356
|
+
return self
|
|
357
|
+
end
|
|
358
|
+
function MessageEmitter:readMessageFromPacket(serializedPacket)
|
|
359
|
+
return buffer.readu8(serializedPacket.buffer, 0)
|
|
360
|
+
end
|
|
361
|
+
function MessageEmitter:executeFunctions(message, serializedPacket)
|
|
362
|
+
local isServer = RunService:IsServer()
|
|
363
|
+
local functionsMap = if isServer then self.serverFunctions else self.clientFunctions
|
|
364
|
+
local _message = message
|
|
365
|
+
local functions = functionsMap[_message]
|
|
366
|
+
if functions == nil then
|
|
141
367
|
return nil
|
|
142
368
|
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
|
|
369
|
+
local serializer = self:getSerializer(message)
|
|
370
|
+
local _packet = serializer
|
|
371
|
+
if _packet ~= nil then
|
|
372
|
+
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
151
373
|
end
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
374
|
+
local packet = _packet
|
|
375
|
+
for callback in functions do
|
|
376
|
+
local _result = packet
|
|
377
|
+
if _result ~= nil then
|
|
378
|
+
_result = _result.data
|
|
156
379
|
end
|
|
380
|
+
callback(_result)
|
|
157
381
|
end
|
|
158
|
-
local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
159
|
-
send(player, getPacket())
|
|
160
382
|
end
|
|
161
|
-
function MessageEmitter:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
local
|
|
166
|
-
|
|
383
|
+
function MessageEmitter:executeEventCallbacks(message, serializedPacket, player)
|
|
384
|
+
local isServer = RunService:IsServer()
|
|
385
|
+
local callbacksMap = if isServer then self.serverCallbacks else self.clientCallbacks
|
|
386
|
+
local _message = message
|
|
387
|
+
local callbacks = callbacksMap[_message]
|
|
388
|
+
if callbacks == nil then
|
|
167
389
|
return nil
|
|
168
390
|
end
|
|
169
|
-
local
|
|
170
|
-
|
|
391
|
+
local serializer = self:getSerializer(message)
|
|
392
|
+
local _packet = serializer
|
|
393
|
+
if _packet ~= nil then
|
|
394
|
+
_packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
|
|
171
395
|
end
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
396
|
+
local packet = _packet
|
|
397
|
+
for callback in callbacks do
|
|
398
|
+
if isServer then
|
|
399
|
+
local _exp = player
|
|
400
|
+
local _result = packet
|
|
401
|
+
if _result ~= nil then
|
|
402
|
+
_result = _result.data
|
|
177
403
|
end
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if result == DropRequest then
|
|
184
|
-
return nil
|
|
404
|
+
callback(_exp, _result)
|
|
405
|
+
else
|
|
406
|
+
local _result = packet
|
|
407
|
+
if _result ~= nil then
|
|
408
|
+
_result = _result.data
|
|
185
409
|
end
|
|
410
|
+
callback(_result)
|
|
186
411
|
end
|
|
187
412
|
end
|
|
188
|
-
local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
|
|
189
|
-
send:broadcast(self:getPacket(message, data))
|
|
190
413
|
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
|
|
414
|
+
function MessageEmitter:once(message, callback, callbacksMap)
|
|
415
|
+
local destructor
|
|
416
|
+
destructor = self:on(message, function(player, data)
|
|
417
|
+
destructor()
|
|
418
|
+
callback(player, data)
|
|
419
|
+
end, callbacksMap)
|
|
420
|
+
return destructor
|
|
249
421
|
end
|
|
250
|
-
function MessageEmitter:on(message, callback)
|
|
251
|
-
local
|
|
422
|
+
function MessageEmitter:on(message, callback, callbacksMap)
|
|
423
|
+
local _callbacksMap = callbacksMap
|
|
252
424
|
local _message = message
|
|
253
|
-
if not (
|
|
425
|
+
if not (_callbacksMap[_message] ~= nil) then
|
|
426
|
+
local _callbacksMap_1 = callbacksMap
|
|
254
427
|
local _message_1 = message
|
|
255
|
-
|
|
428
|
+
_callbacksMap_1[_message_1] = {}
|
|
256
429
|
end
|
|
430
|
+
local _callbacksMap_1 = callbacksMap
|
|
257
431
|
local _message_1 = message
|
|
258
|
-
local callbacks =
|
|
432
|
+
local callbacks = _callbacksMap_1[_message_1]
|
|
259
433
|
local _callback = callback
|
|
260
434
|
callbacks[_callback] = true
|
|
435
|
+
local _callbacksMap_2 = callbacksMap
|
|
261
436
|
local _message_2 = message
|
|
262
|
-
|
|
437
|
+
_callbacksMap_2[_message_2] = callbacks
|
|
263
438
|
return function()
|
|
264
439
|
local _callback_1 = callback
|
|
265
440
|
-- ▼ Set.delete ▼
|
package/out/middleware.d.ts
CHANGED
|
@@ -3,11 +3,19 @@ type DropRequestSymbol = symbol & {
|
|
|
3
3
|
_drop_req?: undefined;
|
|
4
4
|
};
|
|
5
5
|
export declare const DropRequest: DropRequestSymbol;
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export type
|
|
10
|
-
|
|
6
|
+
export type ClientMiddleware<Data = unknown> = {
|
|
7
|
+
_client?: void;
|
|
8
|
+
} & ((message: BaseMessage) => (player: Player | Player[], ctx: MiddlewareContext<Data>) => DropRequestSymbol | void);
|
|
9
|
+
export type ServerMiddleware<Data = unknown> = {
|
|
10
|
+
_server?: void;
|
|
11
|
+
} & SharedMiddleware<Data>;
|
|
12
|
+
export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (ctx: MiddlewareContext<Data>) => DropRequestSymbol | void;
|
|
13
|
+
export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data> & SharedMiddleware<Data>;
|
|
14
|
+
export interface MiddlewareContext<Data = unknown> {
|
|
15
|
+
readonly data: Readonly<Data>;
|
|
16
|
+
updateData: (newData: Data) => void;
|
|
17
|
+
getRawData: () => SerializedPacket;
|
|
18
|
+
}
|
|
11
19
|
export declare class MiddlewareProvider<MessageData> {
|
|
12
20
|
private readonly clientGlobalMiddlewares;
|
|
13
21
|
private readonly serverGlobalMiddlewares;
|
|
@@ -21,9 +29,9 @@ export declare class MiddlewareProvider<MessageData> {
|
|
|
21
29
|
getClientGlobal<Data>(): ClientMiddleware<Data>[];
|
|
22
30
|
/** @hidden */
|
|
23
31
|
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;
|
|
32
|
+
useClient<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ClientMiddleware<MessageData[Kind]> | readonly ClientMiddleware<MessageData[Kind]>[] | ClientMiddleware | readonly ClientMiddleware[], order?: number): this;
|
|
33
|
+
useServer<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ServerMiddleware<MessageData[Kind]> | readonly ServerMiddleware<MessageData[Kind]>[] | ServerMiddleware | readonly ServerMiddleware[], order?: number): this;
|
|
34
|
+
useShared<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: SharedMiddleware<MessageData[Kind]> | readonly SharedMiddleware<MessageData[Kind]>[] | SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
|
|
27
35
|
useClientGlobal(middlewares: ClientMiddleware | readonly ClientMiddleware[], order?: number): this;
|
|
28
36
|
useServerGlobal(middlewares: ServerMiddleware | readonly ServerMiddleware[], order?: number): this;
|
|
29
37
|
useSharedGlobal(middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
|
package/out/middleware.luau
CHANGED
|
@@ -78,8 +78,8 @@ do
|
|
|
78
78
|
local _newValue = table.create(#_exp)
|
|
79
79
|
local _callback = function(middleware)
|
|
80
80
|
return function(message)
|
|
81
|
-
return function(_,
|
|
82
|
-
return middleware(message)(
|
|
81
|
+
return function(_, ctx)
|
|
82
|
+
return middleware(message)(ctx)
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
end
|
|
@@ -133,8 +133,8 @@ do
|
|
|
133
133
|
local _newValue = table.create(#_exp)
|
|
134
134
|
local _callback = function(middleware)
|
|
135
135
|
return function(message)
|
|
136
|
-
return function(_,
|
|
137
|
-
return middleware(message)(
|
|
136
|
+
return function(_, ctx)
|
|
137
|
+
return middleware(message)(ctx)
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
140
|
end
|
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.1
|
|
3
|
+
"version": "1.2.1",
|
|
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
|
}
|