@rbxts/tether 1.3.24 → 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/builtin-middlewares.luau +2 -2
- 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 -318
- 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 +5 -5
- 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
|
|
|
@@ -96,12 +96,12 @@ do
|
|
|
96
96
|
local schemaString = if schema ~= nil then " " .. table.concat(string.split(repr(schema[1], {
|
|
97
97
|
pretty = true,
|
|
98
98
|
}), "\n"), "\n ") else "unknown"
|
|
99
|
-
local text = { "\n", horizontalLine, "\n", "Packet sent to ", (if RunService:IsServer() then "client" else "server"), "!\n", " - Message: ", message, "\n", " - Data: ", repr(data, {
|
|
99
|
+
local text = { "\n", horizontalLine, "\n", "Packet sent to ", (if RunService:IsServer() then "client" else "server"), "!\n", " - Message: ", tostring(message), "\n", " - Data: ", repr(data, {
|
|
100
100
|
pretty = true,
|
|
101
101
|
}), "\n", " - Raw data:\n", " - Buffer: ", bufferToString(buf), "\n", " - Blobs: ", repr(blobs, {
|
|
102
102
|
pretty = false,
|
|
103
103
|
robloxClassName = true,
|
|
104
|
-
}), "\n", " - Packet size: ", bufferSize + blobsSize, " bytes\n", " - Buffer: ", bufferSize, " bytes\n", " - Blobs: ", blobsSize, " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
|
|
104
|
+
}), "\n", " - Packet size: ", tostring(bufferSize + blobsSize), " bytes\n", " - Buffer: ", tostring(bufferSize), " bytes\n", " - Blobs: ", tostring(blobsSize), " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
|
|
105
105
|
print(table.concat(text, ""))
|
|
106
106
|
end
|
|
107
107
|
end
|
|
@@ -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,214 +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
|
-
functions[function(data)
|
|
133
|
-
returnValue = data
|
|
134
|
-
return returnValue
|
|
135
|
-
end] = true
|
|
136
|
-
self.server.emit(message, data, unreliable)
|
|
137
|
-
while returnValue == nil do
|
|
138
|
-
RunService.Heartbeat:Wait()
|
|
139
|
-
end
|
|
140
|
-
return returnValue
|
|
141
|
-
end),
|
|
142
|
-
setCallback = function(message, returnMessage, callback)
|
|
143
|
-
if RunService:IsClient() then
|
|
144
|
-
error(noServerListen)
|
|
145
|
-
end
|
|
146
|
-
return self.server.on(message, function(player, data)
|
|
147
|
-
local returnValue = callback(player, data)
|
|
148
|
-
self.client.emit(player, returnMessage, returnValue)
|
|
149
|
-
end)
|
|
150
|
-
end,
|
|
151
|
-
}
|
|
152
|
-
self.client = {
|
|
153
|
-
on = function(message, callback)
|
|
154
|
-
if RunService:IsServer() then
|
|
155
|
-
error(noClientListen)
|
|
156
|
-
end
|
|
157
|
-
return self:on(message, callback, self.clientCallbacks)
|
|
158
|
-
end,
|
|
159
|
-
once = function(message, callback)
|
|
160
|
-
if RunService:IsServer() then
|
|
161
|
-
error(noClientListen)
|
|
162
|
-
end
|
|
163
|
-
return self:once(message, callback, self.clientCallbacks)
|
|
164
|
-
end,
|
|
165
|
-
emit = function(player, message, data, unreliable)
|
|
166
|
-
if unreliable == nil then
|
|
167
|
-
unreliable = false
|
|
168
|
-
end
|
|
169
|
-
if RunService:IsClient() then
|
|
170
|
-
error("[@rbxts/tether]: Cannot emit message to client from client")
|
|
171
|
-
end
|
|
172
|
-
task.spawn(function()
|
|
173
|
-
local _binding = self:runClientMiddlewares(message, data)
|
|
174
|
-
local dropRequest = _binding[1]
|
|
175
|
-
local newData = _binding[2]
|
|
176
|
-
if dropRequest then
|
|
177
|
-
return nil
|
|
178
|
-
end
|
|
179
|
-
local _clientQueue = self.clientQueue
|
|
180
|
-
local _arg0 = { player, message, newData, unreliable }
|
|
181
|
-
table.insert(_clientQueue, _arg0)
|
|
182
|
-
if not self.options.batchRemotes then
|
|
183
|
-
self:update()
|
|
184
|
-
end
|
|
185
|
-
end)
|
|
186
|
-
end,
|
|
187
|
-
emitExcept = function(player, message, data, unreliable)
|
|
188
|
-
if unreliable == nil then
|
|
189
|
-
unreliable = false
|
|
190
|
-
end
|
|
191
|
-
local shouldSendTo = function(p)
|
|
192
|
-
local _player = player
|
|
193
|
-
local _result
|
|
194
|
-
if typeof(_player) == "Instance" then
|
|
195
|
-
_result = p ~= player
|
|
196
|
-
else
|
|
197
|
-
local _player_1 = player
|
|
198
|
-
local _p = p
|
|
199
|
-
_result = not (table.find(_player_1, _p) ~= nil)
|
|
200
|
-
end
|
|
201
|
-
return _result
|
|
202
|
-
end
|
|
203
|
-
local _client = self.client
|
|
204
|
-
local _exp = Players:GetPlayers()
|
|
205
|
-
-- ▼ ReadonlyArray.filter ▼
|
|
206
|
-
local _newValue = {}
|
|
207
|
-
local _length = 0
|
|
208
|
-
for _k, _v in _exp do
|
|
209
|
-
if shouldSendTo(_v, _k - 1, _exp) == true then
|
|
210
|
-
_length += 1
|
|
211
|
-
_newValue[_length] = _v
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
-- ▲ ReadonlyArray.filter ▲
|
|
215
|
-
_client.emit(_newValue, message, data, unreliable)
|
|
216
|
-
end,
|
|
217
|
-
emitAll = function(message, data, unreliable)
|
|
218
|
-
if unreliable == nil then
|
|
219
|
-
unreliable = false
|
|
220
|
-
end
|
|
221
|
-
if RunService:IsClient() then
|
|
222
|
-
error("[@rbxts/tether]: Cannot emit message to all clients from client")
|
|
223
|
-
end
|
|
224
|
-
task.spawn(function()
|
|
225
|
-
local _binding = self:runClientMiddlewares(message, data)
|
|
226
|
-
local dropRequest = _binding[1]
|
|
227
|
-
local newData = _binding[2]
|
|
228
|
-
if dropRequest then
|
|
229
|
-
return nil
|
|
230
|
-
end
|
|
231
|
-
local _clientBroadcastQueue = self.clientBroadcastQueue
|
|
232
|
-
local _arg0 = { message, newData, unreliable }
|
|
233
|
-
table.insert(_clientBroadcastQueue, _arg0)
|
|
234
|
-
if not self.options.batchRemotes then
|
|
235
|
-
self:update()
|
|
236
|
-
end
|
|
237
|
-
end)
|
|
238
|
-
end,
|
|
239
|
-
invoke = TS.async(function(message, returnMessage, player, data, unreliable)
|
|
240
|
-
if unreliable == nil then
|
|
241
|
-
unreliable = false
|
|
242
|
-
end
|
|
243
|
-
if RunService:IsClient() then
|
|
244
|
-
error("[@rbxts/tether]: Cannot invoke client function from client")
|
|
245
|
-
end
|
|
246
|
-
local _serverFunctions = self.serverFunctions
|
|
247
|
-
local _returnMessage = returnMessage
|
|
248
|
-
if not (_serverFunctions[_returnMessage] ~= nil) then
|
|
249
|
-
local _serverFunctions_1 = self.serverFunctions
|
|
250
|
-
local _returnMessage_1 = returnMessage
|
|
251
|
-
_serverFunctions_1[_returnMessage_1] = {}
|
|
252
|
-
end
|
|
253
|
-
local _serverFunctions_1 = self.serverFunctions
|
|
254
|
-
local _returnMessage_1 = returnMessage
|
|
255
|
-
local functions = _serverFunctions_1[_returnMessage_1]
|
|
256
|
-
local returnValue
|
|
257
|
-
functions[function(data)
|
|
258
|
-
returnValue = data
|
|
259
|
-
return returnValue
|
|
260
|
-
end] = true
|
|
261
|
-
self.client.emit(player, message, data, unreliable)
|
|
262
|
-
while returnValue == nil do
|
|
263
|
-
RunService.Heartbeat:Wait()
|
|
264
|
-
end
|
|
265
|
-
return returnValue
|
|
266
|
-
end),
|
|
267
|
-
setCallback = function(message, returnMessage, callback)
|
|
268
|
-
if RunService:IsServer() then
|
|
269
|
-
error(noClientListen)
|
|
270
|
-
end
|
|
271
|
-
return self.client.on(message, function(data)
|
|
272
|
-
local returnValue = callback(data)
|
|
273
|
-
self.server.emit(returnMessage, returnValue)
|
|
274
|
-
end)
|
|
275
|
-
end,
|
|
276
|
-
}
|
|
277
83
|
self.trash:add(function()
|
|
278
84
|
self.clientCallbacks = {}
|
|
279
85
|
self.serverCallbacks = {}
|
|
@@ -286,9 +92,10 @@ do
|
|
|
286
92
|
function MessageEmitter:create(options, meta)
|
|
287
93
|
local emitter = MessageEmitter.new(Object.assign({}, defaultMesssageEmitterOptions, options))
|
|
288
94
|
if meta == nil then
|
|
289
|
-
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")
|
|
290
96
|
return emitter:initialize()
|
|
291
97
|
end
|
|
98
|
+
-- https://discord.com/channels/476080952636997633/506983834877689856/1363938149486821577
|
|
292
99
|
for kind, _binding in pairs(meta) do
|
|
293
100
|
local guard = _binding.guard
|
|
294
101
|
local serializerMetadata = _binding.serializerMetadata
|
|
@@ -301,6 +108,87 @@ do
|
|
|
301
108
|
end
|
|
302
109
|
return emitter:initialize()
|
|
303
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
|
|
304
192
|
function MessageEmitter:initialize()
|
|
305
193
|
setLuneContext("client")
|
|
306
194
|
if RunService:IsClient() then
|
|
@@ -337,11 +225,11 @@ do
|
|
|
337
225
|
return nil
|
|
338
226
|
end
|
|
339
227
|
elapsed -= batchRate
|
|
340
|
-
self:
|
|
228
|
+
self:relay()
|
|
341
229
|
end))
|
|
342
230
|
return self
|
|
343
231
|
end
|
|
344
|
-
function MessageEmitter:
|
|
232
|
+
function MessageEmitter:relay()
|
|
345
233
|
local getPacket = function(info)
|
|
346
234
|
return info.packet
|
|
347
235
|
end
|
|
@@ -572,79 +460,6 @@ do
|
|
|
572
460
|
self.clientQueue = {}
|
|
573
461
|
end
|
|
574
462
|
end
|
|
575
|
-
function MessageEmitter:runClientMiddlewares(message, data, player)
|
|
576
|
-
if not self:validateData(message, data) then
|
|
577
|
-
return { true, data }
|
|
578
|
-
end
|
|
579
|
-
local players = player or Players:GetPlayers()
|
|
580
|
-
local ctx = {
|
|
581
|
-
message = message,
|
|
582
|
-
data = data,
|
|
583
|
-
getRawData = function()
|
|
584
|
-
return self:getPacket(message, data)
|
|
585
|
-
end,
|
|
586
|
-
}
|
|
587
|
-
for _, globalMiddleware in self.middleware:getClientGlobal() do
|
|
588
|
-
local result = globalMiddleware(players, ctx)
|
|
589
|
-
if not self:validateData(message, ctx.data, "Invalid data after global client middleware") then
|
|
590
|
-
return { false, ctx.data }
|
|
591
|
-
end
|
|
592
|
-
if result == DropRequest then
|
|
593
|
-
self.middleware:notifyRequestDropped(message, "Global client middleware")
|
|
594
|
-
return { true, ctx.data }
|
|
595
|
-
end
|
|
596
|
-
end
|
|
597
|
-
for _, middleware in self.middleware:getClient(message) do
|
|
598
|
-
local result = middleware(players, ctx)
|
|
599
|
-
if not self:validateData(message, ctx.data, "Invalid data after client middleware") then
|
|
600
|
-
return { false, ctx.data }
|
|
601
|
-
end
|
|
602
|
-
if result == DropRequest then
|
|
603
|
-
self.middleware:notifyRequestDropped(message, "Client middleware")
|
|
604
|
-
return { true, ctx.data }
|
|
605
|
-
end
|
|
606
|
-
end
|
|
607
|
-
if not self:validateData(message, ctx.data) then
|
|
608
|
-
return { true, ctx.data }
|
|
609
|
-
end
|
|
610
|
-
return { false, ctx.data }
|
|
611
|
-
end
|
|
612
|
-
function MessageEmitter:runServerMiddlewares(message, data)
|
|
613
|
-
if not self:validateData(message, data) then
|
|
614
|
-
return { true, data }
|
|
615
|
-
end
|
|
616
|
-
local ctx = {
|
|
617
|
-
message = message,
|
|
618
|
-
data = data,
|
|
619
|
-
getRawData = function()
|
|
620
|
-
return self:getPacket(message, data)
|
|
621
|
-
end,
|
|
622
|
-
}
|
|
623
|
-
for _, globalMiddleware in self.middleware:getServerGlobal() do
|
|
624
|
-
if not self:validateData(message, ctx.data, "Invalid data after global server middleware") then
|
|
625
|
-
return { false, ctx.data }
|
|
626
|
-
end
|
|
627
|
-
local result = globalMiddleware(ctx)
|
|
628
|
-
if result == DropRequest then
|
|
629
|
-
self.middleware:notifyRequestDropped(message, "Global server middleware")
|
|
630
|
-
return { true, ctx.data }
|
|
631
|
-
end
|
|
632
|
-
end
|
|
633
|
-
for _, middleware in self.middleware:getServer(message) do
|
|
634
|
-
if not self:validateData(message, ctx.data, "Invalid data after server middleware") then
|
|
635
|
-
return { false, ctx.data }
|
|
636
|
-
end
|
|
637
|
-
local result = middleware(ctx)
|
|
638
|
-
if result == DropRequest then
|
|
639
|
-
self.middleware:notifyRequestDropped(message, "Server middleware")
|
|
640
|
-
return { true, ctx.data }
|
|
641
|
-
end
|
|
642
|
-
end
|
|
643
|
-
if not self:validateData(message, ctx.data) then
|
|
644
|
-
return { true, ctx.data }
|
|
645
|
-
end
|
|
646
|
-
return { false, ctx.data }
|
|
647
|
-
end
|
|
648
463
|
function MessageEmitter:validateData(message, data, requestDropReason)
|
|
649
464
|
if requestDropReason == nil then
|
|
650
465
|
requestDropReason = "Invalid data"
|
|
@@ -662,9 +477,9 @@ do
|
|
|
662
477
|
function MessageEmitter:onRemoteFire(isServer, serializedPackets, player)
|
|
663
478
|
for _, packet in serializedPackets do
|
|
664
479
|
if buffer.len(packet.messageBuf) > 1 then
|
|
665
|
-
return warn("[
|
|
480
|
+
return warn("[tether::warning] Rejected packet because message buffer was larger than one byte")
|
|
666
481
|
end
|
|
667
|
-
local message =
|
|
482
|
+
local message = readMessage(packet)
|
|
668
483
|
self:executeEventCallbacks(isServer, message, packet, player)
|
|
669
484
|
self:executeFunctions(isServer, message, packet)
|
|
670
485
|
end
|
|
@@ -697,6 +512,15 @@ do
|
|
|
697
512
|
end
|
|
698
513
|
end
|
|
699
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
|
|
700
524
|
function MessageEmitter:deserializeAndValidate(message, serializedPacket)
|
|
701
525
|
local serializer = self:getSerializer(message)
|
|
702
526
|
local _packet = serializer
|
|
@@ -707,43 +531,10 @@ do
|
|
|
707
531
|
self:validateData(message, packet)
|
|
708
532
|
return packet
|
|
709
533
|
end
|
|
710
|
-
function MessageEmitter:once(message, callback, callbacksMap)
|
|
711
|
-
local destructor
|
|
712
|
-
destructor = self:on(message, function(player, data)
|
|
713
|
-
destructor()
|
|
714
|
-
callback(player, data)
|
|
715
|
-
end, callbacksMap)
|
|
716
|
-
return destructor
|
|
717
|
-
end
|
|
718
|
-
function MessageEmitter:on(message, callback, callbacksMap)
|
|
719
|
-
local _callbacksMap = callbacksMap
|
|
720
|
-
local _message = message
|
|
721
|
-
if not (_callbacksMap[_message] ~= nil) then
|
|
722
|
-
local _callbacksMap_1 = callbacksMap
|
|
723
|
-
local _message_1 = message
|
|
724
|
-
_callbacksMap_1[_message_1] = {}
|
|
725
|
-
end
|
|
726
|
-
local _callbacksMap_1 = callbacksMap
|
|
727
|
-
local _message_1 = message
|
|
728
|
-
local callbacks = _callbacksMap_1[_message_1]
|
|
729
|
-
local _callback = callback
|
|
730
|
-
callbacks[_callback] = true
|
|
731
|
-
local _callbacksMap_2 = callbacksMap
|
|
732
|
-
local _message_2 = message
|
|
733
|
-
_callbacksMap_2[_message_2] = callbacks
|
|
734
|
-
return function()
|
|
735
|
-
local _callback_1 = callback
|
|
736
|
-
-- ▼ Set.delete ▼
|
|
737
|
-
local _valueExisted = callbacks[_callback_1] ~= nil
|
|
738
|
-
callbacks[_callback_1] = nil
|
|
739
|
-
-- ▲ Set.delete ▲
|
|
740
|
-
return _valueExisted
|
|
741
|
-
end
|
|
742
|
-
end
|
|
743
534
|
function MessageEmitter:getPacket(message, data)
|
|
744
535
|
local serializer = self:getSerializer(message)
|
|
745
536
|
local messageBuf = buffer.create(1)
|
|
746
|
-
|
|
537
|
+
writeMessage(messageBuf, message)
|
|
747
538
|
if serializer == nil then
|
|
748
539
|
return {
|
|
749
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/tether",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"main": "out/init.luau",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"setup-rokit": "rokit trust lune-org/lune && rokit trust rojo-rbx/rojo && rokit install",
|
|
@@ -32,20 +32,20 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
35
|
-
"@rbxts/types": "^1.0.
|
|
35
|
+
"@rbxts/types": "^1.0.896",
|
|
36
36
|
"rbxts-transformer-flamework": "^1.3.2",
|
|
37
37
|
"roblox-ts": "^3.0.0",
|
|
38
38
|
"ts-toolbelt": "^9.6.0",
|
|
39
|
-
"typescript": "^5
|
|
39
|
+
"typescript": "^5"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@flamework/core": "^1.3.2",
|
|
43
43
|
"@rbxts/destroyable": "^1.0.9",
|
|
44
|
-
"@rbxts/flamework-meta-utils": "^1.0.
|
|
44
|
+
"@rbxts/flamework-meta-utils": "^1.0.7",
|
|
45
45
|
"@rbxts/object-utils": "^1.0.4",
|
|
46
46
|
"@rbxts/repr": "^1.0.3",
|
|
47
47
|
"@rbxts/serio": "^1.0.19",
|
|
48
|
-
"@rbxts/services": "^1.
|
|
48
|
+
"@rbxts/services": "^1.6.0",
|
|
49
49
|
"@rbxts/trash": "^1.0.8"
|
|
50
50
|
}
|
|
51
51
|
}
|
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 {};
|