multyx-client 0.1.7 → 0.1.9
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/items/object.d.ts +1 -1
- package/dist/items/object.js +11 -2
- package/dist/items/value.d.ts +10 -0
- package/dist/items/value.js +55 -1
- package/dist/utils.js +41 -12
- package/multyx.js +1 -1
- package/package.json +1 -1
- package/src/items/object.ts +9 -2
- package/src/items/value.ts +75 -1
- package/src/utils.ts +42 -11
package/dist/items/object.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export default class MultyxClientObject {
|
|
|
11
11
|
editable: boolean;
|
|
12
12
|
private editCallbacks;
|
|
13
13
|
get value(): RawObject<MultyxClientList | MultyxClientObject | MultyxClientValue>;
|
|
14
|
-
|
|
14
|
+
onWrite(callback: (key: any, value: any) => void): void;
|
|
15
15
|
[Edit](updatePath: string[], value: any): void;
|
|
16
16
|
[key: string]: any;
|
|
17
17
|
constructor(multyx: Multyx, object: RawObject | EditWrapper<RawObject>, propertyPath: string[] | undefined, editable: boolean);
|
package/dist/items/object.js
CHANGED
|
@@ -16,7 +16,7 @@ class MultyxClientObject {
|
|
|
16
16
|
parsed[prop] = this.object[prop];
|
|
17
17
|
return parsed;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
onWrite(callback) {
|
|
20
20
|
this.editCallbacks.push(callback);
|
|
21
21
|
}
|
|
22
22
|
[utils_1.Edit](updatePath, value) {
|
|
@@ -123,7 +123,10 @@ class MultyxClientObject {
|
|
|
123
123
|
}
|
|
124
124
|
// Only create new MultyxClientItem when needed
|
|
125
125
|
if (this.object[property] instanceof value_1.default && (typeof incoming !== 'object' || incoming === null)) {
|
|
126
|
-
|
|
126
|
+
const bool = this.object[property].set(serverSet ? new utils_1.EditWrapper(incoming) : incoming);
|
|
127
|
+
if (serverSet)
|
|
128
|
+
this.editCallbacks.forEach(callback => callback(property, this.object[property]));
|
|
129
|
+
return bool;
|
|
127
130
|
}
|
|
128
131
|
// Attempting to edit property not editable to client
|
|
129
132
|
if (!allowed) {
|
|
@@ -134,6 +137,8 @@ class MultyxClientObject {
|
|
|
134
137
|
}
|
|
135
138
|
// Creating a new value
|
|
136
139
|
this.object[property] = new ((0, router_1.default)(incoming))(this.multyx, serverSet ? new utils_1.EditWrapper(incoming) : incoming, [...this.propertyPath, property], this.editable);
|
|
140
|
+
if (serverSet)
|
|
141
|
+
this.editCallbacks.forEach(callback => callback(property, this.object[property]));
|
|
137
142
|
this.notifyPropertyWaiters(property);
|
|
138
143
|
return true;
|
|
139
144
|
}
|
|
@@ -146,6 +151,7 @@ class MultyxClientObject {
|
|
|
146
151
|
return false;
|
|
147
152
|
}
|
|
148
153
|
delete this.object[property];
|
|
154
|
+
this.editCallbacks.forEach(callback => callback(property, undefined));
|
|
149
155
|
if (!native) {
|
|
150
156
|
this.multyx.ws.send(message_1.Message.Native({
|
|
151
157
|
instruction: 'edit',
|
|
@@ -207,15 +213,18 @@ class MultyxClientObject {
|
|
|
207
213
|
return false;
|
|
208
214
|
if (current instanceof value_1.default && (typeof incoming !== 'object' || incoming === null)) {
|
|
209
215
|
current.set(new utils_1.EditWrapper(incoming));
|
|
216
|
+
this.editCallbacks.forEach(callback => callback(property, current));
|
|
210
217
|
return true;
|
|
211
218
|
}
|
|
212
219
|
const canHydrate = typeof (current === null || current === void 0 ? void 0 : current.hydrateFromServer) === 'function';
|
|
213
220
|
if (Array.isArray(incoming) && canHydrate && current.type === 'list') {
|
|
214
221
|
current.hydrateFromServer(incoming);
|
|
222
|
+
this.editCallbacks.forEach(callback => callback(property, current));
|
|
215
223
|
return true;
|
|
216
224
|
}
|
|
217
225
|
if (isPlainObject(incoming) && canHydrate && current.type === 'object') {
|
|
218
226
|
current.hydrateFromServer(incoming);
|
|
227
|
+
this.editCallbacks.forEach(callback => callback(property, current));
|
|
219
228
|
return true;
|
|
220
229
|
}
|
|
221
230
|
return false;
|
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/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={249: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(625),r=s(703),l=n(s(416)),a=s(449);class h{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.type="list",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 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,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||s instanceof o.MultyxClientValue)&&s.set(t.slice(1),e)}set(t,e){if(Array.isArray(t))return this.recursiveSet(t,e);const s=this.get(t),i=e instanceof r.EditWrapper,n=i||this.editable,a=i||(0,o.IsMultyxClientItem)(e)?e.value:e;if(void 0===a)return this.delete(t,i);if(i&&this.tryApplyServerValue(t,a,s))return!0;if(this.list[t]instanceof o.MultyxClientValue&&("object"!=typeof a||null===a)){const e=this.list[t].set(i?new r.EditWrapper(a):a);return this.enqueueEditCallbacks(t,s),e}return n?(this.list[t]=new((0,l.default)(a))(this.multyx,i?new r.EditWrapper(a):a,[...this.propertyPath,t.toString()],this.editable),this.notifyIndexWaiters(t),this.enqueueEditCallbacks(t,s),!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 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(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,...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 h)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]()}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,s){const i=this.list[t];if(!i)return!1;if(i instanceof o.MultyxClientValue&&("object"!=typeof e||null===e))return i.set(new r.EditWrapper(e)),this.enqueueEditCallbacks(t,s),!0;const n="function"==typeof(null==i?void 0:i.hydrateFromServer);return Array.isArray(e)&&n&&"list"===i.type?(i.hydrateFromServer(e),this.enqueueEditCallbacks(t,s),!0):!(null===(l=e)||"object"!=typeof l||Array.isArray(l)||!n||"object"!==i.type||(i.hydrateFromServer(e),this.enqueueEditCallbacks(t,s),0));var l}notifyIndexWaiters(t){var e,s;const i=Symbol.for("_"+this.propertyPath.join(".")+"."+t);this.multyx.events.has(i)&&this.multyx[r.Done].push(...null!==(s=null===(e=this.multyx.events.get(i))||void 0===e?void 0:e.map(e=>()=>e(this.list[t])))&&void 0!==s?s:[])}enqueueEditCallbacks(t,e){for(const s of this.editCallbacks)this.multyx[r.Add](()=>s(t,this.get(t),e))}}i=Symbol.toPrimitive,e.default=h},280:(t,e,s)=>{var i;Object.defineProperty(e,"__esModule",{value:!0});const n=s(449),o=s(703);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 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(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},416:(t,e,s)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){return Array.isArray(t)?s(249).default:"object"==typeof t?s(922).default:s(280).default}},449:(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||void 0===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,data:o[0]}:"7"==i?{instruction:"dcon",client: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},625: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(249));e.MultyxClientList=n.default;const o=i(s(922));e.MultyxClientObject=o.default;const r=i(s(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,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}}},832:(t,e,s)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Controller=void 0;const i=s(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 s="top"in e,i="bottom"in e,n="left"in e,o="right"in e,r=e.anchor,l=t.getBoundingClientRect(),a=(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)},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(s&&i&&e.top!==-e.bottom||n&&o&&e.left!==-e.right)return a(!0,"top","bottom","left","right");s?(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):i?(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=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 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&&!i)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(!s&&!i&&!o)return a(!1,"top","bottom","right");if(n)return a(!0,"left");e.left=0,s?(e.right=-Math.abs(h*e.top*2),e.bottom=-e.top):i?(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(!s&&!i&&!n)return a(!1,"top","bottom","left");if(o)return a(!0,"right");e.right=0,s?(e.left=-Math.abs(h*e.top*2),e.bottom=-e.top):i?(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&&!i)return a(!1,"right","bottom");if(n||s)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&&!i)return a(!1,"left","bottom");if(o||s)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&&!s)return a(!1,"right","top");if(i||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(!s&&!n)return a(!1,"top","left");if(o||i)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(!s&&!i)return a(!1,"top","bottom");if(i?s||(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 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}:{})))}}},922: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(449),o=s(703),r=s(625),l=i(s(416)),a=i(s(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 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.type="object",this.editCallbacks=[],this.object={},this.propertyPath=s,this.multyx=t,this.editable=i;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,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 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 s=this.get(t[0]);return(s instanceof a.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){if(Array.isArray(t))return this.recursiveSet(t,e);const s=e instanceof o.EditWrapper,i=s||this.editable,n=s||(0,r.IsMultyxClientItem)(e)?e.value:e;return void 0===n?this.delete(t,s):!(!s||!this.applyServerValue(t,n))||(this.object[t]instanceof a.default&&("object"!=typeof n||null===n)?this.object[t].set(s?new o.EditWrapper(n):n):i?(this.object[t]=new((0,l.default)(n))(this.multyx,s?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 s in t)null===(e=this.object[s])||void 0===e||e[o.Unpack](t[s])}hydrateFromServer(t){if(!h(t))return;const e=new Set(Object.keys(this.object));for(const[s,i]of Object.entries(t))e.delete(s),this.set(s,new o.EditWrapper(i));for(const t of e)this.delete(t,!0)}applyServerValue(t,e){const s=this.object[t];if(!s)return!1;if(s instanceof a.default&&("object"!=typeof e||null===e))return s.set(new o.EditWrapper(e)),!0;const i="function"==typeof(null==s?void 0:s.hydrateFromServer);return(Array.isArray(e)&&i&&"list"===s.type||!(!h(e)||!i||"object"!==s.type))&&(s.hydrateFromServer(e),!0)}notifyPropertyWaiters(t){var e,s;const i=Symbol.for("_"+this.propertyPath.join(".")+"."+t);this.multyx.events.has(i)&&this.multyx[o.Done].push(...null!==(s=null===(e=this.multyx.events.get(i))||void 0===e?void 0:e.map(e=>()=>e(this.object[t])))&&void 0!==s?s:[])}}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 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(449),o=s(703),r=s(832),l=s(625),a=s(960);class h{constructor(e={},s){var i;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!==(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(h.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(h.Custom))||void 0===i||i.forEach(t=>t(r))),null===(o=this.events.get(h.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(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,s,i,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!==(s=this.events.get(h.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(h.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(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,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"})}}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})(),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}onWrite(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;if(void 0===n)return this.delete(t,i);if(i&&this.applyServerValue(t,n))return!0;if(this.object[t]instanceof a.default&&("object"!=typeof n||null===n)){const e=this.object[t].set(i?new o.EditWrapper(n):n);return i&&this.editCallbacks.forEach(e=>e(t,this.object[t])),e}return s?(this.object[t]=new((0,l.default)(n))(this.multyx,i?new o.EditWrapper(n):n,[...this.propertyPath,t],this.editable),i&&this.editCallbacks.forEach(e=>e(t,this.object[t])),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],this.editCallbacks.forEach(e=>e(t,void 0)),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)),this.editCallbacks.forEach(e=>e(t,i)),!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),this.editCallbacks.forEach(e=>e(t,i)),!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.9",
|
|
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/items/object.ts
CHANGED
|
@@ -24,7 +24,7 @@ export default class MultyxClientObject {
|
|
|
24
24
|
return parsed;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
onWrite(callback: (key: any, value: any) => void) {
|
|
28
28
|
this.editCallbacks.push(callback);
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -136,7 +136,9 @@ export default class MultyxClientObject {
|
|
|
136
136
|
|
|
137
137
|
// Only create new MultyxClientItem when needed
|
|
138
138
|
if(this.object[property] instanceof MultyxClientValue && (typeof incoming !== 'object' || incoming === null)) {
|
|
139
|
-
|
|
139
|
+
const bool = this.object[property].set(serverSet ? new EditWrapper(incoming) : incoming);
|
|
140
|
+
if(serverSet) this.editCallbacks.forEach(callback => callback(property, this.object[property]));
|
|
141
|
+
return bool;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
// Attempting to edit property not editable to client
|
|
@@ -154,6 +156,7 @@ export default class MultyxClientObject {
|
|
|
154
156
|
[...this.propertyPath, property],
|
|
155
157
|
this.editable
|
|
156
158
|
);
|
|
159
|
+
if(serverSet) this.editCallbacks.forEach(callback => callback(property, this.object[property]));
|
|
157
160
|
|
|
158
161
|
this.notifyPropertyWaiters(property);
|
|
159
162
|
|
|
@@ -170,6 +173,7 @@ export default class MultyxClientObject {
|
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
delete this.object[property];
|
|
176
|
+
this.editCallbacks.forEach(callback => callback(property, undefined));
|
|
173
177
|
|
|
174
178
|
if(!native) {
|
|
175
179
|
this.multyx.ws.send(Message.Native({
|
|
@@ -237,17 +241,20 @@ export default class MultyxClientObject {
|
|
|
237
241
|
|
|
238
242
|
if(current instanceof MultyxClientValue && (typeof incoming !== 'object' || incoming === null)) {
|
|
239
243
|
current.set(new EditWrapper(incoming));
|
|
244
|
+
this.editCallbacks.forEach(callback => callback(property, current));
|
|
240
245
|
return true;
|
|
241
246
|
}
|
|
242
247
|
|
|
243
248
|
const canHydrate = typeof (current as any)?.hydrateFromServer === 'function';
|
|
244
249
|
if(Array.isArray(incoming) && canHydrate && (current as any).type === 'list') {
|
|
245
250
|
(current as any).hydrateFromServer(incoming);
|
|
251
|
+
this.editCallbacks.forEach(callback => callback(property, current));
|
|
246
252
|
return true;
|
|
247
253
|
}
|
|
248
254
|
|
|
249
255
|
if(isPlainObject(incoming) && canHydrate && (current as any).type === 'object') {
|
|
250
256
|
(current as any).hydrateFromServer(incoming);
|
|
257
|
+
this.editCallbacks.forEach(callback => callback(property, current));
|
|
251
258
|
return true;
|
|
252
259
|
}
|
|
253
260
|
|
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/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
|
});
|