multyx-client 0.1.7 → 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/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/value.ts +75 -1
- package/src/utils.ts +42 -11
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}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/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
|
});
|