multyx-client 0.1.1 → 0.1.2

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/src/index.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { Message } from "./message";
2
- import { Unpack, EditWrapper, Add } from './utils';
3
- import { RawObject } from "./types";
1
+ import { Message, UncompressUpdate } from "./message";
2
+ import { Unpack, EditWrapper, Add, Edit, Done } from './utils';
3
+ import { RawObject, ResponseUpdate } from "./types";
4
4
  import { Controller } from "./controller";
5
- import { MultyxClientObject } from "./items";
5
+ import { MultyxClientObject, MultyxClientValue } from "./items";
6
6
  import { DefaultOptions, Options } from "./options";
7
-
8
7
  export default class Multyx {
9
8
  ws: WebSocket;
10
9
  uuid: string;
@@ -12,15 +11,17 @@ export default class Multyx {
12
11
  ping: number;
13
12
  events: Map<string | Symbol, ((data?: any) => void)[]>;
14
13
  self: RawObject;
14
+ tps: number;
15
15
  all: RawObject;
16
- clients: RawObject;
17
- teams: RawObject;
16
+ space: string;
17
+ clients: { [key: string]: MultyxClientObject };
18
+ teams: MultyxClientObject;
18
19
  controller: Controller;
19
20
 
20
21
  options: Options;
21
22
 
22
23
  // Queue of functions to be called after each frame
23
- private listenerQueue: ((...args: any[]) => void)[];
24
+ [Done]: ((...args: any[]) => void)[] = [];
24
25
 
25
26
  static Start = Symbol('start');
26
27
  static Connection = Symbol('connection');
@@ -33,16 +34,17 @@ export default class Multyx {
33
34
  constructor(options: Options = {}, callback?: () => void) {
34
35
  this.options = { ...DefaultOptions, ...options };
35
36
 
36
- const url = `ws${this.options.secure ? 's' : ''}://${this.options.uri}:${this.options.port}/`;
37
+ const url = `ws${this.options.secure ? 's' : ''}://${this.options.uri.split('/')[0]}:${this.options.port}/${this.options.uri.split('/')[1] ?? ''}`;
37
38
  this.ws = new WebSocket(url);
38
39
  this.ping = 0;
40
+ this.space = 'default';
39
41
  this.events = new Map();
40
42
  this.self = {};
43
+ this.tps = 0;
41
44
  this.all = {};
42
- this.teams = {};
45
+ this.teams = new MultyxClientObject(this, {}, [], true);
43
46
  this.clients = {};
44
47
  this.controller = new Controller(this.ws);
45
- this.listenerQueue = [];
46
48
 
47
49
  callback?.();
48
50
 
@@ -53,25 +55,51 @@ export default class Multyx {
53
55
  if(msg.native) {
54
56
  this.parseNativeEvent(msg);
55
57
  this.events.get(Multyx.Native)?.forEach(cb => cb(msg));
56
- } else if(msg.name in this.events) {
57
- this.events[msg.name](msg.data);
58
+ } else {
59
+ this.events.get(msg.name)?.forEach(cb => {
60
+ const response = cb(msg.data);
61
+ if(response !== undefined) this.send(msg.name, response);
62
+ });
58
63
  this.events.get(Multyx.Custom)?.forEach(cb => cb(msg));
59
64
  }
60
65
  this.events.get(Multyx.Any)?.forEach(cb => cb(msg));
61
66
  }
62
67
  }
63
68
 
64
- on(name: string | Symbol, callback: (data: RawObject) => void) {
69
+ /**
70
+ * Listen for a message from the server
71
+ * @param name Name of the message
72
+ * @param callback Function to call when the message is received
73
+ */
74
+ on(name: string | Symbol, callback: (data: RawObject) => any) {
65
75
  const events = this.events.get(name) ?? [];
66
76
  events.push(callback);
67
77
  this.events.set(name, events);
68
78
  }
69
79
 
70
- send(name: string, data: any, expectResponse: boolean = false) {
80
+ /**
81
+ * Send a message to the server
82
+ * @param name Name of the message
83
+ * @param data Data to send
84
+ */
85
+ send(name: string, data: any) {
71
86
  if(name[0] === '_') name = '_' + name;
72
- this.ws.send(Message.Create(name, data));
73
- if(!expectResponse) return;
87
+ const update = {
88
+ instruction: 'resp',
89
+ name,
90
+ response: data
91
+ } as ResponseUpdate;
92
+ this.ws.send(Message.Native(update));
93
+ }
74
94
 
95
+ /**
96
+ * Send a message to the server and wait for a response
97
+ * @param name Name of the message
98
+ * @param data Data to send
99
+ * @returns Promise that resolves when the message is received
100
+ */
101
+ await(name: string, data?: any) {
102
+ this.send(name, data);
75
103
  return new Promise(res => this.events.set(Symbol.for("_" + name), [res]));
76
104
  }
77
105
 
@@ -92,20 +120,21 @@ export default class Multyx {
92
120
  }
93
121
  }
94
122
 
95
-
96
123
  /**
97
- * Create a callback function that gets called for any current or future client
98
- * @param callbackfn Function to call for every client
124
+ * Add a function to be called after each frame
125
+ * @param callback Function to call after each frame
99
126
  */
100
- forAll(callback: (client: MultyxClientObject) => void) {
101
- this.on(Multyx.Start, () => {
102
- this.teams.all.clients.forAll((uuid) => callback(this.clients[uuid]));
103
- });
104
- this.on(Multyx.Connection, callback);
127
+ [Add](callback: () => void) {
128
+ this[Done].push(callback);
105
129
  }
106
130
 
131
+ /**
132
+ * Parse a native event from the server
133
+ * @param msg Message to parse
134
+ */
107
135
  private parseNativeEvent(msg: Message) {
108
- if(this.options.logUpdateFrame) console.log(msg);
136
+ msg.data = msg.data.map(UncompressUpdate);
137
+ if(this.options.logUpdateFrame) console.log(msg.data);
109
138
 
110
139
  for(const update of msg.data) {
111
140
  switch(update.instruction) {
@@ -114,7 +143,7 @@ export default class Multyx {
114
143
  this.initialize(update);
115
144
 
116
145
  for(const listener of this.events.get(Multyx.Start) ?? []) {
117
- this.listenerQueue.push(() => listener(update));
146
+ this[Done].push(() => listener(update));
118
147
  }
119
148
 
120
149
  // Clear start event as it will never be called again
@@ -124,10 +153,27 @@ export default class Multyx {
124
153
 
125
154
  // Client or team data edit
126
155
  case 'edit': {
127
- this.parseEdit(update);
156
+ if(update.path.length == 1) {
157
+ if(update.team) {
158
+ this.teams.set(update.path[0], new EditWrapper(update.value));
159
+ } else {
160
+ this.clients[update.path[0]] = new MultyxClientObject(
161
+ this,
162
+ new EditWrapper(update.value),
163
+ [update.path[0]],
164
+ false
165
+ );
166
+ }
167
+ } else {
168
+ const agent = update.team
169
+ ? this.teams.get(update.path[0]) as MultyxClientObject
170
+ : this.clients[update.path[0]];
171
+ if(!agent) return;
172
+ agent.set(update.path.slice(1), new EditWrapper(update.value));
173
+ }
128
174
 
129
175
  for(const listener of this.events.get(Multyx.Edit) ?? []) {
130
- this.listenerQueue.push(() => listener(update));
176
+ this[Done].push(() => listener(update));
131
177
  }
132
178
  break;
133
179
  }
@@ -148,7 +194,7 @@ export default class Multyx {
148
194
  );
149
195
 
150
196
  for(const listener of this.events.get(Multyx.Connection) ?? []) {
151
- this.listenerQueue.push(() => listener(this.clients[update.uuid]));
197
+ this[Done].push(() => listener(this.clients[update.uuid]));
152
198
  }
153
199
  break;
154
200
  }
@@ -157,7 +203,7 @@ export default class Multyx {
157
203
  case 'dcon': {
158
204
  for(const listener of this.events.get(Multyx.Disconnect) ?? []) {
159
205
  const clientValue = this.clients[update.client].value;
160
- this.listenerQueue.push(() => listener(clientValue));
206
+ this[Done].push(() => listener(clientValue));
161
207
  }
162
208
  delete this.clients[update.client];
163
209
  break;
@@ -166,7 +212,8 @@ export default class Multyx {
166
212
  // Response to client
167
213
  case 'resp': {
168
214
  const promiseResolve = this.events.get(Symbol.for("_" + update.name))[0];
169
- promiseResolve(update.response);
215
+ this.events.delete(Symbol.for("_" + update.name));
216
+ this[Done].push(() => promiseResolve(update.response));
170
217
  break;
171
218
  }
172
219
 
@@ -178,17 +225,17 @@ export default class Multyx {
178
225
  }
179
226
  }
180
227
 
181
- this.listenerQueue.forEach(x => x());
182
- this.listenerQueue.length = 0;
228
+ this[Done].forEach(x => x());
229
+ this[Done].length = 0;
183
230
  }
184
231
 
185
232
  private initialize(update: RawObject) {
233
+ this.tps = update.tps;
186
234
  this.uuid = update.client.uuid;
187
235
  this.joinTime = update.client.joinTime;
188
236
  this.controller.listening = new Set(update.client.controller);
189
237
 
190
238
  // Create MultyxClientObject for all teams
191
- this.teams = new MultyxClientObject(this, {}, [], true);
192
239
  for(const team of Object.keys(update.teams)) {
193
240
  this.teams[team] = new EditWrapper(update.teams[team]);
194
241
  }
@@ -222,40 +269,36 @@ export default class Multyx {
222
269
  }
223
270
  }
224
271
 
225
- private parseEdit(update: RawObject) {
226
- let route: any = update.team ? this.teams : this.clients;
227
- if(!route) return;
228
-
229
- // Loop through path to get to object being edited
230
- for(const p of update.path.slice(0, -1)) {
231
- // Create new object at path if non-existent
232
- if(!(p in route)) route[p] = new EditWrapper({});
233
- route = route[p];
234
- }
235
-
236
- const prop = update.path.slice(-1)[0];
237
- route[prop] = new EditWrapper(update.value);
238
- }
239
-
240
272
  private parseSelf(update: RawObject) {
241
- if(update.prop == 'controller') {
273
+ if(update.property == 'controller') {
242
274
  this.controller.listening = new Set(update.data);
243
- } else if(update.prop == 'uuid') {
275
+ } else if(update.property == 'uuid') {
244
276
  this.uuid = update.data;
245
- } else if(update.prop == 'constraint') {
277
+ } else if(update.property == 'constraint') {
246
278
  let route = this.uuid == update.data.path[0] ? this.self : this.teams[update.data.path[0]];
247
279
  for(const prop of update.data.path.slice(1)) route = route?.[prop];
248
280
  if(route === undefined) return;
249
281
 
250
282
  route[Unpack]({ [update.data.name]: update.data.args });
283
+ } else if(update.property == 'space') {
284
+ this.space = update.data;
285
+ this.updateSpace();
251
286
  }
252
287
  }
253
288
 
254
- /**
255
- * Add function to listener queue
256
- * @param fn Function to call once frame is complete
257
- */
258
- [Add](fn: ((...args: any[]) => void)) {
259
- this.listenerQueue.push(fn);
289
+ // Hide all spaces except the current one
290
+ private updateSpace() {
291
+ if(this.space == 'default') {
292
+ (document.querySelectorAll('[data-multyx-space]') as NodeListOf<HTMLElement>).forEach(space => {
293
+ space.style.display = 'block';
294
+ space.style.pointerEvents = 'auto';
295
+ });
296
+ return;
297
+ }
298
+
299
+ (document.querySelectorAll('[data-multyx-space]') as NodeListOf<HTMLElement>).forEach(space => {
300
+ space.style.display = space.dataset.multyxSpace == this.space ? 'block' : 'none';
301
+ space.style.pointerEvents = space.dataset.multyxSpace == this.space ? 'auto' : 'none';
302
+ });
260
303
  }
261
304
  }
@@ -6,9 +6,14 @@ type MultyxClientItem<T = any> = T extends any[] ? MultyxClientList
6
6
  : T extends object ? MultyxClientObject
7
7
  : MultyxClientValue;
8
8
 
9
+ function IsMultyxClientItem(value: any): value is MultyxClientItem {
10
+ return value instanceof MultyxClientList || value instanceof MultyxClientObject || value instanceof MultyxClientValue;
11
+ }
12
+
9
13
  export {
10
14
  MultyxClientList,
11
15
  MultyxClientObject,
12
16
  MultyxClientValue,
13
17
  MultyxClientItem,
18
+ IsMultyxClientItem,
14
19
  };
package/src/items/list.ts CHANGED
@@ -1,76 +1,251 @@
1
1
  import Multyx from '../';
2
- import { MultyxClientItem } from '.';
3
- import { EditWrapper } from '../utils';
4
- import MultyxClientObject from "./object";
2
+ import { IsMultyxClientItem, type MultyxClientItem, type MultyxClientObject, MultyxClientValue } from '.';
3
+ import { Add, Done, Edit, EditWrapper, Unpack } from '../utils';
4
+ import MultyxClientItemRouter from './router';
5
+ import { Message } from '../message';
5
6
 
6
- export default class MultyxClientList extends MultyxClientObject {
7
- length: number;
7
+ export default class MultyxClientList {
8
+ protected list: MultyxClientItem[];
9
+ private multyx: Multyx;
10
+ propertyPath: string[];
11
+ editable: boolean;
12
+
13
+ private editCallbacks: ((index: number, value: any, oldValue: any) => void)[] = [];
14
+
15
+ addEditCallback(callback: (index: number, value: any, oldValue: any) => void) {
16
+ this.editCallbacks.push(callback);
17
+ }
8
18
 
9
19
  get value() {
10
20
  const parsed: any[] = [];
11
- for(let i=0; i<this.length; i++) parsed[i] = this.get(i).value;
21
+ for(let i=0; i<this.length; i++) parsed[i] = this.get(i)?.value;
12
22
  return parsed;
13
23
  }
14
24
 
15
- constructor(multyx: Multyx, list: any[] | EditWrapper<any[]>, propertyPath: string[] = [], editable: boolean){
16
- super(multyx, {}, propertyPath, editable);
25
+ get length() {
26
+ return this.list.length;
27
+ }
17
28
 
18
- this.length = 0;
19
- this.push(...(list instanceof EditWrapper ? list.value.map(x => new EditWrapper(x)) : list));
29
+ set length(length: number) {
30
+ this.list.length = length;
31
+ }
32
+
33
+ /**
34
+ * Shifting operations are needed to ensure that operations on elements in
35
+ * the list do not need to worry about the index of the element.
36
+ *
37
+ * Instead of changing each element's value when shifting, these shift
38
+ * operations are used to ensure that each MultyxClientItem stays the same
39
+ * @param index Index to shift from, -1 if special operation
40
+ * @param shift Shift amount, positive for right, negative for left
41
+ */
42
+ private handleShiftOperation(index: number, shift: number) {
43
+ const operation = index >= 0
44
+ ? shift >= 0 ? 'right': 'left'
45
+ : shift == 0 ? 'reverse' : shift < 0 ? 'length' : 'unknown';
46
+
47
+ switch(operation) {
48
+ // Reverse the array
49
+ case 'reverse':
50
+ for(let i=0; i<Math.floor(this.length/2); i++) {
51
+ const temp = this.list[i];
52
+ this.list[i] = this.list[this.length-1-i];
53
+ this.list[this.length-1-i] = temp;
54
+ }
55
+ break;
56
+
57
+ // Shift items to the left
58
+ case 'left':
59
+ for(let i=index; i<this.length; i++) {
60
+ if(i+shift < 0) continue;
61
+ this.list[i+shift] = this.list[i];
62
+ }
63
+ break;
64
+
65
+ // Shift items to the right
66
+ case 'right':
67
+ for(let i=this.length-1; i>=index; i--) {
68
+ this.list[i+shift] = this.list[i];
69
+ }
70
+ break;
71
+
72
+ // Alter the length of the array
73
+ case 'length':
74
+ this.length += shift;
75
+ break;
76
+
77
+ // Unknown operation
78
+ default:
79
+ if(this.multyx.options.verbose) {
80
+ console.error("Unknown shift operation: " + operation);
81
+ }
82
+ }
83
+ }
84
+
85
+ constructor(multyx: Multyx, list: any[] | EditWrapper<any[]>, propertyPath: string[] = [], editable: boolean){
86
+ this.list = [];
87
+ this.propertyPath = propertyPath;
88
+ this.multyx = multyx;
89
+ this.editable = editable;
90
+
91
+ const isEditWrapper = list instanceof EditWrapper;
92
+ if(list instanceof MultyxClientList) list = list.value;
93
+ if(list instanceof EditWrapper) list = list.value;
94
+
95
+ for(let i=0; i<list.length; i++) {
96
+ this.set(i, isEditWrapper
97
+ ? new EditWrapper(list[i])
98
+ : list[i]
99
+ );
100
+ }
20
101
 
21
102
  return new Proxy(this, {
22
- has: (o, p) => {
23
- if(p in o) return true;
24
- return o.has(p);
103
+ has: (o, p: any) => {
104
+ if(typeof p == 'number') return o.has(p);
105
+ return p in o;
25
106
  },
26
- get: (o, p) => {
107
+ get: (o, p: any) => {
27
108
  if(p in o) return o[p];
28
- return o.get(p);
109
+ if(!isNaN(parseInt(p))) p = parseInt(p);
110
+ return o.get(p) as MultyxClientItem;
29
111
  },
30
- set: (o, p, v) => {
112
+ set: (o, p: any, v) => {
31
113
  if(p in o) {
32
114
  o[p] = v;
33
115
  return true;
34
116
  }
35
- return o.set(p as string, v);
117
+ return !!o.set(p, v);
36
118
  },
37
- deleteProperty: (o, p) => {
38
- return o.delete(p as string, false);
119
+ deleteProperty: (o, p: any) => {
120
+ if(typeof p == 'number') return o.delete(p);
121
+ return false;
39
122
  }
40
123
  });
41
124
  }
42
125
 
43
- set(index: string | number, value: any) {
44
- if(typeof index == 'string') index = parseInt(index);
45
- if(value === undefined) return this.delete(index, false);
46
- if(value instanceof EditWrapper && value.value === undefined) return this.delete(index, true);
126
+ has(index: number): boolean {
127
+ return index >= 0 && index < this.length;
128
+ }
129
+
130
+ get(index: number | string[]): MultyxClientItem {
131
+ if(typeof index === 'number') return this.list[index];
132
+ if(index.length == 0) return this;
133
+ if(index.length == 1) return this.list[parseInt(index[0])];
134
+
135
+ const item = this.list[parseInt(index[0])];
136
+ if(!item || (item instanceof MultyxClientValue)) return undefined;
137
+ return item.get(index.slice(1));
138
+ }
139
+
140
+ private recursiveSet(path: string[], value: any): boolean {
141
+ if(path.length == 0) {
142
+ if(this.multyx.options.verbose) {
143
+ console.error(`Attempting to edit MultyxClientList with no path. Setting '${this.propertyPath.join('.')}' to ${value}`);
144
+ }
145
+ return false;
146
+ }
147
+
148
+ if(path[0] == "shift" && value instanceof EditWrapper) {
149
+ this.handleShiftOperation(parseInt(path[1]), value.value);
150
+ return true;
151
+ }
47
152
 
48
- const result = super.set(index, value);
49
- if(result && index >= this.length) this.length = index+1;
153
+ if(path.length == 1) return this.set(parseInt(path[0]), value);
50
154
 
51
- return result;
155
+ let next = this.get(parseInt(path[0]));
156
+ if(next instanceof MultyxClientValue || next == undefined) {
157
+ this.set(parseInt(path[0]), new EditWrapper({}));
158
+ next = this.get(parseInt(path[0])) as MultyxClientObject;
159
+ }
160
+ return next.set(path.slice(1), value);
52
161
  }
53
162
 
54
- delete(index: string | number, native: boolean = false) {
163
+ set(index: number | string[], value: any): boolean {
164
+ if(Array.isArray(index)) return this.recursiveSet(index, value);
165
+
166
+ const oldValue = this.get(index);
167
+
168
+ const serverSet = value instanceof EditWrapper;
169
+ const allowed = serverSet || this.editable;
170
+ if(serverSet || IsMultyxClientItem(value)) value = value.value;
171
+ if(value === undefined) return this.delete(index, serverSet);
172
+
173
+ // If value is a MultyxClientValue, set the value
174
+ if(this.list[index] instanceof MultyxClientValue && typeof value != 'object') {
175
+ return this.list[index].set(serverSet ? new EditWrapper(value) : value);
176
+ }
177
+
178
+ // Attempting to edit property not editable to client
179
+ if(!allowed) {
180
+ if(this.multyx.options.verbose) {
181
+ console.error(`Attempting to set property that is not editable. Setting '${this.propertyPath.join('.') + '.' + index}' to ${value}`);
182
+ }
183
+ return false;
184
+ }
185
+
186
+ this.list[index] = new (MultyxClientItemRouter(value))(
187
+ this.multyx,
188
+ serverSet ? new EditWrapper(value) : value,
189
+ [...this.propertyPath, index.toString()],
190
+ this.editable
191
+ );
192
+
193
+ const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + '.' + index);
194
+ if(this.multyx.events.has(propSymbol)) {
195
+ this.multyx[Done].push(...this.multyx.events.get(propSymbol).map(e =>
196
+ () => e(this.list[index])
197
+ ));
198
+ }
199
+
200
+ // We have to push into queue, since object may not be fully created
201
+ // and there may still be more updates to parse
202
+ for(const listener of this.editCallbacks) {
203
+ this.multyx[Add](() => listener(index, this.get(index), oldValue));
204
+ }
205
+
206
+ return true;
207
+ }
208
+
209
+ delete(index: number, native: boolean = false) {
210
+ const oldValue = this.get(index);
211
+
55
212
  if(typeof index == 'string') index = parseInt(index);
56
213
 
57
- const res = super.delete(index, native);
58
- if(res) this.length = this.reduce((a, c, i) => c !== undefined ? i+1 : a, 0);
59
- return res;
214
+ if(!this.editable && !native) {
215
+ if(this.multyx.options.verbose) {
216
+ console.error(`Attempting to delete property that is not editable. Deleting '${this.propertyPath.join('.') + '.' + index}'`);
217
+ }
218
+ return false;
219
+ }
220
+
221
+ delete this.list[index];
222
+
223
+ for(const listener of this.editCallbacks) {
224
+ this.multyx[Add](() => listener(index, undefined, oldValue));
225
+ }
226
+
227
+ if(!native) {
228
+ this.multyx.ws.send(Message.Native({
229
+ instruction: 'edit',
230
+ path: [...this.propertyPath, index.toString()],
231
+ value: undefined
232
+ }));
233
+ }
234
+
235
+ return true;
60
236
  }
61
237
 
62
238
  /**
63
- * Create a callback function that gets called for any current or future element in list
64
- * @param callbackfn Function to call for every element
239
+ * Wait for a specific index to be set
240
+ * @param index Index to wait for
241
+ * @returns Promise that resolves when the value is set
65
242
  */
66
- forAll(callbackfn: (value: any, index: number) => void) {
67
- for(let i=0; i<this.length; i++) {
68
- callbackfn(this.get(i), i);
69
- }
70
- super.forAll((key, value) => callbackfn(value, key));
243
+ await(index: number) {
244
+ if(this.has(index)) return Promise.resolve(this.get(index));
245
+ const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + '.' + index);
246
+ return new Promise(res => this.multyx.on(propSymbol, res));
71
247
  }
72
248
 
73
-
74
249
  /* All general array methods */
75
250
  push(...items: any) {
76
251
  for(const item of items) this.set(this.length, item);
@@ -152,8 +327,9 @@ export default class MultyxClientList extends MultyxClientObject {
152
327
  }
153
328
 
154
329
  map(callbackfn: (value: any, index: number, array: MultyxClientList) => any) {
330
+ const mapped = [];
155
331
  for(let i=0; i<this.length; i++) {
156
- this.set(i, callbackfn(this.get(i), i, this));
332
+ mapped.push(callbackfn(this.get(i), i, this));
157
333
  }
158
334
  }
159
335
 
@@ -229,22 +405,6 @@ export default class MultyxClientList extends MultyxClientObject {
229
405
  return -1;
230
406
  }
231
407
 
232
- deorder(): MultyxClientItem[] {
233
- const values = [];
234
- for(const index in this.object) {
235
- values.push(this.get(index));
236
- }
237
- return values;
238
- }
239
-
240
- deorderEntries(): [number, MultyxClientItem][] {
241
- const values = [];
242
- for(const index in this.object) {
243
- values.push([parseInt(index), this.get(index)]);
244
- }
245
- return values;
246
- }
247
-
248
408
  entries(): [any, number][] {
249
409
  const entryList: [any, number][] = [];
250
410
  for(let i=0; i<this.length; i++) {
@@ -257,6 +417,11 @@ export default class MultyxClientList extends MultyxClientObject {
257
417
  return Array(this.length).fill(0).map((_, i) => i);
258
418
  }
259
419
 
420
+ [Unpack](constraints: any[]) {
421
+ for(let i=0; i<this.length; i++) {
422
+ this.get(i)?.[Unpack](constraints[i]);
423
+ }
424
+ }
260
425
 
261
426
  /* Native methods to allow MultyxClientList to be treated as array */
262
427
  [Symbol.iterator](): Iterator<MultyxClientItem> {