Browse Source

refactor, fix #1963: Refactored atlas, added threadManager and fixed objects being added to zones before the zones are ready

tags/v0.12.0
Shaun 1 year ago
parent
commit
33d9fd5b03
12 changed files with 404 additions and 292 deletions
  1. +5
    -1
      src/server/components/social/startEvent.js
  2. +5
    -1
      src/server/components/social/stopEvent.js
  3. +0
    -19
      src/server/config/maps/mapList.js
  4. +6
    -4
      src/server/config/quests/questBuilder.js
  5. +1
    -1
      src/server/config/serverConfig.js
  6. +9
    -4
      src/server/events/events.js
  7. +17
    -16
      src/server/index.js
  8. +9
    -1
      src/server/security/connections/route.js
  9. +63
    -234
      src/server/world/atlas.js
  10. +25
    -0
      src/server/world/mapManager.js
  11. +250
    -0
      src/server/world/threadManager.js
  12. +14
    -11
      src/server/world/worker.js

+ 5
- 1
src/server/components/social/startEvent.js View File

@@ -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


+ 5
- 1
src/server/components/social/stopEvent.js View File

@@ -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


+ 0
- 19
src/server/config/maps/mapList.js View File

@@ -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;
}
};

+ 6
- 4
src/server/config/quests/questBuilder.js View File

@@ -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)


+ 1
- 1
src/server/config/serverConfig.js View File

@@ -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',


+ 9
- 4
src/server/events/events.js View File

@@ -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];


+ 17
- 16
src/server/index.js View File

@@ -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) {


+ 9
- 1
src/server/security/connections/route.js View File

@@ -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


+ 63
- 234
src/server/world/atlas.js View File

@@ -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;


+ 25
- 0
src/server/world/mapManager.js View File

@@ -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
};

+ 250
- 0
src/server/world/threadManager.js View File

@@ -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
};

+ 14
- 11
src/server/world/worker.js View File

@@ -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;


Loading…
Cancel
Save