@flamework-experimental/networking 2.0.0-alpha.1 → 2.0.0-alpha.3
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 +35 -27
- package/flamework.build +1 -1
- package/out/event/createEvent.d.ts +22 -4
- package/out/event/createEvent.luau +47 -49
- package/out/events/createClientMethod.luau +7 -3
- package/out/events/createGenericHandler.luau +6 -3
- package/out/events/createNetworkingEvent.luau +7 -5
- package/out/events/createServerMethod.d.ts +1 -1
- package/out/events/createServerMethod.luau +8 -3
- package/out/events/types.d.ts +4 -3
- package/out/function/createFunctionReceiver.d.ts +10 -9
- package/out/function/createFunctionReceiver.luau +53 -55
- package/out/function/createFunctionSender.d.ts +3 -2
- package/out/function/createFunctionSender.luau +118 -40
- package/out/functions/createClientMethod.d.ts +1 -1
- package/out/functions/createClientMethod.luau +9 -8
- package/out/functions/createGenericHandler.luau +8 -12
- package/out/functions/createNetworkingFunction.luau +7 -5
- package/out/functions/createServerMethod.d.ts +1 -1
- package/out/functions/createServerMethod.luau +9 -8
- package/out/functions/types.d.ts +10 -12
- package/out/handlers.d.ts +35 -36
- package/out/index.d.ts +6 -0
- package/out/init.luau +6 -0
- package/out/middleware/createGuards.d.ts +10 -0
- package/out/middleware/createGuards.luau +32 -0
- package/out/middleware/processor.d.ts +62 -0
- package/out/middleware/processor.luau +214 -0
- package/out/middleware/types.d.ts +26 -15
- package/out/util/createOnce.d.ts +14 -0
- package/out/util/createOnce.luau +66 -0
- package/out/util/createSignalContainer.d.ts +2 -1
- package/out/util/createSignalContainer.luau +2 -2
- package/out/util/signal.d.ts +27 -0
- package/out/util/signal.luau +135 -0
- package/out/util/trimArguments.d.ts +12 -0
- package/out/util/trimArguments.luau +48 -0
- package/package.json +3 -4
- package/out/middleware/createGuardMiddleware.d.ts +0 -6
- package/out/middleware/createGuardMiddleware.luau +0 -44
- package/out/middleware/createMiddlewareProcessor.d.ts +0 -3
- package/out/middleware/createMiddlewareProcessor.luau +0 -24
- package/out/util/timeoutPromise.d.ts +0 -1
- package/out/util/timeoutPromise.luau +0 -10
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
A signal of networking's own, and the recycled threads it and the receive pipeline run on.
|
|
3
|
+
|
|
4
|
+
Plain Luau rather than TypeScript: roblox-ts turns every `...args` into a table and spreads it with
|
|
5
|
+
`unpack`, which stops at the first nil it happens to land on. Here a vararg list keeps its count
|
|
6
|
+
from the remote to the handler, and nothing is allocated on the way.
|
|
7
|
+
|
|
8
|
+
Arguments are handed over by reference. A BindableEvent would copy them, turning a decoded
|
|
9
|
+
`Map<Instance, ...>` into one keyed by strings and raising on a `Set<boolean>`.
|
|
10
|
+
|
|
11
|
+
Like an engine signal, each handler runs on a thread of its own: one that yields does not hold up
|
|
12
|
+
the others, and one that raises has its error printed while the others still run. The newest
|
|
13
|
+
connection runs first, which is the order the engine runs a BindableEvent's handlers in; one made
|
|
14
|
+
during a fire does not receive it, and one disconnected before its turn is skipped.
|
|
15
|
+
]]
|
|
16
|
+
|
|
17
|
+
--- Threads that finished their last job, waiting in `coroutine.yield()` for the next.
|
|
18
|
+
local idle: { thread } = {}
|
|
19
|
+
|
|
20
|
+
--- How many finished threads are kept. More than a few are only ever needed while handlers yield;
|
|
21
|
+
--- past this, a thread that finishes simply ends.
|
|
22
|
+
local MAX_IDLE = 16
|
|
23
|
+
|
|
24
|
+
local function call(callback: (...any) -> ...any, ...: any)
|
|
25
|
+
callback(...)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
--[[
|
|
29
|
+
The body of a recycled thread: waits for a job, runs it, parks itself, and runs whatever it is
|
|
30
|
+
resumed with next. A job that raises ends the thread, and the error is printed by the scheduler
|
|
31
|
+
that resumed it; a job that yields keeps the thread until it returns, so it is only ever parked
|
|
32
|
+
when idle.
|
|
33
|
+
|
|
34
|
+
It takes no arguments, and every job arrives through `coroutine.yield()`: a job passed as the
|
|
35
|
+
thread's own arguments would stay in this frame's varargs for as long as the thread lives,
|
|
36
|
+
keeping its handler, sender and arguments reachable long after it returned.
|
|
37
|
+
]]
|
|
38
|
+
local function runner()
|
|
39
|
+
local thread = coroutine.running()
|
|
40
|
+
|
|
41
|
+
while true do
|
|
42
|
+
call(coroutine.yield())
|
|
43
|
+
|
|
44
|
+
if #idle >= MAX_IDLE then
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
table.insert(idle, thread)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
--[[
|
|
52
|
+
Runs `callback(...)` at once on a thread of its own, recycling one that finished its last job. An
|
|
53
|
+
idle thread that was killed meanwhile (`task.cancel`) is dropped rather than resumed.
|
|
54
|
+
]]
|
|
55
|
+
local function spawn(callback: (...any) -> ...any, ...: any)
|
|
56
|
+
local count = #idle
|
|
57
|
+
while count > 0 do
|
|
58
|
+
local thread = idle[count]
|
|
59
|
+
idle[count] = nil
|
|
60
|
+
count -= 1
|
|
61
|
+
|
|
62
|
+
if coroutine.status(thread) == "suspended" then
|
|
63
|
+
task.spawn(thread, callback, ...)
|
|
64
|
+
return
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
-- A new thread, started up to its first `coroutine.yield()`, gets its first job the same way.
|
|
69
|
+
local thread = coroutine.create(runner)
|
|
70
|
+
coroutine.resume(thread)
|
|
71
|
+
task.spawn(thread, callback, ...)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
local Connection = {}
|
|
75
|
+
Connection.__index = Connection
|
|
76
|
+
|
|
77
|
+
--- Disconnects the handler. Takes effect at once, even for a fire under way.
|
|
78
|
+
function Connection:Disconnect()
|
|
79
|
+
if not self.Connected then
|
|
80
|
+
return
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
self.Connected = false
|
|
84
|
+
self._callback = nil
|
|
85
|
+
|
|
86
|
+
-- Copied rather than edited in place: a fire under way keeps walking the list it started with.
|
|
87
|
+
local signal = self._signal
|
|
88
|
+
local connections = signal._connections
|
|
89
|
+
local index = table.find(connections, self)
|
|
90
|
+
if index ~= nil then
|
|
91
|
+
local remaining = table.clone(connections)
|
|
92
|
+
table.remove(remaining, index)
|
|
93
|
+
signal._connections = remaining
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
--- The name maids and janitors call.
|
|
98
|
+
Connection.Destroy = Connection.Disconnect
|
|
99
|
+
|
|
100
|
+
local Signal = {}
|
|
101
|
+
Signal.__index = Signal
|
|
102
|
+
|
|
103
|
+
function Signal:Connect(callback: (...any) -> ...any)
|
|
104
|
+
local connection = setmetatable({
|
|
105
|
+
Connected = true,
|
|
106
|
+
_callback = callback,
|
|
107
|
+
_signal = self,
|
|
108
|
+
}, Connection)
|
|
109
|
+
|
|
110
|
+
-- Newest first, copied so that a fire under way does not reach it.
|
|
111
|
+
local connections = self._connections
|
|
112
|
+
local updated = table.create(#connections + 1)
|
|
113
|
+
updated[1] = connection
|
|
114
|
+
table.move(connections, 1, #connections, 2, updated)
|
|
115
|
+
self._connections = updated
|
|
116
|
+
|
|
117
|
+
return connection
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
function Signal:Fire(...: any)
|
|
121
|
+
for _, connection in self._connections do
|
|
122
|
+
if connection.Connected then
|
|
123
|
+
spawn(connection._callback, ...)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
local function createSignal()
|
|
129
|
+
return setmetatable({ _connections = {} }, Signal)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
createSignal = createSignal,
|
|
134
|
+
spawn = spawn,
|
|
135
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `args` cut after its last value, so that spreading it passes every argument.
|
|
3
|
+
*
|
|
4
|
+
* roblox-ts spreads a list as `unpack(list)`, which stops at `#list`. When the last slot is nil, `#`
|
|
5
|
+
* may stop at any earlier gap: `#{1, nil, nil, 4, nil}` can be 1, which drops the 4. A list that ends
|
|
6
|
+
* in a value always spreads whole. The `{ ... }` that roblox-ts rebuilds from that spread at the next
|
|
7
|
+
* hop also ends in a value, and so does every hop after it that passes the list on. So a list is
|
|
8
|
+
* trimmed where it enters, and again wherever code may build its own: a middleware that names its
|
|
9
|
+
* parameters passes on a list that can end in nil. Nothing is lost by trimming: an argument that was
|
|
10
|
+
* not passed reads as nil too.
|
|
11
|
+
*/
|
|
12
|
+
export declare function trimArguments<T extends unknown[]>(args: T): T;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
--[[
|
|
3
|
+
*
|
|
4
|
+
* `args` cut after its last value, so that spreading it passes every argument.
|
|
5
|
+
*
|
|
6
|
+
* roblox-ts spreads a list as `unpack(list)`, which stops at `#list`. When the last slot is nil, `#`
|
|
7
|
+
* may stop at any earlier gap: `#{1, nil, nil, 4, nil}` can be 1, which drops the 4. A list that ends
|
|
8
|
+
* in a value always spreads whole. The `{ ... }` that roblox-ts rebuilds from that spread at the next
|
|
9
|
+
* hop also ends in a value, and so does every hop after it that passes the list on. So a list is
|
|
10
|
+
* trimmed where it enters, and again wherever code may build its own: a middleware that names its
|
|
11
|
+
* parameters passes on a list that can end in nil. Nothing is lost by trimming: an argument that was
|
|
12
|
+
* not passed reads as nil too.
|
|
13
|
+
|
|
14
|
+
]]
|
|
15
|
+
local function trimArguments(args)
|
|
16
|
+
-- `pairs` visits every value, holes or not. Read as a map, the list gives the raw Luau (1-based)
|
|
17
|
+
-- index, which is the length up to that value.
|
|
18
|
+
local length = 0
|
|
19
|
+
for index in pairs(args) do
|
|
20
|
+
if index > length then
|
|
21
|
+
length = index
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
if #args == length then
|
|
25
|
+
return args
|
|
26
|
+
end
|
|
27
|
+
-- Sized up front, so that the last slot is the last value and `#` is exact.
|
|
28
|
+
local trimmed = table.create(length)
|
|
29
|
+
do
|
|
30
|
+
local i = 0
|
|
31
|
+
local _shouldIncrement = false
|
|
32
|
+
while true do
|
|
33
|
+
if _shouldIncrement then
|
|
34
|
+
i += 1
|
|
35
|
+
else
|
|
36
|
+
_shouldIncrement = true
|
|
37
|
+
end
|
|
38
|
+
if not (i < length) then
|
|
39
|
+
break
|
|
40
|
+
end
|
|
41
|
+
trimmed[i + 1] = args[i + 1]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
return trimmed
|
|
45
|
+
end
|
|
46
|
+
return {
|
|
47
|
+
trimArguments = trimArguments,
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flamework-experimental/networking",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.3",
|
|
4
4
|
"main": "out/init.luau",
|
|
5
5
|
"types": "out/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@flamework-experimental/core": "
|
|
18
|
+
"@flamework-experimental/core": "^2.0.0-alpha.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@flamework-experimental/core": "2.0.0-alpha.
|
|
21
|
+
"@flamework-experimental/core": "2.0.0-alpha.2",
|
|
22
22
|
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
23
23
|
"@rbxts/types": "^1.0.948",
|
|
24
24
|
"roblox-ts": "^3.0.0"
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@rbxts/object-utils": "^1.0.4",
|
|
28
28
|
"@rbxts/services": "^1.1.5",
|
|
29
|
-
"@rbxts/signal": "^1.0.3",
|
|
30
29
|
"@rbxts/t": "^3.1.0"
|
|
31
30
|
}
|
|
32
31
|
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { t } from "@rbxts/t";
|
|
2
|
-
import { MiddlewareFactory } from "./types";
|
|
3
|
-
import { SignalContainer } from "../util/createSignalContainer";
|
|
4
|
-
import { EventNetworkingEvents } from "../handlers";
|
|
5
|
-
import { NetworkInfo } from "../types";
|
|
6
|
-
export declare function createGuardMiddleware<I extends unknown[], O>(name: string, fixedParameters: t.check<unknown>[], restParameter: t.check<unknown> | undefined, networkInfo: NetworkInfo, warnOnInvalid: boolean, signals: SignalContainer<EventNetworkingEvents>, failureValue?: O): MiddlewareFactory<I, O>;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
-- Compiled with roblox-ts v3.0.0
|
|
2
|
-
local TS = _G[script]
|
|
3
|
-
local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Players
|
|
4
|
-
local function createGuardMiddleware(name, fixedParameters, restParameter, networkInfo, warnOnInvalid, signals, failureValue)
|
|
5
|
-
return function(processNext)
|
|
6
|
-
return function(player, ...)
|
|
7
|
-
local args = { ... }
|
|
8
|
-
do
|
|
9
|
-
local i = 0
|
|
10
|
-
local _shouldIncrement = false
|
|
11
|
-
while true do
|
|
12
|
-
if _shouldIncrement then
|
|
13
|
-
i += 1
|
|
14
|
-
else
|
|
15
|
-
_shouldIncrement = true
|
|
16
|
-
end
|
|
17
|
-
if not (i < math.max(#fixedParameters, #args)) then
|
|
18
|
-
break
|
|
19
|
-
end
|
|
20
|
-
local guard = fixedParameters[i + 1] or restParameter
|
|
21
|
-
if guard and not guard(args[i + 1]) then
|
|
22
|
-
if warnOnInvalid then
|
|
23
|
-
if player then
|
|
24
|
-
warn(`'{player}' sent invalid arguments for event '{name}' (arg #{i}):`, args[i + 1])
|
|
25
|
-
else
|
|
26
|
-
warn(`Server sent invalid arguments for event '{name}' (arg #{i}):`, args[i + 1])
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
signals:fire("onBadRequest", player or Players.LocalPlayer, {
|
|
30
|
-
networkInfo = networkInfo,
|
|
31
|
-
argIndex = i,
|
|
32
|
-
argValue = args[i + 1],
|
|
33
|
-
})
|
|
34
|
-
return failureValue
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
return processNext(player, unpack(args))
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
return {
|
|
43
|
-
createGuardMiddleware = createGuardMiddleware,
|
|
44
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { NetworkInfo } from "../types";
|
|
2
|
-
import { Middleware, MiddlewareFactory, MiddlewareProcessor } from "./types";
|
|
3
|
-
export declare function createMiddlewareProcessor<I extends readonly unknown[], O>(middlewareFactories: MiddlewareFactory<I, O>[] | undefined, networkInfo: NetworkInfo, finalize: Middleware<I, O>): MiddlewareProcessor<I, O>;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
-- Compiled with roblox-ts v3.0.0
|
|
2
|
-
local TS = _G[script]
|
|
3
|
-
local function createMiddlewareProcessor(middlewareFactories, networkInfo, finalize)
|
|
4
|
-
local middleware = {}
|
|
5
|
-
if not middlewareFactories or #middlewareFactories == 0 then
|
|
6
|
-
middleware[1] = finalize
|
|
7
|
-
else
|
|
8
|
-
for i = #middlewareFactories - 1, 0, -1 do
|
|
9
|
-
local factory = middlewareFactories[i + 1]
|
|
10
|
-
local processNext = middleware[i + 2] or finalize
|
|
11
|
-
middleware[i + 1] = factory(TS.async(function(player, ...)
|
|
12
|
-
local args = { ... }
|
|
13
|
-
return processNext(player, unpack(args))
|
|
14
|
-
end), networkInfo)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
return TS.async(function(player, ...)
|
|
18
|
-
local args = { ... }
|
|
19
|
-
return middleware[1](player, unpack(args))
|
|
20
|
-
end)
|
|
21
|
-
end
|
|
22
|
-
return {
|
|
23
|
-
createMiddlewareProcessor = createMiddlewareProcessor,
|
|
24
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function timeoutPromise(timeout: number, rejectValue: unknown): Promise<unknown>;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
-- Compiled with roblox-ts v3.0.0
|
|
2
|
-
local TS = _G[script]
|
|
3
|
-
local function timeoutPromise(timeout, rejectValue)
|
|
4
|
-
return TS.Promise.delay(timeout):andThen(function()
|
|
5
|
-
return TS.Promise.reject(rejectValue)
|
|
6
|
-
end)
|
|
7
|
-
end
|
|
8
|
-
return {
|
|
9
|
-
timeoutPromise = timeoutPromise,
|
|
10
|
-
}
|