@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.
Files changed (44) hide show
  1. package/README.md +35 -27
  2. package/flamework.build +1 -1
  3. package/out/event/createEvent.d.ts +22 -4
  4. package/out/event/createEvent.luau +47 -49
  5. package/out/events/createClientMethod.luau +7 -3
  6. package/out/events/createGenericHandler.luau +6 -3
  7. package/out/events/createNetworkingEvent.luau +7 -5
  8. package/out/events/createServerMethod.d.ts +1 -1
  9. package/out/events/createServerMethod.luau +8 -3
  10. package/out/events/types.d.ts +4 -3
  11. package/out/function/createFunctionReceiver.d.ts +10 -9
  12. package/out/function/createFunctionReceiver.luau +53 -55
  13. package/out/function/createFunctionSender.d.ts +3 -2
  14. package/out/function/createFunctionSender.luau +118 -40
  15. package/out/functions/createClientMethod.d.ts +1 -1
  16. package/out/functions/createClientMethod.luau +9 -8
  17. package/out/functions/createGenericHandler.luau +8 -12
  18. package/out/functions/createNetworkingFunction.luau +7 -5
  19. package/out/functions/createServerMethod.d.ts +1 -1
  20. package/out/functions/createServerMethod.luau +9 -8
  21. package/out/functions/types.d.ts +10 -12
  22. package/out/handlers.d.ts +35 -36
  23. package/out/index.d.ts +6 -0
  24. package/out/init.luau +6 -0
  25. package/out/middleware/createGuards.d.ts +10 -0
  26. package/out/middleware/createGuards.luau +32 -0
  27. package/out/middleware/processor.d.ts +62 -0
  28. package/out/middleware/processor.luau +214 -0
  29. package/out/middleware/types.d.ts +26 -15
  30. package/out/util/createOnce.d.ts +14 -0
  31. package/out/util/createOnce.luau +66 -0
  32. package/out/util/createSignalContainer.d.ts +2 -1
  33. package/out/util/createSignalContainer.luau +2 -2
  34. package/out/util/signal.d.ts +27 -0
  35. package/out/util/signal.luau +135 -0
  36. package/out/util/trimArguments.d.ts +12 -0
  37. package/out/util/trimArguments.luau +48 -0
  38. package/package.json +3 -4
  39. package/out/middleware/createGuardMiddleware.d.ts +0 -6
  40. package/out/middleware/createGuardMiddleware.luau +0 -44
  41. package/out/middleware/createMiddlewareProcessor.d.ts +0 -3
  42. package/out/middleware/createMiddlewareProcessor.luau +0 -24
  43. package/out/util/timeoutPromise.d.ts +0 -1
  44. 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.1",
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.0",
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
- }