UNPKG

multyx-client

Version:

Framework designed to simplify the creation of multiplayer browser games by addressing the complexities of managing server-client communication, shared state, and input handling

260 lines (259 loc) 10.9 kB
"use strict"; var _a; Object.defineProperty(exports, "__esModule", { value: true }); const message_1 = require("./message"); const utils_1 = require("./utils"); const controller_1 = require("./controller"); const items_1 = require("./items"); const options_1 = require("./options"); class Multyx { constructor(options = {}, callback) { var _b; // Queue of functions to be called after each frame this[_a] = []; this.options = Object.assign(Object.assign({}, options_1.DefaultOptions), options); const url = `ws${this.options.secure ? 's' : ''}://${this.options.uri.split('/')[0]}:${this.options.port}/${(_b = this.options.uri.split('/')[1]) !== null && _b !== void 0 ? _b : ''}`; this.ws = new WebSocket(url); this.ping = 0; this.space = 'default'; this.events = new Map(); this.self = {}; this.tps = 0; this.all = {}; this.teams = new items_1.MultyxClientObject(this, {}, [], true); this.clients = {}; this.controller = new controller_1.Controller(this.ws); callback === null || callback === void 0 ? void 0 : callback(); this.ws.onmessage = event => { var _b, _c, _d, _e; const msg = message_1.Message.Parse(event.data); this.ping = 2 * (Date.now() - msg.time); if (msg.native) { this.parseNativeEvent(msg); (_b = this.events.get(Multyx.Native)) === null || _b === void 0 ? void 0 : _b.forEach(cb => cb(msg)); } else { (_c = this.events.get(msg.name)) === null || _c === void 0 ? void 0 : _c.forEach(cb => { const response = cb(msg.data); if (response !== undefined) this.send(msg.name, response); }); (_d = this.events.get(Multyx.Custom)) === null || _d === void 0 ? void 0 : _d.forEach(cb => cb(msg)); } (_e = this.events.get(Multyx.Any)) === null || _e === void 0 ? void 0 : _e.forEach(cb => cb(msg)); }; } /** * Listen for a message from the server * @param name Name of the message * @param callback Function to call when the message is received */ on(name, callback) { var _b; const events = (_b = this.events.get(name)) !== null && _b !== void 0 ? _b : []; events.push(callback); this.events.set(name, events); } /** * Send a message to the server * @param name Name of the message * @param data Data to send */ send(name, data) { if (name[0] === '_') name = '_' + name; const update = { instruction: 'resp', name, response: data }; this.ws.send(message_1.Message.Native(update)); } /** * Send a message to the server and wait for a response * @param name Name of the message * @param data Data to send * @returns Promise that resolves when the message is received */ await(name, data) { this.send(name, data); return new Promise(res => this.events.set(Symbol.for("_" + name), [res])); } /** * Loop over a function * @param callback Function to call on a loop * @param timesPerSecond Recommended to leave blank. Number of times to loop in each second, if undefined, use requestAnimationFrame */ loop(callback, timesPerSecond) { if (timesPerSecond) { this.on(Multyx.Start, () => setInterval(callback, Math.round(1000 / timesPerSecond))); } else { const caller = () => { callback(); requestAnimationFrame(caller); }; this.on(Multyx.Start, () => requestAnimationFrame(caller)); } } /** * Add a function to be called after each frame * @param callback Function to call after each frame */ [(_a = utils_1.Done, utils_1.Add)](callback) { this[utils_1.Done].push(callback); } /** * Parse a native event from the server * @param msg Message to parse */ parseNativeEvent(msg) { var _b, _c, _d, _e; msg.data = msg.data.map(message_1.UncompressUpdate); if (this.options.logUpdateFrame) console.log(msg.data); for (const update of msg.data) { switch (update.instruction) { // Initialization case 'init': { this.initialize(update); for (const listener of (_b = this.events.get(Multyx.Start)) !== null && _b !== void 0 ? _b : []) { this[utils_1.Done].push(() => listener(update)); } // Clear start event as it will never be called again if (this.events.has(Multyx.Start)) this.events.get(Multyx.Start).length = 0; break; } // Client or team data edit case 'edit': { if (update.path.length == 1) { if (update.team) { this.teams.set(update.path[0], new utils_1.EditWrapper(update.value)); } else { this.clients[update.path[0]] = new items_1.MultyxClientObject(this, new utils_1.EditWrapper(update.value), [update.path[0]], false); } } else { const agent = update.team ? this.teams.get(update.path[0]) : this.clients[update.path[0]]; if (!agent) return; agent.set(update.path.slice(1), new utils_1.EditWrapper(update.value)); } for (const listener of (_c = this.events.get(Multyx.Edit)) !== null && _c !== void 0 ? _c : []) { this[utils_1.Done].push(() => listener(update)); } break; } // Other data change case 'self': { this.parseSelf(update); break; } // Connection case 'conn': { this.clients[update.uuid] = new items_1.MultyxClientObject(this, update.data, [update.uuid], false); for (const listener of (_d = this.events.get(Multyx.Connection)) !== null && _d !== void 0 ? _d : []) { this[utils_1.Done].push(() => listener(this.clients[update.uuid])); } break; } // Disconnection case 'dcon': { for (const listener of (_e = this.events.get(Multyx.Disconnect)) !== null && _e !== void 0 ? _e : []) { const clientValue = this.clients[update.client].value; this[utils_1.Done].push(() => listener(clientValue)); } delete this.clients[update.client]; break; } // Response to client case 'resp': { const promiseResolve = this.events.get(Symbol.for("_" + update.name))[0]; this.events.delete(Symbol.for("_" + update.name)); this[utils_1.Done].push(() => promiseResolve(update.response)); break; } default: { if (this.options.verbose) { console.error("Server error: Unknown native Multyx instruction"); } } } } this[utils_1.Done].forEach(x => x()); this[utils_1.Done].length = 0; } initialize(update) { this.tps = update.tps; this.uuid = update.client.uuid; this.joinTime = update.client.joinTime; this.controller.listening = new Set(update.client.controller); // Create MultyxClientObject for all teams for (const team of Object.keys(update.teams)) { this.teams[team] = new utils_1.EditWrapper(update.teams[team]); } this.all = this.teams['all']; // Create MultyxClientObject for all clients this.clients = {}; for (const [uuid, client] of Object.entries(update.clients)) { if (uuid == this.uuid) continue; this.clients[uuid] = new items_1.MultyxClientObject(this, new utils_1.EditWrapper(client), [uuid], false); } ; const client = new items_1.MultyxClientObject(this, new utils_1.EditWrapper(update.client.self), [this.uuid], true); this.self = client; this.clients[this.uuid] = client; // Apply all constraints on self and teams for (const [uuid, table] of Object.entries(update.constraintTable)) { const obj = this.uuid == uuid ? this.self : this.teams[uuid]; obj[utils_1.Unpack](table); } } parseSelf(update) { if (update.property == 'controller') { this.controller.listening = new Set(update.data); } else if (update.property == 'uuid') { this.uuid = update.data; } else if (update.property == 'constraint') { let route = this.uuid == update.data.path[0] ? this.self : this.teams[update.data.path[0]]; for (const prop of update.data.path.slice(1)) route = route === null || route === void 0 ? void 0 : route[prop]; if (route === undefined) return; route[utils_1.Unpack]({ [update.data.name]: update.data.args }); } else if (update.property == 'space') { this.space = update.data; this.updateSpace(); } } // Hide all spaces except the current one updateSpace() { if (this.space == 'default') { document.querySelectorAll('[data-multyx-space]').forEach(space => { space.style.display = 'block'; space.style.pointerEvents = 'auto'; }); return; } document.querySelectorAll('[data-multyx-space]').forEach(space => { space.style.display = space.dataset.multyxSpace == this.space ? 'block' : 'none'; space.style.pointerEvents = space.dataset.multyxSpace == this.space ? 'auto' : 'none'; }); } } Multyx.Start = Symbol('start'); Multyx.Connection = Symbol('connection'); Multyx.Disconnect = Symbol('disconnect'); Multyx.Edit = Symbol('edit'); Multyx.Native = Symbol('native'); Multyx.Custom = Symbol('custom'); Multyx.Any = Symbol('any'); exports.default = Multyx;