//System Imports const childProcess = require('child_process'); //Imports const objects = require('../objects/objects'); const connections = require('../security/connections'); const { mapList } = require('./mapManager'); //Internals const threads = []; const listenersOnZoneIdle = []; //Helpers const getThreadFromName = name => { return threads.find(t => t.name === name); }; const getThreadFromId = threadId => { return threads.find(t => t.id === threadId); }; const messageHandlers = { onReady: function (thread) { thread.worker.send({ method: 'init', args: { zoneName: thread.name, zoneId: thread.id, path: thread.path } }); }, onInitialized: function (thread) { thread.isReady = true; thread.cbOnInitialized(thread); delete thread.cbOnInitialized; delete thread.promise; }, event: function (thread, message) { objects.sendEvent(message, thread); }, events: function (thread, message) { objects.sendEvents(message, thread); }, object: function (thread, message) { objects.updateObject(message); }, track: function (thread, message) { let player = objects.objects.find(o => o.id === message.serverId); if (!player) return; player.auth.gaTracker.track(message.obj); }, callDifferentThread: function (thread, message) { let obj = connections.players.find(p => (p.name === message.playerName)); if (!obj) return; let newThread = getThreadFromName(obj.zoneName); if (!newThread) return; newThread.worker.send({ module: message.data.module, method: message.data.method, args: message.data.args }); }, rezone: async function (thread, message) { const { args: { obj, newZone, keepPos = true } } = message; if (thread.instanced) { thread.worker.kill(); threads.spliceWhere(t => t === thread); } //When messages are sent from map threads, they have an id (id of the object in the map thread) // as well as a serverId (id of the object in the main thread) const serverId = obj.serverId; obj.id = serverId; obj.destroyed = false; const serverObj = objects.objects.find(o => o.id === obj.id); const mapExists = mapList.some(m => m.name === newZone); if (mapExists) { serverObj.zoneName = newZone; obj.zoneName = newZone; } else { obj.zoneName = clientConfig.config.defaultZone; serverObj.zoneName = clientConfig.config.defaultZone; } delete serverObj.zoneId; delete obj.zoneId; const isRezone = true; await atlas.addObject(obj, keepPos, isRezone); }, onZoneIdle: function (thread) { listenersOnZoneIdle.forEach(l => l(thread)); } }; const onMessage = (thread, message) => { if (message.module) { try { global[message.module][message.method](message); } catch (e) { /* eslint-disable-next-line no-console */ console.log('No global method found', message.module, message.method); process.exit(); } } else if (message.event === 'onCrashed') { thread.worker.kill(); process.exit(); } else messageHandlers[message.method](thread, message); }; const spawnThread = async ({ name, path, instanced }) => { let cbOnInitialized; const promise = new Promise(resolveOnReady => { cbOnInitialized = resolveOnReady; }); const worker = childProcess.fork('./world/worker', [name]); const id = instanced ? _.getGuid() : name; const thread = { id, name, instanced, path, worker, isReady: false, promise, cbOnInitialized }; worker.on('message', onMessage.bind(null, thread)); threads.push(thread); return promise; }; const getThread = async ({ zoneName, zoneId }) => { const result = { resetObjPosition: false, thread: null }; let map = mapList.find(m => m.name === zoneName); if (!map) map = mapList.find(m => m.name === clientConfig.config.defaultZone); let thread = threads.find(t => t.id === zoneId && t.name === zoneName); if (!thread) { if (map.instanced) { result.resetObjPosition = true; thread = await spawnThread(map); } else thread = getThreadFromName(map.name); } if (!thread.isReady) await thread.promise; result.thread = thread; return result; }; const killThread = thread => { thread.worker.kill(); threads.spliceWhere(t => t === thread); }; const spawnMapThreads = async () => { const promises = mapList .filter(m => !m.disabled && !m.instanced) .map(m => { return new Promise(async res => { await spawnThread(m); }); }); await Promise.all(promises); }; const sendMessageToThread = ({ threadId, msg }) => { const thread = threads.find(t => t.id === threadId); if (thread) thread.worker.send(msg); }; const messageAllThreads = message => { threads.forEach(t => t.worker.send(message)); }; const returnWhenThreadsIdle = async () => { return new Promise(res => { let doneCount = 0; const onZoneIdle = thread => { doneCount++; if (doneCount.length < threads.length) return; listenersOnZoneIdle.spliceWhere(l => l === onZoneIdle); res(); }; listenersOnZoneIdle.push(onZoneIdle); threads.forEach(t => { t.worker.send({ method: 'notifyOnceIdle' }); }); }); }; //Exports module.exports = { getThread, killThread, getThreadFromId, spawnMapThreads, messageAllThreads, sendMessageToThread, returnWhenThreadsIdle };