@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
package/README.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# Flamework
|
|
2
2
|
|
|
3
|
-
Flamework is an extensible framework for roblox-ts
|
|
3
|
+
Flamework is an extensible framework for roblox-ts. It is built around modules that are portable,
|
|
4
|
+
isolated and easy to test.
|
|
4
5
|
|
|
5
6
|
## Documentation
|
|
6
7
|
|
|
7
|
-
**[docs/](docs/README.md)
|
|
8
|
-
point to plugins
|
|
8
|
+
Start with **[docs/](docs/README.md)**. It holds a twelve-part guide, which starts from a working
|
|
9
|
+
entry point and builds up to plugins, project layout, scopes and testing. It also holds reference
|
|
10
|
+
material:
|
|
9
11
|
|
|
10
12
|
| | |
|
|
11
13
|
|---|---|
|
|
12
|
-
| [Guide](docs/README.md#guide) | Getting started, modules, providers, lifecycle events, components, networking, macros, plugins, project structure, migrating from v1. |
|
|
14
|
+
| [Guide](docs/README.md#guide) | Getting started, modules, providers, lifecycle events, components, networking, macros, plugins, project structure, migrating from v1, scopes, testing in the place. |
|
|
13
15
|
| [Internals](docs/reference/internals.md) | What the transformer does to your code and what the runtime does with the result. |
|
|
14
16
|
| [Transformer plugins](docs/reference/transformer-plugins.md) | Adding macro types of your own. |
|
|
15
17
|
|
|
@@ -27,6 +29,7 @@ bun install
|
|
|
27
29
|
bun run build # builds every package in dependency order
|
|
28
30
|
bun run test # build + transformer tests + runtime specs
|
|
29
31
|
bun run lint
|
|
32
|
+
bun run test:place # the in-place suite in Roblox Studio (tests/place); needs Studio, Rojo and Lune
|
|
30
33
|
```
|
|
31
34
|
|
|
32
35
|
### Packages
|
|
@@ -36,31 +39,36 @@ bun run lint
|
|
|
36
39
|
| `packages/core` | Modules, dependency injection, plugins and lifecycle events |
|
|
37
40
|
| `packages/components` | CollectionService components, built on the core plugin system |
|
|
38
41
|
| `packages/networking` | Remote events and functions |
|
|
39
|
-
| `packages/testing` |
|
|
42
|
+
| `packages/testing` | Tests that run inside a place: sections, cleanup, a bindable and a remote that run them, and a cloud entry point. Also `flamework-test` (`cli/`), the CLI that runs them in Roblox Studio on this machine or through Open Cloud |
|
|
40
43
|
| `packages/transformer` | The roblox-ts transformer |
|
|
41
44
|
| `packages/transformer-plugin` | Public API for writing transformer plugins |
|
|
42
|
-
| `packages/specs` | Runtime specs,
|
|
45
|
+
| `packages/specs` | Runtime specs, built by `rbxtsc` and run under Lune |
|
|
43
46
|
|
|
44
47
|
### Tests
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- **Transformer tests** (`bun run test:unit`)
|
|
49
|
-
|
|
50
|
-
- **Runtime specs** (`bun run test:runtime`)
|
|
51
|
-
`networking` under Lune
|
|
52
|
-
roblox-ts's `TS.import` tree over the filesystem
|
|
53
|
-
|
|
54
|
-
`Enum`, and a `Heartbeat` pump so `Promise.delay`
|
|
55
|
-
|
|
56
|
-
construction, dependencies and streaming
|
|
57
|
-
middleware and the generated guards.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
`@Provider`'s metadata, component streaming, and the client
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Specs live in [`packages/specs`](packages/specs)
|
|
66
|
-
Flamework
|
|
49
|
+
`bun run test` runs two suites:
|
|
50
|
+
|
|
51
|
+
- **Transformer tests** (`bun run test:unit`) build a fixture project with the real `rbxtsc` and
|
|
52
|
+
check the Luau it emits: guard generation, identifiers, nested macros and the plugin system.
|
|
53
|
+
- **Runtime specs** (`bun run test:runtime`) run the built `@flamework-experimental/core`,
|
|
54
|
+
`components` and `networking` packages under Lune. The harness in [`tests/runtime`](tests/runtime)
|
|
55
|
+
models roblox-ts's `TS.import` tree over the filesystem. It also stubs the parts of the Roblox API
|
|
56
|
+
that Flamework uses: Instances, attributes, CollectionService, RemoteEvents, Players, signals,
|
|
57
|
+
`task`, `Enum`, and a `Heartbeat` pump so that `Promise.delay` runs (and with it, request
|
|
58
|
+
timeouts). The specs cover dependency injection, modules, hooks and the per-frame lifecycle
|
|
59
|
+
events; component construction, dependencies and streaming; and both halves of networking:
|
|
60
|
+
events, functions, middleware and the generated guards.
|
|
61
|
+
|
|
62
|
+
The specs run twice, once as `Server` and once as `Client`, because some code paths depend on the
|
|
63
|
+
realm: `@Provider`'s metadata, component streaming, and the client and server halves of
|
|
64
|
+
networking. Running a realm-specific spec from both sides proves that the two sides agree. For
|
|
65
|
+
example, from the server a function receives on `$name` and sends on `@name`, and from the client
|
|
66
|
+
it does the reverse, so the two runs pin down the wire format from both ends.
|
|
67
|
+
|
|
68
|
+
Specs live in [`packages/specs`](packages/specs). `rbxtsc` builds them like any other project that
|
|
69
|
+
uses Flamework, so they test the transformer and the runtime together.
|
|
70
|
+
|
|
71
|
+
A third suite runs against the real engine, and `bun run test` leaves it out. It lives in the
|
|
72
|
+
[test place](tests/place/README.md), a small game linked to the packages' builds.
|
|
73
|
+
`bun run test:place` runs its `@flamework-experimental/testing` sections in Roblox Studio, on both
|
|
74
|
+
realms, under four Rojo projects (see [Testing in Roblox Studio](docs/testing/studio.md)).
|
package/flamework.build
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Serialization } from "@flamework-experimental/core";
|
|
2
|
-
import { MiddlewareFactory
|
|
2
|
+
import { MiddlewareFactory } from "../middleware/types";
|
|
3
3
|
import { NetworkInfo } from "../types";
|
|
4
|
+
import { Guards } from "../middleware/processor";
|
|
5
|
+
import { SignalConnection } from "../util/signal";
|
|
4
6
|
export interface CreateEventOptions {
|
|
5
7
|
/**
|
|
6
8
|
* The namespace this event should be created in.
|
|
@@ -30,6 +32,11 @@ export interface CreateEventOptions {
|
|
|
30
32
|
* A list of middleware that this event uses when it receives an event.
|
|
31
33
|
*/
|
|
32
34
|
incomingMiddleware?: MiddlewareFactory<any[], void>[];
|
|
35
|
+
/**
|
|
36
|
+
* The generated argument checks, run on what arrives before any middleware. A message that fails
|
|
37
|
+
* them is dropped.
|
|
38
|
+
*/
|
|
39
|
+
incomingGuards?: Guards;
|
|
33
40
|
/**
|
|
34
41
|
* Unpacks the argument list of incoming events. Absent when the project does not enable
|
|
35
42
|
* serialization. Outgoing lists reach `fire*` already packed: the transformer generates the
|
|
@@ -46,14 +53,25 @@ export interface EventInterface {
|
|
|
46
53
|
fireServer(...args: unknown[]): void;
|
|
47
54
|
fireClient(player: Player, ...args: unknown[]): void;
|
|
48
55
|
fireAllClients(...args: unknown[]): void;
|
|
49
|
-
connectServer(callback: (player: Player, ...args: unknown[]) => void):
|
|
50
|
-
connectClient(callback: (...args: unknown[]) => void):
|
|
51
|
-
|
|
56
|
+
connectServer(callback: (player: Player, ...args: unknown[]) => void): SignalConnection;
|
|
57
|
+
connectClient(callback: (...args: unknown[]) => void): SignalConnection;
|
|
58
|
+
/**
|
|
59
|
+
* Runs the receiving half as if the remote had delivered `args` (plain values, never a payload), on
|
|
60
|
+
* a recycled thread of its own, so that a middleware that yields does not hold up the caller.
|
|
61
|
+
*/
|
|
62
|
+
predict(player: Player | undefined, ...args: unknown[]): void;
|
|
52
63
|
}
|
|
53
64
|
/**
|
|
54
65
|
* The argument list a remote delivered: `args` as they are without a decoder, otherwise the buffer
|
|
55
66
|
* and blob list unpacked. `undefined` when the payload was malformed, after reporting it through
|
|
56
67
|
* `onMalformed`. Decoding runs under `pcall`: a hostile buffer raises instead of yielding garbage.
|
|
68
|
+
*
|
|
69
|
+
* Either way the list is trimmed after its last value, so that no hop after it loses the values
|
|
70
|
+
* that follow a nil (see `trimArguments`). A decoded list keeps one slot per declared parameter,
|
|
71
|
+
* so an absent trailing optional would otherwise end it in nil.
|
|
72
|
+
*
|
|
73
|
+
* An event's own messages are decoded in the receive pipeline (`processor.luau`); this is for a
|
|
74
|
+
* function's requests and responses, which carry a plain request id ahead of the payload.
|
|
57
75
|
*/
|
|
58
76
|
export declare function decodeArguments(decoder: Serialization.Decoder | undefined, player: Player | undefined, args: unknown[], onMalformed?: (player: Player | undefined, message: string) => void): unknown[] | undefined;
|
|
59
77
|
export declare function createEvent(options: CreateEventOptions): EventInterface;
|
|
@@ -2,77 +2,70 @@
|
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
|
|
4
4
|
local createRemoteInstance = TS.import(script, script.Parent, "createRemoteInstance").createRemoteInstance
|
|
5
|
-
local
|
|
6
|
-
|
|
7
|
-
local
|
|
5
|
+
local _processor = TS.import(script, script.Parent.Parent, "middleware", "processor")
|
|
6
|
+
local createProcessor = _processor.createProcessor
|
|
7
|
+
local createReceiver = _processor.createReceiver
|
|
8
|
+
local decode = _processor.decode
|
|
9
|
+
local deliverTo = _processor.deliverTo
|
|
10
|
+
local _signal = TS.import(script, script.Parent.Parent, "util", "signal")
|
|
11
|
+
local createSignal = _signal.createSignal
|
|
12
|
+
local spawn = _signal.spawn
|
|
13
|
+
local trimArguments = TS.import(script, script.Parent.Parent, "util", "trimArguments").trimArguments
|
|
8
14
|
--[[
|
|
9
15
|
*
|
|
10
16
|
* The argument list a remote delivered: `args` as they are without a decoder, otherwise the buffer
|
|
11
17
|
* and blob list unpacked. `undefined` when the payload was malformed, after reporting it through
|
|
12
18
|
* `onMalformed`. Decoding runs under `pcall`: a hostile buffer raises instead of yielding garbage.
|
|
19
|
+
*
|
|
20
|
+
* Either way the list is trimmed after its last value, so that no hop after it loses the values
|
|
21
|
+
* that follow a nil (see `trimArguments`). A decoded list keeps one slot per declared parameter,
|
|
22
|
+
* so an absent trailing optional would otherwise end it in nil.
|
|
23
|
+
*
|
|
24
|
+
* An event's own messages are decoded in the receive pipeline (`processor.luau`); this is for a
|
|
25
|
+
* function's requests and responses, which carry a plain request id ahead of the payload.
|
|
13
26
|
|
|
14
27
|
]]
|
|
15
28
|
local function decodeArguments(decoder, player, args, onMalformed)
|
|
16
29
|
if not decoder then
|
|
17
|
-
return args
|
|
30
|
+
return trimArguments(args)
|
|
18
31
|
end
|
|
19
|
-
local
|
|
20
|
-
local payload = _binding[1]
|
|
21
|
-
local blobs = _binding[2]
|
|
22
|
-
if not (type(payload) == "buffer") or (blobs ~= nil and not (type(blobs) == "table")) then
|
|
23
|
-
local _result = onMalformed
|
|
24
|
-
if _result ~= nil then
|
|
25
|
-
_result(player, "payload is not a buffer with an optional blob list")
|
|
26
|
-
end
|
|
27
|
-
return nil
|
|
28
|
-
end
|
|
29
|
-
local ok, result = pcall(decoder, payload, (blobs or NO_BLOBS))
|
|
32
|
+
local ok, result = decode(decoder, args[1], args[2])
|
|
30
33
|
if not ok then
|
|
31
34
|
local _result = onMalformed
|
|
32
35
|
if _result ~= nil then
|
|
33
|
-
_result(player,
|
|
36
|
+
_result(player, result)
|
|
34
37
|
end
|
|
35
38
|
return nil
|
|
36
39
|
end
|
|
37
|
-
return result
|
|
40
|
+
return trimArguments(result)
|
|
38
41
|
end
|
|
39
42
|
local function createEvent(options)
|
|
40
43
|
local remote = createRemoteInstance(if options.reliability == "unreliable" then "UnreliableRemoteEvent" else "RemoteEvent", options.namespace, options.debugName, options.id)
|
|
41
|
-
|
|
42
|
-
local
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
-- Which half this event is, fixed when it is made: the server's handlers are given the sender.
|
|
45
|
+
local isServer = RunService:IsServer()
|
|
46
|
+
-- Passes the arguments by reference. A BindableEvent would copy them, turning a decoded
|
|
47
|
+
-- `Map<Instance, ...>` into one keyed by strings and raising on a `Set<boolean>`.
|
|
48
|
+
local signal = createSignal()
|
|
49
|
+
-- Guards, then middleware, then the signal: plain calls in the thread that received the message.
|
|
50
|
+
local process = createProcessor(options.incomingMiddleware, options.networkInfo, deliverTo(signal, isServer), options.incomingGuards, nil, nil)
|
|
51
|
+
-- Nothing to deliver to until something connects (a `predict` may come first), so the remote is
|
|
52
|
+
-- only listened to from the first connection on.
|
|
53
|
+
local listening = false
|
|
54
|
+
local listen = function()
|
|
55
|
+
if listening then
|
|
56
|
+
return nil
|
|
48
57
|
end
|
|
49
|
-
|
|
50
|
-
local receive = function(player, args)
|
|
51
|
-
local decoded = decodeArguments(options.incomingDecoder, player, args, options.onMalformed)
|
|
52
|
-
if decoded then
|
|
53
|
-
invoke(player, unpack(decoded))
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
local createConnection = function(callback)
|
|
57
|
-
if bindable then
|
|
58
|
-
return bindable.Event:Connect(callback)
|
|
59
|
-
end
|
|
60
|
-
bindable = Instance.new("BindableEvent")
|
|
58
|
+
listening = true
|
|
61
59
|
-- We defer to allow any other immediate connections to take place before unloading Roblox's queue.
|
|
62
60
|
task.defer(function()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end)
|
|
61
|
+
-- Runs in the thread the engine gives the handler, one per message.
|
|
62
|
+
local receive = createReceiver(options.incomingDecoder, options.onMalformed, process, isServer)
|
|
63
|
+
if isServer then
|
|
64
|
+
remote.OnServerEvent:Connect(receive)
|
|
68
65
|
else
|
|
69
|
-
remote.OnClientEvent:Connect(
|
|
70
|
-
local args = { ... }
|
|
71
|
-
return receive(nil, args)
|
|
72
|
-
end)
|
|
66
|
+
remote.OnClientEvent:Connect(receive)
|
|
73
67
|
end
|
|
74
68
|
end)
|
|
75
|
-
return bindable.Event:Connect(callback)
|
|
76
69
|
end
|
|
77
70
|
return {
|
|
78
71
|
fireEither = function(self, player, ...)
|
|
@@ -98,14 +91,19 @@ local function createEvent(options)
|
|
|
98
91
|
connectServer = function(self, callback)
|
|
99
92
|
local _arg0 = RunService:IsServer()
|
|
100
93
|
assert(_arg0)
|
|
101
|
-
|
|
94
|
+
listen()
|
|
95
|
+
return signal:Connect(callback)
|
|
102
96
|
end,
|
|
103
97
|
connectClient = function(self, callback)
|
|
104
98
|
local _arg0 = RunService:IsClient()
|
|
105
99
|
assert(_arg0)
|
|
106
|
-
|
|
100
|
+
listen()
|
|
101
|
+
return signal:Connect(callback)
|
|
102
|
+
end,
|
|
103
|
+
predict = function(self, player, ...)
|
|
104
|
+
local args = { ... }
|
|
105
|
+
spawn(process, player, unpack(args))
|
|
107
106
|
end,
|
|
108
|
-
invoke = invoke,
|
|
109
107
|
}
|
|
110
108
|
end
|
|
111
109
|
return {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local trimArguments = TS.import(script, script.Parent.Parent, "util", "trimArguments").trimArguments
|
|
2
4
|
local function createClientMethod(receiver, sender)
|
|
5
|
+
-- A method that takes an argument list trims it before spreading it, or an explicit trailing
|
|
6
|
+
-- `undefined` would lose the arguments after a gap (see `trimArguments`).
|
|
3
7
|
local method = {
|
|
4
8
|
fire = function(self, ...)
|
|
5
9
|
local args = { ... }
|
|
6
|
-
sender:fireServer(unpack(args))
|
|
10
|
+
sender:fireServer(unpack(trimArguments(args)))
|
|
7
11
|
end,
|
|
8
12
|
_fire = function(self, ...)
|
|
9
13
|
local packed = { ... }
|
|
@@ -14,13 +18,13 @@ local function createClientMethod(receiver, sender)
|
|
|
14
18
|
end,
|
|
15
19
|
predict = function(self, ...)
|
|
16
20
|
local args = { ... }
|
|
17
|
-
|
|
21
|
+
receiver:predict(nil, unpack(trimArguments(args)))
|
|
18
22
|
end,
|
|
19
23
|
}
|
|
20
24
|
setmetatable(method, {
|
|
21
25
|
__call = function(method, ...)
|
|
22
26
|
local args = { ... }
|
|
23
|
-
method:fire(unpack(args))
|
|
27
|
+
method:fire(unpack((trimArguments(args))))
|
|
24
28
|
end,
|
|
25
29
|
})
|
|
26
30
|
return method
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
|
-
local
|
|
3
|
+
local createGuards = TS.import(script, script.Parent.Parent, "middleware", "createGuards").createGuards
|
|
4
4
|
local createEvent = TS.import(script, script.Parent.Parent, "event", "createEvent").createEvent
|
|
5
5
|
local getNamespaceConfig = TS.import(script, script.Parent.Parent, "util", "getNamespaceConfig").getNamespaceConfig
|
|
6
6
|
local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Players
|
|
@@ -44,11 +44,13 @@ local function createGenericHandler(globalName, namespaceName, metadata, config,
|
|
|
44
44
|
name = effectiveName,
|
|
45
45
|
globalName = globalName,
|
|
46
46
|
}
|
|
47
|
+
-- The generated guards run ahead of all user middleware, so middleware never sees a payload that
|
|
48
|
+
-- failed them.
|
|
49
|
+
local incomingGuards
|
|
47
50
|
if not config.disableIncomingGuards and isIncoming then
|
|
48
51
|
local guards = metadata.incoming[name]
|
|
49
52
|
assert(guards)
|
|
50
|
-
|
|
51
|
-
table.insert(incomingMiddleware, 1, _arg0)
|
|
53
|
+
incomingGuards = createGuards(name, guards[1], guards[2], networkInfo, config.warnOnInvalidGuards, signals)
|
|
52
54
|
end
|
|
53
55
|
-- A malformed serialized payload is reported like a failed guard, with no argument index.
|
|
54
56
|
local onMalformed = function(player, message)
|
|
@@ -70,6 +72,7 @@ local function createGenericHandler(globalName, namespaceName, metadata, config,
|
|
|
70
72
|
debugName = name,
|
|
71
73
|
networkInfo = networkInfo,
|
|
72
74
|
incomingMiddleware = incomingMiddleware,
|
|
75
|
+
incomingGuards = incomingGuards,
|
|
73
76
|
}
|
|
74
77
|
local _left = "incomingDecoder"
|
|
75
78
|
local _result
|
|
@@ -5,6 +5,7 @@ local createClientMethod = TS.import(script, script.Parent, "createClientMethod"
|
|
|
5
5
|
local createServerMethod = TS.import(script, script.Parent, "createServerMethod").createServerMethod
|
|
6
6
|
local createSignalContainer = TS.import(script, script.Parent.Parent, "util", "createSignalContainer").createSignalContainer
|
|
7
7
|
local createGenericHandler = TS.import(script, script.Parent, "createGenericHandler").createGenericHandler
|
|
8
|
+
local createOnce = TS.import(script, script.Parent.Parent, "util", "createOnce").createOnce
|
|
8
9
|
local function getDefaultConfiguration(config)
|
|
9
10
|
local _object = {}
|
|
10
11
|
local _left = "middleware"
|
|
@@ -30,7 +31,9 @@ end
|
|
|
30
31
|
local function createNetworkingEvent(globalName)
|
|
31
32
|
local signals = createSignalContainer()
|
|
32
33
|
local server
|
|
33
|
-
|
|
34
|
+
-- Built once: building it waits for the server's remotes, and a thread that asks meanwhile gets the
|
|
35
|
+
-- same handler rather than a second one wired to the same remotes.
|
|
36
|
+
local client = createOnce()
|
|
34
37
|
return {
|
|
35
38
|
createServer = function(self, config, meta)
|
|
36
39
|
if RunService:IsRunning() and not RunService:IsServer() then
|
|
@@ -45,10 +48,9 @@ local function createNetworkingEvent(globalName)
|
|
|
45
48
|
if RunService:IsRunning() and not RunService:IsClient() then
|
|
46
49
|
return nil
|
|
47
50
|
end
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
51
|
-
return client
|
|
51
|
+
return client(function()
|
|
52
|
+
return createGenericHandler(globalName, nil, meta, getDefaultConfiguration(config), signals, createClientMethod)
|
|
53
|
+
end)
|
|
52
54
|
end,
|
|
53
55
|
registerHandler = function(self, key, callback)
|
|
54
56
|
return signals:connect(key, callback)
|
|
@@ -8,6 +8,6 @@ export declare function createServerMethod(receiver: EventInterface, sender: Eve
|
|
|
8
8
|
except: (players: Player | Player[], ...args: unknown[]) => void;
|
|
9
9
|
broadcast: (...args: unknown[]) => void;
|
|
10
10
|
readonly _flamework_receive?: unknown[] | undefined;
|
|
11
|
-
connect: (cb: (player: Player, ...args: unknown[]) => void) =>
|
|
11
|
+
connect: (cb: (player: Player, ...args: unknown[]) => void) => import("../util/signal").SignalConnection;
|
|
12
12
|
predict: (player: Player, ...args: unknown[]) => void;
|
|
13
13
|
};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Players
|
|
4
|
+
local trimArguments = TS.import(script, script.Parent.Parent, "util", "trimArguments").trimArguments
|
|
4
5
|
local function createServerMethod(receiver, sender)
|
|
6
|
+
-- A method that takes an argument list trims it before spreading it, or an explicit trailing
|
|
7
|
+
-- `undefined` would lose the arguments after a gap (see `trimArguments`).
|
|
5
8
|
local method = {
|
|
6
9
|
fire = function(self, players, ...)
|
|
7
10
|
local args = { ... }
|
|
11
|
+
args = trimArguments(args)
|
|
8
12
|
local _players = players
|
|
9
13
|
if typeof(_players) == "Instance" then
|
|
10
14
|
sender:fireClient(players, unpack(args))
|
|
@@ -16,10 +20,11 @@ local function createServerMethod(receiver, sender)
|
|
|
16
20
|
end,
|
|
17
21
|
broadcast = function(self, ...)
|
|
18
22
|
local args = { ... }
|
|
19
|
-
sender:fireAllClients(unpack(args))
|
|
23
|
+
sender:fireAllClients(unpack(trimArguments(args)))
|
|
20
24
|
end,
|
|
21
25
|
except = function(self, players, ...)
|
|
22
26
|
local args = { ... }
|
|
27
|
+
args = trimArguments(args)
|
|
23
28
|
local _players = players
|
|
24
29
|
if typeof(_players) == "Instance" then
|
|
25
30
|
players = { players }
|
|
@@ -47,13 +52,13 @@ local function createServerMethod(receiver, sender)
|
|
|
47
52
|
end,
|
|
48
53
|
predict = function(self, player, ...)
|
|
49
54
|
local args = { ... }
|
|
50
|
-
receiver
|
|
55
|
+
receiver:predict(player, unpack(trimArguments(args)))
|
|
51
56
|
end,
|
|
52
57
|
}
|
|
53
58
|
setmetatable(method, {
|
|
54
59
|
__call = function(method, player, ...)
|
|
55
60
|
local args = { ... }
|
|
56
|
-
method:fire(player, unpack(args))
|
|
61
|
+
method:fire(player, unpack(trimArguments(args)))
|
|
57
62
|
end,
|
|
58
63
|
})
|
|
59
64
|
return method
|
package/out/events/types.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "../types";
|
|
11
11
|
import { EventNetworkingEvents } from "../handlers";
|
|
12
12
|
import { EventMiddleware } from "../middleware/types";
|
|
13
|
+
import { SignalConnection } from "../util/signal";
|
|
13
14
|
import { Modding } from "@flamework-experimental/core";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -56,7 +57,7 @@ export interface RawServerReceiver<I extends unknown[]> {
|
|
|
56
57
|
* Connect to this networking event.
|
|
57
58
|
* @param callback The callback that will be fired
|
|
58
59
|
*/
|
|
59
|
-
connect(cb: (player: Player, ...args: I) => void):
|
|
60
|
+
connect(cb: (player: Player, ...args: I) => void): SignalConnection;
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
63
|
* Fires a server event using player as the sender.
|
|
@@ -91,7 +92,7 @@ export interface RawClientReceiver<I extends unknown[]> {
|
|
|
91
92
|
* Connect to this networking event.
|
|
92
93
|
* @param callback The callback that will be fired
|
|
93
94
|
*/
|
|
94
|
-
connect(cb: (...args: I) => void):
|
|
95
|
+
connect(cb: (...args: I) => void): SignalConnection;
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
98
|
* Fires a client event.
|
|
@@ -174,7 +175,7 @@ export interface GlobalEvent<S, C> {
|
|
|
174
175
|
registerHandler<K extends keyof EventNetworkingEvents>(
|
|
175
176
|
key: K,
|
|
176
177
|
callback: EventNetworkingEvents[K],
|
|
177
|
-
):
|
|
178
|
+
): SignalConnection;
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
export type EventNamespaces<T> = ExcludeMembers<T, Callback>;
|
|
@@ -2,6 +2,7 @@ import { Serialization } from "@flamework-experimental/core";
|
|
|
2
2
|
import { NetworkInfo } from "../types";
|
|
3
3
|
import { NetworkingFunctionError } from "./errors";
|
|
4
4
|
import { MiddlewareFactory } from "../middleware/types";
|
|
5
|
+
import { Guards } from "../middleware/processor";
|
|
5
6
|
export interface CreateFunctionReceiverOptions {
|
|
6
7
|
/**
|
|
7
8
|
* The namespace this event should be created in.
|
|
@@ -22,18 +23,18 @@ export interface CreateFunctionReceiverOptions {
|
|
|
22
23
|
*/
|
|
23
24
|
networkInfo: NetworkInfo;
|
|
24
25
|
/**
|
|
25
|
-
*
|
|
26
|
+
* The middleware a request goes through before it reaches the callback.
|
|
26
27
|
*/
|
|
27
28
|
incomingMiddleware?: MiddlewareFactory<any[], any>[];
|
|
28
29
|
/**
|
|
29
|
-
*
|
|
30
|
+
* The generated argument checks, run on a request before any middleware. A request that fails
|
|
31
|
+
* them is rejected with `BadRequest`.
|
|
30
32
|
*/
|
|
31
|
-
|
|
33
|
+
incomingGuards?: Guards;
|
|
32
34
|
/**
|
|
33
|
-
* Unpacks the
|
|
34
|
-
* Absent when the project does not enable serialization.
|
|
35
|
+
* Unpacks the request's argument list. Absent when the project does not enable serialization.
|
|
35
36
|
*/
|
|
36
|
-
|
|
37
|
+
argsDecoder?: Serialization.Decoder;
|
|
37
38
|
/**
|
|
38
39
|
* Called when a request cannot be decoded; the caller receives `BadRequest`.
|
|
39
40
|
*/
|
|
@@ -44,9 +45,9 @@ export interface RequestInfo {
|
|
|
44
45
|
requests: Map<number, (value: unknown, rejection?: NetworkingFunctionError) => void>;
|
|
45
46
|
}
|
|
46
47
|
export interface FunctionReceiverInterface {
|
|
47
|
-
/** `
|
|
48
|
-
setServerCallback(callback: (player: Player, ...args: unknown[]) => unknown,
|
|
49
|
-
setClientCallback(callback: (...args: unknown[]) => unknown,
|
|
48
|
+
/** `pack`: turns a successful result into `[payload, blobs?]`, generated by the transformer. */
|
|
49
|
+
setServerCallback(callback: (player: Player, ...args: unknown[]) => unknown, pack?: (value: unknown) => unknown): void;
|
|
50
|
+
setClientCallback(callback: (...args: unknown[]) => unknown, pack?: (value: unknown) => unknown): void;
|
|
50
51
|
invoke(player: Player | undefined, ...args: unknown[]): Promise<unknown>;
|
|
51
52
|
}
|
|
52
53
|
export declare function createFunctionReceiver(options: CreateFunctionReceiverOptions): FunctionReceiverInterface;
|