multyx-client 0.1.6 → 0.1.8
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/dist/controller.js +5 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/items/list.d.ts +5 -0
- package/dist/items/list.js +64 -18
- package/dist/items/object.d.ts +4 -0
- package/dist/items/object.js +50 -12
- package/dist/items/value.d.ts +10 -0
- package/dist/items/value.js +55 -1
- package/dist/message.d.ts +2 -14
- package/dist/message.js +3 -3
- package/dist/utils.js +41 -12
- package/multyx.js +1 -1
- package/package.json +1 -1
- package/src/controller.ts +5 -5
- package/src/index.ts +4 -2
- package/src/items/list.ts +78 -22
- package/src/items/object.ts +59 -13
- package/src/items/value.ts +75 -1
- package/src/message.ts +3 -3
- package/src/utils.ts +42 -11
package/dist/controller.js
CHANGED
|
@@ -19,7 +19,7 @@ class Controller {
|
|
|
19
19
|
};
|
|
20
20
|
document.addEventListener('keydown', e => {
|
|
21
21
|
if (this.preventDefault)
|
|
22
|
-
e.preventDefault;
|
|
22
|
+
e.preventDefault();
|
|
23
23
|
const key = e.key.toLowerCase();
|
|
24
24
|
// When holding down key
|
|
25
25
|
if (this.keys[key] && this.listening.has('keyhold')) {
|
|
@@ -40,7 +40,7 @@ class Controller {
|
|
|
40
40
|
});
|
|
41
41
|
document.addEventListener('keyup', e => {
|
|
42
42
|
if (this.preventDefault)
|
|
43
|
-
e.preventDefault;
|
|
43
|
+
e.preventDefault();
|
|
44
44
|
const key = e.key.toLowerCase();
|
|
45
45
|
delete this.keys[key];
|
|
46
46
|
delete this.keys[e.code];
|
|
@@ -52,7 +52,7 @@ class Controller {
|
|
|
52
52
|
// Mouse input events
|
|
53
53
|
document.addEventListener('mousedown', e => {
|
|
54
54
|
if (this.preventDefault)
|
|
55
|
-
e.preventDefault;
|
|
55
|
+
e.preventDefault();
|
|
56
56
|
if (this.mouseGetter) {
|
|
57
57
|
const mouse = this.mouseGetter();
|
|
58
58
|
this.mouse.x = mouse.x;
|
|
@@ -70,7 +70,7 @@ class Controller {
|
|
|
70
70
|
});
|
|
71
71
|
document.addEventListener('mouseup', e => {
|
|
72
72
|
if (this.preventDefault)
|
|
73
|
-
e.preventDefault;
|
|
73
|
+
e.preventDefault();
|
|
74
74
|
if (this.mouseGetter) {
|
|
75
75
|
const mouse = this.mouseGetter();
|
|
76
76
|
this.mouse.x = mouse.x;
|
|
@@ -88,7 +88,7 @@ class Controller {
|
|
|
88
88
|
});
|
|
89
89
|
document.addEventListener('mousemove', e => {
|
|
90
90
|
if (this.preventDefault)
|
|
91
|
-
e.preventDefault;
|
|
91
|
+
e.preventDefault();
|
|
92
92
|
if (this.mouseGetter) {
|
|
93
93
|
const mouse = this.mouseGetter();
|
|
94
94
|
this.mouse.x = mouse.x;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Add, Done } from './utils';
|
|
1
|
+
import { Add, Done, Interpolate } from './utils';
|
|
2
2
|
import { RawObject } from "./types";
|
|
3
3
|
import { Controller } from "./controller";
|
|
4
4
|
import { MultyxClientObject } from "./items";
|
|
@@ -27,6 +27,7 @@ export default class Multyx {
|
|
|
27
27
|
static Native: symbol;
|
|
28
28
|
static Custom: symbol;
|
|
29
29
|
static Any: symbol;
|
|
30
|
+
static Interpolate: typeof Interpolate;
|
|
30
31
|
constructor(options?: Options, callback?: () => void);
|
|
31
32
|
/**
|
|
32
33
|
* Listen for a message from the server
|
package/dist/index.js
CHANGED
package/dist/items/list.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import Multyx from '../';
|
|
|
2
2
|
import { type MultyxClientItem, type MultyxClientObject, MultyxClientValue } from '.';
|
|
3
3
|
import { Edit, EditWrapper, Unpack } from '../utils';
|
|
4
4
|
export default class MultyxClientList {
|
|
5
|
+
readonly type = "list";
|
|
5
6
|
protected list: MultyxClientItem[];
|
|
6
7
|
private multyx;
|
|
7
8
|
propertyPath: string[];
|
|
@@ -62,4 +63,8 @@ export default class MultyxClientList {
|
|
|
62
63
|
toString: () => string;
|
|
63
64
|
valueOf: () => any[];
|
|
64
65
|
[Symbol.toPrimitive]: () => any[];
|
|
66
|
+
hydrateFromServer(values: any[]): void;
|
|
67
|
+
private tryApplyServerValue;
|
|
68
|
+
private notifyIndexWaiters;
|
|
69
|
+
private enqueueEditCallbacks;
|
|
65
70
|
}
|
package/dist/items/list.js
CHANGED
|
@@ -8,6 +8,7 @@ const _1 = require(".");
|
|
|
8
8
|
const utils_1 = require("../utils");
|
|
9
9
|
const router_1 = __importDefault(require("./router"));
|
|
10
10
|
const message_1 = require("../message");
|
|
11
|
+
const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11
12
|
class MultyxClientList {
|
|
12
13
|
addEditCallback(callback) {
|
|
13
14
|
this.editCallbacks.push(callback);
|
|
@@ -73,6 +74,7 @@ class MultyxClientList {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
constructor(multyx, list, propertyPath = [], editable) {
|
|
77
|
+
this.type = 'list';
|
|
76
78
|
this.editCallbacks = [];
|
|
77
79
|
this.toString = () => this.value.toString();
|
|
78
80
|
this.valueOf = () => this.value;
|
|
@@ -151,40 +153,39 @@ class MultyxClientList {
|
|
|
151
153
|
this.set(parseInt(path[0]), new utils_1.EditWrapper({}));
|
|
152
154
|
next = this.get(parseInt(path[0]));
|
|
153
155
|
}
|
|
156
|
+
if (!next || next instanceof _1.MultyxClientValue) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
154
159
|
return next.set(path.slice(1), value);
|
|
155
160
|
}
|
|
156
161
|
set(index, value) {
|
|
157
|
-
var _b, _c;
|
|
158
162
|
if (Array.isArray(index))
|
|
159
163
|
return this.recursiveSet(index, value);
|
|
160
164
|
const oldValue = this.get(index);
|
|
161
165
|
const serverSet = value instanceof utils_1.EditWrapper;
|
|
162
166
|
const allowed = serverSet || this.editable;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (value === undefined)
|
|
167
|
+
const incoming = (serverSet || (0, _1.IsMultyxClientItem)(value)) ? value.value : value;
|
|
168
|
+
if (incoming === undefined)
|
|
166
169
|
return this.delete(index, serverSet);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
if (serverSet && this.tryApplyServerValue(index, incoming, oldValue)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
// If value is a MultyxClientValue, set the value directly
|
|
174
|
+
if (this.list[index] instanceof _1.MultyxClientValue && (typeof incoming !== 'object' || incoming === null)) {
|
|
175
|
+
const result = this.list[index].set(serverSet ? new utils_1.EditWrapper(incoming) : incoming);
|
|
176
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
177
|
+
return result;
|
|
170
178
|
}
|
|
171
179
|
// Attempting to edit property not editable to client
|
|
172
180
|
if (!allowed) {
|
|
173
181
|
if (this.multyx.options.verbose) {
|
|
174
|
-
console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + index}' to ${
|
|
182
|
+
console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + index}' to ${incoming}`);
|
|
175
183
|
}
|
|
176
184
|
return false;
|
|
177
185
|
}
|
|
178
|
-
this.list[index] = new ((0, router_1.default)(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.multyx[utils_1.Done].push(...((_c = (_b = this.multyx.events.get(propSymbol)) === null || _b === void 0 ? void 0 : _b.map(e => () => e(this.list[index]))) !== null && _c !== void 0 ? _c : []));
|
|
182
|
-
}
|
|
183
|
-
// We have to push into queue, since object may not be fully created
|
|
184
|
-
// and there may still be more updates to parse
|
|
185
|
-
for (const listener of this.editCallbacks) {
|
|
186
|
-
this.multyx[utils_1.Add](() => listener(index, this.get(index), oldValue));
|
|
187
|
-
}
|
|
186
|
+
this.list[index] = new ((0, router_1.default)(incoming))(this.multyx, serverSet ? new utils_1.EditWrapper(incoming) : incoming, [...this.propertyPath, index.toString()], this.editable);
|
|
187
|
+
this.notifyIndexWaiters(index);
|
|
188
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
188
189
|
return true;
|
|
189
190
|
}
|
|
190
191
|
delete(index, native = false) {
|
|
@@ -403,6 +404,51 @@ class MultyxClientList {
|
|
|
403
404
|
values[i] = this.get(i);
|
|
404
405
|
return values[Symbol.iterator]();
|
|
405
406
|
}
|
|
407
|
+
hydrateFromServer(values) {
|
|
408
|
+
if (!Array.isArray(values))
|
|
409
|
+
return;
|
|
410
|
+
for (let i = 0; i < values.length; i++) {
|
|
411
|
+
this.set(i, new utils_1.EditWrapper(values[i]));
|
|
412
|
+
}
|
|
413
|
+
for (let i = values.length; i < this.length; i++) {
|
|
414
|
+
this.delete(i, true);
|
|
415
|
+
}
|
|
416
|
+
this.length = values.length;
|
|
417
|
+
}
|
|
418
|
+
tryApplyServerValue(index, incoming, oldValue) {
|
|
419
|
+
const current = this.list[index];
|
|
420
|
+
if (!current)
|
|
421
|
+
return false;
|
|
422
|
+
if (current instanceof _1.MultyxClientValue && (typeof incoming !== 'object' || incoming === null)) {
|
|
423
|
+
current.set(new utils_1.EditWrapper(incoming));
|
|
424
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
const canHydrate = typeof (current === null || current === void 0 ? void 0 : current.hydrateFromServer) === 'function';
|
|
428
|
+
if (Array.isArray(incoming) && canHydrate && current.type === 'list') {
|
|
429
|
+
current.hydrateFromServer(incoming);
|
|
430
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
if (isPlainObject(incoming) && canHydrate && current.type === 'object') {
|
|
434
|
+
current.hydrateFromServer(incoming);
|
|
435
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
notifyIndexWaiters(index) {
|
|
441
|
+
var _b, _c;
|
|
442
|
+
const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + '.' + index);
|
|
443
|
+
if (this.multyx.events.has(propSymbol)) {
|
|
444
|
+
this.multyx[utils_1.Done].push(...((_c = (_b = this.multyx.events.get(propSymbol)) === null || _b === void 0 ? void 0 : _b.map(e => () => e(this.list[index]))) !== null && _c !== void 0 ? _c : []));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
enqueueEditCallbacks(index, oldValue) {
|
|
448
|
+
for (const listener of this.editCallbacks) {
|
|
449
|
+
this.multyx[utils_1.Add](() => listener(index, this.get(index), oldValue));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
406
452
|
}
|
|
407
453
|
_a = Symbol.toPrimitive;
|
|
408
454
|
exports.default = MultyxClientList;
|
package/dist/items/object.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type Multyx from '../index';
|
|
|
4
4
|
import { type MultyxClientList, type MultyxClientItem } from ".";
|
|
5
5
|
import MultyxClientValue from "./value";
|
|
6
6
|
export default class MultyxClientObject {
|
|
7
|
+
readonly type = "object";
|
|
7
8
|
protected object: RawObject<MultyxClientItem>;
|
|
8
9
|
private multyx;
|
|
9
10
|
propertyPath: string[];
|
|
@@ -33,4 +34,7 @@ export default class MultyxClientObject {
|
|
|
33
34
|
* @param constraints Packed constraints object mirroring MultyxClientObject shape
|
|
34
35
|
*/
|
|
35
36
|
[Unpack](constraints: RawObject): void;
|
|
37
|
+
hydrateFromServer(value: RawObject): void;
|
|
38
|
+
private applyServerValue;
|
|
39
|
+
private notifyPropertyWaiters;
|
|
36
40
|
}
|
package/dist/items/object.js
CHANGED
|
@@ -8,6 +8,7 @@ const utils_1 = require("../utils");
|
|
|
8
8
|
const _1 = require(".");
|
|
9
9
|
const router_1 = __importDefault(require("./router"));
|
|
10
10
|
const value_1 = __importDefault(require("./value"));
|
|
11
|
+
const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11
12
|
class MultyxClientObject {
|
|
12
13
|
get value() {
|
|
13
14
|
const parsed = {};
|
|
@@ -33,6 +34,7 @@ class MultyxClientObject {
|
|
|
33
34
|
(_a = this.get(updatePath[0])) === null || _a === void 0 ? void 0 : _a[utils_1.Edit](updatePath.slice(1), value);
|
|
34
35
|
}
|
|
35
36
|
constructor(multyx, object, propertyPath = [], editable) {
|
|
37
|
+
this.type = 'object';
|
|
36
38
|
this.editCallbacks = [];
|
|
37
39
|
this.object = {};
|
|
38
40
|
this.propertyPath = propertyPath;
|
|
@@ -109,32 +111,30 @@ class MultyxClientObject {
|
|
|
109
111
|
return next.set(path.slice(1), value);
|
|
110
112
|
}
|
|
111
113
|
set(property, value) {
|
|
112
|
-
var _a, _b;
|
|
113
114
|
if (Array.isArray(property))
|
|
114
115
|
return this.recursiveSet(property, value);
|
|
115
116
|
const serverSet = value instanceof utils_1.EditWrapper;
|
|
116
117
|
const allowed = serverSet || this.editable;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (value === undefined)
|
|
118
|
+
const incoming = (serverSet || (0, _1.IsMultyxClientItem)(value)) ? value.value : value;
|
|
119
|
+
if (incoming === undefined)
|
|
120
120
|
return this.delete(property, serverSet);
|
|
121
|
+
if (serverSet && this.applyServerValue(property, incoming)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
121
124
|
// Only create new MultyxClientItem when needed
|
|
122
|
-
if (this.object[property] instanceof value_1.default && typeof
|
|
123
|
-
return this.object[property].set(serverSet ? new utils_1.EditWrapper(
|
|
125
|
+
if (this.object[property] instanceof value_1.default && (typeof incoming !== 'object' || incoming === null)) {
|
|
126
|
+
return this.object[property].set(serverSet ? new utils_1.EditWrapper(incoming) : incoming);
|
|
124
127
|
}
|
|
125
128
|
// Attempting to edit property not editable to client
|
|
126
129
|
if (!allowed) {
|
|
127
130
|
if (this.multyx.options.verbose) {
|
|
128
|
-
console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + property}' to ${
|
|
131
|
+
console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + property}' to ${incoming}`);
|
|
129
132
|
}
|
|
130
133
|
return false;
|
|
131
134
|
}
|
|
132
135
|
// Creating a new value
|
|
133
|
-
this.object[property] = new ((0, router_1.default)(
|
|
134
|
-
|
|
135
|
-
if (this.multyx.events.has(propSymbol)) {
|
|
136
|
-
this.multyx[utils_1.Done].push(...((_b = (_a = this.multyx.events.get(propSymbol)) === null || _a === void 0 ? void 0 : _a.map(e => () => e(this.object[property]))) !== null && _b !== void 0 ? _b : []));
|
|
137
|
-
}
|
|
136
|
+
this.object[property] = new ((0, router_1.default)(incoming))(this.multyx, serverSet ? new utils_1.EditWrapper(incoming) : incoming, [...this.propertyPath, property], this.editable);
|
|
137
|
+
this.notifyPropertyWaiters(property);
|
|
138
138
|
return true;
|
|
139
139
|
}
|
|
140
140
|
delete(property, native = false) {
|
|
@@ -189,5 +189,43 @@ class MultyxClientObject {
|
|
|
189
189
|
(_a = this.object[prop]) === null || _a === void 0 ? void 0 : _a[utils_1.Unpack](constraints[prop]);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
+
hydrateFromServer(value) {
|
|
193
|
+
if (!isPlainObject(value))
|
|
194
|
+
return;
|
|
195
|
+
const remaining = new Set(Object.keys(this.object));
|
|
196
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
197
|
+
remaining.delete(key);
|
|
198
|
+
this.set(key, new utils_1.EditWrapper(entry));
|
|
199
|
+
}
|
|
200
|
+
for (const key of remaining) {
|
|
201
|
+
this.delete(key, true);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
applyServerValue(property, incoming) {
|
|
205
|
+
const current = this.object[property];
|
|
206
|
+
if (!current)
|
|
207
|
+
return false;
|
|
208
|
+
if (current instanceof value_1.default && (typeof incoming !== 'object' || incoming === null)) {
|
|
209
|
+
current.set(new utils_1.EditWrapper(incoming));
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
const canHydrate = typeof (current === null || current === void 0 ? void 0 : current.hydrateFromServer) === 'function';
|
|
213
|
+
if (Array.isArray(incoming) && canHydrate && current.type === 'list') {
|
|
214
|
+
current.hydrateFromServer(incoming);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if (isPlainObject(incoming) && canHydrate && current.type === 'object') {
|
|
218
|
+
current.hydrateFromServer(incoming);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
notifyPropertyWaiters(property) {
|
|
224
|
+
var _a, _b;
|
|
225
|
+
const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + '.' + property);
|
|
226
|
+
if (this.multyx.events.has(propSymbol)) {
|
|
227
|
+
this.multyx[utils_1.Done].push(...((_b = (_a = this.multyx.events.get(propSymbol)) === null || _a === void 0 ? void 0 : _a.map(e => () => e(this.object[property]))) !== null && _b !== void 0 ? _b : []));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
192
230
|
}
|
|
193
231
|
exports.default = MultyxClientObject;
|
package/dist/items/value.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export default class MultyxClientValue {
|
|
|
12
12
|
private readModifiers;
|
|
13
13
|
private editCallbacks;
|
|
14
14
|
get value(): Value;
|
|
15
|
+
private interpolationModifier?;
|
|
16
|
+
private latestSample?;
|
|
17
|
+
private previousSample?;
|
|
18
|
+
private interpolationFrameMs;
|
|
15
19
|
set value(v: Value);
|
|
16
20
|
addReadModifier(modifier: (value: Value) => Value): void;
|
|
17
21
|
addEditCallback(callback: (value: Value, previousValue: Value) => void): void;
|
|
@@ -27,4 +31,10 @@ export default class MultyxClientValue {
|
|
|
27
31
|
toString: () => string;
|
|
28
32
|
valueOf: () => Value;
|
|
29
33
|
[Symbol.toPrimitive]: () => Value;
|
|
34
|
+
Lerp(maxFrameDuration?: number): this;
|
|
35
|
+
PredictiveLerp(maxFrameDuration?: number): this;
|
|
36
|
+
private applyInterpolation;
|
|
37
|
+
private attachInterpolationModifier;
|
|
38
|
+
private captureSample;
|
|
39
|
+
private interpolateValue;
|
|
30
40
|
}
|
package/dist/items/value.js
CHANGED
|
@@ -3,12 +3,14 @@ var _a;
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const message_1 = require("../message");
|
|
5
5
|
const utils_1 = require("../utils");
|
|
6
|
+
const DEFAULT_INTERPOLATION_FRAME_MS = 250;
|
|
6
7
|
class MultyxClientValue {
|
|
7
8
|
get value() {
|
|
8
9
|
return this.readModifiers.reduce((value, modifier) => modifier(value), this._value);
|
|
9
10
|
}
|
|
10
11
|
set value(v) {
|
|
11
12
|
this._value = v;
|
|
13
|
+
this.captureSample(v);
|
|
12
14
|
}
|
|
13
15
|
addReadModifier(modifier) {
|
|
14
16
|
this.readModifiers.push(modifier);
|
|
@@ -25,6 +27,7 @@ class MultyxClientValue {
|
|
|
25
27
|
var _b, _c;
|
|
26
28
|
this.readModifiers = [];
|
|
27
29
|
this.editCallbacks = [];
|
|
30
|
+
this.interpolationFrameMs = DEFAULT_INTERPOLATION_FRAME_MS;
|
|
28
31
|
/* Native methods to allow MultyxValue to be treated as primitive */
|
|
29
32
|
this.toString = () => this.value.toString();
|
|
30
33
|
this.valueOf = () => this.value;
|
|
@@ -64,7 +67,7 @@ class MultyxClientValue {
|
|
|
64
67
|
return false;
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
|
-
if (this.
|
|
70
|
+
if (this._value === nv) {
|
|
68
71
|
this.value = nv;
|
|
69
72
|
return true;
|
|
70
73
|
}
|
|
@@ -95,6 +98,57 @@ class MultyxClientValue {
|
|
|
95
98
|
this.constraints[cname] = constraint;
|
|
96
99
|
}
|
|
97
100
|
}
|
|
101
|
+
Lerp(maxFrameDuration = DEFAULT_INTERPOLATION_FRAME_MS) {
|
|
102
|
+
return this.applyInterpolation('lerp', maxFrameDuration);
|
|
103
|
+
}
|
|
104
|
+
PredictiveLerp(maxFrameDuration = DEFAULT_INTERPOLATION_FRAME_MS) {
|
|
105
|
+
return this.applyInterpolation('predictive', maxFrameDuration);
|
|
106
|
+
}
|
|
107
|
+
applyInterpolation(mode, maxFrameDuration) {
|
|
108
|
+
if (typeof this._value !== 'number' || Number.isNaN(this._value)) {
|
|
109
|
+
throw new Error(`MultyxClientValue.${mode === 'lerp' ? 'Lerp' : 'PredictiveLerp'} can only be applied to numeric values`);
|
|
110
|
+
}
|
|
111
|
+
this.interpolationFrameMs = Math.max(1, maxFrameDuration);
|
|
112
|
+
this.attachInterpolationModifier(mode);
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
attachInterpolationModifier(mode) {
|
|
116
|
+
if (this.interpolationModifier) {
|
|
117
|
+
this.readModifiers = this.readModifiers.filter(fn => fn !== this.interpolationModifier);
|
|
118
|
+
}
|
|
119
|
+
this.interpolationModifier = (value) => this.interpolateValue(value, mode);
|
|
120
|
+
this.readModifiers.push(this.interpolationModifier);
|
|
121
|
+
}
|
|
122
|
+
captureSample(value) {
|
|
123
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
124
|
+
this.latestSample = undefined;
|
|
125
|
+
this.previousSample = undefined;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
if (!this.latestSample) {
|
|
130
|
+
this.latestSample = { value, time: now };
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.previousSample = Object.assign({}, this.latestSample);
|
|
134
|
+
this.latestSample = { value, time: now };
|
|
135
|
+
}
|
|
136
|
+
interpolateValue(baseValue, mode) {
|
|
137
|
+
if (typeof baseValue !== 'number' || !this.latestSample || !this.previousSample) {
|
|
138
|
+
return baseValue;
|
|
139
|
+
}
|
|
140
|
+
const durationRaw = this.latestSample.time - this.previousSample.time;
|
|
141
|
+
if (durationRaw <= 0)
|
|
142
|
+
return baseValue;
|
|
143
|
+
const duration = Math.max(1, Math.min(durationRaw, this.interpolationFrameMs));
|
|
144
|
+
const elapsed = Math.max(0, Math.min(Date.now() - this.latestSample.time, duration));
|
|
145
|
+
const ratio = duration === 0 ? 1 : elapsed / duration;
|
|
146
|
+
const delta = this.latestSample.value - this.previousSample.value;
|
|
147
|
+
if (mode === 'predictive') {
|
|
148
|
+
return this.latestSample.value + delta * ratio;
|
|
149
|
+
}
|
|
150
|
+
return this.previousSample.value + delta * ratio;
|
|
151
|
+
}
|
|
98
152
|
}
|
|
99
153
|
_a = Symbol.toPrimitive;
|
|
100
154
|
exports.default = MultyxClientValue;
|
package/dist/message.d.ts
CHANGED
|
@@ -9,8 +9,6 @@ export declare function UncompressUpdate(str: string): {
|
|
|
9
9
|
name?: undefined;
|
|
10
10
|
response?: undefined;
|
|
11
11
|
uuid?: undefined;
|
|
12
|
-
publicData?: undefined;
|
|
13
|
-
clientUUID?: undefined;
|
|
14
12
|
client?: undefined;
|
|
15
13
|
tps?: undefined;
|
|
16
14
|
constraintTable?: undefined;
|
|
@@ -27,8 +25,6 @@ export declare function UncompressUpdate(str: string): {
|
|
|
27
25
|
name?: undefined;
|
|
28
26
|
response?: undefined;
|
|
29
27
|
uuid?: undefined;
|
|
30
|
-
publicData?: undefined;
|
|
31
|
-
clientUUID?: undefined;
|
|
32
28
|
client?: undefined;
|
|
33
29
|
tps?: undefined;
|
|
34
30
|
constraintTable?: undefined;
|
|
@@ -45,8 +41,6 @@ export declare function UncompressUpdate(str: string): {
|
|
|
45
41
|
property?: undefined;
|
|
46
42
|
data?: undefined;
|
|
47
43
|
uuid?: undefined;
|
|
48
|
-
publicData?: undefined;
|
|
49
|
-
clientUUID?: undefined;
|
|
50
44
|
client?: undefined;
|
|
51
45
|
tps?: undefined;
|
|
52
46
|
constraintTable?: undefined;
|
|
@@ -56,15 +50,13 @@ export declare function UncompressUpdate(str: string): {
|
|
|
56
50
|
} | {
|
|
57
51
|
instruction: string;
|
|
58
52
|
uuid: string;
|
|
59
|
-
|
|
53
|
+
data: any;
|
|
60
54
|
team?: undefined;
|
|
61
55
|
path?: undefined;
|
|
62
56
|
value?: undefined;
|
|
63
57
|
property?: undefined;
|
|
64
|
-
data?: undefined;
|
|
65
58
|
name?: undefined;
|
|
66
59
|
response?: undefined;
|
|
67
|
-
clientUUID?: undefined;
|
|
68
60
|
client?: undefined;
|
|
69
61
|
tps?: undefined;
|
|
70
62
|
constraintTable?: undefined;
|
|
@@ -73,7 +65,7 @@ export declare function UncompressUpdate(str: string): {
|
|
|
73
65
|
space?: undefined;
|
|
74
66
|
} | {
|
|
75
67
|
instruction: string;
|
|
76
|
-
|
|
68
|
+
client: string;
|
|
77
69
|
team?: undefined;
|
|
78
70
|
path?: undefined;
|
|
79
71
|
value?: undefined;
|
|
@@ -82,8 +74,6 @@ export declare function UncompressUpdate(str: string): {
|
|
|
82
74
|
name?: undefined;
|
|
83
75
|
response?: undefined;
|
|
84
76
|
uuid?: undefined;
|
|
85
|
-
publicData?: undefined;
|
|
86
|
-
client?: undefined;
|
|
87
77
|
tps?: undefined;
|
|
88
78
|
constraintTable?: undefined;
|
|
89
79
|
clients?: undefined;
|
|
@@ -105,8 +95,6 @@ export declare function UncompressUpdate(str: string): {
|
|
|
105
95
|
name?: undefined;
|
|
106
96
|
response?: undefined;
|
|
107
97
|
uuid?: undefined;
|
|
108
|
-
publicData?: undefined;
|
|
109
|
-
clientUUID?: undefined;
|
|
110
98
|
} | undefined;
|
|
111
99
|
export declare function CompressUpdate(update: Update): string;
|
|
112
100
|
export declare class Message {
|
package/dist/message.js
CHANGED
|
@@ -23,9 +23,9 @@ function UncompressUpdate(str) {
|
|
|
23
23
|
if (instruction == '5')
|
|
24
24
|
return { instruction: 'resp', name: specifier, response: data[0] };
|
|
25
25
|
if (instruction == '6')
|
|
26
|
-
return { instruction: 'conn', uuid: specifier,
|
|
26
|
+
return { instruction: 'conn', uuid: specifier, data: data[0] };
|
|
27
27
|
if (instruction == '7')
|
|
28
|
-
return { instruction: 'dcon',
|
|
28
|
+
return { instruction: 'dcon', client: specifier };
|
|
29
29
|
if (instruction == '8')
|
|
30
30
|
return {
|
|
31
31
|
instruction: 'init',
|
|
@@ -54,7 +54,7 @@ function CompressUpdate(update) {
|
|
|
54
54
|
code = 2;
|
|
55
55
|
pieces = [update.name, JSON.stringify(update.response)];
|
|
56
56
|
}
|
|
57
|
-
if (!pieces ||
|
|
57
|
+
if (!pieces || code === undefined)
|
|
58
58
|
return '';
|
|
59
59
|
let compressed = code.toString();
|
|
60
60
|
for (let i = 0; i < pieces.length; i++) {
|
package/dist/utils.js
CHANGED
|
@@ -29,33 +29,62 @@ exports.EditWrapper = EditWrapper;
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
function Interpolate(object, property, interpolationCurve) {
|
|
32
|
+
if (!Array.isArray(interpolationCurve) || interpolationCurve.length === 0) {
|
|
33
|
+
throw new Error('Interpolation curve must contain at least one slice');
|
|
34
|
+
}
|
|
35
|
+
const curve = [...interpolationCurve].sort((a, b) => a.time - b.time);
|
|
36
|
+
const curveMaxTime = curve[curve.length - 1].time;
|
|
37
|
+
const usesNormalizedCurve = curveMaxTime <= 1;
|
|
38
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
32
39
|
let start = { value: object[property], time: Date.now() };
|
|
33
40
|
let end = { value: object[property], time: Date.now() };
|
|
34
41
|
Object.defineProperty(object, property, {
|
|
42
|
+
configurable: true,
|
|
43
|
+
enumerable: true,
|
|
35
44
|
get: () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
if (start.time === end.time)
|
|
46
|
+
return end.value;
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const duration = Math.max(end.time - start.time, 1);
|
|
49
|
+
const elapsed = Math.max(0, now - end.time);
|
|
50
|
+
const targetTime = usesNormalizedCurve
|
|
51
|
+
? clamp(elapsed / duration, 0, curveMaxTime)
|
|
52
|
+
: clamp(elapsed, 0, curveMaxTime);
|
|
53
|
+
let lower = curve[0];
|
|
54
|
+
let upper = curve[curve.length - 1];
|
|
55
|
+
for (const slice of curve) {
|
|
56
|
+
if (slice.time <= targetTime) {
|
|
41
57
|
lower = slice;
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
upper = slice;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
let ratio;
|
|
64
|
+
if (upper.time === lower.time) {
|
|
65
|
+
ratio = lower.progress;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const sliceTime = (targetTime - lower.time) / (upper.time - lower.time);
|
|
69
|
+
ratio = lower.progress + sliceTime * (upper.progress - lower.progress);
|
|
44
70
|
}
|
|
45
|
-
const sliceTime = (time - lower.time) / (upper.time - lower.time);
|
|
46
|
-
const ratio = lower.progress + sliceTime * (upper.progress - lower.progress);
|
|
47
71
|
if (Number.isNaN(ratio))
|
|
48
72
|
return start.value;
|
|
49
|
-
|
|
73
|
+
if (typeof start.value === 'number' && typeof end.value === 'number') {
|
|
74
|
+
return end.value * ratio + start.value * (1 - ratio);
|
|
75
|
+
}
|
|
76
|
+
return ratio >= 1 ? end.value : start.value;
|
|
50
77
|
},
|
|
51
78
|
set: (value) => {
|
|
79
|
+
const now = Date.now();
|
|
52
80
|
// Don't lerp between edit requests sent in same frame
|
|
53
|
-
if (
|
|
81
|
+
if (now - end.time < 10) {
|
|
54
82
|
end.value = value;
|
|
83
|
+
end.time = now;
|
|
55
84
|
return true;
|
|
56
85
|
}
|
|
57
86
|
start = Object.assign({}, end);
|
|
58
|
-
end = { value, time:
|
|
87
|
+
end = { value, time: now };
|
|
59
88
|
return true;
|
|
60
89
|
}
|
|
61
90
|
});
|
package/multyx.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Multyx=e():t.Multyx=e()}(self,(()=>(()=>{"use strict";var t={376:(t,e,s)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Controller=void 0;const i=s(210);e.Controller=class{constructor(t){this.listening=new Set,this.ws=t,this.preventDefault=!1,this.keys={},this.mouse={x:NaN,y:NaN,down:!1,centerX:0,centerY:0,scaleX:1,scaleY:1},document.addEventListener("keydown",(t=>{this.preventDefault&&t.preventDefault;const e=t.key.toLowerCase();this.keys[e]&&this.listening.has("keyhold")&&this.relayInput("keyhold",{code:e}),this.keys[t.code]&&this.listening.has("keyhold")&&this.relayInput("keyhold",{code:t.code}),this.listening.has(e)&&!this.keys[e]&&this.relayInput("keydown",{code:t.key}),this.listening.has(t.code)&&!this.keys[t.code]&&this.relayInput("keydown",{code:t.code}),this.keys[e]=!0,this.keys[t.code]=!0})),document.addEventListener("keyup",(t=>{this.preventDefault&&t.preventDefault;const e=t.key.toLowerCase();delete this.keys[e],delete this.keys[t.code],this.listening.has(e)&&this.relayInput("keyup",{code:e}),this.listening.has(t.code)&&this.relayInput("keyup",{code:t.code})})),document.addEventListener("mousedown",(t=>{if(this.preventDefault&&t.preventDefault,this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.mouse.down=!0,this.listening.has("mousedown")&&this.relayInput("mousedown",{x:this.mouse.x,y:this.mouse.y})})),document.addEventListener("mouseup",(t=>{if(this.preventDefault&&t.preventDefault,this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.mouse.down=!1,this.listening.has("mouseup")&&this.relayInput("mouseup",{x:this.mouse.x,y:this.mouse.y})})),document.addEventListener("mousemove",(t=>{if(this.preventDefault&&t.preventDefault,this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.listening.has("mousemove")&&this.relayInput("mousemove",{x:this.mouse.x,y:this.mouse.y})}))}mapCanvasPosition(t,e){const s="top"in e,i="bottom"in e,n="left"in e,o="right"in e,r=e.anchor,l=t.getBoundingClientRect(),h=(t,...e)=>{const s=t?"Cannot include value for ":"Must include value for ",i=1==e.length?e[0]:e.slice(0,-1).join(", ")+(t?" and ":" or ")+e.slice(-1)[0],n=r?" if anchoring at "+r:" if not anchoring";console.error(s+i+n)},a=l.width/l.height,u=l.height/l.width;if((Number.isNaN(a)||Number.isNaN(u))&&console.error("Canvas element bounding box is flat, canvas must be present on the screen"),r){if("center"==r){if(s&&i&&e.top!==-e.bottom||n&&o&&e.left!==-e.right)return h(!0,"top","bottom","left","right");s?(e.left=n?e.left:o?-e.right:-Math.abs(a*e.top),e.right=n?-e.left:o?e.right:Math.abs(a*e.top),e.bottom=-e.top):i?(e.left=n?e.left:o?-e.right:-Math.abs(a*e.bottom),e.right=n?-e.left:o?e.right:Math.abs(a*e.bottom),e.top=-e.bottom):n?(e.top=s?e.top:i?-e.bottom:-Math.abs(u*e.left),e.bottom=s?-e.top:i?e.bottom:Math.abs(u*e.left),e.right=-e.left):o&&(e.top=s?e.top:i?-e.bottom:-Math.abs(u*e.right),e.bottom=s?-e.top:i?e.bottom:Math.abs(u*e.right),e.left=-e.right)}else if("bottom"==r){if(!n&&!o&&!s)return h(!1,"left","right","top");if(e.bottom)return h(!0,"bottom");e.bottom=0,n?(e.top=Math.abs(u*e.left*2),e.right=-e.left):o?(e.top=Math.abs(u*e.right*2),e.left=-e.right):(e.left=-Math.abs(a*e.top/2),e.right=-e.left)}else if("top"==r){if(!n&&!o&&!i)return h(!1,"left","right","bottom");if(e.top)return h(!0,"top");e.top=0,n?(e.bottom=Math.abs(u*e.left*2),e.right=-e.left):o?(e.bottom=Math.abs(u*e.right*2),e.left=-e.right):(e.left=-Math.abs(a*e.bottom/2),e.right=-e.left)}else if("left"==r){if(!s&&!i&&!o)return h(!1,"top","bottom","right");if(n)return h(!0,"left");e.left=0,s?(e.right=-Math.abs(a*e.top*2),e.bottom=-e.top):i?(e.right=Math.abs(a*e.bottom*2),e.top=-e.bottom):(e.top=-Math.abs(u*e.right/2),e.bottom=-e.top)}else if("right"==r){if(!s&&!i&&!n)return h(!1,"top","bottom","left");if(o)return h(!0,"right");e.right=0,s?(e.left=-Math.abs(a*e.top*2),e.bottom=-e.top):i?(e.left=Math.abs(a*e.bottom*2),e.top=-e.bottom):(e.top=-Math.abs(u*e.right/2),e.bottom=-e.top)}else if("topleft"==r){if(!o&&!i)return h(!1,"right","bottom");if(n||s)return h(!0,"left","top");e.left=e.top=0,o?e.bottom=Math.abs(u*e.right):e.right=Math.abs(a*e.bottom)}else if("topright"==r){if(!n&&!i)return h(!1,"left","bottom");if(o||s)return h(!0,"right","top");e.right=e.top=0,n?e.bottom=Math.abs(u*e.left):e.left=Math.abs(a*e.bottom)}else if("bottomleft"==r){if(!o&&!s)return h(!1,"right","top");if(i||n)return h(!0,"bottom","left");e.left=e.bottom=0,o?e.top=Math.abs(u*e.right):e.right=Math.abs(a*e.top)}else if("bottomright"==r){if(!s&&!n)return h(!1,"top","left");if(o||i)return h(!0,"bottom","right");e.right=e.bottom=0,n?e.top=Math.abs(u*e.left):e.left=Math.abs(a*e.top)}}else{if(!s&&!i)return h(!1,"top","bottom");if(i?s||(e.top=e.bottom-t.height):e.bottom=e.top+t.height,!n&&!o)return h(!1,"left","right");o?n||(e.left=e.right-t.width):e.right=e.left+t.width}const c=t.getContext("2d");null==c||c.setTransform(1,0,0,1,0,0),t.width=Math.floor(Math.abs(e.right-e.left)),t.height=Math.floor(Math.abs(e.bottom-e.top)),e.right<e.left&&(null==c||c.scale(-1,1)),e.top>e.bottom&&(null==c||c.scale(1,-1)),null==c||c.translate(-e.left,-e.top)}mapMousePosition(t,e,s=document.body,i=1,n=i){const o=window.innerWidth/(s instanceof HTMLCanvasElement?s.width:s.clientWidth),r=window.innerHeight/(s instanceof HTMLCanvasElement?s.height:s.clientHeight),l=s.getBoundingClientRect();this.mouse.centerX=l.left+t*o,this.mouse.centerY=l.top+e*r,this.mouse.scaleX=i*o,this.mouse.scaleY=n*r}mapMouseToCanvas(t){const e=t.getContext("2d"),s=null==e?void 0:e.getTransform(),i=t.getBoundingClientRect(),n=i.width/t.width,o=i.height/t.height;this.mouse.centerX=i.left+(null==s?void 0:s.e)*n,this.mouse.centerY=i.top+(null==s?void 0:s.f)*o,this.mouse.scaleX=n*(null==s?void 0:s.a),this.mouse.scaleY=o*(null==s?void 0:s.d)}setMouseAs(t){this.mouseGetter=t}relayInput(t,e){if(1!==this.ws.readyState)throw new Error("Websocket connection is "+(2==this.ws.readyState?"closing":"closed"));this.ws.send(i.Message.Native(Object.assign({instruction:"input",input:t},e?{data:e}:{})))}}},34:function(t,e,s){var i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.MultyxClientValue=e.MultyxClientObject=e.MultyxClientList=void 0,e.IsMultyxClientItem=function(t){return t instanceof n.default||t instanceof o.default||t instanceof r.default};const n=i(s(70));e.MultyxClientList=n.default;const o=i(s(614));e.MultyxClientObject=o.default;const r=i(s(501));e.MultyxClientValue=r.default},70:function(t,e,s){var i,n=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});const o=s(34),r=s(787),l=n(s(735)),h=s(210);class a{addEditCallback(t){this.editCallbacks.push(t)}get value(){var t;const e=[];for(let s=0;s<this.length;s++)e[s]=null===(t=this.get(s))||void 0===t?void 0:t.value;return e}get length(){return this.list.length}set length(t){this.list.length=t}handleShiftOperation(t,e){const s=t>=0?e>=0?"right":"left":0==e?"reverse":e<0?"length":"unknown";switch(s){case"reverse":for(let t=0;t<Math.floor(this.length/2);t++){const e=this.list[t];this.list[t]=this.list[this.length-1-t],this.list[this.length-1-t]=e}break;case"left":for(let s=t;s<this.length;s++)s+e<0||(this.list[s+e]=this.list[s]);break;case"right":for(let s=this.length-1;s>=t;s--)this.list[s+e]=this.list[s];break;case"length":this.length+=e;break;default:this.multyx.options.verbose&&console.error("Unknown shift operation: "+s)}}constructor(t,e,s=[],n){this.editCallbacks=[],this.toString=()=>this.value.toString(),this.valueOf=()=>this.value,this[i]=()=>this.value,this.list=[],this.propertyPath=s,this.multyx=t,this.editable=n;const o=e instanceof r.EditWrapper;e instanceof a&&(e=e.value),e instanceof r.EditWrapper&&(e=e.value);for(let t=0;t<e.length;t++)this.set(t,o?new r.EditWrapper(e[t]):e[t]);return new Proxy(this,{has:(t,e)=>"number"==typeof e?t.has(e):e in t,get:(t,e)=>e in t?t[e]:(isNaN(parseInt(e))||(e=parseInt(e)),t.get(e)),set:(t,e,s)=>e in t?(t[e]=s,!0):!!t.set(e,s),deleteProperty:(t,e)=>"number"==typeof e&&t.delete(e)})}has(t){return t>=0&&t<this.length}get(t){if("number"==typeof t)return this.list[t];if(0==t.length)return this;if(1==t.length)return this.list[parseInt(t[0])];const e=this.list[parseInt(t[0])];return!e||e instanceof o.MultyxClientValue?void 0:e.get(t.slice(1))}recursiveSet(t,e){if(0==t.length)return this.multyx.options.verbose&&console.error(`Attempting to edit MultyxClientList with no path. Setting '${this.propertyPath.join(".")}' to ${e}`),!1;if("shift"==t[0]&&e instanceof r.EditWrapper)return this.handleShiftOperation(parseInt(t[1]),e.value),!0;if(1==t.length)return this.set(parseInt(t[0]),e);let s=this.get(parseInt(t[0]));return(s instanceof o.MultyxClientValue||null==s)&&(this.set(parseInt(t[0]),new r.EditWrapper({})),s=this.get(parseInt(t[0]))),s.set(t.slice(1),e)}set(t,e){var s,i;if(Array.isArray(t))return this.recursiveSet(t,e);const n=this.get(t),h=e instanceof r.EditWrapper,a=h||this.editable;if((h||(0,o.IsMultyxClientItem)(e))&&(e=e.value),void 0===e)return this.delete(t,h);if(this.list[t]instanceof o.MultyxClientValue&&"object"!=typeof e)return this.list[t].set(h?new r.EditWrapper(e):e);if(!a)return this.multyx.options.verbose&&console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join(".")+"."+t}' to ${e}`),!1;this.list[t]=new((0,l.default)(e))(this.multyx,h?new r.EditWrapper(e):e,[...this.propertyPath,t.toString()],this.editable);const u=Symbol.for("_"+this.propertyPath.join(".")+"."+t);this.multyx.events.has(u)&&this.multyx[r.Done].push(...null!==(i=null===(s=this.multyx.events.get(u))||void 0===s?void 0:s.map((e=>()=>e(this.list[t]))))&&void 0!==i?i:[]);for(const e of this.editCallbacks)this.multyx[r.Add]((()=>e(t,this.get(t),n)));return!0}delete(t,e=!1){const s=this.get(t);if("string"==typeof t&&(t=parseInt(t)),!this.editable&&!e)return this.multyx.options.verbose&&console.error(`Attempting to delete property that is not editable. Deleting '${this.propertyPath.join(".")+"."+t}'`),!1;delete this.list[t];for(const e of this.editCallbacks)this.multyx[r.Add]((()=>e(t,void 0,s)));return e||this.multyx.ws.send(h.Message.Native({instruction:"edit",path:[...this.propertyPath,t.toString()],value:void 0})),!0}await(t){if(this.has(t))return Promise.resolve(this.get(t));const e=Symbol.for("_"+this.propertyPath.join(".")+"."+t);return new Promise((t=>this.multyx.on(e,t)))}push(...t){for(const e of t)this.set(this.length,e);return this.length}pop(){if(0===this.length)return;const t=this.get(this.length);return this.delete(this.length),t}unshift(...t){for(let e=this.length-1;e>=0;e--)e>=t.length?this.set(e,this.get(e-t.length)):this.set(e,t[e]);return this.length}shift(){if(0==this.length)return;this.length--;const t=this.get(0);for(let t=0;t<this.length;t++)this.set(t,this.get(t+1));return t}slice(t,e){return this.list.slice(t,e)}splice(t,e,...s){return this.list.splice(t,null!=e?e:0,...s)}setSplice(t,e,...s){void 0===e&&(e=this.length-t);let i=s.length-e;if(i>0)for(let s=this.length-1;s>=t+e;s--)this.set(s+i,this.get(s));else if(i<0){for(let s=t+e;s<this.length;s++)this.set(s+i,this.get(s));const s=this.length;for(let t=s+i;t<s;t++)this.set(t,void 0)}for(let e=t;e<s.length;e++)this.set(e,s[e])}filter(t){return this.list.filter(((e,s)=>t(e,s,this)))}setFilter(t){const e=[];for(let s=0;s<this.length;s++)e.push(t(this.get(s),s,this));let s=0;for(let t=0;t<e.length;t++)e[t]&&s&&this.set(t-s,this.get(t)),e[t]||s--}map(t){const e=[];for(let s=0;s<this.length;s++)e.push(t(this.get(s),s,this));return e}flat(){return this.list.flat()}setFlat(){for(let t=0;t<this.length;t++){const e=this.get(t);if(e instanceof a)for(let s=0;s<e.length;s++)t++,this.set(t,e[s])}}reduce(t,e){for(let s=0;s<this.length;s++)e=t(e,this.get(s),s,this);return e}reduceRight(t,e){for(let s=this.length-1;s>=0;s--)e=t(e,this.get(s),s,this);return e}reverse(){let t=this.length-1;for(let e=0;e<t;e++){const s=this.get(e),i=this.get(t);this.set(e,i),this.set(t,s)}return this}forEach(t){for(let e=0;e<this.length;e++)t(this.get(e),e,this)}every(t){for(let e=0;e<this.length;e++)if(!t(this.get(e),e,this))return!1;return!0}some(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return!0;return!1}find(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return this.get(e)}findIndex(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return e;return-1}entries(){const t=[];for(let e=0;e<this.length;e++)t.push([this.get(e),e]);return t}keys(){return Array(this.length).fill(0).map(((t,e)=>e))}[r.Edit](){}[r.Unpack](t){var e;for(let s=0;s<this.length;s++)null===(e=this.get(s))||void 0===e||e[r.Unpack](t[s])}[Symbol.iterator](){const t=[];for(let e=0;e<this.length;e++)t[e]=this.get(e);return t[Symbol.iterator]()}}i=Symbol.toPrimitive,e.default=a},614:function(t,e,s){var i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});const n=s(210),o=s(787),r=s(34),l=i(s(735)),h=i(s(501));class a{get value(){const t={};for(const e in this.object)t[e]=this.object[e];return t}addEditCallback(t){this.editCallbacks.push(t)}[o.Edit](t,e){var s;1!=t.length?(0==t.length&&this.multyx.options.verbose&&console.error("Update path is empty. Attempting to edit MultyxClientObject with no path."),this.has(t[0])||this.set(t[0],new o.EditWrapper({})),null===(s=this.get(t[0]))||void 0===s||s[o.Edit](t.slice(1),e)):this.set(t[0],new o.EditWrapper(e))}constructor(t,e,s=[],i){this.editCallbacks=[],this.object={},this.propertyPath=s,this.multyx=t,this.editable=i;const n=e instanceof o.EditWrapper;e instanceof a&&(e=e.value),e instanceof o.EditWrapper&&(e=e.value);for(const t in e)this.set(t,n?new o.EditWrapper(e[t]):e[t]);if(this.constructor===a)return new Proxy(this,{has:(t,e)=>t.has(e),get:(t,e)=>e in t?t[e]:t.get(e),set:(t,e,s)=>e in t?(t[e]=s,!0):t.set(e,s),deleteProperty:(t,e)=>t.delete(e,!1)})}has(t){return t in this.object}get(t){if("string"==typeof t)return this.object[t];if(0==t.length)return this;if(1==t.length)return this.object[t[0]];const e=this.object[t[0]];return!e||e instanceof h.default?void 0:e.get(t.slice(1))}recursiveSet(t,e){if(0==t.length)return this.multyx.options.verbose&&console.error(`Attempting to edit MultyxClientObject with no path. Setting '${this.propertyPath.join(".")}' to ${e}`),!1;if(1==t.length)return this.set(t[0],e);let s=this.get(t[0]);return(s instanceof h.default||null==s)&&(isNaN(parseInt(t[1]))?(this.set(t[0],new o.EditWrapper({})),s=this.get(t[0])):(this.set(t[0],new o.EditWrapper([])),s=this.get(t[0]))),s.set(t.slice(1),e)}set(t,e){var s,i;if(Array.isArray(t))return this.recursiveSet(t,e);const n=e instanceof o.EditWrapper,a=n||this.editable;if((n||(0,r.IsMultyxClientItem)(e))&&(e=e.value),void 0===e)return this.delete(t,n);if(this.object[t]instanceof h.default&&"object"!=typeof e)return this.object[t].set(n?new o.EditWrapper(e):e);if(!a)return this.multyx.options.verbose&&console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join(".")+"."+t}' to ${e}`),!1;this.object[t]=new((0,l.default)(e))(this.multyx,n?new o.EditWrapper(e):e,[...this.propertyPath,t],this.editable);const u=Symbol.for("_"+this.propertyPath.join(".")+"."+t);return this.multyx.events.has(u)&&this.multyx[o.Done].push(...null!==(i=null===(s=this.multyx.events.get(u))||void 0===s?void 0:s.map((e=>()=>e(this.object[t]))))&&void 0!==i?i:[]),!0}delete(t,e=!1){return this.editable||e?(delete this.object[t],e||this.multyx.ws.send(n.Message.Native({instruction:"edit",path:[...this.propertyPath,t],value:void 0})),!0):(this.multyx.options.verbose&&console.error(`Attempting to delete property that is not editable. Deleting '${this.propertyPath.join(".")+"."+t}'`),!1)}keys(){return Object.keys(this.object)}values(){return Object.values(this.object)}entries(){const t=[];for(let e in this.object)t.push([e,this.get(e)]);return t}await(t){if(this.has(t))return Promise.resolve(this.get(t));const e=Symbol.for("_"+this.propertyPath.join(".")+"."+t);return new Promise((t=>this.multyx.on(e,t)))}[o.Unpack](t){var e;for(const s in t)null===(e=this.object[s])||void 0===e||e[o.Unpack](t[s])}}e.default=a},735:(t,e,s)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){return Array.isArray(t)?s(70).default:"object"==typeof t?s(614).default:s(501).default}},501:(t,e,s)=>{var i;Object.defineProperty(e,"__esModule",{value:!0});const n=s(210),o=s(787);class r{get value(){return this.readModifiers.reduce(((t,e)=>e(t)),this._value)}set value(t){this._value=t}addReadModifier(t){this.readModifiers.push(t)}addEditCallback(t){this.editCallbacks.push(t)}[o.Edit](t,e){0==t.length&&this.set(new o.EditWrapper(e))}constructor(t,e,s=[],n){var r,l;this.readModifiers=[],this.editCallbacks=[],this.toString=()=>this.value.toString(),this.valueOf=()=>this.value,this[i]=()=>this.value,this.propertyPath=s,this.editable=n,this.multyx=t,this.constraints={},this.set(e);const h=Symbol.for("_"+this.propertyPath.join("."));this.multyx.events.has(h)&&this.multyx[o.Done].push(...null!==(l=null===(r=this.multyx.events.get(h))||void 0===r?void 0:r.map((t=>()=>t(this.value))))&&void 0!==l?l:[])}set(t){if(t instanceof o.EditWrapper){const e=this.value;return this.value=t.value,this.editCallbacks.forEach((s=>s(t.value,e))),!0}if(!this.editable)return this.multyx.options.verbose&&console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join(".")}' to ${t}`),!1;let e=t;for(const s in this.constraints)if(e=(0,this.constraints[s])(e),null===e)return this.multyx.options.verbose&&console.error(`Attempting to set property that failed on constraint. Setting '${this.propertyPath.join(".")}' to ${t}, stopped by constraint '${s}'`),!1;return this.value===e?(this.value=e,!0):(this.value=e,this.multyx.ws.send(n.Message.Native({instruction:"edit",path:this.propertyPath,value:e})),!0)}bindElement(t){this.addEditCallback(((e,s)=>{e!==s&&(t.innerText=e.toString())}))}[o.Unpack](t){for(const[e,s]of Object.entries(t)){const t=(0,o.BuildConstraint)(e,s);t&&(this.constraints[e]=t)}}}i=Symbol.toPrimitive,e.default=r},210:(t,e)=>{function s(t){let e,s;if("edit"==t.instruction?(e=0,s=[t.path.join("."),JSON.stringify(t.value)]):"input"==t.instruction?(e=1,s=[t.input,JSON.stringify(t.data)]):"resp"==t.instruction&&(e=2,s=[t.name,JSON.stringify(t.response)]),!s||!e)return"";let i=e.toString();for(let t=0;t<s.length;t++)i+=s[t].replace(/;/g,";_"),t<s.length-1&&(i+=";,");return JSON.stringify([i])}Object.defineProperty(e,"__esModule",{value:!0}),e.Message=void 0,e.UncompressUpdate=function(t){const[e,...s]=t.split(/;,/),i=e[0],n=e.slice(1).replace(/;_/g,";"),o=s.map((t=>t.replace(/;_/g,";"))).map((t=>"undefined"==t?void 0:JSON.parse(t)));return"0"==i?{instruction:"edit",team:!1,path:n.split("."),value:o[0]}:"1"==i?{instruction:"edit",team:!0,path:n.split("."),value:o[0]}:"2"==i?{instruction:"self",property:"controller",data:JSON.parse(n)}:"3"==i?{instruction:"self",property:"uuid",data:JSON.parse(n)}:"4"==i?{instruction:"self",property:"constraint",data:JSON.parse(n)}:"9"==i?{instruction:"self",property:"space",data:JSON.parse(n)}:"5"==i?{instruction:"resp",name:n,response:o[0]}:"6"==i?{instruction:"conn",uuid:n,publicData:o[0]}:"7"==i?{instruction:"dcon",clientUUID:n}:"8"==i?{instruction:"init",client:JSON.parse(n),tps:o[0],constraintTable:o[1],clients:o[2],teams:o[3],space:o[4]}:void 0},e.CompressUpdate=s;class i{constructor(t,e,s=!1){this.name=t,this.data=e,this.time=Date.now(),this.native=s}static BundleOperations(t,e){return Array.isArray(e)||(e=[e]),JSON.stringify(new i("_",{operations:e,deltaTime:t}))}static Native(t){return s(t)}static Parse(t){var e,s;const n=JSON.parse(t);return Array.isArray(n)?new i("_",n,!0):new i(null!==(e=n.name)&&void 0!==e?e:"",null!==(s=n.data)&&void 0!==s?s:"",!1)}static Create(t,e){if(0==t.length)throw new Error("Multyx message cannot have empty name");if("_"==t[0]&&(t="_"+t),"function"==typeof e)throw new Error("Multyx data must be JSON storable");return JSON.stringify(new i(t,e))}}e.Message=i},944:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.DefaultOptions=void 0,e.DefaultOptions={port:8443,secure:!1,uri:"localhost",verbose:!1,logUpdateFrame:!1}},787:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.EditWrapper=e.Edit=e.Add=e.Done=e.Unpack=void 0,e.Interpolate=function(t,e,s){let i={value:t[e],time:Date.now()},n={value:t[e],time:Date.now()};Object.defineProperty(t,e,{get:()=>{const t=n.time-i.time;let e=s[0],o=s[0];for(const i of s)t>i.time&&i.time>e.time&&(e=i),t<i.time&&i.time<o.time&&(o=i);const r=(t-e.time)/(o.time-e.time),l=e.progress+r*(o.progress-e.progress);return Number.isNaN(l)?i.value:n.value*l+i.value*(1-l)},set:t=>Date.now()-n.time<10?(n.value=t,!0):(i=Object.assign({},n),n={value:t,time:Date.now()},!0)})},e.BuildConstraint=function(t,e){return"min"==t?t=>t>=e[0]?t:e[0]:"max"==t?t=>t<=e[0]?t:e[0]:"int"==t?t=>Math.floor(t):"ban"==t?t=>e.includes(t)?null:t:"disabled"==t?t=>e[0]?null:t:t=>t},e.Unpack=Symbol("unpack"),e.Done=Symbol("done"),e.Add=Symbol("add"),e.Edit=Symbol("edit"),e.EditWrapper=class{constructor(t){this.value=t}}}},e={};function s(i){var n=e[i];if(void 0!==n)return n.exports;var o=e[i]={exports:{}};return t[i].call(o.exports,o,o.exports,s),o.exports}var i={};return(()=>{var t,e=i;const n=s(210),o=s(787),r=s(376),l=s(34),h=s(944);class a{constructor(e={},s){var i;if(this[t]=[],this.options=Object.assign(Object.assign({},h.DefaultOptions),e),!this.options.uri)throw new Error("URI is required");const o=`ws${this.options.secure?"s":""}://${this.options.uri.split("/")[0]}:${this.options.port}/${null!==(i=this.options.uri.split("/")[1])&&void 0!==i?i:""}`;this.ws=new WebSocket(o),this.ping=0,this.space="default",this.events=new Map,this.self={},this.tps=0,this.all={},this.teams=new l.MultyxClientObject(this,{},[],!0),this.clients={},this.controller=new r.Controller(this.ws),null==s||s(),this.ws.onmessage=t=>{var e,s,i,o;const r=n.Message.Parse(t.data);this.ping=2*(Date.now()-r.time),r.native?(this.parseNativeEvent(r),null===(e=this.events.get(a.Native))||void 0===e||e.forEach((t=>t(r)))):(null===(s=this.events.get(r.name))||void 0===s||s.forEach((t=>{const e=t(r.data);void 0!==e&&this.send(r.name,e)})),null===(i=this.events.get(a.Custom))||void 0===i||i.forEach((t=>t(r)))),null===(o=this.events.get(a.Any))||void 0===o||o.forEach((t=>t(r)))}}on(t,e){var s;const i=null!==(s=this.events.get(t))&&void 0!==s?s:[];i.push(e),this.events.set(t,i)}send(t,e){"_"===t[0]&&(t="_"+t);const s={instruction:"resp",name:t,response:e};this.ws.send(n.Message.Native(s))}await(t,e){return this.send(t,e),new Promise((e=>this.events.set(Symbol.for("_"+t),[e])))}loop(t,e){if(e)this.on(a.Start,(()=>setInterval(t,Math.round(1e3/e))));else{const e=()=>{t(),requestAnimationFrame(e)};this.on(a.Start,(()=>requestAnimationFrame(e)))}}[(t=o.Done,o.Add)](t){this[o.Done].push(t)}parseNativeEvent(t){var e,s,i,r,h;t.data=t.data.map(n.UncompressUpdate),this.options.logUpdateFrame&&console.log(t.data);for(const n of t.data)switch(n.instruction){case"init":this.initialize(n);for(const t of null!==(e=this.events.get(a.Start))&&void 0!==e?e:[])this[o.Done].push((()=>t(n)));this.events.has(a.Start)&&(this.events.get(a.Start).length=0);break;case"edit":if(1==n.path.length)n.team?this.teams.set(n.path[0],new o.EditWrapper(n.value)):this.clients[n.path[0]]=new l.MultyxClientObject(this,new o.EditWrapper(n.value),[n.path[0]],!1);else{const t=n.team?this.teams.get(n.path[0]):this.clients[n.path[0]];if(!t)return;t.set(n.path.slice(1),new o.EditWrapper(n.value))}for(const t of null!==(s=this.events.get(a.Edit))&&void 0!==s?s:[])this[o.Done].push((()=>t(n)));break;case"self":this.parseSelf(n);break;case"conn":this.clients[n.uuid]=new l.MultyxClientObject(this,n.data,[n.uuid],!1);for(const t of null!==(i=this.events.get(a.Connection))&&void 0!==i?i:[])this[o.Done].push((()=>t(this.clients[n.uuid])));break;case"dcon":for(const t of null!==(r=this.events.get(a.Disconnect))&&void 0!==r?r:[]){const e=this.clients[n.client].value;this[o.Done].push((()=>t(e)))}delete this.clients[n.client];break;case"resp":{const t=null===(h=this.events.get(Symbol.for("_"+n.name)))||void 0===h?void 0:h[0];this.events.delete(Symbol.for("_"+n.name)),t&&this[o.Done].push((()=>t(n.response)));break}default:this.options.verbose&&console.error("Server error: Unknown native Multyx instruction")}this[o.Done].forEach((t=>t())),this[o.Done].length=0}initialize(t){this.tps=t.tps,this.uuid=t.client.uuid,this.joinTime=t.client.joinTime,this.controller.listening=new Set(t.client.controller);for(const e of Object.keys(t.teams))this.teams[e]=new o.EditWrapper(t.teams[e]);this.all=this.teams.all,this.clients={};for(const[e,s]of Object.entries(t.clients))e!=this.uuid&&(this.clients[e]=new l.MultyxClientObject(this,new o.EditWrapper(s),[e],!1));const e=new l.MultyxClientObject(this,new o.EditWrapper(t.client.self),[this.uuid],!0);this.self=e,this.clients[this.uuid]=e;for(const[e,s]of Object.entries(t.constraintTable))(this.uuid==e?this.self:this.teams[e])[o.Unpack](s)}parseSelf(t){if("controller"==t.property)this.controller.listening=new Set(t.data);else if("uuid"==t.property)this.uuid=t.data;else if("constraint"==t.property){let e=this.uuid==t.data.path[0]?this.self:this.teams[t.data.path[0]];for(const s of t.data.path.slice(1))e=null==e?void 0:e[s];if(void 0===e)return;e[o.Unpack]({[t.data.name]:t.data.args})}else"space"==t.property&&(this.space=t.data,this.updateSpace())}updateSpace(){"default"!=this.space?document.querySelectorAll("[data-multyx-space]").forEach((t=>{t.style.display=t.dataset.multyxSpace==this.space?"block":"none",t.style.pointerEvents=t.dataset.multyxSpace==this.space?"auto":"none"})):document.querySelectorAll("[data-multyx-space]").forEach((t=>{t.style.display="block",t.style.pointerEvents="auto"}))}}a.Start=Symbol("start"),a.Connection=Symbol("connection"),a.Disconnect=Symbol("disconnect"),a.Edit=Symbol("edit"),a.Native=Symbol("native"),a.Custom=Symbol("custom"),a.Any=Symbol("any"),e.default=a})(),i.default})()));
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Multyx=e():t.Multyx=e()}(self,()=>(()=>{"use strict";var t={249:function(t,e,i){var s,n=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});const o=i(625),r=i(703),l=n(i(416)),a=i(449);class h{addEditCallback(t){this.editCallbacks.push(t)}get value(){var t;const e=[];for(let i=0;i<this.length;i++)e[i]=null===(t=this.get(i))||void 0===t?void 0:t.value;return e}get length(){return this.list.length}set length(t){this.list.length=t}handleShiftOperation(t,e){const i=t>=0?e>=0?"right":"left":0==e?"reverse":e<0?"length":"unknown";switch(i){case"reverse":for(let t=0;t<Math.floor(this.length/2);t++){const e=this.list[t];this.list[t]=this.list[this.length-1-t],this.list[this.length-1-t]=e}break;case"left":for(let i=t;i<this.length;i++)i+e<0||(this.list[i+e]=this.list[i]);break;case"right":for(let i=this.length-1;i>=t;i--)this.list[i+e]=this.list[i];break;case"length":this.length+=e;break;default:this.multyx.options.verbose&&console.error("Unknown shift operation: "+i)}}constructor(t,e,i=[],n){this.type="list",this.editCallbacks=[],this.toString=()=>this.value.toString(),this.valueOf=()=>this.value,this[s]=()=>this.value,this.list=[],this.propertyPath=i,this.multyx=t,this.editable=n;const o=e instanceof r.EditWrapper;e instanceof h&&(e=e.value),e instanceof r.EditWrapper&&(e=e.value);for(let t=0;t<e.length;t++)this.set(t,o?new r.EditWrapper(e[t]):e[t]);return new Proxy(this,{has:(t,e)=>"number"==typeof e?t.has(e):e in t,get:(t,e)=>e in t?t[e]:(isNaN(parseInt(e))||(e=parseInt(e)),t.get(e)),set:(t,e,i)=>e in t?(t[e]=i,!0):!!t.set(e,i),deleteProperty:(t,e)=>"number"==typeof e&&t.delete(e)})}has(t){return t>=0&&t<this.length}get(t){if("number"==typeof t)return this.list[t];if(0==t.length)return this;if(1==t.length)return this.list[parseInt(t[0])];const e=this.list[parseInt(t[0])];return!e||e instanceof o.MultyxClientValue?void 0:e.get(t.slice(1))}recursiveSet(t,e){if(0==t.length)return this.multyx.options.verbose&&console.error(`Attempting to edit MultyxClientList with no path. Setting '${this.propertyPath.join(".")}' to ${e}`),!1;if("shift"==t[0]&&e instanceof r.EditWrapper)return this.handleShiftOperation(parseInt(t[1]),e.value),!0;if(1==t.length)return this.set(parseInt(t[0]),e);let i=this.get(parseInt(t[0]));return(i instanceof o.MultyxClientValue||null==i)&&(this.set(parseInt(t[0]),new r.EditWrapper({})),i=this.get(parseInt(t[0]))),!(!i||i instanceof o.MultyxClientValue)&&i.set(t.slice(1),e)}set(t,e){if(Array.isArray(t))return this.recursiveSet(t,e);const i=this.get(t),s=e instanceof r.EditWrapper,n=s||this.editable,a=s||(0,o.IsMultyxClientItem)(e)?e.value:e;if(void 0===a)return this.delete(t,s);if(s&&this.tryApplyServerValue(t,a,i))return!0;if(this.list[t]instanceof o.MultyxClientValue&&("object"!=typeof a||null===a)){const e=this.list[t].set(s?new r.EditWrapper(a):a);return this.enqueueEditCallbacks(t,i),e}return n?(this.list[t]=new((0,l.default)(a))(this.multyx,s?new r.EditWrapper(a):a,[...this.propertyPath,t.toString()],this.editable),this.notifyIndexWaiters(t),this.enqueueEditCallbacks(t,i),!0):(this.multyx.options.verbose&&console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join(".")+"."+t}' to ${a}`),!1)}delete(t,e=!1){const i=this.get(t);if("string"==typeof t&&(t=parseInt(t)),!this.editable&&!e)return this.multyx.options.verbose&&console.error(`Attempting to delete property that is not editable. Deleting '${this.propertyPath.join(".")+"."+t}'`),!1;delete this.list[t];for(const e of this.editCallbacks)this.multyx[r.Add](()=>e(t,void 0,i));return e||this.multyx.ws.send(a.Message.Native({instruction:"edit",path:[...this.propertyPath,t.toString()],value:void 0})),!0}await(t){if(this.has(t))return Promise.resolve(this.get(t));const e=Symbol.for("_"+this.propertyPath.join(".")+"."+t);return new Promise(t=>this.multyx.on(e,t))}push(...t){for(const e of t)this.set(this.length,e);return this.length}pop(){if(0===this.length)return;const t=this.get(this.length);return this.delete(this.length),t}unshift(...t){for(let e=this.length-1;e>=0;e--)e>=t.length?this.set(e,this.get(e-t.length)):this.set(e,t[e]);return this.length}shift(){if(0==this.length)return;this.length--;const t=this.get(0);for(let t=0;t<this.length;t++)this.set(t,this.get(t+1));return t}slice(t,e){return this.list.slice(t,e)}splice(t,e,...i){return this.list.splice(t,null!=e?e:0,...i)}setSplice(t,e,...i){void 0===e&&(e=this.length-t);let s=i.length-e;if(s>0)for(let i=this.length-1;i>=t+e;i--)this.set(i+s,this.get(i));else if(s<0){for(let i=t+e;i<this.length;i++)this.set(i+s,this.get(i));const i=this.length;for(let t=i+s;t<i;t++)this.set(t,void 0)}for(let e=t;e<i.length;e++)this.set(e,i[e])}filter(t){return this.list.filter((e,i)=>t(e,i,this))}setFilter(t){const e=[];for(let i=0;i<this.length;i++)e.push(t(this.get(i),i,this));let i=0;for(let t=0;t<e.length;t++)e[t]&&i&&this.set(t-i,this.get(t)),e[t]||i--}map(t){const e=[];for(let i=0;i<this.length;i++)e.push(t(this.get(i),i,this));return e}flat(){return this.list.flat()}setFlat(){for(let t=0;t<this.length;t++){const e=this.get(t);if(e instanceof h)for(let i=0;i<e.length;i++)t++,this.set(t,e[i])}}reduce(t,e){for(let i=0;i<this.length;i++)e=t(e,this.get(i),i,this);return e}reduceRight(t,e){for(let i=this.length-1;i>=0;i--)e=t(e,this.get(i),i,this);return e}reverse(){let t=this.length-1;for(let e=0;e<t;e++){const i=this.get(e),s=this.get(t);this.set(e,s),this.set(t,i)}return this}forEach(t){for(let e=0;e<this.length;e++)t(this.get(e),e,this)}every(t){for(let e=0;e<this.length;e++)if(!t(this.get(e),e,this))return!1;return!0}some(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return!0;return!1}find(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return this.get(e)}findIndex(t){for(let e=0;e<this.length;e++)if(t(this.get(e),e,this))return e;return-1}entries(){const t=[];for(let e=0;e<this.length;e++)t.push([this.get(e),e]);return t}keys(){return Array(this.length).fill(0).map((t,e)=>e)}[r.Edit](){}[r.Unpack](t){var e;for(let i=0;i<this.length;i++)null===(e=this.get(i))||void 0===e||e[r.Unpack](t[i])}[Symbol.iterator](){const t=[];for(let e=0;e<this.length;e++)t[e]=this.get(e);return t[Symbol.iterator]()}hydrateFromServer(t){if(Array.isArray(t)){for(let e=0;e<t.length;e++)this.set(e,new r.EditWrapper(t[e]));for(let e=t.length;e<this.length;e++)this.delete(e,!0);this.length=t.length}}tryApplyServerValue(t,e,i){const s=this.list[t];if(!s)return!1;if(s instanceof o.MultyxClientValue&&("object"!=typeof e||null===e))return s.set(new r.EditWrapper(e)),this.enqueueEditCallbacks(t,i),!0;const n="function"==typeof(null==s?void 0:s.hydrateFromServer);return Array.isArray(e)&&n&&"list"===s.type?(s.hydrateFromServer(e),this.enqueueEditCallbacks(t,i),!0):!(null===(l=e)||"object"!=typeof l||Array.isArray(l)||!n||"object"!==s.type||(s.hydrateFromServer(e),this.enqueueEditCallbacks(t,i),0));var l}notifyIndexWaiters(t){var e,i;const s=Symbol.for("_"+this.propertyPath.join(".")+"."+t);this.multyx.events.has(s)&&this.multyx[r.Done].push(...null!==(i=null===(e=this.multyx.events.get(s))||void 0===e?void 0:e.map(e=>()=>e(this.list[t])))&&void 0!==i?i:[])}enqueueEditCallbacks(t,e){for(const i of this.editCallbacks)this.multyx[r.Add](()=>i(t,this.get(t),e))}}s=Symbol.toPrimitive,e.default=h},280:(t,e,i)=>{var s;Object.defineProperty(e,"__esModule",{value:!0});const n=i(449),o=i(703);class r{get value(){return this.readModifiers.reduce((t,e)=>e(t),this._value)}set value(t){this._value=t,this.captureSample(t)}addReadModifier(t){this.readModifiers.push(t)}addEditCallback(t){this.editCallbacks.push(t)}[o.Edit](t,e){0==t.length&&this.set(new o.EditWrapper(e))}constructor(t,e,i=[],n){var r,l;this.readModifiers=[],this.editCallbacks=[],this.interpolationFrameMs=250,this.toString=()=>this.value.toString(),this.valueOf=()=>this.value,this[s]=()=>this.value,this.propertyPath=i,this.editable=n,this.multyx=t,this.constraints={},this.set(e);const a=Symbol.for("_"+this.propertyPath.join("."));this.multyx.events.has(a)&&this.multyx[o.Done].push(...null!==(l=null===(r=this.multyx.events.get(a))||void 0===r?void 0:r.map(t=>()=>t(this.value)))&&void 0!==l?l:[])}set(t){if(t instanceof o.EditWrapper){const e=this.value;return this.value=t.value,this.editCallbacks.forEach(i=>i(t.value,e)),!0}if(!this.editable)return this.multyx.options.verbose&&console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join(".")}' to ${t}`),!1;let e=t;for(const i in this.constraints)if(e=(0,this.constraints[i])(e),null===e)return this.multyx.options.verbose&&console.error(`Attempting to set property that failed on constraint. Setting '${this.propertyPath.join(".")}' to ${t}, stopped by constraint '${i}'`),!1;return this._value===e?(this.value=e,!0):(this.value=e,this.multyx.ws.send(n.Message.Native({instruction:"edit",path:this.propertyPath,value:e})),!0)}bindElement(t){this.addEditCallback((e,i)=>{e!==i&&(t.innerText=e.toString())})}[o.Unpack](t){for(const[e,i]of Object.entries(t)){const t=(0,o.BuildConstraint)(e,i);t&&(this.constraints[e]=t)}}Lerp(t=250){return this.applyInterpolation("lerp",t)}PredictiveLerp(t=250){return this.applyInterpolation("predictive",t)}applyInterpolation(t,e){if("number"!=typeof this._value||Number.isNaN(this._value))throw new Error(`MultyxClientValue.${"lerp"===t?"Lerp":"PredictiveLerp"} can only be applied to numeric values`);return this.interpolationFrameMs=Math.max(1,e),this.attachInterpolationModifier(t),this}attachInterpolationModifier(t){this.interpolationModifier&&(this.readModifiers=this.readModifiers.filter(t=>t!==this.interpolationModifier)),this.interpolationModifier=e=>this.interpolateValue(e,t),this.readModifiers.push(this.interpolationModifier)}captureSample(t){if("number"!=typeof t||Number.isNaN(t))return this.latestSample=void 0,void(this.previousSample=void 0);const e=Date.now();this.latestSample?(this.previousSample=Object.assign({},this.latestSample),this.latestSample={value:t,time:e}):this.latestSample={value:t,time:e}}interpolateValue(t,e){if("number"!=typeof t||!this.latestSample||!this.previousSample)return t;const i=this.latestSample.time-this.previousSample.time;if(i<=0)return t;const s=Math.max(1,Math.min(i,this.interpolationFrameMs)),n=Math.max(0,Math.min(Date.now()-this.latestSample.time,s)),o=0===s?1:n/s,r=this.latestSample.value-this.previousSample.value;return"predictive"===e?this.latestSample.value+r*o:this.previousSample.value+r*o}}s=Symbol.toPrimitive,e.default=r},416:(t,e,i)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){return Array.isArray(t)?i(249).default:"object"==typeof t?i(922).default:i(280).default}},449:(t,e)=>{function i(t){let e,i;if("edit"==t.instruction?(e=0,i=[t.path.join("."),JSON.stringify(t.value)]):"input"==t.instruction?(e=1,i=[t.input,JSON.stringify(t.data)]):"resp"==t.instruction&&(e=2,i=[t.name,JSON.stringify(t.response)]),!i||void 0===e)return"";let s=e.toString();for(let t=0;t<i.length;t++)s+=i[t].replace(/;/g,";_"),t<i.length-1&&(s+=";,");return JSON.stringify([s])}Object.defineProperty(e,"__esModule",{value:!0}),e.Message=void 0,e.UncompressUpdate=function(t){const[e,...i]=t.split(/;,/),s=e[0],n=e.slice(1).replace(/;_/g,";"),o=i.map(t=>t.replace(/;_/g,";")).map(t=>"undefined"==t?void 0:JSON.parse(t));return"0"==s?{instruction:"edit",team:!1,path:n.split("."),value:o[0]}:"1"==s?{instruction:"edit",team:!0,path:n.split("."),value:o[0]}:"2"==s?{instruction:"self",property:"controller",data:JSON.parse(n)}:"3"==s?{instruction:"self",property:"uuid",data:JSON.parse(n)}:"4"==s?{instruction:"self",property:"constraint",data:JSON.parse(n)}:"9"==s?{instruction:"self",property:"space",data:JSON.parse(n)}:"5"==s?{instruction:"resp",name:n,response:o[0]}:"6"==s?{instruction:"conn",uuid:n,data:o[0]}:"7"==s?{instruction:"dcon",client:n}:"8"==s?{instruction:"init",client:JSON.parse(n),tps:o[0],constraintTable:o[1],clients:o[2],teams:o[3],space:o[4]}:void 0},e.CompressUpdate=i;class s{constructor(t,e,i=!1){this.name=t,this.data=e,this.time=Date.now(),this.native=i}static BundleOperations(t,e){return Array.isArray(e)||(e=[e]),JSON.stringify(new s("_",{operations:e,deltaTime:t}))}static Native(t){return i(t)}static Parse(t){var e,i;const n=JSON.parse(t);return Array.isArray(n)?new s("_",n,!0):new s(null!==(e=n.name)&&void 0!==e?e:"",null!==(i=n.data)&&void 0!==i?i:"",!1)}static Create(t,e){if(0==t.length)throw new Error("Multyx message cannot have empty name");if("_"==t[0]&&(t="_"+t),"function"==typeof e)throw new Error("Multyx data must be JSON storable");return JSON.stringify(new s(t,e))}}e.Message=s},625:function(t,e,i){var s=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.MultyxClientValue=e.MultyxClientObject=e.MultyxClientList=void 0,e.IsMultyxClientItem=function(t){return t instanceof n.default||t instanceof o.default||t instanceof r.default};const n=s(i(249));e.MultyxClientList=n.default;const o=s(i(922));e.MultyxClientObject=o.default;const r=s(i(280));e.MultyxClientValue=r.default},703:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.EditWrapper=e.Edit=e.Add=e.Done=e.Unpack=void 0,e.Interpolate=function(t,e,i){if(!Array.isArray(i)||0===i.length)throw new Error("Interpolation curve must contain at least one slice");const s=[...i].sort((t,e)=>t.time-e.time),n=s[s.length-1].time,o=n<=1;let r={value:t[e],time:Date.now()},l={value:t[e],time:Date.now()};Object.defineProperty(t,e,{configurable:!0,enumerable:!0,get:()=>{if(r.time===l.time)return l.value;const t=Date.now(),e=Math.max(l.time-r.time,1),i=Math.max(0,t-l.time),a=(h=o?i/e:i,u=0,p=n,Math.min(Math.max(h,u),p));var h,u,p;let c,d=s[0],f=s[s.length-1];for(const t of s){if(!(t.time<=a)){f=t;break}d=t}if(f.time===d.time)c=d.progress;else{const t=(a-d.time)/(f.time-d.time);c=d.progress+t*(f.progress-d.progress)}return Number.isNaN(c)?r.value:"number"==typeof r.value&&"number"==typeof l.value?l.value*c+r.value*(1-c):c>=1?l.value:r.value},set:t=>{const e=Date.now();return e-l.time<10?(l.value=t,l.time=e,!0):(r=Object.assign({},l),l={value:t,time:e},!0)}})},e.BuildConstraint=function(t,e){return"min"==t?t=>t>=e[0]?t:e[0]:"max"==t?t=>t<=e[0]?t:e[0]:"int"==t?t=>Math.floor(t):"ban"==t?t=>e.includes(t)?null:t:"disabled"==t?t=>e[0]?null:t:t=>t},e.Unpack=Symbol("unpack"),e.Done=Symbol("done"),e.Add=Symbol("add"),e.Edit=Symbol("edit"),e.EditWrapper=class{constructor(t){this.value=t}}},832:(t,e,i)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Controller=void 0;const s=i(449);e.Controller=class{constructor(t){this.listening=new Set,this.ws=t,this.preventDefault=!1,this.keys={},this.mouse={x:NaN,y:NaN,down:!1,centerX:0,centerY:0,scaleX:1,scaleY:1},document.addEventListener("keydown",t=>{this.preventDefault&&t.preventDefault();const e=t.key.toLowerCase();this.keys[e]&&this.listening.has("keyhold")&&this.relayInput("keyhold",{code:e}),this.keys[t.code]&&this.listening.has("keyhold")&&this.relayInput("keyhold",{code:t.code}),this.listening.has(e)&&!this.keys[e]&&this.relayInput("keydown",{code:t.key}),this.listening.has(t.code)&&!this.keys[t.code]&&this.relayInput("keydown",{code:t.code}),this.keys[e]=!0,this.keys[t.code]=!0}),document.addEventListener("keyup",t=>{this.preventDefault&&t.preventDefault();const e=t.key.toLowerCase();delete this.keys[e],delete this.keys[t.code],this.listening.has(e)&&this.relayInput("keyup",{code:e}),this.listening.has(t.code)&&this.relayInput("keyup",{code:t.code})}),document.addEventListener("mousedown",t=>{if(this.preventDefault&&t.preventDefault(),this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.mouse.down=!0,this.listening.has("mousedown")&&this.relayInput("mousedown",{x:this.mouse.x,y:this.mouse.y})}),document.addEventListener("mouseup",t=>{if(this.preventDefault&&t.preventDefault(),this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.mouse.down=!1,this.listening.has("mouseup")&&this.relayInput("mouseup",{x:this.mouse.x,y:this.mouse.y})}),document.addEventListener("mousemove",t=>{if(this.preventDefault&&t.preventDefault(),this.mouseGetter){const t=this.mouseGetter();this.mouse.x=t.x,this.mouse.y=t.y}else this.mouse.x=(t.clientX-this.mouse.centerX)/this.mouse.scaleX,this.mouse.y=(t.clientY-this.mouse.centerY)/this.mouse.scaleY;this.listening.has("mousemove")&&this.relayInput("mousemove",{x:this.mouse.x,y:this.mouse.y})})}mapCanvasPosition(t,e){const i="top"in e,s="bottom"in e,n="left"in e,o="right"in e,r=e.anchor,l=t.getBoundingClientRect(),a=(t,...e)=>{const i=t?"Cannot include value for ":"Must include value for ",s=1==e.length?e[0]:e.slice(0,-1).join(", ")+(t?" and ":" or ")+e.slice(-1)[0],n=r?" if anchoring at "+r:" if not anchoring";console.error(i+s+n)},h=l.width/l.height,u=l.height/l.width;if((Number.isNaN(h)||Number.isNaN(u))&&console.error("Canvas element bounding box is flat, canvas must be present on the screen"),r){if("center"==r){if(i&&s&&e.top!==-e.bottom||n&&o&&e.left!==-e.right)return a(!0,"top","bottom","left","right");i?(e.left=n?e.left:o?-e.right:-Math.abs(h*e.top),e.right=n?-e.left:o?e.right:Math.abs(h*e.top),e.bottom=-e.top):s?(e.left=n?e.left:o?-e.right:-Math.abs(h*e.bottom),e.right=n?-e.left:o?e.right:Math.abs(h*e.bottom),e.top=-e.bottom):n?(e.top=i?e.top:s?-e.bottom:-Math.abs(u*e.left),e.bottom=i?-e.top:s?e.bottom:Math.abs(u*e.left),e.right=-e.left):o&&(e.top=i?e.top:s?-e.bottom:-Math.abs(u*e.right),e.bottom=i?-e.top:s?e.bottom:Math.abs(u*e.right),e.left=-e.right)}else if("bottom"==r){if(!n&&!o&&!i)return a(!1,"left","right","top");if(e.bottom)return a(!0,"bottom");e.bottom=0,n?(e.top=Math.abs(u*e.left*2),e.right=-e.left):o?(e.top=Math.abs(u*e.right*2),e.left=-e.right):(e.left=-Math.abs(h*e.top/2),e.right=-e.left)}else if("top"==r){if(!n&&!o&&!s)return a(!1,"left","right","bottom");if(e.top)return a(!0,"top");e.top=0,n?(e.bottom=Math.abs(u*e.left*2),e.right=-e.left):o?(e.bottom=Math.abs(u*e.right*2),e.left=-e.right):(e.left=-Math.abs(h*e.bottom/2),e.right=-e.left)}else if("left"==r){if(!i&&!s&&!o)return a(!1,"top","bottom","right");if(n)return a(!0,"left");e.left=0,i?(e.right=-Math.abs(h*e.top*2),e.bottom=-e.top):s?(e.right=Math.abs(h*e.bottom*2),e.top=-e.bottom):(e.top=-Math.abs(u*e.right/2),e.bottom=-e.top)}else if("right"==r){if(!i&&!s&&!n)return a(!1,"top","bottom","left");if(o)return a(!0,"right");e.right=0,i?(e.left=-Math.abs(h*e.top*2),e.bottom=-e.top):s?(e.left=Math.abs(h*e.bottom*2),e.top=-e.bottom):(e.top=-Math.abs(u*e.right/2),e.bottom=-e.top)}else if("topleft"==r){if(!o&&!s)return a(!1,"right","bottom");if(n||i)return a(!0,"left","top");e.left=e.top=0,o?e.bottom=Math.abs(u*e.right):e.right=Math.abs(h*e.bottom)}else if("topright"==r){if(!n&&!s)return a(!1,"left","bottom");if(o||i)return a(!0,"right","top");e.right=e.top=0,n?e.bottom=Math.abs(u*e.left):e.left=Math.abs(h*e.bottom)}else if("bottomleft"==r){if(!o&&!i)return a(!1,"right","top");if(s||n)return a(!0,"bottom","left");e.left=e.bottom=0,o?e.top=Math.abs(u*e.right):e.right=Math.abs(h*e.top)}else if("bottomright"==r){if(!i&&!n)return a(!1,"top","left");if(o||s)return a(!0,"bottom","right");e.right=e.bottom=0,n?e.top=Math.abs(u*e.left):e.left=Math.abs(h*e.top)}}else{if(!i&&!s)return a(!1,"top","bottom");if(s?i||(e.top=e.bottom-t.height):e.bottom=e.top+t.height,!n&&!o)return a(!1,"left","right");o?n||(e.left=e.right-t.width):e.right=e.left+t.width}const p=t.getContext("2d");null==p||p.setTransform(1,0,0,1,0,0),t.width=Math.floor(Math.abs(e.right-e.left)),t.height=Math.floor(Math.abs(e.bottom-e.top)),e.right<e.left&&(null==p||p.scale(-1,1)),e.top>e.bottom&&(null==p||p.scale(1,-1)),null==p||p.translate(-e.left,-e.top)}mapMousePosition(t,e,i=document.body,s=1,n=s){const o=window.innerWidth/(i instanceof HTMLCanvasElement?i.width:i.clientWidth),r=window.innerHeight/(i instanceof HTMLCanvasElement?i.height:i.clientHeight),l=i.getBoundingClientRect();this.mouse.centerX=l.left+t*o,this.mouse.centerY=l.top+e*r,this.mouse.scaleX=s*o,this.mouse.scaleY=n*r}mapMouseToCanvas(t){const e=t.getContext("2d"),i=null==e?void 0:e.getTransform(),s=t.getBoundingClientRect(),n=s.width/t.width,o=s.height/t.height;this.mouse.centerX=s.left+(null==i?void 0:i.e)*n,this.mouse.centerY=s.top+(null==i?void 0:i.f)*o,this.mouse.scaleX=n*(null==i?void 0:i.a),this.mouse.scaleY=o*(null==i?void 0:i.d)}setMouseAs(t){this.mouseGetter=t}relayInput(t,e){if(1!==this.ws.readyState)throw new Error("Websocket connection is "+(2==this.ws.readyState?"closing":"closed"));this.ws.send(s.Message.Native(Object.assign({instruction:"input",input:t},e?{data:e}:{})))}}},922:function(t,e,i){var s=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});const n=i(449),o=i(703),r=i(625),l=s(i(416)),a=s(i(280)),h=t=>null!==t&&"object"==typeof t&&!Array.isArray(t);class u{get value(){const t={};for(const e in this.object)t[e]=this.object[e];return t}addEditCallback(t){this.editCallbacks.push(t)}[o.Edit](t,e){var i;1!=t.length?(0==t.length&&this.multyx.options.verbose&&console.error("Update path is empty. Attempting to edit MultyxClientObject with no path."),this.has(t[0])||this.set(t[0],new o.EditWrapper({})),null===(i=this.get(t[0]))||void 0===i||i[o.Edit](t.slice(1),e)):this.set(t[0],new o.EditWrapper(e))}constructor(t,e,i=[],s){this.type="object",this.editCallbacks=[],this.object={},this.propertyPath=i,this.multyx=t,this.editable=s;const n=e instanceof o.EditWrapper;e instanceof u&&(e=e.value),e instanceof o.EditWrapper&&(e=e.value);for(const t in e)this.set(t,n?new o.EditWrapper(e[t]):e[t]);if(this.constructor===u)return new Proxy(this,{has:(t,e)=>t.has(e),get:(t,e)=>e in t?t[e]:t.get(e),set:(t,e,i)=>e in t?(t[e]=i,!0):t.set(e,i),deleteProperty:(t,e)=>t.delete(e,!1)})}has(t){return t in this.object}get(t){if("string"==typeof t)return this.object[t];if(0==t.length)return this;if(1==t.length)return this.object[t[0]];const e=this.object[t[0]];return!e||e instanceof a.default?void 0:e.get(t.slice(1))}recursiveSet(t,e){if(0==t.length)return this.multyx.options.verbose&&console.error(`Attempting to edit MultyxClientObject with no path. Setting '${this.propertyPath.join(".")}' to ${e}`),!1;if(1==t.length)return this.set(t[0],e);let i=this.get(t[0]);return(i instanceof a.default||null==i)&&(isNaN(parseInt(t[1]))?(this.set(t[0],new o.EditWrapper({})),i=this.get(t[0])):(this.set(t[0],new o.EditWrapper([])),i=this.get(t[0]))),i.set(t.slice(1),e)}set(t,e){if(Array.isArray(t))return this.recursiveSet(t,e);const i=e instanceof o.EditWrapper,s=i||this.editable,n=i||(0,r.IsMultyxClientItem)(e)?e.value:e;return void 0===n?this.delete(t,i):!(!i||!this.applyServerValue(t,n))||(this.object[t]instanceof a.default&&("object"!=typeof n||null===n)?this.object[t].set(i?new o.EditWrapper(n):n):s?(this.object[t]=new((0,l.default)(n))(this.multyx,i?new o.EditWrapper(n):n,[...this.propertyPath,t],this.editable),this.notifyPropertyWaiters(t),!0):(this.multyx.options.verbose&&console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join(".")+"."+t}' to ${n}`),!1))}delete(t,e=!1){return this.editable||e?(delete this.object[t],e||this.multyx.ws.send(n.Message.Native({instruction:"edit",path:[...this.propertyPath,t],value:void 0})),!0):(this.multyx.options.verbose&&console.error(`Attempting to delete property that is not editable. Deleting '${this.propertyPath.join(".")+"."+t}'`),!1)}keys(){return Object.keys(this.object)}values(){return Object.values(this.object)}entries(){const t=[];for(let e in this.object)t.push([e,this.get(e)]);return t}await(t){if(this.has(t))return Promise.resolve(this.get(t));const e=Symbol.for("_"+this.propertyPath.join(".")+"."+t);return new Promise(t=>this.multyx.on(e,t))}[o.Unpack](t){var e;for(const i in t)null===(e=this.object[i])||void 0===e||e[o.Unpack](t[i])}hydrateFromServer(t){if(!h(t))return;const e=new Set(Object.keys(this.object));for(const[i,s]of Object.entries(t))e.delete(i),this.set(i,new o.EditWrapper(s));for(const t of e)this.delete(t,!0)}applyServerValue(t,e){const i=this.object[t];if(!i)return!1;if(i instanceof a.default&&("object"!=typeof e||null===e))return i.set(new o.EditWrapper(e)),!0;const s="function"==typeof(null==i?void 0:i.hydrateFromServer);return(Array.isArray(e)&&s&&"list"===i.type||!(!h(e)||!s||"object"!==i.type))&&(i.hydrateFromServer(e),!0)}notifyPropertyWaiters(t){var e,i;const s=Symbol.for("_"+this.propertyPath.join(".")+"."+t);this.multyx.events.has(s)&&this.multyx[o.Done].push(...null!==(i=null===(e=this.multyx.events.get(s))||void 0===e?void 0:e.map(e=>()=>e(this.object[t])))&&void 0!==i?i:[])}}e.default=u},960:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.DefaultOptions=void 0,e.DefaultOptions={port:8443,secure:!1,uri:"localhost",verbose:!1,logUpdateFrame:!1}}},e={};function i(s){var n=e[s];if(void 0!==n)return n.exports;var o=e[s]={exports:{}};return t[s].call(o.exports,o,o.exports,i),o.exports}var s={};return(()=>{var t,e=s;const n=i(449),o=i(703),r=i(832),l=i(625),a=i(960);class h{constructor(e={},i){var s;if(this[t]=[],this.options=Object.assign(Object.assign({},a.DefaultOptions),e),!this.options.uri)throw new Error("URI is required");const o=`ws${this.options.secure?"s":""}://${this.options.uri.split("/")[0]}:${this.options.port}/${null!==(s=this.options.uri.split("/")[1])&&void 0!==s?s:""}`;this.ws=new WebSocket(o),this.ping=0,this.space="default",this.events=new Map,this.self={},this.tps=0,this.all={},this.teams=new l.MultyxClientObject(this,{},[],!0),this.clients={},this.controller=new r.Controller(this.ws),null==i||i(),this.ws.onmessage=t=>{var e,i,s,o;const r=n.Message.Parse(t.data);this.ping=2*(Date.now()-r.time),r.native?(this.parseNativeEvent(r),null===(e=this.events.get(h.Native))||void 0===e||e.forEach(t=>t(r))):(null===(i=this.events.get(r.name))||void 0===i||i.forEach(t=>{const e=t(r.data);void 0!==e&&this.send(r.name,e)}),null===(s=this.events.get(h.Custom))||void 0===s||s.forEach(t=>t(r))),null===(o=this.events.get(h.Any))||void 0===o||o.forEach(t=>t(r))}}on(t,e){var i;const s=null!==(i=this.events.get(t))&&void 0!==i?i:[];s.push(e),this.events.set(t,s)}send(t,e){"_"===t[0]&&(t="_"+t);const i={instruction:"resp",name:t,response:e};this.ws.send(n.Message.Native(i))}await(t,e){return this.send(t,e),new Promise(e=>this.events.set(Symbol.for("_"+t),[e]))}loop(t,e){if(e)this.on(h.Start,()=>setInterval(t,Math.round(1e3/e)));else{const e=()=>{t(),requestAnimationFrame(e)};this.on(h.Start,()=>requestAnimationFrame(e))}}[(t=o.Done,o.Add)](t){this[o.Done].push(t)}parseNativeEvent(t){var e,i,s,r,a;t.data=t.data.map(n.UncompressUpdate),this.options.logUpdateFrame&&console.log(t.data);for(const n of t.data)switch(n.instruction){case"init":this.initialize(n);for(const t of null!==(e=this.events.get(h.Start))&&void 0!==e?e:[])this[o.Done].push(()=>t(n));this.events.has(h.Start)&&(this.events.get(h.Start).length=0);break;case"edit":if(1==n.path.length)n.team?this.teams.set(n.path[0],new o.EditWrapper(n.value)):this.clients[n.path[0]]=new l.MultyxClientObject(this,new o.EditWrapper(n.value),[n.path[0]],!1);else{const t=n.team?this.teams.get(n.path[0]):this.clients[n.path[0]];if(!t)return;t.set(n.path.slice(1),new o.EditWrapper(n.value))}for(const t of null!==(i=this.events.get(h.Edit))&&void 0!==i?i:[])this[o.Done].push(()=>t(n));break;case"self":this.parseSelf(n);break;case"conn":this.clients[n.uuid]=new l.MultyxClientObject(this,n.data,[n.uuid],!1);for(const t of null!==(s=this.events.get(h.Connection))&&void 0!==s?s:[])this[o.Done].push(()=>t(this.clients[n.uuid]));break;case"dcon":for(const t of null!==(r=this.events.get(h.Disconnect))&&void 0!==r?r:[]){const e=this.clients[n.client].value;this[o.Done].push(()=>t(e))}delete this.clients[n.client];break;case"resp":{const t=null===(a=this.events.get(Symbol.for("_"+n.name)))||void 0===a?void 0:a[0];this.events.delete(Symbol.for("_"+n.name)),t&&this[o.Done].push(()=>t(n.response));break}default:this.options.verbose&&console.error("Server error: Unknown native Multyx instruction")}this[o.Done].forEach(t=>t()),this[o.Done].length=0}initialize(t){this.tps=t.tps,this.uuid=t.client.uuid,this.joinTime=t.client.joinTime,this.controller.listening=new Set(t.client.controller);for(const e of Object.keys(t.teams))this.teams[e]=new o.EditWrapper(t.teams[e]);this.all=this.teams.all,this.clients={};for(const[e,i]of Object.entries(t.clients))e!=this.uuid&&(this.clients[e]=new l.MultyxClientObject(this,new o.EditWrapper(i),[e],!1));const e=new l.MultyxClientObject(this,new o.EditWrapper(t.client.self),[this.uuid],!0);this.self=e,this.clients[this.uuid]=e;for(const[e,i]of Object.entries(t.constraintTable))(this.uuid==e?this.self:this.teams[e])[o.Unpack](i)}parseSelf(t){if("controller"==t.property)this.controller.listening=new Set(t.data);else if("uuid"==t.property)this.uuid=t.data;else if("constraint"==t.property){let e=this.uuid==t.data.path[0]?this.self:this.teams[t.data.path[0]];for(const i of t.data.path.slice(1))e=null==e?void 0:e[i];if(void 0===e)return;e[o.Unpack]({[t.data.name]:t.data.args})}else"space"==t.property&&(this.space=t.data,this.updateSpace())}updateSpace(){"default"!=this.space?document.querySelectorAll("[data-multyx-space]").forEach(t=>{t.style.display=t.dataset.multyxSpace==this.space?"block":"none",t.style.pointerEvents=t.dataset.multyxSpace==this.space?"auto":"none"}):document.querySelectorAll("[data-multyx-space]").forEach(t=>{t.style.display="block",t.style.pointerEvents="auto"})}}h.Start=Symbol("start"),h.Connection=Symbol("connection"),h.Disconnect=Symbol("disconnect"),h.Edit=Symbol("edit"),h.Native=Symbol("native"),h.Custom=Symbol("custom"),h.Any=Symbol("any"),h.Interpolate=o.Interpolate,e.default=h})(),s.default})());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multyx-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Framework designed to simplify the creation of multiplayer browser games by addressing the complexities of managing server-client communication, shared state, and input handling",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
package/src/controller.ts
CHANGED
|
@@ -32,7 +32,7 @@ export class Controller {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
document.addEventListener('keydown', e => {
|
|
35
|
-
if(this.preventDefault) e.preventDefault;
|
|
35
|
+
if(this.preventDefault) e.preventDefault();
|
|
36
36
|
|
|
37
37
|
const key = e.key.toLowerCase();
|
|
38
38
|
|
|
@@ -57,7 +57,7 @@ export class Controller {
|
|
|
57
57
|
|
|
58
58
|
});
|
|
59
59
|
document.addEventListener('keyup', e => {
|
|
60
|
-
if(this.preventDefault) e.preventDefault;
|
|
60
|
+
if(this.preventDefault) e.preventDefault();
|
|
61
61
|
|
|
62
62
|
const key = e.key.toLowerCase();
|
|
63
63
|
|
|
@@ -69,7 +69,7 @@ export class Controller {
|
|
|
69
69
|
|
|
70
70
|
// Mouse input events
|
|
71
71
|
document.addEventListener('mousedown', e => {
|
|
72
|
-
if(this.preventDefault) e.preventDefault;
|
|
72
|
+
if(this.preventDefault) e.preventDefault();
|
|
73
73
|
|
|
74
74
|
if(this.mouseGetter) {
|
|
75
75
|
const mouse = this.mouseGetter();
|
|
@@ -86,7 +86,7 @@ export class Controller {
|
|
|
86
86
|
});
|
|
87
87
|
});
|
|
88
88
|
document.addEventListener('mouseup', e => {
|
|
89
|
-
if(this.preventDefault) e.preventDefault;
|
|
89
|
+
if(this.preventDefault) e.preventDefault();
|
|
90
90
|
|
|
91
91
|
if(this.mouseGetter) {
|
|
92
92
|
const mouse = this.mouseGetter();
|
|
@@ -103,7 +103,7 @@ export class Controller {
|
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
105
|
document.addEventListener('mousemove', e => {
|
|
106
|
-
if(this.preventDefault) e.preventDefault;
|
|
106
|
+
if(this.preventDefault) e.preventDefault();
|
|
107
107
|
|
|
108
108
|
if(this.mouseGetter) {
|
|
109
109
|
const mouse = this.mouseGetter();
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Message, UncompressUpdate } from "./message";
|
|
2
|
-
import { Unpack, EditWrapper, Add,
|
|
2
|
+
import { Unpack, EditWrapper, Add, Done, Interpolate } from './utils';
|
|
3
3
|
import { RawObject, ResponseUpdate } from "./types";
|
|
4
4
|
import { Controller } from "./controller";
|
|
5
|
-
import { MultyxClientObject
|
|
5
|
+
import { MultyxClientObject } from "./items";
|
|
6
6
|
import { DefaultOptions, Options } from "./options";
|
|
7
7
|
export default class Multyx {
|
|
8
8
|
ws: WebSocket;
|
|
@@ -31,6 +31,8 @@ export default class Multyx {
|
|
|
31
31
|
static Custom = Symbol('custom');
|
|
32
32
|
static Any = Symbol('any');
|
|
33
33
|
|
|
34
|
+
static Interpolate = Interpolate;
|
|
35
|
+
|
|
34
36
|
constructor(options: Options = {}, callback?: () => void) {
|
|
35
37
|
this.options = { ...DefaultOptions, ...options };
|
|
36
38
|
|
package/src/items/list.ts
CHANGED
|
@@ -4,7 +4,10 @@ import { Add, Done, Edit, EditWrapper, Unpack } from '../utils';
|
|
|
4
4
|
import MultyxClientItemRouter from './router';
|
|
5
5
|
import { Message } from '../message';
|
|
6
6
|
|
|
7
|
+
const isPlainObject = (value: any) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
8
|
+
|
|
7
9
|
export default class MultyxClientList {
|
|
10
|
+
readonly type = 'list';
|
|
8
11
|
protected list: MultyxClientItem[];
|
|
9
12
|
private multyx: Multyx;
|
|
10
13
|
propertyPath: string[];
|
|
@@ -157,9 +160,14 @@ export default class MultyxClientList {
|
|
|
157
160
|
let next = this.get(parseInt(path[0]));
|
|
158
161
|
if(next instanceof MultyxClientValue || next == undefined) {
|
|
159
162
|
this.set(parseInt(path[0]), new EditWrapper({}));
|
|
160
|
-
next = this.get(parseInt(path[0]))
|
|
163
|
+
next = this.get(parseInt(path[0]));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if(!next || next instanceof MultyxClientValue) {
|
|
167
|
+
return false;
|
|
161
168
|
}
|
|
162
|
-
|
|
169
|
+
|
|
170
|
+
return (next as MultyxClientObject | MultyxClientList).set(path.slice(1), value);
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
set(index: number | string[], value: any): boolean {
|
|
@@ -169,41 +177,37 @@ export default class MultyxClientList {
|
|
|
169
177
|
|
|
170
178
|
const serverSet = value instanceof EditWrapper;
|
|
171
179
|
const allowed = serverSet || this.editable;
|
|
172
|
-
|
|
173
|
-
if(
|
|
180
|
+
const incoming = (serverSet || IsMultyxClientItem(value)) ? value.value : value;
|
|
181
|
+
if(incoming === undefined) return this.delete(index, serverSet);
|
|
182
|
+
|
|
183
|
+
if(serverSet && this.tryApplyServerValue(index, incoming, oldValue)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
174
186
|
|
|
175
|
-
// If value is a MultyxClientValue, set the value
|
|
176
|
-
if(this.list[index] instanceof MultyxClientValue && typeof
|
|
177
|
-
|
|
187
|
+
// If value is a MultyxClientValue, set the value directly
|
|
188
|
+
if(this.list[index] instanceof MultyxClientValue && (typeof incoming !== 'object' || incoming === null)) {
|
|
189
|
+
const result = this.list[index].set(serverSet ? new EditWrapper(incoming) : incoming);
|
|
190
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
191
|
+
return result;
|
|
178
192
|
}
|
|
179
193
|
|
|
180
194
|
// Attempting to edit property not editable to client
|
|
181
195
|
if(!allowed) {
|
|
182
196
|
if(this.multyx.options.verbose) {
|
|
183
|
-
console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + index}' to ${
|
|
197
|
+
console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + index}' to ${incoming}`);
|
|
184
198
|
}
|
|
185
199
|
return false;
|
|
186
200
|
}
|
|
187
201
|
|
|
188
|
-
this.list[index] = new (MultyxClientItemRouter(
|
|
202
|
+
this.list[index] = new (MultyxClientItemRouter(incoming))(
|
|
189
203
|
this.multyx,
|
|
190
|
-
serverSet ? new EditWrapper(
|
|
204
|
+
serverSet ? new EditWrapper(incoming) : incoming,
|
|
191
205
|
[...this.propertyPath, index.toString()],
|
|
192
206
|
this.editable
|
|
193
207
|
);
|
|
194
208
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.multyx[Done].push(...(this.multyx.events.get(propSymbol)?.map(e =>
|
|
198
|
-
() => e(this.list[index])
|
|
199
|
-
) ?? []));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// We have to push into queue, since object may not be fully created
|
|
203
|
-
// and there may still be more updates to parse
|
|
204
|
-
for(const listener of this.editCallbacks) {
|
|
205
|
-
this.multyx[Add](() => listener(index, this.get(index), oldValue));
|
|
206
|
-
}
|
|
209
|
+
this.notifyIndexWaiters(index);
|
|
210
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
207
211
|
|
|
208
212
|
return true;
|
|
209
213
|
}
|
|
@@ -452,4 +456,56 @@ export default class MultyxClientList {
|
|
|
452
456
|
toString = () => this.value.toString();
|
|
453
457
|
valueOf = () => this.value;
|
|
454
458
|
[Symbol.toPrimitive] = () => this.value;
|
|
459
|
+
|
|
460
|
+
hydrateFromServer(values: any[]) {
|
|
461
|
+
if(!Array.isArray(values)) return;
|
|
462
|
+
for(let i=0; i<values.length; i++) {
|
|
463
|
+
this.set(i, new EditWrapper(values[i]));
|
|
464
|
+
}
|
|
465
|
+
for(let i=values.length; i<this.length; i++) {
|
|
466
|
+
this.delete(i, true);
|
|
467
|
+
}
|
|
468
|
+
this.length = values.length;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private tryApplyServerValue(index: number, incoming: any, oldValue: MultyxClientItem | undefined) {
|
|
472
|
+
const current = this.list[index];
|
|
473
|
+
if(!current) return false;
|
|
474
|
+
|
|
475
|
+
if(current instanceof MultyxClientValue && (typeof incoming !== 'object' || incoming === null)) {
|
|
476
|
+
current.set(new EditWrapper(incoming));
|
|
477
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const canHydrate = typeof (current as any)?.hydrateFromServer === 'function';
|
|
482
|
+
if(Array.isArray(incoming) && canHydrate && (current as any).type === 'list') {
|
|
483
|
+
(current as any).hydrateFromServer(incoming);
|
|
484
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if(isPlainObject(incoming) && canHydrate && (current as any).type === 'object') {
|
|
489
|
+
(current as any).hydrateFromServer(incoming);
|
|
490
|
+
this.enqueueEditCallbacks(index, oldValue);
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private notifyIndexWaiters(index: number) {
|
|
498
|
+
const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + '.' + index);
|
|
499
|
+
if(this.multyx.events.has(propSymbol)) {
|
|
500
|
+
this.multyx[Done].push(...(this.multyx.events.get(propSymbol)?.map(e =>
|
|
501
|
+
() => e(this.list[index])
|
|
502
|
+
) ?? []));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private enqueueEditCallbacks(index: number, oldValue: MultyxClientItem | undefined) {
|
|
507
|
+
for(const listener of this.editCallbacks) {
|
|
508
|
+
this.multyx[Add](() => listener(index, this.get(index), oldValue));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
455
511
|
}
|
package/src/items/object.ts
CHANGED
|
@@ -7,7 +7,10 @@ import { IsMultyxClientItem, type MultyxClientList, type MultyxClientItem } from
|
|
|
7
7
|
import MultyxClientItemRouter from "./router";
|
|
8
8
|
import MultyxClientValue from "./value";
|
|
9
9
|
|
|
10
|
+
const isPlainObject = (value: any) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
+
|
|
10
12
|
export default class MultyxClientObject {
|
|
13
|
+
readonly type = 'object';
|
|
11
14
|
protected object: RawObject<MultyxClientItem>;
|
|
12
15
|
private multyx: Multyx;
|
|
13
16
|
propertyPath: string[];
|
|
@@ -124,36 +127,35 @@ export default class MultyxClientObject {
|
|
|
124
127
|
|
|
125
128
|
const serverSet = value instanceof EditWrapper;
|
|
126
129
|
const allowed = serverSet || this.editable;
|
|
127
|
-
|
|
128
|
-
if(
|
|
130
|
+
const incoming = (serverSet || IsMultyxClientItem(value)) ? value.value : value;
|
|
131
|
+
if(incoming === undefined) return this.delete(property, serverSet);
|
|
132
|
+
|
|
133
|
+
if(serverSet && this.applyServerValue(property, incoming)) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
129
136
|
|
|
130
137
|
// Only create new MultyxClientItem when needed
|
|
131
|
-
if(this.object[property] instanceof MultyxClientValue && typeof
|
|
132
|
-
return this.object[property].set(serverSet ? new EditWrapper(
|
|
138
|
+
if(this.object[property] instanceof MultyxClientValue && (typeof incoming !== 'object' || incoming === null)) {
|
|
139
|
+
return this.object[property].set(serverSet ? new EditWrapper(incoming) : incoming);
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
// Attempting to edit property not editable to client
|
|
136
143
|
if(!allowed) {
|
|
137
144
|
if(this.multyx.options.verbose) {
|
|
138
|
-
console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + property}' to ${
|
|
145
|
+
console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + property}' to ${incoming}`);
|
|
139
146
|
}
|
|
140
147
|
return false;
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
// Creating a new value
|
|
144
|
-
this.object[property] = new (MultyxClientItemRouter(
|
|
151
|
+
this.object[property] = new (MultyxClientItemRouter(incoming))(
|
|
145
152
|
this.multyx,
|
|
146
|
-
serverSet ? new EditWrapper(
|
|
153
|
+
serverSet ? new EditWrapper(incoming) : incoming,
|
|
147
154
|
[...this.propertyPath, property],
|
|
148
155
|
this.editable
|
|
149
156
|
);
|
|
150
157
|
|
|
151
|
-
|
|
152
|
-
if(this.multyx.events.has(propSymbol)) {
|
|
153
|
-
this.multyx[Done].push(...(this.multyx.events.get(propSymbol)?.map(e =>
|
|
154
|
-
() => e(this.object[property])
|
|
155
|
-
) ?? []));
|
|
156
|
-
}
|
|
158
|
+
this.notifyPropertyWaiters(property);
|
|
157
159
|
|
|
158
160
|
return true;
|
|
159
161
|
}
|
|
@@ -216,4 +218,48 @@ export default class MultyxClientObject {
|
|
|
216
218
|
this.object[prop]?.[Unpack](constraints[prop]);
|
|
217
219
|
}
|
|
218
220
|
}
|
|
221
|
+
|
|
222
|
+
hydrateFromServer(value: RawObject) {
|
|
223
|
+
if(!isPlainObject(value)) return;
|
|
224
|
+
const remaining = new Set(Object.keys(this.object));
|
|
225
|
+
for(const [key, entry] of Object.entries(value)) {
|
|
226
|
+
remaining.delete(key);
|
|
227
|
+
this.set(key, new EditWrapper(entry));
|
|
228
|
+
}
|
|
229
|
+
for(const key of remaining) {
|
|
230
|
+
this.delete(key, true);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private applyServerValue(property: string, incoming: any) {
|
|
235
|
+
const current = this.object[property];
|
|
236
|
+
if(!current) return false;
|
|
237
|
+
|
|
238
|
+
if(current instanceof MultyxClientValue && (typeof incoming !== 'object' || incoming === null)) {
|
|
239
|
+
current.set(new EditWrapper(incoming));
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const canHydrate = typeof (current as any)?.hydrateFromServer === 'function';
|
|
244
|
+
if(Array.isArray(incoming) && canHydrate && (current as any).type === 'list') {
|
|
245
|
+
(current as any).hydrateFromServer(incoming);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if(isPlainObject(incoming) && canHydrate && (current as any).type === 'object') {
|
|
250
|
+
(current as any).hydrateFromServer(incoming);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private notifyPropertyWaiters(property: string) {
|
|
258
|
+
const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + '.' + property);
|
|
259
|
+
if(this.multyx.events.has(propSymbol)) {
|
|
260
|
+
this.multyx[Done].push(...(this.multyx.events.get(propSymbol)?.map(e =>
|
|
261
|
+
() => e(this.object[property])
|
|
262
|
+
) ?? []));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
219
265
|
}
|
package/src/items/value.ts
CHANGED
|
@@ -3,6 +3,10 @@ import { Message } from "../message";
|
|
|
3
3
|
import { Constraint, RawObject, Value } from "../types";
|
|
4
4
|
import { BuildConstraint, Done, Edit, EditWrapper, Unpack } from '../utils';
|
|
5
5
|
|
|
6
|
+
type InterpolationMode = 'lerp' | 'predictive';
|
|
7
|
+
type NumericSample = { value: number, time: number };
|
|
8
|
+
const DEFAULT_INTERPOLATION_FRAME_MS = 250;
|
|
9
|
+
|
|
6
10
|
export default class MultyxClientValue {
|
|
7
11
|
private _value: Value;
|
|
8
12
|
private multyx: Multyx;
|
|
@@ -17,8 +21,14 @@ export default class MultyxClientValue {
|
|
|
17
21
|
return this.readModifiers.reduce((value, modifier) => modifier(value), this._value);
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
private interpolationModifier?: (value: Value) => Value;
|
|
25
|
+
private latestSample?: NumericSample;
|
|
26
|
+
private previousSample?: NumericSample;
|
|
27
|
+
private interpolationFrameMs: number = DEFAULT_INTERPOLATION_FRAME_MS;
|
|
28
|
+
|
|
20
29
|
set value(v) {
|
|
21
30
|
this._value = v;
|
|
31
|
+
this.captureSample(v);
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
addReadModifier(modifier: (value: Value) => Value) {
|
|
@@ -79,7 +89,7 @@ export default class MultyxClientValue {
|
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
if(this.
|
|
92
|
+
if(this._value === nv) {
|
|
83
93
|
this.value = nv;
|
|
84
94
|
return true;
|
|
85
95
|
}
|
|
@@ -118,4 +128,68 @@ export default class MultyxClientValue {
|
|
|
118
128
|
toString = () => this.value.toString();
|
|
119
129
|
valueOf = () => this.value;
|
|
120
130
|
[Symbol.toPrimitive] = () => this.value;
|
|
131
|
+
|
|
132
|
+
Lerp(maxFrameDuration: number = DEFAULT_INTERPOLATION_FRAME_MS) {
|
|
133
|
+
return this.applyInterpolation('lerp', maxFrameDuration);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
PredictiveLerp(maxFrameDuration: number = DEFAULT_INTERPOLATION_FRAME_MS) {
|
|
137
|
+
return this.applyInterpolation('predictive', maxFrameDuration);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private applyInterpolation(mode: InterpolationMode, maxFrameDuration: number) {
|
|
141
|
+
if(typeof this._value !== 'number' || Number.isNaN(this._value)) {
|
|
142
|
+
throw new Error(`MultyxClientValue.${mode === 'lerp' ? 'Lerp' : 'PredictiveLerp'} can only be applied to numeric values`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.interpolationFrameMs = Math.max(1, maxFrameDuration);
|
|
146
|
+
this.attachInterpolationModifier(mode);
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private attachInterpolationModifier(mode: InterpolationMode) {
|
|
151
|
+
if(this.interpolationModifier) {
|
|
152
|
+
this.readModifiers = this.readModifiers.filter(fn => fn !== this.interpolationModifier);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.interpolationModifier = (value: Value) => this.interpolateValue(value, mode);
|
|
156
|
+
this.readModifiers.push(this.interpolationModifier);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private captureSample(value: Value) {
|
|
160
|
+
if(typeof value !== 'number' || Number.isNaN(value)) {
|
|
161
|
+
this.latestSample = undefined;
|
|
162
|
+
this.previousSample = undefined;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
if(!this.latestSample) {
|
|
168
|
+
this.latestSample = { value, time: now };
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.previousSample = { ...this.latestSample };
|
|
173
|
+
this.latestSample = { value, time: now };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private interpolateValue(baseValue: Value, mode: InterpolationMode): Value {
|
|
177
|
+
if(typeof baseValue !== 'number' || !this.latestSample || !this.previousSample) {
|
|
178
|
+
return baseValue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const durationRaw = this.latestSample.time - this.previousSample.time;
|
|
182
|
+
if(durationRaw <= 0) return baseValue;
|
|
183
|
+
|
|
184
|
+
const duration = Math.max(1, Math.min(durationRaw, this.interpolationFrameMs));
|
|
185
|
+
const elapsed = Math.max(0, Math.min(Date.now() - this.latestSample.time, duration));
|
|
186
|
+
const ratio = duration === 0 ? 1 : elapsed / duration;
|
|
187
|
+
const delta = this.latestSample.value - this.previousSample.value;
|
|
188
|
+
|
|
189
|
+
if(mode === 'predictive') {
|
|
190
|
+
return this.latestSample.value + delta * ratio;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return this.previousSample.value + delta * ratio;
|
|
194
|
+
}
|
|
121
195
|
}
|
package/src/message.ts
CHANGED
|
@@ -15,8 +15,8 @@ export function UncompressUpdate(str: string) {
|
|
|
15
15
|
if(instruction == '9') return { instruction: 'self', property: "space", data: JSON.parse(specifier) };
|
|
16
16
|
|
|
17
17
|
if(instruction == '5') return { instruction: 'resp', name: specifier, response: data[0] };
|
|
18
|
-
if(instruction == '6') return { instruction: 'conn', uuid: specifier,
|
|
19
|
-
if(instruction == '7') return { instruction: 'dcon',
|
|
18
|
+
if(instruction == '6') return { instruction: 'conn', uuid: specifier, data: data[0] };
|
|
19
|
+
if(instruction == '7') return { instruction: 'dcon', client: specifier };
|
|
20
20
|
|
|
21
21
|
if(instruction == '8') return {
|
|
22
22
|
instruction: 'init',
|
|
@@ -45,7 +45,7 @@ export function CompressUpdate(update: Update) {
|
|
|
45
45
|
pieces = [update.name, JSON.stringify(update.response)];
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
if(!pieces ||
|
|
48
|
+
if(!pieces || code === undefined) return '';
|
|
49
49
|
let compressed = code.toString();
|
|
50
50
|
for(let i = 0; i < pieces.length; i++) {
|
|
51
51
|
compressed += pieces[i].replace(/;/g, ';_');
|
package/src/utils.ts
CHANGED
|
@@ -36,34 +36,65 @@ export function Interpolate(
|
|
|
36
36
|
progress: number,
|
|
37
37
|
}[]
|
|
38
38
|
) {
|
|
39
|
+
if(!Array.isArray(interpolationCurve) || interpolationCurve.length === 0) {
|
|
40
|
+
throw new Error('Interpolation curve must contain at least one slice');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const curve = [...interpolationCurve].sort((a, b) => a.time - b.time);
|
|
44
|
+
const curveMaxTime = curve[curve.length - 1].time;
|
|
45
|
+
const usesNormalizedCurve = curveMaxTime <= 1;
|
|
46
|
+
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
|
|
47
|
+
|
|
39
48
|
let start = { value: object[property], time: Date.now() };
|
|
40
49
|
let end = { value: object[property], time: Date.now() };
|
|
41
50
|
|
|
42
51
|
Object.defineProperty(object, property, {
|
|
52
|
+
configurable: true,
|
|
53
|
+
enumerable: true,
|
|
43
54
|
get: () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
if(start.time === end.time) return end.value;
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const duration = Math.max(end.time - start.time, 1);
|
|
58
|
+
const elapsed = Math.max(0, now - end.time);
|
|
59
|
+
const targetTime = usesNormalizedCurve
|
|
60
|
+
? clamp(elapsed / duration, 0, curveMaxTime)
|
|
61
|
+
: clamp(elapsed, 0, curveMaxTime);
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
let lower = curve[0];
|
|
64
|
+
let upper = curve[curve.length - 1];
|
|
65
|
+
for(const slice of curve) {
|
|
66
|
+
if(slice.time <= targetTime) {
|
|
67
|
+
lower = slice;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
upper = slice;
|
|
71
|
+
break;
|
|
51
72
|
}
|
|
52
73
|
|
|
53
|
-
|
|
54
|
-
|
|
74
|
+
let ratio: number;
|
|
75
|
+
if(upper.time === lower.time) {
|
|
76
|
+
ratio = lower.progress;
|
|
77
|
+
} else {
|
|
78
|
+
const sliceTime = (targetTime - lower.time) / (upper.time - lower.time);
|
|
79
|
+
ratio = lower.progress + sliceTime * (upper.progress - lower.progress);
|
|
80
|
+
}
|
|
55
81
|
|
|
56
82
|
if(Number.isNaN(ratio)) return start.value;
|
|
57
|
-
|
|
83
|
+
if(typeof start.value === 'number' && typeof end.value === 'number') {
|
|
84
|
+
return end.value * ratio + start.value * (1 - ratio);
|
|
85
|
+
}
|
|
86
|
+
return ratio >= 1 ? end.value : start.value;
|
|
58
87
|
},
|
|
59
88
|
set: (value) => {
|
|
89
|
+
const now = Date.now();
|
|
60
90
|
// Don't lerp between edit requests sent in same frame
|
|
61
|
-
if(
|
|
91
|
+
if(now - end.time < 10) {
|
|
62
92
|
end.value = value;
|
|
93
|
+
end.time = now;
|
|
63
94
|
return true;
|
|
64
95
|
}
|
|
65
96
|
start = { ...end };
|
|
66
|
-
end = { value, time:
|
|
97
|
+
end = { value, time: now };
|
|
67
98
|
return true;
|
|
68
99
|
}
|
|
69
100
|
});
|