diff --git a/src/server/components/social/startEvent.js b/src/server/components/social/startEvent.js index c5cc5ee0..9b4532bf 100644 --- a/src/server/components/social/startEvent.js +++ b/src/server/components/social/startEvent.js @@ -1,5 +1,9 @@ +//Imports +const { messageAllThreads } = require('../../world/threadManager'); + +//Exports module.exports = async (cpnSocial, eventName) => { - atlas.messageAllThreads({ + messageAllThreads({ threadModule: 'eventManager', method: 'startEventByCode', data: eventName diff --git a/src/server/components/social/stopEvent.js b/src/server/components/social/stopEvent.js index 964fd1bd..06e7c7d5 100644 --- a/src/server/components/social/stopEvent.js +++ b/src/server/components/social/stopEvent.js @@ -1,5 +1,9 @@ +//Imports +const { messageAllThreads } = require('../../world/threadManager'); + +//Exports module.exports = async (cpnSocial, eventName) => { - atlas.messageAllThreads({ + messageAllThreads({ threadModule: 'eventManager', method: 'stopEventByCode', data: eventName diff --git a/src/server/config/maps/mapList.js b/src/server/config/maps/mapList.js deleted file mode 100644 index 80d1de9c..00000000 --- a/src/server/config/maps/mapList.js +++ /dev/null @@ -1,19 +0,0 @@ -let events = require('../../misc/events'); - -let config = [ - { - name: 'cave', - path: 'config/maps' - }, - { - name: 'fjolarok', - path: 'config/maps' - } -]; - -module.exports = { - init: function () { - events.emit('onBeforeGetMapList', config); - this.mapList = config; - } -}; diff --git a/src/server/config/quests/questBuilder.js b/src/server/config/quests/questBuilder.js index 9033ffd0..c70148bd 100644 --- a/src/server/config/quests/questBuilder.js +++ b/src/server/config/quests/questBuilder.js @@ -1,7 +1,9 @@ -let questTemplate = require('./templates/questTemplate'); -let globalQuests = require('../questsBase'); -let mapList = require('../maps/mapList'); +//Imports +const questTemplate = require('./templates/questTemplate'); +const globalQuests = require('../questsBase'); +const { mapList } = require('../../world/mapManager'); +//Exports module.exports = { instance: null, @@ -11,7 +13,7 @@ module.exports = { obtain: function (obj, template) { let zoneName = template?.zoneName ?? obj.zoneName; - let zone = mapList.mapList.find(m => m.name === zoneName); + let zone = mapList.find(m => m.name === zoneName); //Zone doesn't exist any more. Probably been renamed if (!zone) diff --git a/src/server/config/serverConfig.js b/src/server/config/serverConfig.js index 89ce797c..16f02a31 100644 --- a/src/server/config/serverConfig.js +++ b/src/server/config/serverConfig.js @@ -10,7 +10,7 @@ module.exports = { //Options: // sqlite // rethink - db: process.env.IWD_DB || 'sqlite', + db: process.env.IWD_DB || 'rethink', dbHost: process.env.IWD_DB_HOST || 'localhost', dbPort: process.env.IWD_DB_PORT || 28015, dbName: process.env.IWD_DB_NAME || 'live', diff --git a/src/server/events/events.js b/src/server/events/events.js index 529658f8..fa1ac9ee 100644 --- a/src/server/events/events.js +++ b/src/server/events/events.js @@ -1,7 +1,11 @@ -let phaseTemplate = require('./phases/phaseTemplate'); -let fs = require('fs'); -let mapList = require('../config/maps/mapList'); +//System Imports +const fs = require('fs'); +//Imports +const phaseTemplate = require('./phases/phaseTemplate'); +const { mapList } = require('../world/mapManager'); + +//Helpers const applyVariablesToDescription = (desc, variables) => { if (!variables) return desc; @@ -14,6 +18,7 @@ const applyVariablesToDescription = (desc, variables) => { return desc; }; +//Exports module.exports = { configs: [], nextId: 0, @@ -22,7 +27,7 @@ module.exports = { this.instance = instance; const zoneName = this.instance.map.name; - const zonePath = mapList.mapList.find(z => z.name === zoneName).path; + const zonePath = mapList.find(z => z.name === zoneName).path; const zoneEventPath = zonePath + '/' + zoneName + '/events'; const paths = ['config/globalEvents', zoneEventPath]; diff --git a/src/server/index.js b/src/server/index.js index 2e14f166..4cec5ff1 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,20 +1,21 @@ require('./globals'); -let server = require('./server/index'); -let components = require('./components/components'); -let mods = require('./misc/mods'); -let animations = require('./config/animations'); -let skins = require('./config/skins'); -let factions = require('./config/factions'); -let classes = require('./config/spirits'); -let spellsConfig = require('./config/spellsConfig'); -let spells = require('./config/spells'); -let itemTypes = require('./items/config/types'); -let recipes = require('./config/recipes/recipes'); -let mapList = require('./config/maps/mapList'); -let fixes = require('./fixes/fixes'); -let profanities = require('./misc/profanities'); +const server = require('./server/index'); +const components = require('./components/components'); +const mods = require('./misc/mods'); +const animations = require('./config/animations'); +const skins = require('./config/skins'); +const factions = require('./config/factions'); +const classes = require('./config/spirits'); +const spellsConfig = require('./config/spellsConfig'); +const spells = require('./config/spells'); +const itemTypes = require('./items/config/types'); +const recipes = require('./config/recipes/recipes'); +const mapManager = require('./world/mapManager'); +const fixes = require('./fixes/fixes'); +const profanities = require('./misc/profanities'); const routerConfig = require('./security/routerConfig'); +const { spawnMapThreads } = require('./world/threadManager'); let startup = { init: function () { @@ -41,7 +42,7 @@ let startup = { recipes.init(); itemTypes.init(); profanities.init(); - mapList.init(); + mapManager.init(); components.init(this.onComponentsReady.bind(this)); }, @@ -55,7 +56,7 @@ let startup = { await leaderboard.init(); - atlas.init(); + await spawnMapThreads(); }, onError: async function (e) { diff --git a/src/server/security/connections/route.js b/src/server/security/connections/route.js index e4a908a4..f0646505 100644 --- a/src/server/security/connections/route.js +++ b/src/server/security/connections/route.js @@ -1,3 +1,7 @@ +//Imports +const { sendMessageToThread } = require('../../world/threadManager'); + +//Helpers const route = function (socket, msg) { const source = this.players.find(p => p.socket.id === socket.id); if (!source) @@ -27,7 +31,10 @@ const route = function (socket, msg) { if (msg.callback) msg.data.callbackId = atlas.registerCallback(msg.callback); - atlas.send(source.zoneId, msg); + sendMessageToThread({ + threadId: source.zoneId, + msg + }); return; } @@ -52,6 +59,7 @@ const routeGlobal = function (msg) { global[msg.module][msg.method](msg); }; +//Exports module.exports = { route, routeGlobal diff --git a/src/server/world/atlas.js b/src/server/world/atlas.js index 928717c9..82d745eb 100644 --- a/src/server/world/atlas.js +++ b/src/server/world/atlas.js @@ -1,21 +1,16 @@ -let childProcess = require('child_process'); -let objects = require('../objects/objects'); -let mapList = require('../config/maps/mapList'); -let connections = require('../security/connections'); -let events = require('../misc/events'); - -const listenersOnZoneIdle = []; - +//Imports +const objects = require('../objects/objects'); +const events = require('../misc/events'); +const { + getThread, killThread, sendMessageToThread, getThreadFromId, returnWhenThreadsIdle +} = require('./threadManager'); + +//Exports module.exports = { nextId: 0, lastCallbackId: 0, - threads: [], callbacks: [], - init: function () { - this.getMapFiles(); - }, - addObject: async function (obj, keepPos, transfer) { const serverObj = objects.objects.find(o => o.id === obj.id); if (!serverObj) @@ -23,23 +18,16 @@ module.exports = { events.emit('onBeforePlayerEnterWorld', obj); - let thread; - - let map = mapList.mapList.find(m => m.name === obj.zoneName); + const { zoneName, zoneId } = obj; - if (!map) - map = mapList.mapList.find(m => m.name === clientConfig.config.defaultZone); - - thread = this.threads.find(t => t.id === obj.zoneId && t.name === obj.zoneName); - - if (!thread) { - if (map.instanced) { - delete obj.x; - delete obj.y; - - thread = await this.spawnMap(map); - } else - thread = this.getThreadFromName(map.name); + const { thread, resetObjPosition } = await getThread({ + zoneName, + zoneId + }); + + if (resetObjPosition) { + delete obj.x; + delete obj.y; } obj.zoneName = thread.name; @@ -52,12 +40,15 @@ module.exports = { const simpleObj = obj.getSimple ? obj.getSimple(true, true) : obj; - this.send(obj.zoneId, { - method: 'addObject', - args: { - keepPos: keepPos, - obj: simpleObj, - transfer: transfer + sendMessageToThread({ + threadId: obj.zoneId, + msg: { + method: 'addObject', + args: { + keepPos: keepPos, + obj: simpleObj, + transfer: transfer + } } }); }, @@ -75,8 +66,7 @@ module.exports = { }); }); - thread.worker.kill(); - this.threads.spliceWhere(t => t === thread); + killThread(thread); if (callback) callback(); @@ -86,8 +76,8 @@ module.exports = { if (!skipLocal) objects.removeObject(obj); - let thread = this.findObjectThread(obj); - if (!thread) + const thread = getThreadFromId(obj.zoneId); + if (!thread) return; if (thread.instanced) { @@ -100,38 +90,50 @@ module.exports = { if (callback) callbackId = this.registerCallback(callback); - this.send(obj.zoneId, { - method: 'removeObject', - args: { - obj: obj.getSimple(true), - callbackId: callbackId + sendMessageToThread({ + threadId: obj.zoneId, + msg: { + method: 'removeObject', + args: { + obj: obj.getSimple(true), + callbackId: callbackId + } } }); }, updateObject: function (obj, msgObj) { - this.send(obj.zoneId, { - method: 'updateObject', - args: { - id: obj.id, - obj: msgObj + sendMessageToThread({ + threadId: obj.zoneId, + msg: { + method: 'updateObject', + args: { + id: obj.id, + obj: msgObj + } } }); }, queueAction: function (obj, action) { - this.send(obj.zoneId, { - method: 'queueAction', - args: { - id: obj.id, - action: action + sendMessageToThread({ + threadId: obj.zoneId, + msg: { + method: 'queueAction', + args: { + id: obj.id, + action: action + } } }); }, performAction: function (obj, action) { - this.send(obj.zoneId, { - method: 'performAction', - args: { - id: obj.id, - action: action + sendMessageToThread({ + threadId: obj.zoneId, + msg: { + method: 'performAction', + args: { + id: obj.id, + action: action + } } }); }, @@ -152,185 +154,12 @@ module.exports = { callback.callback(msg.msg.result); }, - send: function (threadId, msg) { - const thread = this.threads.find(t => t.id === threadId); - if (thread) - thread.worker.send(msg); - }, - - findObjectThread: function ({ zoneId }) { - return this.threads.find(t => t.id === zoneId); - }, - getThreadFromName: function (name) { - return this.threads.find(t => t.name === name); - }, - - getMapFiles: function () { - mapList.mapList - .filter(m => !m.disabled && !m.instanced) - .forEach(m => this.spawnMap(m)); - }, - spawnMap: async function ({ name, path, instanced }) { - return new Promise(resolveOnReady => { - const worker = childProcess.fork('./world/worker', [name]); - - const id = instanced ? _.getGuid() : name; - - const thread = { - id, - name, - instanced, - path, - worker, - cbOnInitialized: resolveOnReady - }; - - const onMessage = this.onMessage.bind(this, thread); - worker.on('message', function (m) { - onMessage(m); - }); - - this.threads.push(thread); - }); - }, - onMessage: function (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 - this.thread[message.method].call(this, thread, message); - }, - - messageAllThreads: function (message) { - this.threads.forEach(t => t.worker.send(message)); - }, - - fireEventOnAllThreads: function ({ msg: { event, data } }) { - this.threads.forEach(t => t.worker.send({ event, data })); - }, - - thread: { - onReady: function (thread) { - thread.worker.send({ - method: 'init', - args: { - zoneName: thread.name, - zoneId: thread.id, - path: thread.path - } - }); - }, - - onInitialized: function (thread) { - thread.cbOnInitialized(thread); - }, - - 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 = this.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(); - this.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.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 this.addObject(obj, keepPos, isRezone); - }, - - onZoneIdle: function (thread) { - listenersOnZoneIdle.forEach(l => l(thread)); - } - }, - returnWhenZonesIdle: async function () { - return new Promise(res => { - const waiting = [...this.threads]; - - const onZoneIdle = thread => { - waiting.spliceWhere(w => w === thread); - - if (waiting.length) - return; - - listenersOnZoneIdle.spliceWhere(l => l === onZoneIdle); - res(); - }; - - listenersOnZoneIdle.push(onZoneIdle); - - this.threads.forEach(t => { - t.worker.send({ - method: 'notifyOnceIdle' - }); - }); - }); + await returnWhenThreadsIdle(); }, forceSavePlayer: async function (playerName, zoneId) { - const thread = this.threads.find(t => t.id === zoneId); + const thread = getThreadFromId(zoneId); if (!thread) return; diff --git a/src/server/world/mapManager.js b/src/server/world/mapManager.js new file mode 100644 index 00000000..4c1765dc --- /dev/null +++ b/src/server/world/mapManager.js @@ -0,0 +1,25 @@ +//Imports +const events = require('../misc/events'); + +//Internals +const mapList = [ + { + name: 'cave', + path: 'config/maps' + }, + { + name: 'fjolarok', + path: 'config/maps' + } +]; + +//Helpers +const init = () => { + events.emit('onBeforeGetMapList', mapList); +}; + +//Exports +module.exports = { + init, + mapList +}; diff --git a/src/server/world/threadManager.js b/src/server/world/threadManager.js new file mode 100644 index 00000000..76f13219 --- /dev/null +++ b/src/server/world/threadManager.js @@ -0,0 +1,250 @@ +//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 +}; diff --git a/src/server/world/worker.js b/src/server/world/worker.js index 360090c1..70830120 100644 --- a/src/server/world/worker.js +++ b/src/server/world/worker.js @@ -1,3 +1,4 @@ +//Globals global.extend = require('../misc/clone'); global.io = require('../db/io'); global._ = require('../misc/helpers'); @@ -7,6 +8,7 @@ global.eventManager = require('../events/events'); global.clientConfig = require('../config/clientConfig'); global.rezoneManager = require('./rezoneManager'); +//Imports const components = require('../components/components'); const mods = require('../misc/mods'); const animations = require('../config/animations'); @@ -17,14 +19,15 @@ const spellsConfig = require('../config/spellsConfig'); const spells = require('../config/spells'); const recipes = require('../config/recipes/recipes'); const itemTypes = require('../items/config/types'); -const mapList = require('../config/maps/mapList'); +const mapManager = require('../world/mapManager'); const itemEffects = require('../items/itemEffects'); const profanities = require('../misc/profanities'); const eventEmitter = require('../misc/events'); +//Worker instancer.mapName = process.argv[2]; -let onCpnsReady = async function () { +const onCpnsReady = async function () { factions.init(); skins.init(); animations.init(); @@ -32,7 +35,7 @@ let onCpnsReady = async function () { spellsConfig.init(); spells.init(); itemTypes.init(); - mapList.init(); + mapManager.init(); recipes.init(); itemEffects.init(); profanities.init(); @@ -45,7 +48,7 @@ let onCpnsReady = async function () { }); }; -let onModsReady = function () { +const onModsReady = function () { components.init(onCpnsReady); }; @@ -67,7 +70,7 @@ const onCrash = async e => { }); }; -let onDbReady = async function () { +const onDbReady = async function () { require('../misc/random'); process.on('uncaughtException', onCrash); @@ -82,17 +85,17 @@ io.init(onDbReady); process.on('message', m => { if (m.module) { - let instances = instancer.instances; - let iLen = instances.length; + const instances = instancer.instances; + const iLen = instances.length; for (let i = 0; i < iLen; i++) { - let objects = instances[i].objects.objects; - let oLen = objects.length; + const objects = instances[i].objects.objects; + const oLen = objects.length; let found = false; for (let j = 0; j < oLen; j++) { - let object = objects[j]; + const object = objects[j]; if (object.name === m.args[0]) { - let mod = object.instance[m.module]; + const mod = object.instance[m.module]; mod[m.method].apply(mod, m.args); found = true;