@flamework-experimental/networking 2.0.0-alpha.3 → 2.0.0-alpha.4

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 CHANGED
@@ -5,15 +5,20 @@ isolated and easy to test.
5
5
 
6
6
  ## Documentation
7
7
 
8
- Start with **[docs/](docs/README.md)**. It holds a twelve-part guide, which starts from a working
8
+ Start with **[docs/](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/README.md)**. It holds a twelve-part guide, which starts from a working
9
9
  entry point and builds up to plugins, project layout, scopes and testing. It also holds reference
10
10
  material:
11
11
 
12
12
  | | |
13
13
  |---|---|
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. |
15
- | [Internals](docs/reference/internals.md) | What the transformer does to your code and what the runtime does with the result. |
16
- | [Transformer plugins](docs/reference/transformer-plugins.md) | Adding macro types of your own. |
14
+ | [Guide](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/README.md#guide) | Getting started, modules, providers, lifecycle events, components, networking, macros, plugins, project structure, migrating from v1, scopes, testing in the place. |
15
+ | [Internals](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/reference/internals.md) | What the transformer does to your code and what the runtime does with the result. |
16
+ | [Transformer plugins](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/reference/transformer-plugins.md) | Adding macro types of your own. |
17
+
18
+ The guide also ships inside the core package, for the version you installed:
19
+ `node_modules/@flamework-experimental/core/docs/README.md` is its index, and the pages are in
20
+ `node_modules/@flamework-experimental/core/docs/guide/`. Instructions for a coding assistant can
21
+ point there, so that it reads the docs for your version.
17
22
 
18
23
  The Flamework website documents v1, most of which no longer applies:
19
24
 
@@ -51,7 +56,7 @@ bun run test:place # the in-place suite in Roblox Studio (tests/place); need
51
56
  - **Transformer tests** (`bun run test:unit`) build a fixture project with the real `rbxtsc` and
52
57
  check the Luau it emits: guard generation, identifiers, nested macros and the plugin system.
53
58
  - **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)
