@hopple/netcode 0.1.0 → 0.1.1-ci.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lockstep.js +1 -1
- package/package.json +8 -1
- package/session.d.ts +1 -1
- package/session.js +1 -1
- package/shared-BR4Zxz2l.js +0 -185
- package/shared-B_2uj6dc.d.ts +0 -70
package/README.md
CHANGED
|
@@ -25,9 +25,11 @@ whoever comes later. A game is welcome to write any part of this its own way.
|
|
|
25
25
|
|
|
26
26
|
## Bytes
|
|
27
27
|
|
|
28
|
-
A match's messages are bytes. `ByteWriter` and `ByteReader
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
A match's messages are bytes. `ByteWriter` and `ByteReader`, from
|
|
29
|
+
[`@hopple/bytes`](https://www.npmjs.com/package/@hopple/bytes) and exported
|
|
30
|
+
here as well, write numbers, strings and packed bits, and read them back
|
|
31
|
+
exactly: integers as varints (a byte under 128), floats as their bits,
|
|
32
|
+
strings as UTF-8.
|
|
31
33
|
|
|
32
34
|
```ts
|
|
33
35
|
import { ByteReader, ByteWriter } from '@hopple/netcode';
|
package/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ByteReader, ByteWriter } from "@hopple/bytes";
|
|
2
2
|
export { ByteReader, ByteWriter };
|
package/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ByteReader, ByteWriter } from "@hopple/bytes";
|
|
2
2
|
export { ByteReader, ByteWriter };
|
package/lockstep.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hopple/netcode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-ci.1",
|
|
4
4
|
"description": "What a Hopple game bundles to play a match over the network: exact byte encoding, a lobby, and a lockstep helper with rollback.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://hopple.app",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/pamarit/hopple.git"
|
|
10
|
+
},
|
|
7
11
|
"keywords": [
|
|
8
12
|
"hopple",
|
|
9
13
|
"game",
|
|
@@ -16,6 +20,9 @@
|
|
|
16
20
|
"publishConfig": {
|
|
17
21
|
"access": "public"
|
|
18
22
|
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@hopple/bytes": "^0.1.1-ci.1"
|
|
25
|
+
},
|
|
19
26
|
"peerDependencies": {
|
|
20
27
|
"@hopple/sdk": "^1.0.0-next.1",
|
|
21
28
|
"@hopple/sdk-preview-network": "^0.1.0"
|
package/session.d.ts
CHANGED
package/session.js
CHANGED
package/shared-BR4Zxz2l.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
//#region src/bytes.ts
|
|
2
|
-
/**
|
|
3
|
-
* Numbers and strings as bytes and back, exactly: what a game sends in a
|
|
4
|
-
* match, written once and read the same way on every device.
|
|
5
|
-
*
|
|
6
|
-
* Integers are varints — a byte for anything under 128 — so a message of small
|
|
7
|
-
* numbers stays small; floats are their eight bytes, little-endian, so they
|
|
8
|
-
* arrive with every bit they left with; strings are UTF-8.
|
|
9
|
-
*
|
|
10
|
-
* ```ts
|
|
11
|
-
* const out = new ByteWriter().uint(PICK).uint(fighter).bool(ready).finish();
|
|
12
|
-
* match.send(out);
|
|
13
|
-
*
|
|
14
|
-
* const read = new ByteReader(data);
|
|
15
|
-
* if (read.uint() === PICK) setPick(from, read.uint(), read.bool());
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* @module bytes
|
|
19
|
-
*/
|
|
20
|
-
const scratch = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
|
|
21
|
-
/**
|
|
22
|
-
* Builds a message.
|
|
23
|
-
*
|
|
24
|
-
* @group Bytes
|
|
25
|
-
*/
|
|
26
|
-
var ByteWriter = class {
|
|
27
|
-
buffer = /* @__PURE__ */ new Uint8Array(64);
|
|
28
|
-
length = 0;
|
|
29
|
-
room(more) {
|
|
30
|
-
if (this.length + more <= this.buffer.length) return;
|
|
31
|
-
const grown = new Uint8Array(Math.max(this.buffer.length * 2, this.length + more));
|
|
32
|
-
grown.set(this.buffer.subarray(0, this.length));
|
|
33
|
-
this.buffer = grown;
|
|
34
|
-
}
|
|
35
|
-
/** One byte, 0..255. */
|
|
36
|
-
byte(value) {
|
|
37
|
-
this.room(1);
|
|
38
|
-
this.buffer[this.length++] = value & 255;
|
|
39
|
-
return this;
|
|
40
|
-
}
|
|
41
|
-
/** A whole number from 0 up to 2⁵³: one byte under 128, two under 16384, and so on. */
|
|
42
|
-
uint(value) {
|
|
43
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`ByteWriter.uint: ${value} is not a whole number from 0 to 2^53.`);
|
|
44
|
-
this.room(8);
|
|
45
|
-
while (value >= 128) {
|
|
46
|
-
this.buffer[this.length++] = value % 128 | 128;
|
|
47
|
-
value = Math.floor(value / 128);
|
|
48
|
-
}
|
|
49
|
-
this.buffer[this.length++] = value;
|
|
50
|
-
return this;
|
|
51
|
-
}
|
|
52
|
-
/** An integer from −2⁵² up to, not including, 2⁵², small ones in a byte whichever their sign. */
|
|
53
|
-
int(value) {
|
|
54
|
-
if (!Number.isSafeInteger(value) || value < -4503599627370496 || value >= 4503599627370496) throw new RangeError(`ByteWriter.int: ${value} is not an integer from -2^52 up to 2^52.`);
|
|
55
|
-
return this.uint(value >= 0 ? value * 2 : -value * 2 - 1);
|
|
56
|
-
}
|
|
57
|
-
bool(value) {
|
|
58
|
-
return this.byte(value ? 1 : 0);
|
|
59
|
-
}
|
|
60
|
-
/** A number's eight bytes, exactly. */
|
|
61
|
-
float64(value) {
|
|
62
|
-
this.room(8);
|
|
63
|
-
scratch.setFloat64(0, value, true);
|
|
64
|
-
for (let i = 0; i < 8; i++) this.buffer[this.length++] = scratch.getUint8(i);
|
|
65
|
-
return this;
|
|
66
|
-
}
|
|
67
|
-
/** Bytes, after their length. */
|
|
68
|
-
bytes(value) {
|
|
69
|
-
this.uint(value.length);
|
|
70
|
-
this.room(value.length);
|
|
71
|
-
this.buffer.set(value, this.length);
|
|
72
|
-
this.length += value.length;
|
|
73
|
-
return this;
|
|
74
|
-
}
|
|
75
|
-
/** A string as UTF-8, after its length in bytes. A lone surrogate is written as U+FFFD. */
|
|
76
|
-
string(value) {
|
|
77
|
-
const encoded = [];
|
|
78
|
-
for (let i = 0; i < value.length; i++) {
|
|
79
|
-
let c = value.charCodeAt(i);
|
|
80
|
-
if (c >= 55296 && c <= 56319 && i + 1 < value.length) {
|
|
81
|
-
const d = value.charCodeAt(i + 1);
|
|
82
|
-
if (d >= 56320 && d <= 57343) {
|
|
83
|
-
c = 65536 + (c - 55296 << 10) + (d - 56320);
|
|
84
|
-
i++;
|
|
85
|
-
} else c = 65533;
|
|
86
|
-
} else if (c >= 55296 && c <= 57343) c = 65533;
|
|
87
|
-
if (c < 128) encoded.push(c);
|
|
88
|
-
else if (c < 2048) encoded.push(192 | c >> 6, 128 | c & 63);
|
|
89
|
-
else if (c < 65536) encoded.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
|
|
90
|
-
else encoded.push(240 | c >> 18, 128 | c >> 12 & 63, 128 | c >> 6 & 63, 128 | c & 63);
|
|
91
|
-
}
|
|
92
|
-
return this.bytes(Uint8Array.from(encoded));
|
|
93
|
-
}
|
|
94
|
-
/** What has been written, as a message of its own. */
|
|
95
|
-
finish() {
|
|
96
|
-
return this.buffer.slice(0, this.length);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
/**
|
|
100
|
-
* Reads a message, in the order it was written. Reading past its end, or a
|
|
101
|
-
* value that is not what it should be, throws a `RangeError`: a message from
|
|
102
|
-
* another device is checked, not believed.
|
|
103
|
-
*
|
|
104
|
-
* @group Bytes
|
|
105
|
-
*/
|
|
106
|
-
var ByteReader = class {
|
|
107
|
-
at = 0;
|
|
108
|
-
data;
|
|
109
|
-
constructor(data) {
|
|
110
|
-
this.data = data;
|
|
111
|
-
}
|
|
112
|
-
/** Whether everything has been read. */
|
|
113
|
-
get done() {
|
|
114
|
-
return this.at >= this.data.length;
|
|
115
|
-
}
|
|
116
|
-
/** How many bytes are left. */
|
|
117
|
-
get left() {
|
|
118
|
-
return this.data.length - this.at;
|
|
119
|
-
}
|
|
120
|
-
byte() {
|
|
121
|
-
if (this.at >= this.data.length) throw new RangeError("ByteReader: read past the end.");
|
|
122
|
-
return this.data[this.at++];
|
|
123
|
-
}
|
|
124
|
-
uint() {
|
|
125
|
-
let value = 0;
|
|
126
|
-
let scale = 1;
|
|
127
|
-
for (let i = 0; i < 8; i++) {
|
|
128
|
-
const byte = this.byte();
|
|
129
|
-
value += (byte & 127) * scale;
|
|
130
|
-
if (byte < 128) {
|
|
131
|
-
if (!Number.isSafeInteger(value)) break;
|
|
132
|
-
return value;
|
|
133
|
-
}
|
|
134
|
-
scale *= 128;
|
|
135
|
-
}
|
|
136
|
-
throw new RangeError("ByteReader: not a whole number.");
|
|
137
|
-
}
|
|
138
|
-
int() {
|
|
139
|
-
const zigzag = this.uint();
|
|
140
|
-
return zigzag % 2 === 0 ? zigzag / 2 : -(zigzag + 1) / 2;
|
|
141
|
-
}
|
|
142
|
-
bool() {
|
|
143
|
-
const byte = this.byte();
|
|
144
|
-
if (byte > 1) throw new RangeError("ByteReader: not a boolean.");
|
|
145
|
-
return byte === 1;
|
|
146
|
-
}
|
|
147
|
-
float64() {
|
|
148
|
-
if (this.left < 8) throw new RangeError("ByteReader: read past the end.");
|
|
149
|
-
for (let i = 0; i < 8; i++) scratch.setUint8(i, this.data[this.at++]);
|
|
150
|
-
return scratch.getFloat64(0, true);
|
|
151
|
-
}
|
|
152
|
-
bytes() {
|
|
153
|
-
const length = this.uint();
|
|
154
|
-
if (length > this.left) throw new RangeError("ByteReader: read past the end.");
|
|
155
|
-
const out = this.data.slice(this.at, this.at + length);
|
|
156
|
-
this.at += length;
|
|
157
|
-
return out;
|
|
158
|
-
}
|
|
159
|
-
string() {
|
|
160
|
-
const bytes = this.bytes();
|
|
161
|
-
let out = "";
|
|
162
|
-
for (let i = 0; i < bytes.length;) {
|
|
163
|
-
const b = bytes[i++];
|
|
164
|
-
let c;
|
|
165
|
-
if (b < 128) c = b;
|
|
166
|
-
else if (b >= 194 && b < 224) c = (b & 31) << 6 | this.tail(bytes, i++);
|
|
167
|
-
else if (b >= 224 && b < 240) {
|
|
168
|
-
c = (b & 15) << 12 | this.tail(bytes, i++) << 6 | this.tail(bytes, i++);
|
|
169
|
-
if (c < 2048 || c >= 55296 && c <= 57343) throw new RangeError("ByteReader: not UTF-8.");
|
|
170
|
-
} else if (b >= 240 && b < 245) {
|
|
171
|
-
c = (b & 7) << 18 | this.tail(bytes, i++) << 12 | this.tail(bytes, i++) << 6 | this.tail(bytes, i++);
|
|
172
|
-
if (c < 65536 || c > 1114111) throw new RangeError("ByteReader: not UTF-8.");
|
|
173
|
-
} else throw new RangeError("ByteReader: not UTF-8.");
|
|
174
|
-
out += String.fromCodePoint(c);
|
|
175
|
-
}
|
|
176
|
-
return out;
|
|
177
|
-
}
|
|
178
|
-
tail(bytes, i) {
|
|
179
|
-
const b = bytes[i];
|
|
180
|
-
if (b === void 0 || (b & 192) !== 128) throw new RangeError("ByteReader: not UTF-8.");
|
|
181
|
-
return b & 63;
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
//#endregion
|
|
185
|
-
export { ByteWriter as n, ByteReader as t };
|
package/shared-B_2uj6dc.d.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
//#region src/bytes.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Numbers and strings as bytes and back, exactly: what a game sends in a
|
|
4
|
-
* match, written once and read the same way on every device.
|
|
5
|
-
*
|
|
6
|
-
* Integers are varints — a byte for anything under 128 — so a message of small
|
|
7
|
-
* numbers stays small; floats are their eight bytes, little-endian, so they
|
|
8
|
-
* arrive with every bit they left with; strings are UTF-8.
|
|
9
|
-
*
|
|
10
|
-
* ```ts
|
|
11
|
-
* const out = new ByteWriter().uint(PICK).uint(fighter).bool(ready).finish();
|
|
12
|
-
* match.send(out);
|
|
13
|
-
*
|
|
14
|
-
* const read = new ByteReader(data);
|
|
15
|
-
* if (read.uint() === PICK) setPick(from, read.uint(), read.bool());
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* @module bytes
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Builds a message.
|
|
22
|
-
*
|
|
23
|
-
* @group Bytes
|
|
24
|
-
*/
|
|
25
|
-
declare class ByteWriter {
|
|
26
|
-
private buffer;
|
|
27
|
-
private length;
|
|
28
|
-
private room;
|
|
29
|
-
/** One byte, 0..255. */
|
|
30
|
-
byte(value: number): this;
|
|
31
|
-
/** A whole number from 0 up to 2⁵³: one byte under 128, two under 16384, and so on. */
|
|
32
|
-
uint(value: number): this;
|
|
33
|
-
/** An integer from −2⁵² up to, not including, 2⁵², small ones in a byte whichever their sign. */
|
|
34
|
-
int(value: number): this;
|
|
35
|
-
bool(value: boolean): this;
|
|
36
|
-
/** A number's eight bytes, exactly. */
|
|
37
|
-
float64(value: number): this;
|
|
38
|
-
/** Bytes, after their length. */
|
|
39
|
-
bytes(value: Uint8Array): this;
|
|
40
|
-
/** A string as UTF-8, after its length in bytes. A lone surrogate is written as U+FFFD. */
|
|
41
|
-
string(value: string): this;
|
|
42
|
-
/** What has been written, as a message of its own. */
|
|
43
|
-
finish(): Uint8Array;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Reads a message, in the order it was written. Reading past its end, or a
|
|
47
|
-
* value that is not what it should be, throws a `RangeError`: a message from
|
|
48
|
-
* another device is checked, not believed.
|
|
49
|
-
*
|
|
50
|
-
* @group Bytes
|
|
51
|
-
*/
|
|
52
|
-
declare class ByteReader {
|
|
53
|
-
private at;
|
|
54
|
-
private readonly data;
|
|
55
|
-
constructor(data: Uint8Array);
|
|
56
|
-
/** Whether everything has been read. */
|
|
57
|
-
get done(): boolean;
|
|
58
|
-
/** How many bytes are left. */
|
|
59
|
-
get left(): number;
|
|
60
|
-
byte(): number;
|
|
61
|
-
uint(): number;
|
|
62
|
-
int(): number;
|
|
63
|
-
bool(): boolean;
|
|
64
|
-
float64(): number;
|
|
65
|
-
bytes(): Uint8Array;
|
|
66
|
-
string(): string;
|
|
67
|
-
private tail;
|
|
68
|
-
}
|
|
69
|
-
//#endregion
|
|
70
|
-
export { ByteWriter as n, ByteReader as t };
|