@rbxts/tether 1.2.2 → 1.2.4
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 +196 -196
- package/out/message-emitter.d.ts +12 -2
- package/out/message-emitter.luau +43 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,197 +1,197 @@
|
|
|
1
|
-
# Tether
|
|
2
|
-
A message-based networking solution for Roblox with automatic binary serialization and type validation.
|
|
3
|
-
|
|
4
|
-
> [!CAUTION]
|
|
5
|
-
> Depends on `rbxts-transformer-flamework`!
|
|
6
|
-
|
|
7
|
-
## Usage
|
|
8
|
-
|
|
9
|
-
### In `shared/messaging.ts`
|
|
10
|
-
```ts
|
|
11
|
-
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
12
|
-
import { MessageEmitter } from "@rbxts/tether";
|
|
13
|
-
|
|
14
|
-
export const messaging = MessageEmitter.create<MessageData>();
|
|
15
|
-
|
|
16
|
-
export const enum Message {
|
|
17
|
-
Test,
|
|
18
|
-
Packed
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface MessageData {
|
|
22
|
-
[Message.Test]: {
|
|
23
|
-
readonly foo: string;
|
|
24
|
-
readonly n: DataType.u8;
|
|
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
|
-
}>;
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
> [!CAUTION]
|
|
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.
|
|
41
|
-
|
|
42
|
-
### Server
|
|
43
|
-
```ts
|
|
44
|
-
import { Message, messaging } from "shared/messaging";
|
|
45
|
-
|
|
46
|
-
messaging.server.on(Message.Test, (player, data) => print(player, "sent data:", data));
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Client
|
|
50
|
-
```ts
|
|
51
|
-
import { Message, messaging } from "shared/messaging";
|
|
52
|
-
|
|
53
|
-
messaging.server.emit(Message.Test, {
|
|
54
|
-
foo: "bar",
|
|
55
|
-
n: 69
|
|
56
|
-
});
|
|
57
|
-
```
|
|
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
|
-
|
|
106
|
-
## Middleware
|
|
107
|
-
Drop, delay, or modify requests
|
|
108
|
-
|
|
109
|
-
### Creating middleware
|
|
110
|
-
|
|
111
|
-
**Note:** These client/server middlewares can be implemented as shared middlewares. This is strictly an example.
|
|
112
|
-
#### Client
|
|
113
|
-
```ts
|
|
114
|
-
import type { ClientMiddleware } from "@rbxts/tether";
|
|
115
|
-
|
|
116
|
-
export function logClient(): ClientMiddleware {
|
|
117
|
-
return message => (player, ctx) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, ctx.data);
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
#### Server
|
|
122
|
-
```ts
|
|
123
|
-
import type { ServerMiddleware } from "@rbxts/tether";
|
|
124
|
-
|
|
125
|
-
export function logServer(): ServerMiddleware {
|
|
126
|
-
return message => ctx => print(`[LOG]: Sent message '${message}' to server with data:`, ctx.data);
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
#### Shared
|
|
131
|
-
```ts
|
|
132
|
-
import { type SharedMiddleware, DropRequest } from "@rbxts/tether";
|
|
133
|
-
|
|
134
|
-
export function rateLimit(interval: number): SharedMiddleware {
|
|
135
|
-
let lastRequest = 0;
|
|
136
|
-
|
|
137
|
-
return message => // message attempting to be sent
|
|
138
|
-
() => { // no data/player - it's a shared middleware
|
|
139
|
-
if (os.clock() - lastRequest < interval)
|
|
140
|
-
return DropRequest;
|
|
141
|
-
|
|
142
|
-
lastRequest = os.clock();
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
#### Transforming data
|
|
148
|
-
```ts
|
|
149
|
-
import type { ServerMiddleware } from "@rbxts/tether";
|
|
150
|
-
|
|
151
|
-
export function incrementNumberData(): ServerMiddleware<number> {
|
|
152
|
-
// sets the data to be used by the any subsequent middlewares as well as sent through the remote
|
|
153
|
-
return () => ({ data, updateData }) => updateData(data + 1);
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Using middleware
|
|
158
|
-
```ts
|
|
159
|
-
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
160
|
-
import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
|
|
161
|
-
|
|
162
|
-
export const messaging = MessageEmitter.create<MessageData>();
|
|
163
|
-
messaging.middleware
|
|
164
|
-
// only allows requests to the server every 5 seconds,
|
|
165
|
-
// drops any requests that occur within 5 seconds of each other
|
|
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)));
|
|
169
|
-
// logs every message fired
|
|
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
|
|
175
|
-
|
|
176
|
-
export const enum Message {
|
|
177
|
-
Test,
|
|
178
|
-
Packed
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export interface MessageData {
|
|
182
|
-
[Message.Test]: {
|
|
183
|
-
readonly foo: string;
|
|
184
|
-
readonly n: DataType.u8;
|
|
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
|
-
}>;
|
|
196
|
-
}
|
|
1
|
+
# Tether
|
|
2
|
+
A message-based networking solution for Roblox with automatic binary serialization and type validation.
|
|
3
|
+
|
|
4
|
+
> [!CAUTION]
|
|
5
|
+
> Depends on `rbxts-transformer-flamework`!
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### In `shared/messaging.ts`
|
|
10
|
+
```ts
|
|
11
|
+
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
12
|
+
import { MessageEmitter } from "@rbxts/tether";
|
|
13
|
+
|
|
14
|
+
export const messaging = MessageEmitter.create<MessageData>();
|
|
15
|
+
|
|
16
|
+
export const enum Message {
|
|
17
|
+
Test,
|
|
18
|
+
Packed
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MessageData {
|
|
22
|
+
[Message.Test]: {
|
|
23
|
+
readonly foo: string;
|
|
24
|
+
readonly n: DataType.u8;
|
|
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
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> [!CAUTION]
|
|
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.
|
|
41
|
+
|
|
42
|
+
### Server
|
|
43
|
+
```ts
|
|
44
|
+
import { Message, messaging } from "shared/messaging";
|
|
45
|
+
|
|
46
|
+
messaging.server.on(Message.Test, (player, data) => print(player, "sent data:", data));
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Client
|
|
50
|
+
```ts
|
|
51
|
+
import { Message, messaging } from "shared/messaging";
|
|
52
|
+
|
|
53
|
+
messaging.server.emit(Message.Test, {
|
|
54
|
+
foo: "bar",
|
|
55
|
+
n: 69
|
|
56
|
+
});
|
|
57
|
+
```
|
|
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
|
+
|
|
106
|
+
## Middleware
|
|
107
|
+
Drop, delay, or modify requests
|
|
108
|
+
|
|
109
|
+
### Creating middleware
|
|
110
|
+
|
|
111
|
+
**Note:** These client/server middlewares can be implemented as shared middlewares. This is strictly an example.
|
|
112
|
+
#### Client
|
|
113
|
+
```ts
|
|
114
|
+
import type { ClientMiddleware } from "@rbxts/tether";
|
|
115
|
+
|
|
116
|
+
export function logClient(): ClientMiddleware {
|
|
117
|
+
return message => (player, ctx) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, ctx.data);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Server
|
|
122
|
+
```ts
|
|
123
|
+
import type { ServerMiddleware } from "@rbxts/tether";
|
|
124
|
+
|
|
125
|
+
export function logServer(): ServerMiddleware {
|
|
126
|
+
return message => ctx => print(`[LOG]: Sent message '${message}' to server with data:`, ctx.data);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Shared
|
|
131
|
+
```ts
|
|
132
|
+
import { type SharedMiddleware, DropRequest } from "@rbxts/tether";
|
|
133
|
+
|
|
134
|
+
export function rateLimit(interval: number): SharedMiddleware {
|
|
135
|
+
let lastRequest = 0;
|
|
136
|
+
|
|
137
|
+
return message => // message attempting to be sent
|
|
138
|
+
() => { // no data/player - it's a shared middleware
|
|
139
|
+
if (os.clock() - lastRequest < interval)
|
|
140
|
+
return DropRequest;
|
|
141
|
+
|
|
142
|
+
lastRequest = os.clock();
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Transforming data
|
|
148
|
+
```ts
|
|
149
|
+
import type { ServerMiddleware } from "@rbxts/tether";
|
|
150
|
+
|
|
151
|
+
export function incrementNumberData(): ServerMiddleware<number> {
|
|
152
|
+
// sets the data to be used by the any subsequent middlewares as well as sent through the remote
|
|
153
|
+
return () => ({ data, updateData }) => updateData(data + 1);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Using middleware
|
|
158
|
+
```ts
|
|
159
|
+
import type { DataType } from "@rbxts/flamework-binary-serializer";
|
|
160
|
+
import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
|
|
161
|
+
|
|
162
|
+
export const messaging = MessageEmitter.create<MessageData>();
|
|
163
|
+
messaging.middleware
|
|
164
|
+
// only allows requests to the server every 5 seconds,
|
|
165
|
+
// drops any requests that occur within 5 seconds of each other
|
|
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)));
|
|
169
|
+
// logs every message fired
|
|
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
|
|
175
|
+
|
|
176
|
+
export const enum Message {
|
|
177
|
+
Test,
|
|
178
|
+
Packed
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface MessageData {
|
|
182
|
+
[Message.Test]: {
|
|
183
|
+
readonly foo: string;
|
|
184
|
+
readonly n: DataType.u8;
|
|
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
|
+
}>;
|
|
196
|
+
}
|
|
197
197
|
```
|
package/out/message-emitter.d.ts
CHANGED
|
@@ -61,14 +61,23 @@ export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
|
61
61
|
*/
|
|
62
62
|
once: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ClientMessageCallback<MessageData[Kind]>) => () => void;
|
|
63
63
|
/**
|
|
64
|
-
* Emits a message to a specific client
|
|
64
|
+
* Emits a message to a specific client or multiple clients
|
|
65
65
|
*
|
|
66
|
-
* @param player The player to whom the message is sent
|
|
66
|
+
* @param player The player(s) to whom the message is sent
|
|
67
67
|
* @param message The message kind to be sent
|
|
68
68
|
* @param data The data associated with the message
|
|
69
69
|
* @param unreliable Whether the message should be sent unreliably
|
|
70
70
|
*/
|
|
71
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 clients except the specified client(s)
|
|
74
|
+
*
|
|
75
|
+
* @param player The player(s) to whom the message is not sent
|
|
76
|
+
* @param message The message kind to be sent
|
|
77
|
+
* @param data The data associated with the message
|
|
78
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
79
|
+
*/
|
|
80
|
+
emitExcept: <Kind extends keyof MessageData>(player: Player | Player[], message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
|
|
72
81
|
/**
|
|
73
82
|
* Emits a message to all connected clients
|
|
74
83
|
*
|
|
@@ -94,6 +103,7 @@ export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
|
94
103
|
};
|
|
95
104
|
private validateData;
|
|
96
105
|
private initialize;
|
|
106
|
+
private onRemoteFire;
|
|
97
107
|
private readMessageFromPacket;
|
|
98
108
|
private executeFunctions;
|
|
99
109
|
private executeEventCallbacks;
|
package/out/message-emitter.luau
CHANGED
|
@@ -175,6 +175,36 @@ do
|
|
|
175
175
|
send(player, getPacket())
|
|
176
176
|
end)
|
|
177
177
|
end,
|
|
178
|
+
emitExcept = function(player, message, data, unreliable)
|
|
179
|
+
if unreliable == nil then
|
|
180
|
+
unreliable = false
|
|
181
|
+
end
|
|
182
|
+
local shouldSendTo = function(p)
|
|
183
|
+
local _player = player
|
|
184
|
+
local _result
|
|
185
|
+
if typeof(_player) == "Instance" then
|
|
186
|
+
_result = p ~= player
|
|
187
|
+
else
|
|
188
|
+
local _player_1 = player
|
|
189
|
+
local _p = p
|
|
190
|
+
_result = not (table.find(_player_1, _p) ~= nil)
|
|
191
|
+
end
|
|
192
|
+
return _result
|
|
193
|
+
end
|
|
194
|
+
local _client = self.client
|
|
195
|
+
local _exp = Players:GetPlayers()
|
|
196
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
197
|
+
local _newValue = {}
|
|
198
|
+
local _length = 0
|
|
199
|
+
for _k, _v in _exp do
|
|
200
|
+
if shouldSendTo(_v, _k - 1, _exp) == true then
|
|
201
|
+
_length += 1
|
|
202
|
+
_newValue[_length] = _v
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
206
|
+
_client.emit(_newValue, message, data, unreliable)
|
|
207
|
+
end,
|
|
178
208
|
emitAll = function(message, data, unreliable)
|
|
179
209
|
if unreliable == nil then
|
|
180
210
|
unreliable = false
|
|
@@ -342,19 +372,26 @@ do
|
|
|
342
372
|
function MessageEmitter:initialize()
|
|
343
373
|
if RunService:IsClient() then
|
|
344
374
|
self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
375
|
+
return self:onRemoteFire(serializedPacket)
|
|
376
|
+
end))
|
|
377
|
+
self.janitor:Add(self.clientEvents.sendUnreliableClientMessage:connect(function(serializedPacket)
|
|
378
|
+
return self:onRemoteFire(serializedPacket)
|
|
348
379
|
end))
|
|
349
380
|
else
|
|
350
381
|
self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, serializedPacket)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
382
|
+
return self:onRemoteFire(serializedPacket, player)
|
|
383
|
+
end))
|
|
384
|
+
self.janitor:Add(self.serverEvents.sendUnreliableServerMessage:connect(function(player, serializedPacket)
|
|
385
|
+
return self:onRemoteFire(serializedPacket, player)
|
|
354
386
|
end))
|
|
355
387
|
end
|
|
356
388
|
return self
|
|
357
389
|
end
|
|
390
|
+
function MessageEmitter:onRemoteFire(serializedPacket, player)
|
|
391
|
+
local sentMessage = self:readMessageFromPacket(serializedPacket)
|
|
392
|
+
self:executeEventCallbacks(sentMessage, serializedPacket, player)
|
|
393
|
+
self:executeFunctions(sentMessage, serializedPacket)
|
|
394
|
+
end
|
|
358
395
|
function MessageEmitter:readMessageFromPacket(serializedPacket)
|
|
359
396
|
return buffer.readu8(serializedPacket.buffer, 0)
|
|
360
397
|
end
|