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