59
+ `components` and `networking` packages under Lune. The harness in [`tests/runtime`](https://github.com/Velover/ExperimentalFlameworkV2/tree/HEAD/tests/runtime)
55
60
  models roblox-ts's `TS.import` tree over the filesystem. It also stubs the parts of the Roblox API
56
61
  that Flamework uses: Instances, attributes, CollectionService, RemoteEvents, Players, signals,
57
62
  `task`, `Enum`, and a `Heartbeat` pump so that `Promise.delay` runs (and with it, request
@@ -65,10 +70,10 @@ bun run test:place # the in-place suite in Roblox Studio (tests/place); need
65
70
  example, from the server a function receives on `$name` and sends on `@name`, and from the client
66
71
  it does the reverse, so the two runs pin down the wire format from both ends.
67
72
 
68
- Specs live in [`packages/specs`](packages/specs). `rbxtsc` builds them like any other project that
73
+ Specs live in [`packages/specs`](https://github.com/Velover/ExperimentalFlameworkV2/tree/HEAD/packages/specs). `rbxtsc` builds them like any other project that
69
74
  uses Flamework, so they test the transformer and the runtime together.
70
75
 
71
76
  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.
77
+ [test place](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/tests/place/README.md), a small game linked to the packages' builds.
73
78
  `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)).
79
+ realms, under four Rojo projects (see [Testing in Roblox Studio](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/testing/studio.md)).
package/flamework.build CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "flameworkVersion": "2.0.0-alpha.4",
3
+ "flameworkVersion": "2.0.0-alpha.5",
4
4
  "identifiers": {},
5
5
  "idGenerationMode": "full",
6
6
  "identifierPrefix": "$n"
@@ -50,7 +50,7 @@ local function createGenericHandler(globalName, namespaceName, metadata, config,
50
50
  if not config.disableIncomingGuards and isIncoming then
51
51
  local guards = metadata.incoming[name]
52
52
  assert(guards)
53
- incomingGuards = createGuards(name, guards[1], guards[2], networkInfo, config.warnOnInvalidGuards, signals)
53
+ incomingGuards = createGuards(name, guards[1], guards[2], guards[3], networkInfo, config.warnOnInvalidGuards, signals)
54
54
  end
55
55
  -- A malformed serialized payload is reported like a failed guard, with no argument index.
56
56
  local onMalformed = function(player, message)
@@ -1,9 +1,11 @@
1
1
  import { EventInterface } from "../event/createEvent";
2
2
  export declare function createServerMethod(receiver: EventInterface, sender: EventInterface): {
3
3
  readonly _flamework_send?: unknown[] | undefined;
4
+ readonly _flamework_fn?: unknown;
4
5
  _fire: (players: Player | Player[], payload?: buffer, blobs?: Array<defined>) => void;
5
6
  _except: (players: Player | Player[], payload?: buffer, blobs?: Array<defined>) => void;
6
7
  _broadcast: (payload?: buffer, blobs?: Array<defined>) => void;
8
+ readonly _flamework_packing?: "plain" | undefined;
7
9
  fire: (players: Player | Player[], ...args: unknown[]) => void;
8
10
  except: (players: Player | Player[], ...args: unknown[]) => void;
9
11
  broadcast: (...args: unknown[]) => void;
@@ -2,10 +2,11 @@ import {
2
2
  FunctionParameters,
3
3
  IntrinsicTupleGuards,
4
4
  IntrinsicNetworkDecoder,
5
+ IntrinsicNetworkUnreliable,
5
6
  IntrinsicObfuscate,
7
+ IsRawMember,
6
8
  NetworkingObfuscationMarker,
7
- NetworkRaw,
8
- NetworkUnreliable,
9
+ NetworkPacking,
9
10
  ObfuscateNames,
10
11
  } from "../types";
11
12
  import { EventNetworkingEvents } from "../handlers";
@@ -17,9 +18,12 @@ import { Modding } from "@flamework-experimental/core";
17
18
  * A sender declared `Networking.RawReliable` / `RawUnreliable`: its arguments travel as they are.
18
19
  * Without the hidden marker below, no call site packs them and the peer runs no decoder.
19
20
  */
20
- export interface RawServerSender<I extends unknown[]> {
21
+ export interface RawServerSender<I extends unknown[], M = "raw"> {
21
22
  (player: Player | Player[], ...args: I): void;
22
23
 
24
+ /** @hidden How the member is packed (see `NetworkPacking`), which keeps differently packed members apart in a union. */
25
+ readonly _flamework_packing?: M;
26
+
23
27
  /**
24
28
  * Sends this request to the specified player(s).
25
29
  * @param players The player(s) that will receive this event
@@ -38,10 +42,13 @@ export interface RawServerSender<I extends unknown[]> {
38
42
  broadcast(...args: I): void;
39
43
  }
40
44
 
41
- export interface ServerSender<I extends unknown[]> extends RawServerSender<I> {
45
+ export interface ServerSender<I extends unknown[], F = unknown, M = NetworkPacking<F>> extends RawServerSender<I, M> {
42
46
  /** @hidden Marks a sender for the transformer, which packs its arguments at each call site. */
43
47
  readonly _flamework_send?: I;
44
48
 
49
+ /** @hidden The declared member, whose markers (`Serialized`) say whether its call sites pack. */
50
+ readonly _flamework_fn?: F;
51
+
45
52
  /** @hidden Sends an argument list the transformer already packed; nothing when the list carries nothing. */
46
53
  _fire(players: Player | Player[], payload?: buffer, blobs?: Array<defined>): void;
47
54
 
@@ -70,19 +77,25 @@ export interface ServerReceiver<I extends unknown[]> extends RawServerReceiver<I
70
77
  readonly _flamework_receive?: I;
71
78
  }
72
79
 
73
- export interface RawClientSender<I extends unknown[]> {
80
+ export interface RawClientSender<I extends unknown[], M = "raw"> {
74
81
  (...args: I): void;
75
82
 
83
+ /** @hidden How the member is packed (see `NetworkPacking`), which keeps differently packed members apart in a union. */
84
+ readonly _flamework_packing?: M;
85
+
76
86
  /**
77
87
  * Sends this request to the server.
78
88
  */
79
89
  fire(...args: I): void;
80
90
  }
81
91
 
82
- export interface ClientSender<I extends unknown[]> extends RawClientSender<I> {
92
+ export interface ClientSender<I extends unknown[], F = unknown, M = NetworkPacking<F>> extends RawClientSender<I, M> {
83
93
  /** @hidden Marks a sender for the transformer, which packs its arguments at each call site. */
84
94
  readonly _flamework_send?: I;
85
95
 
96
+ /** @hidden The declared member, whose markers (`Serialized`) say whether its call sites pack. */
97
+ readonly _flamework_fn?: F;
98
+
86
99
  /** @hidden Sends an argument list the transformer already packed; nothing when the list carries nothing. */
87
100
  _fire(payload?: buffer, blobs?: Array<defined>): void;
88
101
  }
@@ -106,11 +119,11 @@ export interface ClientReceiver<I extends unknown[]> extends RawClientReceiver<I
106
119
  }
107
120
 
108
121
  export type ServerHandler<E, R> = NetworkingObfuscationMarker & {
109
- [k in keyof Events<E>]: E[k] extends NetworkRaw<unknown>
122
+ [k in keyof Events<E>]: IsRawMember<E[k]> extends true
110
123
  ? RawServerSender<FunctionParameters<E[k]>>
111
- : ServerSender<FunctionParameters<E[k]>>;
124
+ : ServerSender<FunctionParameters<E[k]>, E[k]>;
112
125
  } & {
113
- [k in keyof Events<R>]: R[k] extends NetworkRaw<unknown>
126
+ [k in keyof Events<R>]: IsRawMember<R[k]> extends true
114
127
  ? RawServerReceiver<FunctionParameters<R[k]>>
115
128
  : ServerReceiver<FunctionParameters<R[k]>>;
116
129
  } & {
@@ -120,11 +133,11 @@ export type ServerHandler<E, R> = NetworkingObfuscationMarker & {
120
133
  };
121
134
 
122
135
  export type ClientHandler<E, R> = NetworkingObfuscationMarker & {
123
- [k in keyof Events<E>]: E[k] extends NetworkRaw<unknown>
136
+ [k in keyof Events<E>]: IsRawMember<E[k]> extends true
124
137
  ? RawClientSender<FunctionParameters<E[k]>>
125
- : ClientSender<FunctionParameters<E[k]>>;
138
+ : ClientSender<FunctionParameters<E[k]>, E[k]>;
126
139
  } & {
127
- [k in keyof Events<R>]: R[k] extends NetworkRaw<unknown>
140
+ [k in keyof Events<R>]: IsRawMember<R[k]> extends true
128
141
  ? RawClientReceiver<FunctionParameters<R[k]>>
129
142
  : ClientReceiver<FunctionParameters<R[k]>>;
130
143
  } & {
@@ -185,23 +198,22 @@ export type NamespaceMetadata<R, S> = Modding.Emit<{
185
198
  incomingIds: ObfuscateNames<keyof Events<R>>;
186
199
  incoming: IntrinsicObfuscate<{ [k in keyof Events<R>]: IntrinsicTupleGuards<Parameters<Events<R>[k]>> }>;
187
200
  incomingUnreliable: IntrinsicObfuscate<{
188
- [k in keyof Events<R>]: R[k] extends NetworkUnreliable<unknown> ? true : undefined;
201
+ [k in keyof Events<R>]: IntrinsicNetworkUnreliable<R[k], k>;
189
202
  }>;
190
203
 
191
204
  outgoingIds: ObfuscateNames<keyof Events<S>>;
192
205
  outgoingUnreliable: IntrinsicObfuscate<{
193
- [k in keyof Events<S>]: S[k] extends NetworkUnreliable<unknown> ? true : undefined;
206
+ [k in keyof Events<S>]: IntrinsicNetworkUnreliable<S[k], k>;
194
207
  }>;
195
208
 
196
209
  /**
197
- * Decoders for each incoming event's argument list, present only with `networking.serialization`
198
- * on, and absent for raw events and for lists that carry nothing. Outgoing lists are packed
199
- * inline where they are fired; nothing here can encode.
210
+ * Decoders for each incoming event's argument list, present for the events that are packed (all of
211
+ * them with `networking.serialization` on, else the serialized ones) and absent for raw events and
212
+ * for lists that carry nothing. Outgoing lists are packed inline where they are fired; nothing here
213
+ * can encode.
200
214
  */
201
215
  incomingSerializers: IntrinsicObfuscate<{
202
- [k in keyof Events<R>]: R[k] extends NetworkRaw<unknown>
203
- ? undefined
204
- : IntrinsicNetworkDecoder<Parameters<Events<R>[k]>>;
216
+ [k in keyof Events<R>]: IntrinsicNetworkDecoder<Parameters<Events<R>[k]>, R[k], k>;
205
217
  }>;
206
218
 
207
219
  namespaceIds: ObfuscateNames<keyof EventNamespaces<R> | keyof EventNamespaces<S>>;
@@ -3,12 +3,13 @@ import { FunctionSenderInterface } from "../function/createFunctionSender";
3
3
  import { FunctionCreateConfiguration } from "./types";
4
4
  export declare function createClientMethod(config: FunctionCreateConfiguration<unknown>, receiver?: FunctionReceiverInterface, sender?: FunctionSenderInterface): {
5
5
  readonly _flamework_send?: unknown[] | undefined;
6
+ readonly _flamework_fn?: unknown;
6
7
  _invoke: (payload?: buffer, blobs?: Array<defined>) => Promise<unknown>;
7
8
  _invokeWithTimeout: (timeout: number, payload?: buffer, blobs?: Array<defined>) => Promise<unknown>;
9
+ readonly _flamework_packing?: "plain" | undefined;
8
10
  invoke: (...args: unknown[]) => Promise<unknown>;
9
11
  invokeWithTimeout: (timeout: number, ...args: unknown[]) => Promise<unknown>;
10
12
  readonly _flamework_receive?: unknown[] | undefined;
11
- readonly _flamework_fn?: unknown;
12
13
  _setCallback: (callback: (...args: never[]) => unknown, pack: (value: unknown) => unknown) => void;
13
14
  setCallback: (callback: (...args: unknown[]) => unknown | Promise<unknown>) => void;
14
15
  predict: (...args: unknown[]) => Promise<unknown>;
@@ -47,7 +47,7 @@ local function createGenericHandler(globalName, namespaceName, receiverPrefix, s
47
47
  if not config.disableIncomingGuards and isReceiver then
48
48
  local guards = metadata.incoming[name]
49
49
  assert(guards)
50
- incomingGuards = createGuards(name, guards[1], guards[2], networkInfo, config.warnOnInvalidGuards, signals)
50
+ incomingGuards = createGuards(name, guards[1], guards[2], guards[3], networkInfo, config.warnOnInvalidGuards, signals)
51
51
  end
52
52
  -- A malformed serialized payload is reported like a failed guard, with no argument index.
53
53
  local onMalformed = function(player, message)
@@ -3,12 +3,13 @@ import { FunctionSenderInterface } from "../function/createFunctionSender";
3
3
  import { FunctionCreateConfiguration } from "./types";
4
4
  export declare function createServerMethod(config: FunctionCreateConfiguration<unknown>, receiver?: FunctionReceiverInterface, sender?: FunctionSenderInterface): {
5
5
  readonly _flamework_send?: unknown[] | undefined;
6
+ readonly _flamework_fn?: unknown;
6
7
  _invoke: (player: Player, payload?: buffer, blobs?: Array<defined>) => Promise<unknown>;
7
8
  _invokeWithTimeout: (player: Player, timeout: number, payload?: buffer, blobs?: Array<defined>) => Promise<unknown>;
9
+ readonly _flamework_packing?: "plain" | undefined;
8
10
  invoke: (player: Player, ...args: unknown[]) => Promise<unknown>;
9
11
  invokeWithTimeout: (player: Player, timeout: number, ...args: unknown[]) => Promise<unknown>;
10
12
  readonly _flamework_receive?: unknown[] | undefined;
11
- readonly _flamework_fn?: unknown;
12
13
  _setCallback: (callback: (player: Player, ...args: never[]) => unknown, pack: (value: unknown) => unknown) => void;
13
14
  setCallback: (callback: (player: Player, ...args: unknown[]) => unknown | Promise<unknown>) => void;
14
15
  predict: (player: Player, ...args: unknown[]) => Promise<unknown>;
@@ -6,8 +6,9 @@ import {
6
6
  IntrinsicNetworkDecoder,
7
7
  IntrinsicNetworkResultDecoder,
8
8
  IntrinsicObfuscate,
9
+ IsRawMember,
9
10
  NetworkingObfuscationMarker,
10
- NetworkRaw,
11
+ NetworkPacking,
11
12
  ObfuscateNames,
12
13
  } from "../types";
13
14
  import { FunctionNetworkingEvents } from "../handlers";
@@ -19,9 +20,12 @@ import { Modding } from "@flamework-experimental/core";
19
20
  * A sender declared `Networking.Raw`: its arguments and the result travel as they are. Without the
20
21
  * hidden marker below, no call site packs them and the peer runs no decoder.
21
22
  */
22
- export interface RawServerSender<I extends unknown[], O> {
23
+ export interface RawServerSender<I extends unknown[], O, M = "raw"> {
23
24
  (player: Player, ...args: I): Promise<O>;
24
25
 
26
+ /** @hidden How the member is packed (see `NetworkPacking`), which keeps differently packed members apart in a union. */
27
+ readonly _flamework_packing?: M;
28
+
25
29
  /**
26
30
  * Sends this request to the specified player.
27
31
  * @param player The player that will receive this event
@@ -36,10 +40,17 @@ export interface RawServerSender<I extends unknown[], O> {
36
40
  invokeWithTimeout(player: Player, timeout: number, ...args: I): Promise<O>;
37
41
  }
38
42
 
39
- export interface ServerSender<I extends unknown[], O> extends RawServerSender<I, O> {
43
+ export interface ServerSender<I extends unknown[], O, F = unknown, M = NetworkPacking<F>> extends RawServerSender<
44
+ I,
45
+ O,
46
+ M
47
+ > {
40
48
  /** @hidden Marks a sender for the transformer, which packs its arguments at each call site. */
41
49
  readonly _flamework_send?: I;
42
50
 
51
+ /** @hidden The declared function type, whose markers (`Serialized`) say whether its call sites pack. */
52
+ readonly _flamework_fn?: F;
53
+
43
54
  /** @hidden Sends an argument list the transformer already packed; nothing when the list carries nothing. */
44
55
  _invoke(player: Player, payload?: buffer, blobs?: Array<defined>): Promise<O>;
45
56
 
@@ -47,7 +58,10 @@ export interface ServerSender<I extends unknown[], O> extends RawServerSender<I,
47
58
  _invokeWithTimeout(player: Player, timeout: number, payload?: buffer, blobs?: Array<defined>): Promise<O>;
48
59
  }
49
60
 
50
- export interface RawServerReceiver<I extends unknown[], O> {
61
+ export interface RawServerReceiver<I extends unknown[], O, M = "raw"> {
62
+ /** @hidden How the member is packed (see `NetworkPacking`), which keeps differently packed members apart in a union. */
63
+ readonly _flamework_packing?: M;
64
+
51
65
  /**
52
66
  * Connect to a networking event.
53
67
  * @param event The event to connect to
@@ -62,20 +76,27 @@ export interface RawServerReceiver<I extends unknown[], O> {
62
76
  predict(player: Player, ...args: I): Promise<O>;
63
77
  }
64
78
 
65
- export interface ServerReceiver<I extends unknown[], O, F = unknown> extends RawServerReceiver<I, O> {
79
+ export interface ServerReceiver<I extends unknown[], O, F = unknown, M = NetworkPacking<F>> extends RawServerReceiver<
80
+ I,
81
+ O,
82
+ M
83
+ > {
66
84
  /** @hidden Marks a receiver for the transformer, which packs the callback's result at the call site. */
67
85
  readonly _flamework_receive?: I;
68
86
 
69
- /** @hidden The declared function type; its return type is what the transformer packs. */
87
+ /** @hidden The declared function type: its return type is what the transformer packs, its markers say how. */
70
88
  readonly _flamework_fn?: F;
71
89
 
72
90
  /** @hidden Registers a callback with `pack`, which turns a successful result into `[payload, blobs?]`. */
73
91
  _setCallback(callback: (player: Player, ...args: never[]) => unknown, pack: (value: unknown) => unknown): void;
74
92
  }
75
93
 
76
- export interface RawClientSender<I extends unknown[], O> {
94
+ export interface RawClientSender<I extends unknown[], O, M = "raw"> {
77
95
  (...args: I): Promise<O>;
78
96
 
97
+ /** @hidden How the member is packed (see `NetworkPacking`), which keeps differently packed members apart in a union. */
98
+ readonly _flamework_packing?: M;
99
+
79
100
  /**
80
101
  * Sends this request to the server.
81
102
  */
@@ -88,10 +109,17 @@ export interface RawClientSender<I extends unknown[], O> {
88
109
  invokeWithTimeout(timeout: number, ...args: I): Promise<O>;
89
110
  }
90
111
 
91
- export interface ClientSender<I extends unknown[], O> extends RawClientSender<I, O> {
112
+ export interface ClientSender<I extends unknown[], O, F = unknown, M = NetworkPacking<F>> extends RawClientSender<
113
+ I,
114
+ O,
115
+ M
116
+ > {
92
117
  /** @hidden Marks a sender for the transformer, which packs its arguments at each call site. */
93
118
  readonly _flamework_send?: I;
94
119
 
120
+ /** @hidden The declared function type, whose markers (`Serialized`) say whether its call sites pack. */
121
+ readonly _flamework_fn?: F;
122
+
95
123
  /** @hidden Sends an argument list the transformer already packed; nothing when the list carries nothing. */
96
124
  _invoke(payload?: buffer, blobs?: Array<defined>): Promise<O>;
97
125
 
@@ -99,7 +127,10 @@ export interface ClientSender<I extends unknown[], O> extends RawClientSender<I,
99
127
  _invokeWithTimeout(timeout: number, payload?: buffer, blobs?: Array<defined>): Promise<O>;
100
128
  }
101
129
 
102
- export interface RawClientReceiver<I extends unknown[], O> {
130
+ export interface RawClientReceiver<I extends unknown[], O, M = "raw"> {
131
+ /** @hidden How the member is packed (see `NetworkPacking`), which keeps differently packed members apart in a union. */
132
+ readonly _flamework_packing?: M;
133
+
103
134
  /**
104
135
  * Connect to a networking function.
105
136
  * @param event The function to connect to
@@ -113,11 +144,15 @@ export interface RawClientReceiver<I extends unknown[], O> {
113
144
  predict(...args: I): Promise<O>;
114
145
  }
115
146
 
116
- export interface ClientReceiver<I extends unknown[], O, F = unknown> extends RawClientReceiver<I, O> {
147
+ export interface ClientReceiver<I extends unknown[], O, F = unknown, M = NetworkPacking<F>> extends RawClientReceiver<
148
+ I,
149
+ O,
150
+ M
151
+ > {
117
152
  /** @hidden Marks a receiver for the transformer, which packs the callback's result at the call site. */
118
153
  readonly _flamework_receive?: I;
119
154
 
120
- /** @hidden The declared function type; its return type is what the transformer packs. */
155
+ /** @hidden The declared function type: its return type is what the transformer packs, its markers say how. */
121
156
  readonly _flamework_fn?: F;
122
157
 
123
158
  /** @hidden Registers a callback with `pack`, which turns a successful result into `[payload, blobs?]`. */
@@ -125,11 +160,11 @@ export interface ClientReceiver<I extends unknown[], O, F = unknown> extends Raw
125
160
  }
126
161
 
127
162
  export type ServerHandler<E, R> = NetworkingObfuscationMarker & {
128
- [k in keyof Functions<E>]: E[k] extends NetworkRaw<unknown>
163
+ [k in keyof Functions<E>]: IsRawMember<E[k]> extends true
129
164
  ? RawServerSender<FunctionParameters<E[k]>, FunctionReturn<E[k]>>
130
- : ServerSender<FunctionParameters<E[k]>, FunctionReturn<E[k]>>;
165
+ : ServerSender<FunctionParameters<E[k]>, FunctionReturn<E[k]>, E[k]>;
131
166
  } & {
132
- [k in keyof Functions<R>]: R[k] extends NetworkRaw<unknown>
167
+ [k in keyof Functions<R>]: IsRawMember<R[k]> extends true
133
168
  ? RawServerReceiver<FunctionParameters<R[k]>, FunctionReturn<R[k]>>
134
169
  : ServerReceiver<FunctionParameters<R[k]>, FunctionReturn<R[k]>, R[k]>;
135
170
  } & {
@@ -139,11 +174,11 @@ export type ServerHandler<E, R> = NetworkingObfuscationMarker & {
139
174
  };
140
175
 
141
176
  export type ClientHandler<E, R> = NetworkingObfuscationMarker & {
142
- [k in keyof Functions<E>]: E[k] extends NetworkRaw<unknown>
177
+ [k in keyof Functions<E>]: IsRawMember<E[k]> extends true
143
178
  ? RawClientSender<FunctionParameters<E[k]>, FunctionReturn<E[k]>>
144
- : ClientSender<FunctionParameters<E[k]>, FunctionReturn<E[k]>>;
179
+ : ClientSender<FunctionParameters<E[k]>, FunctionReturn<E[k]>, E[k]>;
145
180
  } & {
146
- [k in keyof Functions<R>]: R[k] extends NetworkRaw<unknown>
181
+ [k in keyof Functions<R>]: IsRawMember<R[k]> extends true
147
182
  ? RawClientReceiver<FunctionParameters<R[k]>, FunctionReturn<R[k]>>
148
183
  : ClientReceiver<FunctionParameters<R[k]>, FunctionReturn<R[k]>, R[k]>;
149
184
  } & {
@@ -255,17 +290,16 @@ export type NamespaceMetadata<R, S> = Modding.Emit<{
255
290
  outgoing: IntrinsicObfuscate<{ [k in keyof Functions<S>]: Modding.Target.Guard<Awaited<ReturnType<S[k]>>> }>;
256
291
 
257
292
  /**
258
- * Decoders, present only with `networking.serialization` on and absent for raw functions: the
259
- * argument lists of requests this realm receives and the responses to requests it sends. Requests
260
- * and results are packed inline where they are produced.
293
+ * Decoders for the functions that are packed (all of them with `networking.serialization` on, else
294
+ * the serialized ones), absent for raw functions: the argument lists of requests this realm receives
295
+ * and the responses to requests it sends. Requests and results are packed inline where they are
296
+ * produced.
261
297
  */
262
298
  incomingSerializers: IntrinsicObfuscate<{
263
- [k in keyof Functions<R>]: R[k] extends NetworkRaw<unknown>
264
- ? undefined
265
- : IntrinsicNetworkDecoder<Parameters<R[k]>>;
299
+ [k in keyof Functions<R>]: IntrinsicNetworkDecoder<Parameters<R[k]>, R[k], k>;
266
300
  }>;
267
301
  outgoingResults: IntrinsicObfuscate<{
268
- [k in keyof Functions<S>]: S[k] extends NetworkRaw<unknown> ? undefined : IntrinsicNetworkResultDecoder<S[k]>;
302
+ [k in keyof Functions<S>]: IntrinsicNetworkResultDecoder<S[k], k>;
269
303
  }>;
270
304
 
271
305
  namespaceIds: ObfuscateNames<keyof FunctionNamespaces<R> | keyof FunctionNamespaces<S>>;
package/out/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { Skip as NetworkingSkip } from "./middleware/skip";
4
4
  import { NetworkingFunctionError } from "./function/errors";
5
5
  import { EventMiddleware as _EventMiddleware, FunctionMiddleware as _FunctionMiddleware } from "./middleware/types";
6
6
  import { SignalConnection as _SignalConnection } from "./util/signal";
7
- import { NetworkRaw, NetworkUnreliable } from "./types";
7
+ import { NetworkInfo as _NetworkInfo, NetworkRaw, NetworkSerialized, NetworkUnreliable } from "./types";
8
8
  import type { Modding } from "@flamework-experimental/core";
9
9
  export declare namespace Networking {
10
10
  /**
@@ -44,6 +44,27 @@ export declare namespace Networking {
44
44
  * A function whose requests and results bypass serialization; see {@link RawReliable}.
45
45
  */
46
46
  type Raw<T> = NetworkRaw<T>;
47
+ /**
48
+ * Packs this event's arguments into a buffer even when `networking.serialization` is off, exactly
49
+ * as the switch would. With the switch on, it changes nothing.
50
+ *
51
+ * `Unreliable<Serialized<T>>` and `Serialized<Unreliable<T>>` are the same as `SerializedUnreliable<T>`.
52
+ * It cannot be combined with `Raw`.
53
+ */
54
+ type SerializedReliable<T> = NetworkSerialized<T>;
55
+ /**
56
+ * An unreliable event whose arguments are packed into a buffer; see {@link SerializedReliable}.
57
+ */
58
+ type SerializedUnreliable<T> = NetworkUnreliable<NetworkSerialized<T>>;
59
+ /**
60
+ * A function whose requests and results are packed into a buffer; see {@link SerializedReliable}.
61
+ */
62
+ type Serialized<T> = NetworkSerialized<T>;
63
+ /**
64
+ * What a middleware factory receives as its second argument: the event or function's `name`,
65
+ * `globalName` and `eventType`.
66
+ */
67
+ type NetworkInfo = _NetworkInfo;
47
68
  /**
48
69
  * A function that generates an event middleware.
49
70
  */
package/out/init.luau CHANGED
@@ -60,6 +60,31 @@ do
60
60
  *
61
61
  * A function whose requests and results bypass serialization; see {@link RawReliable}.
62
62
 
63
+ ]]
64
+ --[[
65
+ *
66
+ * Packs this event's arguments into a buffer even when `networking.serialization` is off, exactly
67
+ * as the switch would. With the switch on, it changes nothing.
68
+ *
69
+ * `Unreliable<Serialized<T>>` and `Serialized<Unreliable<T>>` are the same as `SerializedUnreliable<T>`.
70
+ * It cannot be combined with `Raw`.
71
+
72
+ ]]
73
+ --[[
74
+ *
75
+ * An unreliable event whose arguments are packed into a buffer; see {@link SerializedReliable}.
76
+
77
+ ]]
78
+ --[[
79
+ *
80
+ * A function whose requests and results are packed into a buffer; see {@link SerializedReliable}.
81
+
82
+ ]]
83
+ --[[
84
+ *
85
+ * What a middleware factory receives as its second argument: the event or function's `name`,
86
+ * `globalName` and `eventType`.
87
+
63
88
  ]]
64
89
  --[[
65
90
  *
@@ -7,4 +7,4 @@ import { Guards } from "./processor";
7
7
  * The generated argument checks of one event or function, for the receive pipeline, which runs them
8
8
  * ahead of all user middleware. A failure warns (with `warnOnInvalid`) and fires `onBadRequest`.
9
9
  */
10
- export declare function createGuards(name: string, fixedParameters: t.check<unknown>[], restParameter: t.check<unknown> | undefined, networkInfo: NetworkInfo, warnOnInvalid: boolean, signals: SignalContainer<EventNetworkingEvents>): Guards;
10
+ export declare function createGuards(name: string, fixedParameters: t.check<unknown>[], restParameter: t.check<unknown> | undefined, parametersAfterRest: t.check<unknown>[] | undefined, networkInfo: NetworkInfo, warnOnInvalid: boolean, signals: SignalContainer<EventNetworkingEvents>): Guards;
@@ -7,10 +7,11 @@ local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Pl
7
7
  * ahead of all user middleware. A failure warns (with `warnOnInvalid`) and fires `onBadRequest`.
8
8
 
9
9
  ]]
10
- local function createGuards(name, fixedParameters, restParameter, networkInfo, warnOnInvalid, signals)
10
+ local function createGuards(name, fixedParameters, restParameter, parametersAfterRest, networkInfo, warnOnInvalid, signals)
11
11
  return {
12
12
  fixed = fixedParameters,
13
13
  rest = restParameter,
14
+ after = parametersAfterRest,
14
15
  reject = function(player, index, value)
15
16
  if warnOnInvalid then
16
17
  if player then
@@ -15,6 +15,9 @@ export interface Guards {
15
15
  /** Checks every argument past the fixed ones, for a rest parameter. */
16
16
  rest: t.check<unknown> | undefined;
17
17
 
18
+ /** With a rest parameter, `after[i]` checks the argument `i` places after the rest (`(...args: [...B[], C])`). */
19
+ after?: ReadonlyArray<t.check<unknown>>;
20
+
18
21
  /** Called with the first argument that failed, by its 0-based index. */
19
22
  reject: (player: Player | undefined, index: number, value: unknown) => void;
20
23
  }
@@ -74,8 +74,9 @@ end
74
74
  that does not call it drops the message there. `final` ends the chain.
75
75
 
76
76
  `guards`, when given, check the arguments before any middleware runs: `fixed[i]` checks argument
77
- `i`, `rest` every one past them. The first one that fails is reported through `reject` with its
78
- 0-based index, and the processor returns `rejected` without calling anything else.
77
+ `i`, `rest` every one past them, and `after` (for a rest parameter with parameters after it) the
78
+ last ones. The first one that fails is reported through `reject` with its 0-based index, and the
79
+ processor returns `rejected` without calling anything else.
79
80
 
80
81
  `cancelled` is what a Promise that was cancelled reads as (`Networking.Skip` for a function).
81
82
  ]]
@@ -105,9 +106,43 @@ local function createProcessor(
105
106
 
106
107
  local fixed = guards.fixed
107
108
  local rest = guards.rest
109
+ local after = guards.after
108
110
  local reject = guards.reject
109
111
  local fixedCount = #fixed
110
112
 
113
+ if after ~= nil and #after > 0 then
114
+ local afterCount = #after
115
+
116
+ -- `(...args: [A, ...B[], C])`: A first, C last, and every argument between them a B.
117
+ return function(player, ...)
118
+ local count = select("#", ...)
119
+ while count > 0 and select(count, ...) == nil do
120
+ count -= 1
121
+ end
122
+
123
+ local total = math.max(fixedCount + afterCount, count)
124
+ local restEnd = total - afterCount
125
+ for index = 1, total do
126
+ local guard
127
+ if index <= fixedCount then
128
+ guard = fixed[index]
129
+ elseif index > restEnd then
130
+ guard = after[index - restEnd]
131
+ else
132
+ guard = rest
133
+ end
134
+
135
+ local value = select(index, ...)
136
+ if guard ~= nil and not guard(value) then
137
+ reject(player, index - 1, value)
138
+ return rejected
139
+ end
140
+ end
141
+
142
+ return follow(callTrimmed(head, player, ...), cancelled)
143
+ end
144
+ end
145
+
111
146
  return function(player, ...)
112
147
  -- Checked up to the last value: a trailing nil is an argument that was not passed.
113
148
  local count = select("#", ...)
package/out/types.d.ts CHANGED
@@ -26,6 +26,31 @@ export type NetworkUnreliable<T> = T & { _flamework_unreliable: never };
26
26
  */
27
27
  export type NetworkRaw<T> = T & { _flamework_raw: never };
28
28
 
29
+ /**
30
+ * Marks an event or function whose values are packed into a buffer whether or not the project turns
31
+ * `networking.serialization` on: the call sites encode and the metadata decodes, exactly as the switch
32
+ * would have them do.
33
+ */
34
+ export type NetworkSerialized<T> = T & { _flamework_serialized: never };
35
+
36
+ /**
37
+ * A member declared raw and nothing else. One that is also serialized gets the marked handler
38
+ * instead, so that the transformer sees it where it is used and reports the conflict.
39
+ */
40
+ export type IsRawMember<T> =
41
+ T extends NetworkRaw<unknown> ? (T extends NetworkSerialized<unknown> ? false : true) : false;
42
+
43
+ /**
44
+ * How the member `F` is packed. Each sender and function receiver takes it as a type argument of its
45
+ * own and carries it as the hidden `_flamework_packing`, so members that are packed differently have
46
+ * unrelated handler types. (Worked out from `F` inside the interface, it would be compared through
47
+ * `F`, which a `Serialized` member's type extends.) A union of them, from a conditional or a helper
48
+ * that returns one of several members, then keeps every one of them rather than reducing to the one
49
+ * the others extend, and the transformer refuses a call that cannot pack for all of them.
50
+ */
51
+ export type NetworkPacking<F> =
52
+ IsRawMember<F> extends true ? "raw" : F extends NetworkSerialized<unknown> ? "serialized" : "plain";
53
+
29
54
  export interface NetworkingObfuscationMarker {
30
55
  /**
31
56
  * An internal marker type used to signify to Flamework to obfuscate access expressions.
@@ -53,26 +78,42 @@ export type IntrinsicObfuscateArray<T, V = T> = Modding.Intrinsic<"shuffle-array
53
78
  export type IntrinsicTupleGuards<T> = Modding.Intrinsic<"tuple-guards", [T], GuardType>;
54
79
 
55
80
  /**
56
- * Decode code for the argument list `T`, generated only when the project's flamework.config.json
57
- * enables `networking.serialization`; `undefined` otherwise, which passes values through as they
58
- * are. The matching encoding is generated inline at every call site, so no encoder exists at runtime.
81
+ * Decode code for the argument list `T` of the member `F`, generated when the member is packed: with
82
+ * the project's `networking.serialization` on (unless `F` is raw), or when `F` is serialized.
83
+ * `undefined` otherwise, which passes values through as they are. `K` is the member's name, for the
84
+ * transformer's messages. The matching encoding is generated inline at every call site, so no encoder
85
+ * exists at runtime.
59
86
  * @hidden Intrinsic feature not intended for users
60
87
  */
61
- export type IntrinsicNetworkDecoder<T extends Array<unknown>> = Modding.Intrinsic<
88
+ export type IntrinsicNetworkDecoder<T extends Array<unknown>, F = unknown, K = unknown> = Modding.Intrinsic<
62
89
  "network-decoder",
63
- [T],
90
+ [T, F, K],
64
91
  Serialization.Decoder<T> | undefined
65
92
  >;
66
93
 
67
94
  /**
68
95
  * Decode code for the result of the function type `F`, carried as a one-element list. Takes the
69
- * function type rather than its return type so that the return type as declared is known.
96
+ * function type rather than its return type so that the return type as declared is known, and so
97
+ * that its markers are; see {@link IntrinsicNetworkDecoder} for `K`.
70
98
  * @hidden Intrinsic feature not intended for users
71
99
  */
72
- export type IntrinsicNetworkResultDecoder<F> = Modding.Intrinsic<
100
+ export type IntrinsicNetworkResultDecoder<F, K = unknown> = Modding.Intrinsic<
73
101
  "network-result-decoder",
74
- [F],
102
+ [F, K],
75
103
  Serialization.Decoder | undefined
76
104
  >;
77
105
 
78
- type GuardType = [t.check<unknown>[], t.check<unknown> | undefined];
106
+ /**
107
+ * `true` for a member declared unreliable, `undefined` otherwise. An intrinsic rather than a
108
+ * conditional type so that every member of an event network, in both directions, passes through the
109
+ * transformer's check of its markers wherever a handler is created.
110
+ * @hidden Intrinsic feature not intended for users
111
+ */
112
+ export type IntrinsicNetworkUnreliable<F, K = unknown> = Modding.Intrinsic<
113
+ "network-unreliable",
114
+ [F, K],
115
+ true | undefined
116
+ >;
117
+
118
+ /** The guards of the arguments before a rest parameter, of the rest, and of any after it (`[A, ...B[], C]`). */
119
+ type GuardType = [t.check<unknown>[], t.check<unknown> | undefined, t.check<unknown>[]?];
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "@flamework-experimental/networking",
3
- "version": "2.0.0-alpha.3",
3
+ "version": "2.0.0-alpha.4",
4
4
  "main": "out/init.luau",
5
5
  "types": "out/index.d.ts",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Velover/ExperimentalFlameworkV2.git",
9
+ "directory": "packages/networking"
10
+ },
11
+ "homepage": "https://github.com/Velover/ExperimentalFlameworkV2#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/Velover/ExperimentalFlameworkV2/issues"
14
+ },
6
15
  "scripts": {
7
16
  "build": "rbxtsc",
8
17
  "watch": "rbxtsc -w"