@rbxts/tether 1.4.0 → 1.4.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 +1 -1
- package/out/emitters/client-emitter.d.ts +49 -0
- package/out/emitters/client-emitter.luau +153 -0
- package/out/emitters/contextual-emitter.d.ts +26 -0
- package/out/emitters/contextual-emitter.luau +49 -0
- package/out/emitters/message-emitter.d.ts +52 -0
- package/out/{message-emitter.luau → emitters/message-emitter.luau} +109 -336
- package/out/emitters/server-emitter.d.ts +30 -0
- package/out/emitters/server-emitter.luau +104 -0
- package/out/index.d.ts +1 -1
- package/out/init.luau +1 -1
- package/out/logging.d.ts +15 -0
- package/out/logging.luau +4 -0
- package/out/structs.d.ts +3 -2
- package/out/utility.d.ts +5 -0
- package/out/utility.luau +23 -0
- package/package.json +1 -1
- package/out/message-emitter.d.ts +0 -128
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ messaging.server.emit(Message.Test, {
|
|
|
64
64
|
});
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
##
|
|
67
|
+
## Function Messages
|
|
68
68
|
|
|
69
69
|
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.
|
|
70
70
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ContextualEmitter } from "./contextual-emitter";
|
|
2
|
+
import type { BaseMessage, ClientMessageCallback, ClientFunctionMessageCallback } from "../structs";
|
|
3
|
+
export declare class ClientEmitter<MessageData> extends ContextualEmitter<MessageData> {
|
|
4
|
+
readonly context = "client";
|
|
5
|
+
readonly on: <K extends keyof MessageData>(this: ClientEmitter<MessageData>, message: K & BaseMessage, callback: ClientMessageCallback<MessageData[K]>) => () => void;
|
|
6
|
+
readonly once: <K extends keyof MessageData>(this: ClientEmitter<MessageData>, message: K & BaseMessage, callback: ClientMessageCallback<MessageData[K]>) => () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Emits a message to a specific client or multiple clients
|
|
9
|
+
*
|
|
10
|
+
* @param player The player(s) to whom the message is sent
|
|
11
|
+
* @param message The message kind to be sent
|
|
12
|
+
* @param data The data associated with the message
|
|
13
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
14
|
+
*/
|
|
15
|
+
emit<K extends keyof MessageData>(player: Player | Player[], message: K & BaseMessage, data?: MessageData[K], unreliable?: boolean): void;
|
|
16
|
+
/**
|
|
17
|
+
* Emits a message to all clients except the specified client(s)
|
|
18
|
+
*
|
|
19
|
+
* @param player The player(s) to whom the message is not sent
|
|
20
|
+
* @param message The message kind to be sent
|
|
21
|
+
* @param data The data associated with the message
|
|
22
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
23
|
+
*/
|
|
24
|
+
emitExcept<K extends keyof MessageData>(player: Player | Player[], message: K & BaseMessage, data?: MessageData[K], unreliable?: boolean): void;
|
|
25
|
+
/**
|
|
26
|
+
* Emits a message to all connected clients
|
|
27
|
+
*
|
|
28
|
+
* @param message The message kind to be sent
|
|
29
|
+
* @param data The data associated with the message
|
|
30
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
31
|
+
*/
|
|
32
|
+
emitAll<K extends keyof MessageData>(message: K & BaseMessage, data?: MessageData[K], unreliable?: boolean): void;
|
|
33
|
+
/**
|
|
34
|
+
* Sets a callback for a simulated remote function
|
|
35
|
+
*
|
|
36
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
37
|
+
*/
|
|
38
|
+
setCallback<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, callback: ClientFunctionMessageCallback<MessageData[K], MessageData[R]>): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Simulates a remote function invocation
|
|
41
|
+
*
|
|
42
|
+
* @param message The message kind to be sent
|
|
43
|
+
* @param returnMessage The message kind to be returned
|
|
44
|
+
* @param player The player to whom the function is sent
|
|
45
|
+
* @param data The data associated with the message
|
|
46
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
47
|
+
*/
|
|
48
|
+
invoke<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, player: Player, data?: MessageData[K], unreliable?: boolean): Promise<MessageData[R]>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
|
|
4
|
+
local Players = _services.Players
|
|
5
|
+
local RunService = _services.RunService
|
|
6
|
+
local ContextualEmitter = TS.import(script, script.Parent, "contextual-emitter").ContextualEmitter
|
|
7
|
+
local ClientEmitter
|
|
8
|
+
do
|
|
9
|
+
local super = ContextualEmitter
|
|
10
|
+
ClientEmitter = setmetatable({}, {
|
|
11
|
+
__tostring = function()
|
|
12
|
+
return "ClientEmitter"
|
|
13
|
+
end,
|
|
14
|
+
__index = super,
|
|
15
|
+
})
|
|
16
|
+
ClientEmitter.__index = ClientEmitter
|
|
17
|
+
function ClientEmitter.new(...)
|
|
18
|
+
local self = setmetatable({}, ClientEmitter)
|
|
19
|
+
return self:constructor(...) or self
|
|
20
|
+
end
|
|
21
|
+
function ClientEmitter:constructor(...)
|
|
22
|
+
super.constructor(self, ...)
|
|
23
|
+
self.context = "client"
|
|
24
|
+
end
|
|
25
|
+
function ClientEmitter:emit(player, message, data, unreliable)
|
|
26
|
+
if unreliable == nil then
|
|
27
|
+
unreliable = false
|
|
28
|
+
end
|
|
29
|
+
if RunService:IsClient() then
|
|
30
|
+
error("[tether::error] Cannot emit message from client to client")
|
|
31
|
+
end
|
|
32
|
+
task.spawn(function()
|
|
33
|
+
local _binding = self.master:runClientMiddlewares(message, data, player)
|
|
34
|
+
local dropRequest = _binding[1]
|
|
35
|
+
local newData = _binding[2]
|
|
36
|
+
if dropRequest then
|
|
37
|
+
return nil
|
|
38
|
+
end
|
|
39
|
+
self.master:queueMessage(self.context, message, { player, message, newData, unreliable })
|
|
40
|
+
end)
|
|
41
|
+
end
|
|
42
|
+
function ClientEmitter:emitExcept(player, message, data, unreliable)
|
|
43
|
+
if unreliable == nil then
|
|
44
|
+
unreliable = false
|
|
45
|
+
end
|
|
46
|
+
local shouldSendTo = function(p)
|
|
47
|
+
local _player = player
|
|
48
|
+
local _result
|
|
49
|
+
if typeof(_player) == "Instance" then
|
|
50
|
+
_result = p ~= player
|
|
51
|
+
else
|
|
52
|
+
local _player_1 = player
|
|
53
|
+
local _p = p
|
|
54
|
+
_result = not (table.find(_player_1, _p) ~= nil)
|
|
55
|
+
end
|
|
56
|
+
return _result
|
|
57
|
+
end
|
|
58
|
+
local _self = self
|
|
59
|
+
local _exp = Players:GetPlayers()
|
|
60
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
61
|
+
local _newValue = {}
|
|
62
|
+
local _length = 0
|
|
63
|
+
for _k, _v in _exp do
|
|
64
|
+
if shouldSendTo(_v, _k - 1, _exp) == true then
|
|
65
|
+
_length += 1
|
|
66
|
+
_newValue[_length] = _v
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
70
|
+
_self:emit(_newValue, message, data, unreliable)
|
|
71
|
+
end
|
|
72
|
+
function ClientEmitter:emitAll(message, data, unreliable)
|
|
73
|
+
if unreliable == nil then
|
|
74
|
+
unreliable = false
|
|
75
|
+
end
|
|
76
|
+
if RunService:IsClient() then
|
|
77
|
+
error("[tether::error] Cannot emit message from client to all clients")
|
|
78
|
+
end
|
|
79
|
+
task.spawn(function()
|
|
80
|
+
local _binding = self.master:runClientMiddlewares(message, data)
|
|
81
|
+
local dropRequest = _binding[1]
|
|
82
|
+
local newData = _binding[2]
|
|
83
|
+
if dropRequest then
|
|
84
|
+
return nil
|
|
85
|
+
end
|
|
86
|
+
self.master:queueMessage(true, message, { message, newData, unreliable })
|
|
87
|
+
end)
|
|
88
|
+
end
|
|
89
|
+
function ClientEmitter:setCallback(message, returnMessage, callback)
|
|
90
|
+
if RunService:IsServer() then
|
|
91
|
+
error("[tether::error] Cannot listen to server message from client")
|
|
92
|
+
end
|
|
93
|
+
return self:on(message, function(data)
|
|
94
|
+
local returnValue = callback(data)
|
|
95
|
+
-- Defer the response emission to end of frame and swap context to avoid context check issues
|
|
96
|
+
-- task.defer guarantees response is sent by end of current frame, ensuring predictable timing in production
|
|
97
|
+
task.defer(function()
|
|
98
|
+
setLuneContext("client")
|
|
99
|
+
self.master.server:emit(returnMessage, returnValue)
|
|
100
|
+
setLuneContext("both")
|
|
101
|
+
end)
|
|
102
|
+
end)
|
|
103
|
+
end
|
|
104
|
+
function ClientEmitter:invoke(message, returnMessage, player, data, unreliable)
|
|
105
|
+
if unreliable == nil then
|
|
106
|
+
unreliable = false
|
|
107
|
+
end
|
|
108
|
+
if RunService:IsClient() then
|
|
109
|
+
error("[tether::error] Cannot invoke function from client to client")
|
|
110
|
+
end
|
|
111
|
+
local _binding = self.master
|
|
112
|
+
local serverFunctions = _binding.serverFunctions
|
|
113
|
+
local _returnMessage = returnMessage
|
|
114
|
+
if not (serverFunctions[_returnMessage] ~= nil) then
|
|
115
|
+
local _returnMessage_1 = returnMessage
|
|
116
|
+
serverFunctions[_returnMessage_1] = {}
|
|
117
|
+
end
|
|
118
|
+
local _returnMessage_1 = returnMessage
|
|
119
|
+
local functions = serverFunctions[_returnMessage_1]
|
|
120
|
+
local returnValue
|
|
121
|
+
local responseCallback = function(data)
|
|
122
|
+
returnValue = data
|
|
123
|
+
return returnValue
|
|
124
|
+
end
|
|
125
|
+
functions[responseCallback] = true
|
|
126
|
+
self:emit(player, message, data, unreliable)
|
|
127
|
+
return TS.Promise.new(function(resolve, reject)
|
|
128
|
+
-- awful
|
|
129
|
+
local frames = 0
|
|
130
|
+
while true do
|
|
131
|
+
local _condition = returnValue == nil
|
|
132
|
+
if _condition then
|
|
133
|
+
local _original = frames
|
|
134
|
+
frames += 1
|
|
135
|
+
_condition = _original < 400
|
|
136
|
+
end
|
|
137
|
+
if not _condition then
|
|
138
|
+
break
|
|
139
|
+
end
|
|
140
|
+
RunService.Heartbeat:Wait()
|
|
141
|
+
end
|
|
142
|
+
if frames == 400 then
|
|
143
|
+
return reject("[tether::error] Client function timed out (no response)")
|
|
144
|
+
end
|
|
145
|
+
-- clean up the callback after receiving the response
|
|
146
|
+
functions[responseCallback] = nil
|
|
147
|
+
resolve(returnValue)
|
|
148
|
+
end)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
return {
|
|
152
|
+
ClientEmitter = ClientEmitter,
|
|
153
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MessageEmitter } from "./message-emitter";
|
|
2
|
+
import type { BaseMessage, MessageCallback, FunctionMessageCallback } from "../structs";
|
|
3
|
+
type Cleanup = () => void;
|
|
4
|
+
export declare abstract class ContextualEmitter<MessageData> {
|
|
5
|
+
protected readonly master: MessageEmitter<MessageData>;
|
|
6
|
+
abstract readonly context: "client" | "server";
|
|
7
|
+
/**
|
|
8
|
+
* Constructs a new ContextualEmitter.
|
|
9
|
+
* @param master The master emitter to which messages are delegated.
|
|
10
|
+
*/
|
|
11
|
+
constructor(master: MessageEmitter<MessageData>);
|
|
12
|
+
abstract emit<K extends keyof MessageData>(player: (K & BaseMessage) | (Player | Player[]), message?: MessageData[K] | (K & BaseMessage), data?: MessageData[K] | boolean, unreliable?: boolean): void;
|
|
13
|
+
abstract setCallback<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, callback: FunctionMessageCallback<MessageData[K], MessageData[R]>): Cleanup;
|
|
14
|
+
abstract invoke<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, player?: Player | MessageData[K], data?: MessageData[K] | boolean, unreliable?: boolean): Promise<MessageData[R]>;
|
|
15
|
+
/**
|
|
16
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
17
|
+
*/
|
|
18
|
+
on<K extends keyof MessageData>(message: K & BaseMessage, callback: MessageCallback<MessageData[K]>): Cleanup;
|
|
19
|
+
/**
|
|
20
|
+
* Disconnects the callback as soon as it is called for the first time
|
|
21
|
+
*
|
|
22
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
23
|
+
*/
|
|
24
|
+
once<K extends keyof MessageData>(message: K & BaseMessage, callback: MessageCallback<MessageData[K]>): Cleanup;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
|
|
4
|
+
local ContextualEmitter
|
|
5
|
+
do
|
|
6
|
+
ContextualEmitter = {}
|
|
7
|
+
function ContextualEmitter:constructor(master)
|
|
8
|
+
self.master = master
|
|
9
|
+
end
|
|
10
|
+
function ContextualEmitter:on(message, callback)
|
|
11
|
+
local isClient = self.context == "client"
|
|
12
|
+
if RunService:IsClient() and not isClient then
|
|
13
|
+
return error("[tether::error] Cannot listen to server message from client")
|
|
14
|
+
elseif RunService:IsServer() and isClient then
|
|
15
|
+
return error("[tether::error] Cannot listen to client message from server")
|
|
16
|
+
end
|
|
17
|
+
local callbacksMap = if isClient then self.master.clientCallbacks else self.master.serverCallbacks
|
|
18
|
+
local _message = message
|
|
19
|
+
if not (callbacksMap[_message] ~= nil) then
|
|
20
|
+
local _message_1 = message
|
|
21
|
+
callbacksMap[_message_1] = {}
|
|
22
|
+
end
|
|
23
|
+
local _message_1 = message
|
|
24
|
+
local callbacks = callbacksMap[_message_1]
|
|
25
|
+
local _callback = callback
|
|
26
|
+
callbacks[_callback] = true
|
|
27
|
+
local _message_2 = message
|
|
28
|
+
callbacksMap[_message_2] = callbacks
|
|
29
|
+
return function()
|
|
30
|
+
local _callback_1 = callback
|
|
31
|
+
-- ▼ Set.delete ▼
|
|
32
|
+
local _valueExisted = callbacks[_callback_1] ~= nil
|
|
33
|
+
callbacks[_callback_1] = nil
|
|
34
|
+
-- ▲ Set.delete ▲
|
|
35
|
+
return _valueExisted
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
function ContextualEmitter:once(message, callback)
|
|
39
|
+
local destructor
|
|
40
|
+
destructor = self:on(message, function(player, data)
|
|
41
|
+
destructor()
|
|
42
|
+
callback(player, data)
|
|
43
|
+
end)
|
|
44
|
+
return destructor
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
return {
|
|
48
|
+
ContextualEmitter = ContextualEmitter,
|
|
49
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Modding } from "@flamework/core";
|
|
2
|
+
import Destroyable from "@rbxts/destroyable";
|
|
3
|
+
import { MiddlewareProvider } from "../middleware";
|
|
4
|
+
import type { ClientMessageCallback, ServerMessageCallback, BaseMessage, MessageEmitterMetadata } from "../structs";
|
|
5
|
+
import { ServerEmitter } from "./server-emitter";
|
|
6
|
+
import { ClientEmitter } from "./client-emitter";
|
|
7
|
+
interface MessageEmitterOptions<MessageData> {
|
|
8
|
+
readonly batchRemotes: boolean;
|
|
9
|
+
readonly batchRate: number;
|
|
10
|
+
readonly doNotBatch: Set<keyof MessageData>;
|
|
11
|
+
}
|
|
12
|
+
export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
13
|
+
private readonly options;
|
|
14
|
+
readonly server: ServerEmitter<MessageData>;
|
|
15
|
+
readonly client: ClientEmitter<MessageData>;
|
|
16
|
+
readonly middleware: MiddlewareProvider<MessageData>;
|
|
17
|
+
/** @hidden */ clientCallbacks: Map<keyof MessageData, Set<ClientMessageCallback>>;
|
|
18
|
+
/** @hidden */ clientFunctions: Map<keyof MessageData, Set<(data: unknown) => void>>;
|
|
19
|
+
/** @hidden */ serverCallbacks: Map<keyof MessageData, Set<ServerMessageCallback>>;
|
|
20
|
+
/** @hidden */ serverFunctions: Map<keyof MessageData, Set<(data: unknown) => void>>;
|
|
21
|
+
private readonly guards;
|
|
22
|
+
private serializers;
|
|
23
|
+
private serverQueue;
|
|
24
|
+
private clientBroadcastQueue;
|
|
25
|
+
private clientQueue;
|
|
26
|
+
/** @metadata macro */
|
|
27
|
+
static create<MessageData>(options?: Partial<MessageEmitterOptions<MessageData>>, meta?: Modding.Many<MessageEmitterMetadata<MessageData>>): MessageEmitter<MessageData>;
|
|
28
|
+
private constructor();
|
|
29
|
+
/** @hidden */
|
|
30
|
+
queueMessage<K extends keyof MessageData>(context: "client" | "server" | true, message: K & BaseMessage, data: (MessageEmitter<MessageData>["clientQueue"] | MessageEmitter<MessageData>["serverQueue"])[number]): void;
|
|
31
|
+
/** @hidden */
|
|
32
|
+
runClientMiddlewares<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], player?: Player | Player[]): [boolean, MessageData[Kind]];
|
|
33
|
+
/** @hidden */
|
|
34
|
+
runServerMiddlewares<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind]): [boolean, MessageData[Kind]];
|
|
35
|
+
/** Set up emitter connections */
|
|
36
|
+
private initialize;
|
|
37
|
+
/** Send all queued data across the network simultaneously */
|
|
38
|
+
private relay;
|
|
39
|
+
private validateData;
|
|
40
|
+
private onRemoteFire;
|
|
41
|
+
private executeFunctions;
|
|
42
|
+
private executeEventCallbacks;
|
|
43
|
+
private shouldBatch;
|
|
44
|
+
private deserializeAndValidate;
|
|
45
|
+
private getPacket;
|
|
46
|
+
/** @metadata macro */
|
|
47
|
+
private addSerializer;
|
|
48
|
+
/** @metadata macro */
|
|
49
|
+
private createMessageSerializer;
|
|
50
|
+
private getSerializer;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -8,23 +8,25 @@ local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyabl
|
|
|
8
8
|
local Object = TS.import(script, TS.getModule(script, "@rbxts", "object-utils"))
|
|
9
9
|
local createSerializer = TS.import(script, TS.getModule(script, "@rbxts", "serio").out).default
|
|
10
10
|
local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out)
|
|
11
|
-
local _middleware = TS.import(script, script.Parent, "middleware")
|
|
11
|
+
local _middleware = TS.import(script, script.Parent.Parent, "middleware")
|
|
12
12
|
local DropRequest = _middleware.DropRequest
|
|
13
13
|
local MiddlewareProvider = _middleware.MiddlewareProvider
|
|
14
|
-
local
|
|
14
|
+
local ServerEmitter = TS.import(script, script.Parent, "server-emitter").ServerEmitter
|
|
15
|
+
local ClientEmitter = TS.import(script, script.Parent, "client-emitter").ClientEmitter
|
|
16
|
+
local _utility = TS.import(script, script.Parent.Parent, "utility")
|
|
17
|
+
local readMessage = _utility.readMessage
|
|
18
|
+
local writeMessage = _utility.writeMessage
|
|
15
19
|
if setLuneContext == nil then
|
|
16
20
|
setLuneContext = function() end
|
|
17
21
|
end
|
|
18
22
|
setLuneContext("both")
|
|
19
|
-
local noServerListen = "[@rbxts/tether]: Cannot listen to server message from client"
|
|
20
|
-
local noClientListen = "[@rbxts/tether]: Cannot listen to client message from server"
|
|
21
|
-
local metaGenerationFailed = "[@rbxts/tether]: Failed to generate message metadata - make sure you have the Flamework transformer and are using Flamework macro-friendly types in your schemas"
|
|
22
23
|
local guardFailed = function(message, data)
|
|
23
|
-
return `[
|
|
24
|
+
return `[tether::warning]: Type validation guard failed for message '{message}' - check your sent data\nSent data: {repr(data)}`
|
|
24
25
|
end
|
|
25
26
|
local defaultMesssageEmitterOptions = {
|
|
26
27
|
batchRemotes = true,
|
|
27
28
|
batchRate = 1 / 24,
|
|
29
|
+
doNotBatch = {},
|
|
28
30
|
}
|
|
29
31
|
local sendMessage
|
|
30
32
|
do
|
|
@@ -66,232 +68,18 @@ do
|
|
|
66
68
|
end
|
|
67
69
|
super.constructor(self)
|
|
68
70
|
self.options = options
|
|
71
|
+
self.server = ServerEmitter.new(self)
|
|
72
|
+
self.client = ClientEmitter.new(self)
|
|
69
73
|
self.middleware = MiddlewareProvider.new()
|
|
70
|
-
self.guards = {}
|
|
71
|
-
self.serializers = {}
|
|
72
74
|
self.clientCallbacks = {}
|
|
73
75
|
self.clientFunctions = {}
|
|
74
76
|
self.serverCallbacks = {}
|
|
75
77
|
self.serverFunctions = {}
|
|
78
|
+
self.guards = {}
|
|
79
|
+
self.serializers = {}
|
|
76
80
|
self.serverQueue = {}
|
|
77
81
|
self.clientBroadcastQueue = {}
|
|
78
82
|
self.clientQueue = {}
|
|
79
|
-
self.server = {
|
|
80
|
-
on = function(message, callback)
|
|
81
|
-
if RunService:IsClient() then
|
|
82
|
-
error(noServerListen)
|
|
83
|
-
end
|
|
84
|
-
return self:on(message, callback, self.serverCallbacks)
|
|
85
|
-
end,
|
|
86
|
-
once = function(message, callback)
|
|
87
|
-
if RunService:IsClient() then
|
|
88
|
-
error(noServerListen)
|
|
89
|
-
end
|
|
90
|
-
return self:once(message, callback, self.serverCallbacks)
|
|
91
|
-
end,
|
|
92
|
-
emit = function(message, data, unreliable)
|
|
93
|
-
if unreliable == nil then
|
|
94
|
-
unreliable = false
|
|
95
|
-
end
|
|
96
|
-
if RunService:IsServer() then
|
|
97
|
-
error("[@rbxts/tether]: Cannot emit message to server from server")
|
|
98
|
-
end
|
|
99
|
-
task.spawn(function()
|
|
100
|
-
local _binding = self:runServerMiddlewares(message, data)
|
|
101
|
-
local dropRequest = _binding[1]
|
|
102
|
-
local newData = _binding[2]
|
|
103
|
-
if dropRequest then
|
|
104
|
-
return nil
|
|
105
|
-
end
|
|
106
|
-
local _serverQueue = self.serverQueue
|
|
107
|
-
local _arg0 = { message, newData, unreliable }
|
|
108
|
-
table.insert(_serverQueue, _arg0)
|
|
109
|
-
if not self.options.batchRemotes then
|
|
110
|
-
self:update()
|
|
111
|
-
end
|
|
112
|
-
end)
|
|
113
|
-
end,
|
|
114
|
-
invoke = TS.async(function(message, returnMessage, data, unreliable)
|
|
115
|
-
if unreliable == nil then
|
|
116
|
-
unreliable = false
|
|
117
|
-
end
|
|
118
|
-
if RunService:IsServer() then
|
|
119
|
-
error("[@rbxts/tether]: Cannot invoke server function from server")
|
|
120
|
-
end
|
|
121
|
-
local _clientFunctions = self.clientFunctions
|
|
122
|
-
local _returnMessage = returnMessage
|
|
123
|
-
if not (_clientFunctions[_returnMessage] ~= nil) then
|
|
124
|
-
local _clientFunctions_1 = self.clientFunctions
|
|
125
|
-
local _returnMessage_1 = returnMessage
|
|
126
|
-
_clientFunctions_1[_returnMessage_1] = {}
|
|
127
|
-
end
|
|
128
|
-
local _clientFunctions_1 = self.clientFunctions
|
|
129
|
-
local _returnMessage_1 = returnMessage
|
|
130
|
-
local functions = _clientFunctions_1[_returnMessage_1]
|
|
131
|
-
local returnValue
|
|
132
|
-
local responseCallback = function(data)
|
|
133
|
-
returnValue = data
|
|
134
|
-
return returnValue
|
|
135
|
-
end
|
|
136
|
-
functions[responseCallback] = true
|
|
137
|
-
self.server.emit(message, data, unreliable)
|
|
138
|
-
while returnValue == nil do
|
|
139
|
-
RunService.Heartbeat:Wait()
|
|
140
|
-
end
|
|
141
|
-
-- Clean up the callback after receiving the response
|
|
142
|
-
functions[responseCallback] = nil
|
|
143
|
-
return returnValue
|
|
144
|
-
end),
|
|
145
|
-
setCallback = function(message, returnMessage, callback)
|
|
146
|
-
if RunService:IsClient() then
|
|
147
|
-
error(noServerListen)
|
|
148
|
-
end
|
|
149
|
-
return self.server.on(message, function(player, data)
|
|
150
|
-
local returnValue = callback(player, data)
|
|
151
|
-
-- Defer the response emission to end of frame and swap context to avoid context check issues
|
|
152
|
-
-- task.defer guarantees response is sent by end of current frame, ensuring predictable timing in production
|
|
153
|
-
task.defer(function()
|
|
154
|
-
setLuneContext("server")
|
|
155
|
-
self.client.emit(player, returnMessage, returnValue)
|
|
156
|
-
setLuneContext("both")
|
|
157
|
-
end)
|
|
158
|
-
end)
|
|
159
|
-
end,
|
|
160
|
-
}
|
|
161
|
-
self.client = {
|
|
162
|
-
on = function(message, callback)
|
|
163
|
-
if RunService:IsServer() then
|
|
164
|
-
error(noClientListen)
|
|
165
|
-
end
|
|
166
|
-
return self:on(message, callback, self.clientCallbacks)
|
|
167
|
-
end,
|
|
168
|
-
once = function(message, callback)
|
|
169
|
-
if RunService:IsServer() then
|
|
170
|
-
error(noClientListen)
|
|
171
|
-
end
|
|
172
|
-
return self:once(message, callback, self.clientCallbacks)
|
|
173
|
-
end,
|
|
174
|
-
emit = function(player, message, data, unreliable)
|
|
175
|
-
if unreliable == nil then
|
|
176
|
-
unreliable = false
|
|
177
|
-
end
|
|
178
|
-
if RunService:IsClient() then
|
|
179
|
-
error("[@rbxts/tether]: Cannot emit message to client from client")
|
|
180
|
-
end
|
|
181
|
-
task.spawn(function()
|
|
182
|
-
local _binding = self:runClientMiddlewares(message, data)
|
|
183
|
-
local dropRequest = _binding[1]
|
|
184
|
-
local newData = _binding[2]
|
|
185
|
-
if dropRequest then
|
|
186
|
-
return nil
|
|
187
|
-
end
|
|
188
|
-
local _clientQueue = self.clientQueue
|
|
189
|
-
local _arg0 = { player, message, newData, unreliable }
|
|
190
|
-
table.insert(_clientQueue, _arg0)
|
|
191
|
-
if not self.options.batchRemotes then
|
|
192
|
-
self:update()
|
|
193
|
-
end
|
|
194
|
-
end)
|
|
195
|
-
end,
|
|
196
|
-
emitExcept = function(player, message, data, unreliable)
|
|
197
|
-
if unreliable == nil then
|
|
198
|
-
unreliable = false
|
|
199
|
-
end
|
|
200
|
-
local shouldSendTo = function(p)
|
|
201
|
-
local _player = player
|
|
202
|
-
local _result
|
|
203
|
-
if typeof(_player) == "Instance" then
|
|
204
|
-
_result = p ~= player
|
|
205
|
-
else
|
|
206
|
-
local _player_1 = player
|
|
207
|
-
local _p = p
|
|
208
|
-
_result = not (table.find(_player_1, _p) ~= nil)
|
|
209
|
-
end
|
|
210
|
-
return _result
|
|
211
|
-
end
|
|
212
|
-
local _client = self.client
|
|
213
|
-
local _exp = Players:GetPlayers()
|
|
214
|
-
-- ▼ ReadonlyArray.filter ▼
|
|
215
|
-
local _newValue = {}
|
|
216
|
-
local _length = 0
|
|
217
|
-
for _k, _v in _exp do
|
|
218
|
-
if shouldSendTo(_v, _k - 1, _exp) == true then
|
|
219
|
-
_length += 1
|
|
220
|
-
_newValue[_length] = _v
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
-- ▲ ReadonlyArray.filter ▲
|
|
224
|
-
_client.emit(_newValue, message, data, unreliable)
|
|
225
|
-
end,
|
|
226
|
-
emitAll = function(message, data, unreliable)
|
|
227
|
-
if unreliable == nil then
|
|
228
|
-
unreliable = false
|
|
229
|
-
end
|
|
230
|
-
if RunService:IsClient() then
|
|
231
|
-
error("[@rbxts/tether]: Cannot emit message to all clients from client")
|
|
232
|
-
end
|
|
233
|
-
task.spawn(function()
|
|
234
|
-
local _binding = self:runClientMiddlewares(message, data)
|
|
235
|
-
local dropRequest = _binding[1]
|
|
236
|
-
local newData = _binding[2]
|
|
237
|
-
if dropRequest then
|
|
238
|
-
return nil
|
|
239
|
-
end
|
|
240
|
-
local _clientBroadcastQueue = self.clientBroadcastQueue
|
|
241
|
-
local _arg0 = { message, newData, unreliable }
|
|
242
|
-
table.insert(_clientBroadcastQueue, _arg0)
|
|
243
|
-
if not self.options.batchRemotes then
|
|
244
|
-
self:update()
|
|
245
|
-
end
|
|
246
|
-
end)
|
|
247
|
-
end,
|
|
248
|
-
invoke = TS.async(function(message, returnMessage, player, data, unreliable)
|
|
249
|
-
if unreliable == nil then
|
|
250
|
-
unreliable = false
|
|
251
|
-
end
|
|
252
|
-
if RunService:IsClient() then
|
|
253
|
-
error("[@rbxts/tether]: Cannot invoke client function from client")
|
|
254
|
-
end
|
|
255
|
-
local _serverFunctions = self.serverFunctions
|
|
256
|
-
local _returnMessage = returnMessage
|
|
257
|
-
if not (_serverFunctions[_returnMessage] ~= nil) then
|
|
258
|
-
local _serverFunctions_1 = self.serverFunctions
|
|
259
|
-
local _returnMessage_1 = returnMessage
|
|
260
|
-
_serverFunctions_1[_returnMessage_1] = {}
|
|
261
|
-
end
|
|
262
|
-
local _serverFunctions_1 = self.serverFunctions
|
|
263
|
-
local _returnMessage_1 = returnMessage
|
|
264
|
-
local functions = _serverFunctions_1[_returnMessage_1]
|
|
265
|
-
local returnValue
|
|
266
|
-
local responseCallback = function(data)
|
|
267
|
-
returnValue = data
|
|
268
|
-
return returnValue
|
|
269
|
-
end
|
|
270
|
-
functions[responseCallback] = true
|
|
271
|
-
self.client.emit(player, message, data, unreliable)
|
|
272
|
-
while returnValue == nil do
|
|
273
|
-
RunService.Heartbeat:Wait()
|
|
274
|
-
end
|
|
275
|
-
-- Clean up the callback after receiving the response
|
|
276
|
-
functions[responseCallback] = nil
|
|
277
|
-
return returnValue
|
|
278
|
-
end),
|
|
279
|
-
setCallback = function(message, returnMessage, callback)
|
|
280
|
-
if RunService:IsServer() then
|
|
281
|
-
error(noClientListen)
|
|
282
|
-
end
|
|
283
|
-
return self.client.on(message, function(data)
|
|
284
|
-
local returnValue = callback(data)
|
|
285
|
-
-- Defer the response emission to end of frame and swap context to avoid context check issues
|
|
286
|
-
-- task.defer guarantees response is sent by end of current frame, ensuring predictable timing in production
|
|
287
|
-
task.defer(function()
|
|
288
|
-
setLuneContext("client")
|
|
289
|
-
self.server.emit(returnMessage, returnValue)
|
|
290
|
-
setLuneContext("both")
|
|
291
|
-
end)
|
|
292
|
-
end)
|
|
293
|
-
end,
|
|
294
|
-
}
|
|
295
83
|
self.trash:add(function()
|
|
296
84
|
self.clientCallbacks = {}
|
|
297
85
|
self.serverCallbacks = {}
|
|
@@ -304,9 +92,10 @@ do
|
|
|
304
92
|
function MessageEmitter:create(options, meta)
|
|
305
93
|
local emitter = MessageEmitter.new(Object.assign({}, defaultMesssageEmitterOptions, options))
|
|
306
94
|
if meta == nil then
|
|
307
|
-
warn(
|
|
95
|
+
warn("[tether::warning] Failed to generate message metadata - make sure you have the Flamework transformer and are using Flamework macro-friendly types in your schemas")
|
|
308
96
|
return emitter:initialize()
|
|
309
97
|
end
|
|
98
|
+
-- https://discord.com/channels/476080952636997633/506983834877689856/1363938149486821577
|
|
310
99
|
for kind, _binding in pairs(meta) do
|
|
311
100
|
local guard = _binding.guard
|
|
312
101
|
local serializerMetadata = _binding.serializerMetadata
|
|
@@ -319,6 +108,87 @@ do
|
|
|
319
108
|
end
|
|
320
109
|
return emitter:initialize()
|
|
321
110
|
end
|
|
111
|
+
function MessageEmitter:queueMessage(context, message, data)
|
|
112
|
+
local queue = if context == "client" then self.clientQueue elseif context == true then self.clientBroadcastQueue else self.serverQueue
|
|
113
|
+
local _data = data
|
|
114
|
+
table.insert(queue, _data)
|
|
115
|
+
if not self:shouldBatch(message) then
|
|
116
|
+
self:relay()
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
function MessageEmitter:runClientMiddlewares(message, data, player)
|
|
120
|
+
if not self:validateData(message, data) then
|
|
121
|
+
return { true, data }
|
|
122
|
+
end
|
|
123
|
+
local players = player or Players:GetPlayers()
|
|
124
|
+
local ctx = {
|
|
125
|
+
message = message,
|
|
126
|
+
data = data,
|
|
127
|
+
getRawData = function()
|
|
128
|
+
return self:getPacket(message, data)
|
|
129
|
+
end,
|
|
130
|
+
}
|
|
131
|
+
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
132
|
+
local result = globalMiddleware(players, ctx)
|
|
133
|
+
if not self:validateData(message, ctx.data, "Invalid data after global client middleware") then
|
|
134
|
+
return { false, ctx.data }
|
|
135
|
+
end
|
|
136
|
+
if result == DropRequest then
|
|
137
|
+
self.middleware:notifyRequestDropped(message, "Global client middleware")
|
|
138
|
+
return { true, ctx.data }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
for _, middleware in self.middleware:getClient(message) do
|
|
142
|
+
local result = middleware(players, ctx)
|
|
143
|
+
if not self:validateData(message, ctx.data, "Invalid data after client middleware") then
|
|
144
|
+
return { false, ctx.data }
|
|
145
|
+
end
|
|
146
|
+
if result == DropRequest then
|
|
147
|
+
self.middleware:notifyRequestDropped(message, "Client middleware")
|
|
148
|
+
return { true, ctx.data }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
if not self:validateData(message, ctx.data) then
|
|
152
|
+
return { true, ctx.data }
|
|
153
|
+
end
|
|
154
|
+
return { false, ctx.data }
|
|
155
|
+
end
|
|
156
|
+
function MessageEmitter:runServerMiddlewares(message, data)
|
|
157
|
+
if not self:validateData(message, data) then
|
|
158
|
+
return { true, data }
|
|
159
|
+
end
|
|
160
|
+
local ctx = {
|
|
161
|
+
message = message,
|
|
162
|
+
data = data,
|
|
163
|
+
getRawData = function()
|
|
164
|
+
return self:getPacket(message, data)
|
|
165
|
+
end,
|
|
166
|
+
}
|
|
167
|
+
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
168
|
+
if not self:validateData(message, ctx.data, "Invalid data after global server middleware") then
|
|
169
|
+
return { false, ctx.data }
|
|
170
|
+
end
|
|
171
|
+
local result = globalMiddleware(ctx)
|
|
172
|
+
if result == DropRequest then
|
|
173
|
+
self.middleware:notifyRequestDropped(message, "Global server middleware")
|
|
174
|
+
return { true, ctx.data }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
for _, middleware in self.middleware:getServer(message) do
|
|
178
|
+
if not self:validateData(message, ctx.data, "Invalid data after server middleware") then
|
|
179
|
+
return { false, ctx.data }
|
|
180
|
+
end
|
|
181
|
+
local result = middleware(ctx)
|
|
182
|
+
if result == DropRequest then
|
|
183
|
+
self.middleware:notifyRequestDropped(message, "Server middleware")
|
|
184
|
+
return { true, ctx.data }
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
if not self:validateData(message, ctx.data) then
|
|
188
|
+
return { true, ctx.data }
|
|
189
|
+
end
|
|
190
|
+
return { false, ctx.data }
|
|
191
|
+
end
|
|
322
192
|
function MessageEmitter:initialize()
|
|
323
193
|
setLuneContext("client")
|
|
324
194
|
if RunService:IsClient() then
|
|
@@ -355,11 +225,11 @@ do
|
|
|
355
225
|
return nil
|
|
356
226
|
end
|
|
357
227
|
elapsed -= batchRate
|
|
358
|
-
self:
|
|
228
|
+
self:relay()
|
|
359
229
|
end))
|
|
360
230
|
return self
|
|
361
231
|
end
|
|
362
|
-
function MessageEmitter:
|
|
232
|
+
function MessageEmitter:relay()
|
|
363
233
|
local getPacket = function(info)
|
|
364
234
|
return info.packet
|
|
365
235
|
end
|
|
@@ -590,79 +460,6 @@ do
|
|
|
590
460
|
self.clientQueue = {}
|
|
591
461
|
end
|
|
592
462
|
end
|
|
593
|
-
function MessageEmitter:runClientMiddlewares(message, data, player)
|
|
594
|
-
if not self:validateData(message, data) then
|
|
595
|
-
return { true, data }
|
|
596
|
-
end
|
|
597
|
-
local players = player or Players:GetPlayers()
|
|
598
|
-
local ctx = {
|
|
599
|
-
message = message,
|
|
600
|
-
data = data,
|
|
601
|
-
getRawData = function()
|
|
602
|
-
return self:getPacket(message, data)
|
|
603
|
-
end,
|
|
604
|
-
}
|
|
605
|
-
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
606
|
-
local result = globalMiddleware(players, ctx)
|
|
607
|
-
if not self:validateData(message, ctx.data, "Invalid data after global client middleware") then
|
|
608
|
-
return { false, ctx.data }
|
|
609
|
-
end
|
|
610
|
-
if result == DropRequest then
|
|
611
|
-
self.middleware:notifyRequestDropped(message, "Global client middleware")
|
|
612
|
-
return { true, ctx.data }
|
|
613
|
-
end
|
|
614
|
-
end
|
|
615
|
-
for _, middleware in self.middleware:getClient(message) do
|
|
616
|
-
local result = middleware(players, ctx)
|
|
617
|
-
if not self:validateData(message, ctx.data, "Invalid data after client middleware") then
|
|
618
|
-
return { false, ctx.data }
|
|
619
|
-
end
|
|
620
|
-
if result == DropRequest then
|
|
621
|
-
self.middleware:notifyRequestDropped(message, "Client middleware")
|
|
622
|
-
return { true, ctx.data }
|
|
623
|
-
end
|
|
624
|
-
end
|
|
625
|
-
if not self:validateData(message, ctx.data) then
|
|
626
|
-
return { true, ctx.data }
|
|
627
|
-
end
|
|
628
|
-
return { false, ctx.data }
|
|
629
|
-
end
|
|
630
|
-
function MessageEmitter:runServerMiddlewares(message, data)
|
|
631
|
-
if not self:validateData(message, data) then
|
|
632
|
-
return { true, data }
|
|
633
|
-
end
|
|
634
|
-
local ctx = {
|
|
635
|
-
message = message,
|
|
636
|
-
data = data,
|
|
637
|
-
getRawData = function()
|
|
638
|
-
return self:getPacket(message, data)
|
|
639
|
-
end,
|
|
640
|
-
}
|
|
641
|
-
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
642
|
-
if not self:validateData(message, ctx.data, "Invalid data after global server middleware") then
|
|
643
|
-
return { false, ctx.data }
|
|
644
|
-
end
|
|
645
|
-
local result = globalMiddleware(ctx)
|
|
646
|
-
if result == DropRequest then
|
|
647
|
-
self.middleware:notifyRequestDropped(message, "Global server middleware")
|
|
648
|
-
return { true, ctx.data }
|
|
649
|
-
end
|
|
650
|
-
end
|
|
651
|
-
for _, middleware in self.middleware:getServer(message) do
|
|
652
|
-
if not self:validateData(message, ctx.data, "Invalid data after server middleware") then
|
|
653
|
-
return { false, ctx.data }
|
|
654
|
-
end
|
|
655
|
-
local result = middleware(ctx)
|
|
656
|
-
if result == DropRequest then
|
|
657
|
-
self.middleware:notifyRequestDropped(message, "Server middleware")
|
|
658
|
-
return { true, ctx.data }
|
|
659
|
-
end
|
|
660
|
-
end
|
|
661
|
-
if not self:validateData(message, ctx.data) then
|
|
662
|
-
return { true, ctx.data }
|
|
663
|
-
end
|
|
664
|
-
return { false, ctx.data }
|
|
665
|
-
end
|
|
666
463
|
function MessageEmitter:validateData(message, data, requestDropReason)
|
|
667
464
|
if requestDropReason == nil then
|
|
668
465
|
requestDropReason = "Invalid data"
|
|
@@ -680,9 +477,9 @@ do
|
|
|
680
477
|
function MessageEmitter:onRemoteFire(isServer, serializedPackets, player)
|
|
681
478
|
for _, packet in serializedPackets do
|
|
682
479
|
if buffer.len(packet.messageBuf) > 1 then
|
|
683
|
-
return warn("[
|
|
480
|
+
return warn("[tether::warning] Rejected packet because message buffer was larger than one byte")
|
|
684
481
|
end
|
|
685
|
-
local message =
|
|
482
|
+
local message = readMessage(packet)
|
|
686
483
|
self:executeEventCallbacks(isServer, message, packet, player)
|
|
687
484
|
self:executeFunctions(isServer, message, packet)
|
|
688
485
|
end
|
|
@@ -715,6 +512,15 @@ do
|
|
|
715
512
|
end
|
|
716
513
|
end
|
|
717
514
|
end
|
|
515
|
+
function MessageEmitter:shouldBatch(message)
|
|
516
|
+
local _condition = self.options.batchRemotes
|
|
517
|
+
if _condition then
|
|
518
|
+
local _doNotBatch = self.options.doNotBatch
|
|
519
|
+
local _message = message
|
|
520
|
+
_condition = not (_doNotBatch[_message] ~= nil)
|
|
521
|
+
end
|
|
522
|
+
return _condition
|
|
523
|
+
end
|
|
718
524
|
function MessageEmitter:deserializeAndValidate(message, serializedPacket)
|
|
719
525
|
local serializer = self:getSerializer(message)
|
|
720
526
|
local _packet = serializer
|
|
@@ -725,43 +531,10 @@ do
|
|
|
725
531
|
self:validateData(message, packet)
|
|
726
532
|
return packet
|
|
727
533
|
end
|
|
728
|
-
function MessageEmitter:once(message, callback, callbacksMap)
|
|
729
|
-
local destructor
|
|
730
|
-
destructor = self:on(message, function(player, data)
|
|
731
|
-
destructor()
|
|
732
|
-
callback(player, data)
|
|
733
|
-
end, callbacksMap)
|
|
734
|
-
return destructor
|
|
735
|
-
end
|
|
736
|
-
function MessageEmitter:on(message, callback, callbacksMap)
|
|
737
|
-
local _callbacksMap = callbacksMap
|
|
738
|
-
local _message = message
|
|
739
|
-
if not (_callbacksMap[_message] ~= nil) then
|
|
740
|
-
local _callbacksMap_1 = callbacksMap
|
|
741
|
-
local _message_1 = message
|
|
742
|
-
_callbacksMap_1[_message_1] = {}
|
|
743
|
-
end
|
|
744
|
-
local _callbacksMap_1 = callbacksMap
|
|
745
|
-
local _message_1 = message
|
|
746
|
-
local callbacks = _callbacksMap_1[_message_1]
|
|
747
|
-
local _callback = callback
|
|
748
|
-
callbacks[_callback] = true
|
|
749
|
-
local _callbacksMap_2 = callbacksMap
|
|
750
|
-
local _message_2 = message
|
|
751
|
-
_callbacksMap_2[_message_2] = callbacks
|
|
752
|
-
return function()
|
|
753
|
-
local _callback_1 = callback
|
|
754
|
-
-- ▼ Set.delete ▼
|
|
755
|
-
local _valueExisted = callbacks[_callback_1] ~= nil
|
|
756
|
-
callbacks[_callback_1] = nil
|
|
757
|
-
-- ▲ Set.delete ▲
|
|
758
|
-
return _valueExisted
|
|
759
|
-
end
|
|
760
|
-
end
|
|
761
534
|
function MessageEmitter:getPacket(message, data)
|
|
762
535
|
local serializer = self:getSerializer(message)
|
|
763
536
|
local messageBuf = buffer.create(1)
|
|
764
|
-
|
|
537
|
+
writeMessage(messageBuf, message)
|
|
765
538
|
if serializer == nil then
|
|
766
539
|
return {
|
|
767
540
|
messageBuf = messageBuf,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ContextualEmitter } from "./contextual-emitter";
|
|
2
|
+
import type { BaseMessage, ServerMessageCallback, ServerFunctionMessageCallback } from "../structs";
|
|
3
|
+
export declare class ServerEmitter<MessageData> extends ContextualEmitter<MessageData> {
|
|
4
|
+
readonly context = "server";
|
|
5
|
+
readonly on: <K extends keyof MessageData>(this: ServerEmitter<MessageData>, message: K & BaseMessage, callback: ServerMessageCallback<MessageData[K]>) => () => void;
|
|
6
|
+
readonly once: <K extends keyof MessageData>(this: ServerEmitter<MessageData>, message: K & BaseMessage, callback: ServerMessageCallback<MessageData[K]>) => () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Emits a message to the server
|
|
9
|
+
*
|
|
10
|
+
* @param message The message kind to be sent
|
|
11
|
+
* @param data The data associated with the message
|
|
12
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
13
|
+
*/
|
|
14
|
+
emit<K extends keyof MessageData>(message: K & BaseMessage, data?: MessageData[K], unreliable?: boolean): void;
|
|
15
|
+
/**
|
|
16
|
+
* Sets a callback for a simulated remote function
|
|
17
|
+
*
|
|
18
|
+
* @returns A destructor function that disconnects the callback from the message
|
|
19
|
+
*/
|
|
20
|
+
setCallback<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, callback: ServerFunctionMessageCallback<MessageData[K], MessageData[R]>): () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Simulates a remote function invocation
|
|
23
|
+
*
|
|
24
|
+
* @param message The message kind to be sent
|
|
25
|
+
* @param returnMessage The message kind to be returned
|
|
26
|
+
* @param data The data associated with the message
|
|
27
|
+
* @param unreliable Whether the message should be sent unreliably
|
|
28
|
+
*/
|
|
29
|
+
invoke<K extends keyof MessageData, R extends keyof MessageData>(message: K & BaseMessage, returnMessage: R & BaseMessage, data?: MessageData[K], unreliable?: boolean): Promise<MessageData[R]>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
|
|
4
|
+
local ContextualEmitter = TS.import(script, script.Parent, "contextual-emitter").ContextualEmitter
|
|
5
|
+
local ServerEmitter
|
|
6
|
+
do
|
|
7
|
+
local super = ContextualEmitter
|
|
8
|
+
ServerEmitter = setmetatable({}, {
|
|
9
|
+
__tostring = function()
|
|
10
|
+
return "ServerEmitter"
|
|
11
|
+
end,
|
|
12
|
+
__index = super,
|
|
13
|
+
})
|
|
14
|
+
ServerEmitter.__index = ServerEmitter
|
|
15
|
+
function ServerEmitter.new(...)
|
|
16
|
+
local self = setmetatable({}, ServerEmitter)
|
|
17
|
+
return self:constructor(...) or self
|
|
18
|
+
end
|
|
19
|
+
function ServerEmitter:constructor(...)
|
|
20
|
+
super.constructor(self, ...)
|
|
21
|
+
self.context = "server"
|
|
22
|
+
end
|
|
23
|
+
function ServerEmitter:emit(message, data, unreliable)
|
|
24
|
+
if unreliable == nil then
|
|
25
|
+
unreliable = false
|
|
26
|
+
end
|
|
27
|
+
if RunService:IsServer() then
|
|
28
|
+
error("[tether::error] Cannot emit message from server to server")
|
|
29
|
+
end
|
|
30
|
+
task.spawn(function()
|
|
31
|
+
local _binding = self.master:runServerMiddlewares(message, data)
|
|
32
|
+
local dropRequest = _binding[1]
|
|
33
|
+
local newData = _binding[2]
|
|
34
|
+
if dropRequest then
|
|
35
|
+
return nil
|
|
36
|
+
end
|
|
37
|
+
self.master:queueMessage(self.context, message, { message, newData, unreliable })
|
|
38
|
+
end)
|
|
39
|
+
end
|
|
40
|
+
function ServerEmitter:setCallback(message, returnMessage, callback)
|
|
41
|
+
if RunService:IsClient() then
|
|
42
|
+
error("[tether::error] Cannot listen to client message from server")
|
|
43
|
+
end
|
|
44
|
+
return self:on(message, function(player, data)
|
|
45
|
+
local returnValue = callback(player, data)
|
|
46
|
+
-- Defer the response emission to end of frame and swap context to avoid context check issues
|
|
47
|
+
-- task.defer guarantees response is sent by end of current frame, ensuring predictable timing in production
|
|
48
|
+
task.defer(function()
|
|
49
|
+
setLuneContext("server")
|
|
50
|
+
self.master.client:emit(player, returnMessage, returnValue)
|
|
51
|
+
setLuneContext("both")
|
|
52
|
+
end)
|
|
53
|
+
end)
|
|
54
|
+
end
|
|
55
|
+
function ServerEmitter:invoke(message, returnMessage, data, unreliable)
|
|
56
|
+
if unreliable == nil then
|
|
57
|
+
unreliable = false
|
|
58
|
+
end
|
|
59
|
+
if RunService:IsServer() then
|
|
60
|
+
error("[tether::error] Cannot invoke function from server to server")
|
|
61
|
+
end
|
|
62
|
+
local _binding = self.master
|
|
63
|
+
local clientFunctions = _binding.clientFunctions
|
|
64
|
+
local _returnMessage = returnMessage
|
|
65
|
+
if not (clientFunctions[_returnMessage] ~= nil) then
|
|
66
|
+
local _returnMessage_1 = returnMessage
|
|
67
|
+
clientFunctions[_returnMessage_1] = {}
|
|
68
|
+
end
|
|
69
|
+
local _returnMessage_1 = returnMessage
|
|
70
|
+
local functions = clientFunctions[_returnMessage_1]
|
|
71
|
+
local returnValue
|
|
72
|
+
local responseCallback = function(data)
|
|
73
|
+
returnValue = data
|
|
74
|
+
return returnValue
|
|
75
|
+
end
|
|
76
|
+
functions[responseCallback] = true
|
|
77
|
+
self:emit(message, data, unreliable)
|
|
78
|
+
return TS.Promise.new(function(resolve, reject)
|
|
79
|
+
-- awful
|
|
80
|
+
local frames = 0
|
|
81
|
+
while true do
|
|
82
|
+
local _condition = returnValue == nil
|
|
83
|
+
if _condition then
|
|
84
|
+
local _original = frames
|
|
85
|
+
frames += 1
|
|
86
|
+
_condition = _original < 400
|
|
87
|
+
end
|
|
88
|
+
if not _condition then
|
|
89
|
+
break
|
|
90
|
+
end
|
|
91
|
+
RunService.Heartbeat:Wait()
|
|
92
|
+
end
|
|
93
|
+
if frames == 400 then
|
|
94
|
+
return reject("[tether::error] Server function timed out (no response)")
|
|
95
|
+
end
|
|
96
|
+
-- clean up the callback after receiving the response
|
|
97
|
+
functions[responseCallback] = nil
|
|
98
|
+
resolve(returnValue)
|
|
99
|
+
end)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
return {
|
|
103
|
+
ServerEmitter = ServerEmitter,
|
|
104
|
+
}
|
package/out/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { MessageEmitter } from "./message-emitter";
|
|
1
|
+
export { MessageEmitter } from "./emitters/message-emitter";
|
|
2
2
|
export { BuiltinMiddlewares } from "./builtin-middlewares";
|
|
3
3
|
export { DropRequest, type ClientMiddleware, type ServerMiddleware, type SharedMiddleware, type Middleware, type MiddlewareContext } from "./middleware";
|
package/out/init.luau
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
local exports = {}
|
|
4
|
-
exports.MessageEmitter = TS.import(script, script, "message-emitter").MessageEmitter
|
|
4
|
+
exports.MessageEmitter = TS.import(script, script, "emitters", "message-emitter").MessageEmitter
|
|
5
5
|
exports.BuiltinMiddlewares = TS.import(script, script, "builtin-middlewares").BuiltinMiddlewares
|
|
6
6
|
exports.DropRequest = TS.import(script, script, "middleware").DropRequest
|
|
7
7
|
return exports
|
package/out/logging.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const enum Error {
|
|
2
|
+
NoServerListen = "[tether::error] Cannot listen to server message from client",
|
|
3
|
+
NoClientListen = "[tether::error] Cannot listen to client message from server",
|
|
4
|
+
NoServerToServer = "[tether::error] Cannot emit message from server to server",
|
|
5
|
+
NoClientToClient = "[tether::error] Cannot emit message from client to client",
|
|
6
|
+
NoClientToAllClients = "[tether::error] Cannot emit message from client to all clients",
|
|
7
|
+
NoServerToServerFunction = "[tether::error] Cannot invoke function from server to server",
|
|
8
|
+
NoClientToClientFunction = "[tether::error] Cannot invoke function from client to client",
|
|
9
|
+
ServerFunctionTimeout = "[tether::error] Server function timed out (no response)",
|
|
10
|
+
ClientFunctionTimeout = "[tether::error] Client function timed out (no response)"
|
|
11
|
+
}
|
|
12
|
+
export declare const enum Warning {
|
|
13
|
+
MessageBufferTooLong = "[tether::warning] Rejected packet because message buffer was larger than one byte",
|
|
14
|
+
MetaGenerationFailed = "[tether::warning] Failed to generate message metadata - make sure you have the Flamework transformer and are using Flamework macro-friendly types in your schemas"
|
|
15
|
+
}
|
package/out/logging.luau
ADDED
package/out/structs.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Modding } from "@flamework/core";
|
|
2
2
|
import type { SerializerMetadata, SerializedData, Transform, Vector, String, u8, u16, u24, u32, i8, i16, i24, i32, f16, f24, f32, f64 } from "@rbxts/serio";
|
|
3
3
|
export type MessageCallback<T = unknown> = ServerMessageCallback<T> | ClientMessageCallback<T>;
|
|
4
|
+
export type FunctionMessageCallback<T = unknown, R = unknown> = ServerFunctionMessageCallback<T, R> | ClientFunctionMessageCallback<T, R>;
|
|
4
5
|
export type ClientMessageCallback<T = unknown> = (data: T) => void;
|
|
5
|
-
export type
|
|
6
|
+
export type ClientFunctionMessageCallback<T = unknown, R = unknown> = (data: T) => R;
|
|
6
7
|
export type ServerMessageCallback<T = unknown> = (player: Player, data: T) => void;
|
|
7
|
-
export type
|
|
8
|
+
export type ServerFunctionMessageCallback<T = unknown, R = unknown> = (player: Player, data: T) => R;
|
|
8
9
|
export type BaseMessage = number;
|
|
9
10
|
export interface PacketInfo {
|
|
10
11
|
readonly packet: SerializedPacket;
|
package/out/utility.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
+
import type { BaseMessage, SerializedPacket } from "./structs";
|
|
1
2
|
export declare function bufferToString(buf?: buffer): string;
|
|
3
|
+
export declare function encodeMessage(message: BaseMessage): number;
|
|
4
|
+
export declare function decodeMessage(encoded: number): BaseMessage;
|
|
5
|
+
export declare function writeMessage(buf: buffer, message: BaseMessage): void;
|
|
6
|
+
export declare function readMessage(packet: SerializedPacket | buffer): BaseMessage;
|
package/out/utility.luau
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local COEFF = 0xFA
|
|
2
3
|
local function bufferToString(buf)
|
|
3
4
|
local s = { "{ " }
|
|
4
5
|
if buf ~= nil then
|
|
@@ -25,6 +26,28 @@ local function bufferToString(buf)
|
|
|
25
26
|
table.insert(s, "}")
|
|
26
27
|
return table.concat(s, "")
|
|
27
28
|
end
|
|
29
|
+
local function encodeMessage(message)
|
|
30
|
+
message = bit32.band((bit32.bxor(message, COEFF)), 0xFF)
|
|
31
|
+
return bit32.bor((bit32.lshift(message, 3)), (bit32.arshift(message, 5)))
|
|
32
|
+
end
|
|
33
|
+
local function decodeMessage(encoded)
|
|
34
|
+
encoded = bit32.bor((bit32.arshift(encoded, 3)), (bit32.lshift(encoded, 5)))
|
|
35
|
+
return bit32.band((bit32.bxor(encoded, COEFF)), 0xFF)
|
|
36
|
+
end
|
|
37
|
+
local function writeMessage(buf, message)
|
|
38
|
+
local _arg0 = buffer.len(buf) == 1
|
|
39
|
+
assert(_arg0)
|
|
40
|
+
buffer.writeu8(buf, 0, encodeMessage(message))
|
|
41
|
+
end
|
|
42
|
+
local function readMessage(packet)
|
|
43
|
+
local _packet = packet
|
|
44
|
+
local buf = if type(_packet) == "buffer" then packet else packet.messageBuf
|
|
45
|
+
return decodeMessage(buffer.readu8(buf, 0))
|
|
46
|
+
end
|
|
28
47
|
return {
|
|
29
48
|
bufferToString = bufferToString,
|
|
49
|
+
encodeMessage = encodeMessage,
|
|
50
|
+
decodeMessage = decodeMessage,
|
|
51
|
+
writeMessage = writeMessage,
|
|
52
|
+
readMessage = readMessage,
|
|
30
53
|
}
|
package/package.json
CHANGED
package/out/message-emitter.d.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { Modding } from "@flamework/core";
|
|
2
|
-
import Destroyable from "@rbxts/destroyable";
|
|
3
|
-
import { MiddlewareProvider } from "./middleware";
|
|
4
|
-
import type { ClientMessageCallback, ServerMessageCallback, BaseMessage, MessageEmitterMetadata, ClientMessageFunctionCallback, ServerMessageFunctionCallback } from "./structs";
|
|
5
|
-
interface MessageEmitterOptions {
|
|
6
|
-
readonly batchRemotes: boolean;
|
|
7
|
-
readonly batchRate: number;
|
|
8
|
-
}
|
|
9
|
-
export declare class MessageEmitter<MessageData> extends Destroyable {
|
|
10
|
-
private readonly options;
|
|
11
|
-
readonly middleware: MiddlewareProvider<MessageData>;
|
|
12
|
-
private readonly guards;
|
|
13
|
-
private serializers;
|
|
14
|
-
private clientCallbacks;
|
|
15
|
-
private clientFunctions;
|
|
16
|
-
private serverCallbacks;
|
|
17
|
-
private serverFunctions;
|
|
18
|
-
private serverQueue;
|
|
19
|
-
private clientBroadcastQueue;
|
|
20
|
-
private clientQueue;
|
|
21
|
-
/** @metadata macro */
|
|
22
|
-
static create<MessageData>(options?: Partial<MessageEmitterOptions>, meta?: Modding.Many<MessageEmitterMetadata<MessageData>>): MessageEmitter<MessageData>;
|
|
23
|
-
private constructor();
|
|
24
|
-
readonly server: {
|
|
25
|
-
/**
|
|
26
|
-
* @returns A destructor function that disconnects the callback from the message
|
|
27
|
-
*/
|
|
28
|
-
on: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ServerMessageCallback<MessageData[Kind]>) => () => void;
|
|
29
|
-
/**
|
|
30
|
-
* Disconnects the callback as soon as it is called for the first time
|
|
31
|
-
*
|
|
32
|
-
* @returns A destructor function that disconnects the callback from the message
|
|
33
|
-
*/
|
|
34
|
-
once: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ServerMessageCallback<MessageData[Kind]>) => () => void;
|
|
35
|
-
/**
|
|
36
|
-
* Emits a message to the server
|
|
37
|
-
*
|
|
38
|
-
* @param message The message kind to be sent
|
|
39
|
-
* @param data The data associated with the message
|
|
40
|
-
* @param unreliable Whether the message should be sent unreliably
|
|
41
|
-
*/
|
|
42
|
-
emit: <Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
|
|
43
|
-
/**
|
|
44
|
-
* Simulates a remote function invocation.
|
|
45
|
-
*
|
|
46
|
-
* @param message The message kind to be sent
|
|
47
|
-
* @param data The data associated with the message
|
|
48
|
-
* @param unreliable Whether the message should be sent unreliably
|
|
49
|
-
*/
|
|
50
|
-
invoke: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => Promise<MessageData[ReturnKind]>;
|
|
51
|
-
/**
|
|
52
|
-
* Sets a callback for a simulated remote function
|
|
53
|
-
*
|
|
54
|
-
* @returns A destructor function that disconnects the callback from the message
|
|
55
|
-
*/
|
|
56
|
-
setCallback: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, callback: ServerMessageFunctionCallback<MessageData[Kind], MessageData[ReturnKind]>) => () => void;
|
|
57
|
-
};
|
|
58
|
-
readonly client: {
|
|
59
|
-
/**
|
|
60
|
-
* @returns A destructor function that disconnects the callback from the message
|
|
61
|
-
*/
|
|
62
|
-
on: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ClientMessageCallback<MessageData[Kind]>) => () => void;
|
|
63
|
-
/**
|
|
64
|
-
* Disconnects the callback as soon as it is called for the first time
|
|
65
|
-
*
|
|
66
|
-
* @returns A destructor function that disconnects the callback from the message
|
|
67
|
-
*/
|
|
68
|
-
once: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ClientMessageCallback<MessageData[Kind]>) => () => void;
|
|
69
|
-
/**
|
|
70
|
-
* Emits a message to a specific client or multiple clients
|
|
71
|
-
*
|
|
72
|
-
* @param player The player(s) to whom the message is sent
|
|
73
|
-
* @param message The message kind to be sent
|
|
74
|
-
* @param data The data associated with the message
|
|
75
|
-
* @param unreliable Whether the message should be sent unreliably
|
|
76
|
-
*/
|
|
77
|
-
emit: <Kind extends keyof MessageData>(player: Player | Player[], message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
|
|
78
|
-
/**
|
|
79
|
-
* Emits a message to all clients except the specified client(s)
|
|
80
|
-
*
|
|
81
|
-
* @param player The player(s) to whom the message is not sent
|
|
82
|
-
* @param message The message kind to be sent
|
|
83
|
-
* @param data The data associated with the message
|
|
84
|
-
* @param unreliable Whether the message should be sent unreliably
|
|
85
|
-
*/
|
|
86
|
-
emitExcept: <Kind extends keyof MessageData>(player: Player | Player[], message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
|
|
87
|
-
/**
|
|
88
|
-
* Emits a message to all connected clients
|
|
89
|
-
*
|
|
90
|
-
* @param message The message kind to be sent
|
|
91
|
-
* @param data The data associated with the message
|
|
92
|
-
* @param unreliable Whether the message should be sent unreliably
|
|
93
|
-
*/
|
|
94
|
-
emitAll: <Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
|
|
95
|
-
/**
|
|
96
|
-
* Simulates a remote function invocation.
|
|
97
|
-
*
|
|
98
|
-
* @param message The message kind to be sent
|
|
99
|
-
* @param data The data associated with the message
|
|
100
|
-
* @param unreliable Whether the message should be sent unreliably
|
|
101
|
-
*/
|
|
102
|
-
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]>;
|
|
103
|
-
/**
|
|
104
|
-
* Sets a callback for a simulated remote function
|
|
105
|
-
*
|
|
106
|
-
* @returns A destructor function that disconnects the callback from the message
|
|
107
|
-
*/
|
|
108
|
-
setCallback: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, callback: ClientMessageFunctionCallback<MessageData[Kind], MessageData[ReturnKind]>) => () => void;
|
|
109
|
-
};
|
|
110
|
-
private initialize;
|
|
111
|
-
private update;
|
|
112
|
-
private runClientMiddlewares;
|
|
113
|
-
private runServerMiddlewares;
|
|
114
|
-
private validateData;
|
|
115
|
-
private onRemoteFire;
|
|
116
|
-
private executeFunctions;
|
|
117
|
-
private executeEventCallbacks;
|
|
118
|
-
private deserializeAndValidate;
|
|
119
|
-
private once;
|
|
120
|
-
private on;
|
|
121
|
-
private getPacket;
|
|
122
|
-
/** @metadata macro */
|
|
123
|
-
private addSerializer;
|
|
124
|
-
/** @metadata macro */
|
|
125
|
-
private createMessageSerializer;
|
|
126
|
-
private getSerializer;
|
|
127
|
-
}
|
|
128
|
-
export {};
|