UNPKG

multyx

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

312 lines (311 loc) 10.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _a; Object.defineProperty(exports, "__esModule", { value: true }); const value_1 = __importDefault(require("./value")); const router_1 = __importDefault(require("./router")); const _1 = require("."); const native_1 = require("../utils/native"); class MultyxObject { /** * Turn MultyxObject back into regular object * @returns RawObject mirroring shape and values of MultyxObject */ get value() { const parsed = {}; for (const p in this.data) parsed[p] = this.data[p].value; return parsed; } /** * Get the value of MultyxObject that is relayed to public agents * @returns RawObject mirroring shape and values of relayed MultyxObject */ get relayedValue() { if (!this.relayed) return {}; const parsed = {}; for (const p in this.data) { const m = this.data[p].relayedValue; if (m !== undefined) parsed[p] = m; } return parsed; } /** * Create a MultyxItem representation of an object * @param object Object to turn into MultyxItem * @param agent Client or MultyxTeam hosting this MultyxItem * @param propertyPath Entire path from agent to this MultyxObject * @returns MultyxObject */ constructor(object, agent, propertyPath = [agent.uuid]) { /* Native methods to allow MultyxObject to be treated as primitive */ this.toString = () => this.value.toString(); this.valueOf = () => this.value; this[_a] = () => this.value; this.data = {}; this.propertyPath = propertyPath; this.agent = agent; this.disabled = false; this.relayed = true; this.publicTeams = new Set(); if (object instanceof MultyxObject) object = object.value; // Mirror object to be made of strictly MultyxItems for (const prop in object) { let child = object[prop]; if (child instanceof MultyxObject || child instanceof value_1.default) { child = child.value; } // MultyxItemRouter used to circumvent circular dependencies // Check /items/router.ts for extra information this.data[prop] = new ((0, router_1.default)(child))(child, agent, [...propertyPath, prop]); } // Apply proxy inside other constructor rather than here if (this.constructor !== MultyxObject) return this; return new Proxy(this, { has: (o, p) => { return o.has(p); }, // Allow users to access properties in MultyxObject without using get get: (o, p) => { if (p in o) return o[p]; return o.get(p); }, // Allow users to set MultyxObject properties by client.self.a = b set: (o, p, v) => { if (p in o) { o[p] = v; return true; } return !!o.set(p, v); }, // Allow users to delete MultyxObject properties by delete client.self.a; deleteProperty(o, p) { return !!o.delete(p); } }); } disable() { for (const prop in this.data) { this.data[prop].disable(); } this.disabled = true; return this; } enable() { for (const prop in this.data) { this.data[prop].enable(); } this.disabled = false; return this; } relay() { for (const prop in this.data) { this.data[prop].relay(); } this.relayed = true; return this; } unrelay() { for (const prop in this.data) { this.data[prop].unrelay(); } this.relayed = false; return this; } /** * Publicize MultyxValue from specific MultyxTeam * @param team MultyxTeam to share MultyxValue to * @returns Same MultyxValue */ addPublic(team) { if (this.publicTeams.has(team)) return this; this.publicTeams.add(team); for (const prop in this.data) this.data[prop].addPublic(team); return this; } /** * Privitize MultyxValue from specific MultyxTeam * @param team MultyxTeam to hide MultyxValue from * @returns Same MultyxValue */ removePublic(team) { if (!this.publicTeams.has(team)) return this; this.publicTeams.delete(team); for (const prop in this.data) this.data[prop].removePublic(team); return this; } /** * Check if property is in object */ has(property) { return property in this.data; } /** * Get the value of a property */ get(property) { if (typeof property === 'string') return this.data[property]; if (property.length == 0) return this; if (property.length == 1) return this.data[property[0]]; const next = this.data[property[0]]; if (!next || (next instanceof value_1.default)) return undefined; return next.get(property.slice(1)); } /** * Set the explicit value of the property * @example * ```js * // Server * multyx.on('reset', client => client.player.set('x', 5)); * * // Client * client.player.x = 20 * Math.random(); * multyx.send('reset'); * console.log(client.player.x); // 5 * ``` */ set(property, value) { var _b, _c; if (value instanceof native_1.EditWrapper && !this.has(property) && this.disabled) { return false; } // If just a normal value change, no need to update shape, can return if (typeof value !== "object" && this.data[property] instanceof value_1.default || value instanceof native_1.EditWrapper && typeof value.value !== 'object') { return this.data[property].set(value instanceof native_1.EditWrapper ? value.value : value) ? this : false; } const propertyPath = [...this.propertyPath, property]; // If value is a MultyxObject, don't create new object, change path if (value instanceof MultyxObject) { value[native_1.Self](propertyPath); this.data[property] = value; } else { if (value instanceof value_1.default || value instanceof native_1.EditWrapper) { value = value.value; } this.data[property] = new ((0, router_1.default)(value))(value, this.agent, propertyPath); } this.data[property].disabled = this.disabled; this.data[property].relayed = this.relayed; // Propogate publicAgents to clients for (const team of this.publicTeams) { this.data[property].addPublic(team); } const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + '.' + property); if ((_b = this.agent.server) === null || _b === void 0 ? void 0 : _b.events.has(propSymbol)) { (_c = this.agent.server) === null || _c === void 0 ? void 0 : _c.events.get(propSymbol).forEach(event => { event.call(undefined, this.data[property]); if (event.saveHistory) event.delete(); // delete temp events }); } return this; } /** * Delete property from MultyxObject * @param property Name of property to delete * @returns False if deletion failed, same MultyxObject otherwise */ delete(property) { delete this.data[property]; new _1.MultyxUndefined(this.agent, [...this.propertyPath, property]); return this; } /** * Wait for a property in object to be defined * @param property Name of property in object to wait for * @returns Promise that resolves once object[property] is defined */ await(property) { if (this.has(property)) return Promise.resolve(this.get(property)); const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + '.' + property); return new Promise(res => { var _b, _c; const event = (_c = (_b = this.agent) === null || _b === void 0 ? void 0 : _b.server) === null || _c === void 0 ? void 0 : _c.on(propSymbol, (_, v) => res(v)); event.saveHistory = true; // so that caller knows to delete }); } /** * Create a callback that gets called whenever the object is edited * @param property Property to listen for writes on * @param callback Function to call whenever object is edited * @returns Event object representing write callback */ onWrite(property, callback) { const propSymbol = Symbol.for("_" + this.propertyPath.join('.') + "." + property); return this.agent.server.on(propSymbol, (_, v) => callback(v)); } /** * Get all properties in object publicized to specific team * @param team MultyxTeam to get public data for * @returns Raw object */ [native_1.Get](team) { const parsed = {}; for (const prop in this.data) { const m = this.data[prop]; if (m instanceof value_1.default) { if (m.isPublic(team)) parsed[prop] = m.value; } else { parsed[prop] = m[native_1.Get]; } } return parsed; } /** * Build a constraint table * @returns Constraint table */ [native_1.Build]() { if (!this.relayed) return {}; const obj = {}; for (const prop in this.data) { const table = this.data[prop][native_1.Build](); if (Object.keys(table).length == 0) continue; obj[prop] = table; } return obj; } /** * Edit the property path of MultyxObject and any children * @param newPath New property path to take */ [native_1.Self](newPath) { this.propertyPath = newPath; for (const prop in this.data) { this.data[prop][native_1.Self]([...newPath, prop]); } } entries() { return Object.entries(this.data); } keys() { return Object.keys(this.data); } values() { return Object.values(this.data); } } _a = Symbol.toPrimitive; exports.default = MultyxObject;