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
JavaScript
"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;