Browse Source

feat #1866: Rebuilt rezone logic to use handshakes

tags/v0.10.6^2
Shaun 2 years ago
parent
commit
66f3daf87b
14 changed files with 255 additions and 49 deletions
  1. +3
    -1
      src/server/.eslintrc
  2. +0
    -8
      src/server/clientComponents/gatherer.js
  3. +4
    -4
      src/server/clientComponents/pather.js
  4. +2
    -2
      src/server/clientComponents/player.js
  5. +1
    -1
      src/server/components/player.js
  6. +75
    -19
      src/server/components/portal/sendObjToZone.js
  7. +12
    -3
      src/server/objects/objects.js
  8. +3
    -1
      src/server/security/routerConfig.js
  9. +2
    -2
      src/server/world/atlas.js
  10. +20
    -8
      src/server/world/instancer.js
  11. +49
    -0
      src/server/world/instancer/handshakes.js
  12. +55
    -0
      src/server/world/rezoneManager.js
  13. +26
    -0
      src/server/world/syncer.js
  14. +3
    -0
      src/server/world/worker.js

+ 3
- 1
src/server/.eslintrc View File

@@ -58,7 +58,9 @@
"leaderboard": false,
"clientConfig": false,
"random": false,
"consts": false
"consts": false,
"rezoneManager": false,
"eventManager": false
},

