From dc0d914b277c54c37d9b0329d2c8dfd36f15945c Mon Sep 17 00:00:00 2001 From: Micah Allen Date: Thu, 1 Nov 2018 15:05:48 -0400 Subject: [PATCH] Add initial client<-->server RPC --- package.json | 10 +-- src/index.js | 178 ++++++++++++++++++++++++++++++++++++++++----------- src/util.js | 19 +++++- 3 files changed, 161 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 4210c69..43a3512 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { - "name": "rage-eventbus", + "name": "rage-rpc", "version": "0.0.1", - "description": "An asynchronous event bus for RAGE Multiplayer", + "description": "An asynchronous RPC implementation for RAGE Multiplayer", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", - "url": "git+https://github.com/micaww/rage-eventbus.git" + "url": "git+https://github.com/micaww/rage-rpc.git" }, "author": "micaww", "license": "ISC", "bugs": { - "url": "https://github.com/micaww/rage-eventbus/issues" + "url": "https://github.com/micaww/rage-rpc/issues" }, - "homepage": "https://github.com/micaww/rage-eventbus#readme", + "homepage": "https://github.com/micaww/rage-rpc#readme", "dependencies": {} } diff --git a/src/index.js b/src/index.js index 1e8cca4..a0fde9d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,63 +1,163 @@ const util = require('./util.js'); -//const isClient = !!mp.game.joaat; -//const isCEF = !!mp.trigger; +const environment = util.getEnvironment(); +if(!environment) throw 'Unknown RAGE environment'; + +const PROCESS_EVENT = '__rpc:process'; + +const rpc = {}; const listeners = {}; +const pending = {}; -/*mp.events.add('rbus:process', (data) => { +async function callProcedure(name, args, info){ + if(!listeners[name]) throw 'PROCEDURE_NOT_FOUND'; + return listeners[name](args, info); +} -});*/ +const processEvent = (...args) => { + let data = args[0]; + if(environment === "server") data = args[1]; + data = util.parseData(data); -const rbus = {}; + if(data.req){ // someone is trying to remotely call a procedure + const info = { + id: data.id, + environment: data.env + }; + if(environment === "server") info.player = args[0]; + const promise = callProcedure(data.name, data.args, info); + switch(environment){ + case "server": { + promise.then(res => { + info.player.call(PROCESS_EVENT, [util.stringifyData({ + ret: 1, + id: data.id, + res + })]); + }).catch(err => { + info.player.call(PROCESS_EVENT, [util.stringifyData({ + ret: 1, + id: data.id, + err + })]); + }); + break; + } + case "client": { + promise.then(res => { + mp.events.callRemote(PROCESS_EVENT, util.stringifyData({ + ret: 1, + id: data.id, + res + })); + }).catch(err => { + mp.events.callRemote(PROCESS_EVENT, util.stringifyData({ + ret: 1, + id: data.id, + err + })); + }); + break; + } + } + }else if(data.ret){ // a previously called remote procedure has returned + const info = pending[data.id]; + if(info){ + if(data.err) info.reject(data.err); + else info.resolve(data.res); + pending[data.id] = undefined; + } + } +}; + +mp.events.add(PROCESS_EVENT, processEvent); /** - * Register an event listener. - * @param {string} eventName - The name of the event. - * @param {function} cb - The event's callback. The return value will be sent back to the caller. + * Register a procedure. + * @param {string} name - The name of the procedure. + * @param {function} cb - The procedure's callback. The return value will be sent back to the caller. */ -rbus.on = (eventName, cb) => { - if(!listeners[eventName]) listeners[eventName] = []; - listeners[eventName].push(cb); +rpc.register = (name, cb) => { + listeners[name] = cb; }; /** - * Unregister an event listener. - * @param {string} eventName - The name of the event. - * @param {function} cb - The callback that was registered with `on`. + * Unregister a procedure. + * @param {string} name - The name of the procedure. */ -rbus.off = (eventName, cb) => { - if(!listeners[eventName]) return; - listeners[eventName] = listeners[eventName].filter(listener => listener !== cb); +rpc.unregister = (name) => { + listeners[name] = undefined; }; /** - * Calls a local event listener. - * @param {string} eventName - The name of the event. - * @returns {Promise} - The result from the local event listener. + * Calls a local procedure. + * @param {string} name - The name of the locally registered procedure. + * @param args - Any parameters for the procedure. + * @returns {Promise} - The result from the procedure. */ -rbus.send = (eventName) => { - if(!listeners[eventName] || !listeners[eventName].length) return Promise.reject('NO_LISTENERS'); - return Promise.resolve(listeners[eventName][0]()); +rpc.call = (name, args) => callProcedure(name, args, { environment }); + +/** + * Calls a remote procedure registered on the server. + * @param {string} name - The name of the registered procedure. + * @param args - Any parameters for the procedure. + * @returns {Promise} - The result from the procedure. + */ +rpc.callServer = (name, args) => { + switch(environment){ + case "server": { + return rpc.call(name, args); + } + case "client": { + const id = util.uid(); + return new Promise((resolve, reject) => { + pending[id] = { + resolve, + reject + }; + mp.events.callRemote(PROCESS_EVENT, util.stringifyData({ + req: 1, + id, + name, + env: environment, + args + })); + }); + } + } }; /** - * Calls a remote event listener residing on the server. - * @param {string} eventName - The name of the event. - * @returns {Promise} - The result from the remote event listener. + * Calls a remote procedure registered on the client. + * @param player - The player to call the procedure on. + * @param {string} name - The name of the registered procedure. + * @param args - Any parameters for the procedure. + * @returns {Promise} - The result from the procedure. */ -rbus.sendServer = (eventName) => { - +rpc.callClient = (player, name, args) => { + switch(environment){ + case "client": { + if(player === mp.players.local) return rpc.call(name, args); + else return Promise.reject('Only the server can RPC to other clients.'); + } + case "server": { + const id = util.uid(); + return new Promise((resolve, reject) => { + pending[id] = { + resolve, + reject + }; + player.call(PROCESS_EVENT, [util.stringifyData({ + req: 1, + id, + name, + env: environment, + args + })]); + }); + } + } }; -/** - * Calls a remote event listener residing on the client. - * @param player - The player to send to - * @param {string} eventName - The name of the event - * @returns {Promise} - The result from the remote event listener - */ -rbus.sendClient = (player, eventName) => { - -}; - -module.exports = rbus; \ No newline at end of file +module.exports = rpc; \ No newline at end of file diff --git a/src/util.js b/src/util.js index 9504be4..50f1c46 100644 --- a/src/util.js +++ b/src/util.js @@ -3,9 +3,24 @@ const util = {}; util.uid = () => { let firstPart = (Math.random() * 46656) | 0; let secondPart = (Math.random() * 46656) | 0; - firstPart = ("000" + firstPart.toString(36)).slice(-3); - secondPart = ("000" + secondPart.toString(36)).slice(-3); + firstPart = ('000' + firstPart.toString(36)).slice(-3); + secondPart = ('000' + secondPart.toString(36)).slice(-3); return firstPart + secondPart; }; +util.getEnvironment = () => { + if(!mp) return undefined; + if(mp.joaat) return 'server'; + else if(mp.game && mp.game.joaat) return 'client'; + else if(mp.trigger) return 'cef'; +}; + +util.stringifyData = (data) => { + return JSON.stringify(data); +}; + +util.parseData = (data) => { + return JSON.parse(data); +}; + module.exports = util; \ No newline at end of file