"rules": {


+ 0
- 8
src/server/clientComponents/gatherer.js View File

@@ -13,7 +13,6 @@ define([

init: function () {
this.obj.on('onKeyDown', this.onKeyDown.bind(this));
this.hookEvent('onRezone', this.onRezone.bind(this));
},

extend: function (msg) {
@@ -53,13 +52,6 @@ define([
}
},

onRezone: function () {
this.extend({
progress: 100,
action: 'Fishing'
});
},

onKeyDown: function (key) {
if (key !== 'g')
return;


+ 4
- 4
src/server/clientComponents/pather.js View File

@@ -27,9 +27,9 @@ define([
lastY: 0,

init: function () {
events.on('onRespawn', this.onDeath.bind(this));
events.on('onDeath', this.onDeath.bind(this));
events.on('onClearQueue', this.onDeath.bind(this));
events.on('teleportToPosition', this.resetPath.bind(this));
events.on('onDeath', this.resetPath.bind(this));
events.on('onClearQueue', this.resetPath.bind(this));

this.pathPos.x = round(this.obj.x);
this.pathPos.y = round(this.obj.y);
@@ -46,7 +46,7 @@ define([
this.path = [];
},

onDeath: function () {
resetPath: function () {
this.clearPath();
this.pathPos.x = round(this.obj.x);


+ 2
- 2
src/server/clientComponents/player.js View File

@@ -29,7 +29,7 @@ define([
obj.addComponent('serverActions');
obj.addComponent('pather');

this.hookEvent('onRespawn', this.onRespawn.bind(this));
this.hookEvent('teleportToPosition', this.teleportToPosition.bind(this));

events.emit('onGetPortrait', obj.portrait);
},
@@ -74,7 +74,7 @@ define([
}, instant);
},

onRespawn: function ({ x, y }) {
teleportToPosition: function ({ x, y }) {
this.positionCamera(x, y, true);
sound.update(x, y);
},


+ 1
- 1
src/server/components/player.js View File

@@ -235,7 +235,7 @@ module.exports = {

obj.instance.physics.addObject(obj, obj.x, obj.y);

obj.instance.syncer.queue('onRespawn', {
obj.instance.syncer.queue('teleportToPosition', {
x: obj.x,
y: obj.y
}, [obj.serverId]);


+ 75
- 19
src/server/components/portal/sendObjToZone.js View File

@@ -1,9 +1,54 @@
const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos }) => {
const { serverId, instance: { physics, syncer: globalSyncer } } = obj;
/*
atlas.onMessage.event / events -> only send if source zone = obj zoneName

SERVER SIDE
1. Change zone and position
2. Save
3. Syncer: Destroy, Flush and Notify other objects of destroyed obj
4. Log IN CASE (if new events are queued)
5. Stage rezone
6. Tell client rezone is happening

CLIENT SIDE
events.emit('rezoneStart');

events.emit('destroyAllObjects');
events.emit('resetRenderer');
events.emit('resetPhysics');
events.emit('clearUis');

client.request({
threadModule: 'rezoneManager',
method: 'clientAck',
data: {}
});

globalSyncer.flushForTarget(serverId);
SERVER SIDE
7. Server receives ack
8. Map thread tells main thread about rezone
9. Main thread does rezone
10. New map thread registers handshake for map send
11. New map thread sends new map
CLIENT SIDE
events.emit('onGetMap', msg);

client.request({
threadModule: 'instancer',
method: 'clientAck',
data: {}
});

SERVER SIDE
12. Add object to zone
*/

const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos }) => {
const { serverId, instance: { syncer: globalSyncer, physics } } = obj;

if (obj.zoneName === zoneName) {
globalSyncer.flushForTarget(serverId);

physics.removeObject(obj, obj.x, obj.y);

if (toRelativePos) {
@@ -18,7 +63,7 @@ const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos

physics.addObject(obj, obj.x, obj.y);

globalSyncer.queue('onRespawn', {
globalSyncer.queue('teleportToPosition', {
x: obj.x,
y: obj.y
}, [obj.serverId]);
@@ -26,28 +71,39 @@ const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos
return;
}

obj.fireEvent('beforeRezone');

obj.destroyed = true;
//We set this before saving so that objects aren't saved ON portals
obj.zoneName = zoneName;
if (toPos) {
obj.x = toPos.x;
obj.y = toPos.y;
} else if (toRelativePos) {
obj.x = invokingObj.obj.x + toRelativePos.x;
obj.y = invokingObj.obj.y + toRelativePos.y;
}

await obj.auth.doSave();

const simpleObj = obj.getSimple(true, false, true);
//Destroy, flush events and notify other objects
globalSyncer.processDestroyedObject(obj);

if (toPos) {
simpleObj.x = toPos.x;
simpleObj.y = toPos.y;
} else if (toRelativePos) {
simpleObj.x = invokingObj.obj.x + toRelativePos.x;
simpleObj.y = invokingObj.obj.y + toRelativePos.y;
//Test code, remove later
const queued = Object.values(globalSyncer.buffer).filter(b => b.to.includes(serverId));
if (queued.length) {
/* eslint-disable-next-line */
console.log('Found', queued.length, 'events for rezoning object');
}

const simpleObj = obj.getSimple(true, false, true);

rezoneManager.stageRezone(simpleObj, zoneName);

process.send({
method: 'rezone',
id: obj.serverId,
args: {
obj: simpleObj,
newZone: zoneName
method: 'events',
data: {
rezoneStart: [{
obj: { msg: {} },
to: [serverId]
}]
}
});
};


+ 12
- 3
src/server/objects/objects.js View File

@@ -203,7 +203,7 @@ module.exports = {

return newO;
},
sendEvent: function (msg) {
sendEvent: function (msg, sourceThread) {
let player = this.objects.find(p => p.id === msg.id);
if (!player)
return;
@@ -213,7 +213,7 @@ module.exports = {
data: msg.data.data
});
},
sendEvents: function (msg) {
sendEvents: function (msg, sourceThread) {
let players = {};
let objects = this.objects;

@@ -239,13 +239,22 @@ module.exports = {
if (!findPlayer)
continue;
else {
//If the message came from a map the player is no longer in, ignore
if (findPlayer.zoneName !== sourceThread.name)
continue;

player = (players[toId] = {
socket: findPlayer.socket,
events: {}
events: {},
obj: findPlayer
});
}
}

//If the message came from a map the player is no longer in, ignore
if (player.obj.zoneName !== sourceThread.name)
continue;

let eventList = player.events[e] || (player.events[e] = []);
eventList.push(obj);
}


+ 3
- 1
src/server/security/routerConfig.js View File

@@ -24,7 +24,9 @@ const routerConfig = {
globalAllowed: {
clientConfig: ['getClientConfig'],
leaderboard: ['requestList'],
cons: ['unzone']
cons: ['unzone'],
rezoneManager: ['clientAck'],
instancer: ['clientAck']
},
allowTargetId: {
door: ['lock', 'unlock'],


+ 2
- 2
src/server/world/atlas.js View File

@@ -174,11 +174,11 @@ module.exports = {
},

event: function (thread, message) {
objects.sendEvent(message);
objects.sendEvent(message, thread);
},

events: function (thread, message) {
objects.sendEvents(message);
objects.sendEvents(message, thread);
},

object: function (thread, message) {


+ 20
- 8
src/server/world/instancer.js View File

@@ -13,6 +13,9 @@ let eventEmitter = require('../misc/events');
const mods = require('../misc/mods');
const transactions = require('../security/transactions');

//Own helpers
const { stageZoneIn, unstageZoneIn, clientAck } = require('./instancer/handshakes');

module.exports = {
instances: [],
zoneId: -1,
@@ -65,6 +68,9 @@ module.exports = {
[resourceSpawner, syncer, objects, questBuilder, events].forEach(i => i.init(fakeInstance));

this.tick();

this.clientAck = clientAck;
eventEmitter.on('removeObject', unstageZoneIn);
},

startRegen: function (respawnMap, respawnPos) {
@@ -211,15 +217,17 @@ module.exports = {

obj.spawn = map.spawn;

syncer.queue('onGetMap', map.clientMap, [obj.serverId]);
stageZoneIn(msg);

if (!msg.transfer)
objects.addObject(obj, this.onAddObject.bind(this));
else {
let o = objects.transferObject(obj);
questBuilder.obtain(o);
eventEmitter.emit('onAfterPlayerEnterZone', o);
}
process.send({
method: 'events',
data: {
getMap: [{
obj: map.clientMap,
to: [obj.serverId]
}]
}
});
},

onAddObject: function (obj) {
@@ -302,6 +310,10 @@ module.exports = {
return;
}

//We fire this event because even though an object might be destroyed already,
// mods and modules might have staged events/actions we need to clear
eventEmitter.emit('removeObject', { obj: msg.obj });

let obj = msg.obj;
obj = objects.find(o => o.serverId === obj.id);
if (!obj) {


+ 49
- 0
src/server/world/instancer/handshakes.js View File

@@ -0,0 +1,49 @@
//Local State
const stagedZoneIns = [];

//Methods

//Fired when an object is removed through a socket dc
// We do this because a client might DC during rezone handshake
const unstageZoneIn = msg => {
stagedZoneIns.spliceWhere(s => s.obj.serverId === msg.obj.id);
};

const stageZoneIn = msg => {
const { serverId } = msg.obj;

stagedZoneIns.spliceWhere(o => o.obj.serverId === serverId);

stagedZoneIns.push(msg);
};

const doZoneIn = function (staged) {
const { onAddObject, instances: [ { objects, questBuilder, eventEmitter } ] } = instancer;

const { transfer, obj } = staged;

if (!transfer)
objects.addObject(obj, onAddObject.bind(instancer));
else {
let o = objects.transferObject(obj);
questBuilder.obtain(o);
eventEmitter.emit('onAfterPlayerEnterZone', o);
}
};

const clientAck = msg => {
const staged = stagedZoneIns.find(s => s.obj.serverId === msg.sourceId);
if (!staged)
return;

stagedZoneIns.spliceWhere(s => s === staged);

doZoneIn(staged);
};

//Exports
module.exports = {
unstageZoneIn,
stageZoneIn,
clientAck
};

+ 55
- 0
src/server/world/rezoneManager.js View File

@@ -0,0 +1,55 @@
//Imports
const eventEmitter = require('../misc/events');

//Local State
const stagedRezones = [];

//Methods

//Fired when an object is removed through a socket dc
// We do this because a client might DC during rezone handshake
const unstageRezone = msg => {
stagedRezones.spliceWhere(s => s.simplifiedObj.serverId === msg.obj.id);
};

const stageRezone = (simplifiedObj, targetZone) => {
const { serverId } = simplifiedObj;

stagedRezones.spliceWhere(o => o.simplifiedObj.serverId === serverId);

stagedRezones.push({ simplifiedObj, targetZone });
};

const doRezone = stagedRezone => {
const { simplifiedObj, targetZone } = stagedRezone;

process.send({
method: 'rezone',
id: simplifiedObj.serverId,
args: {
obj: simplifiedObj,
newZone: targetZone
}
});
};

const clientAck = msg => {
const staged = stagedRezones.find(s => s.simplifiedObj.serverId === msg.sourceId);
if (!staged)
return;

stagedRezones.spliceWhere(s => s === staged);

doRezone(staged);
};

const init = () => {
eventEmitter.on('removeObject', unstageRezone);
};

//Exports
module.exports = {
init,
stageRezone,
clientAck
};

+ 26
- 0
src/server/world/syncer.js View File

@@ -180,12 +180,38 @@ module.exports = {
}
},

processDestroyedObject: function (obj) {
const { objects, queue } = this;
const { id, serverId } = obj;

obj.destroyed = true;
this.flushForTarget(serverId);

const msg = {
id: id,
destroyed: true
};

objects.removeObject(obj);

const fnQueueMsg = queue.bind(this, 'onGetObject');

//Find any players that have seen this obj
objects
.filter(o => !o.destroyed && o?.player?.hasSeen(id))
.forEach(o => {
fnQueueMsg(msg);
});
},

send: function () {
if (!this.dirty)
return;

this.dirty = false;

console.log(Object.keys(this.buffer));

process.send({
method: 'events',
data: this.buffer


+ 3
- 0
src/server/world/worker.js View File

@@ -5,6 +5,7 @@ global.consts = require('../config/consts');
global.instancer = require('./instancer');
global.eventManager = require('../events/events');
global.clientConfig = require('../config/clientConfig');
global.rezoneManager = require('./rezoneManager');

const components = require('../components/components');
const mods = require('../misc/mods');
@@ -33,6 +34,8 @@ let onCpnsReady = async function () {
recipes.init();
itemEffects.init();
profanities.init();
rezoneManager.init();

await clientConfig.init();

process.send({


Loading…
Cancel
Save