/** * Determines the ID of the game server instance responsible for a game * object of any type (as opposed to {@link module:config~mapToGS| * config.mapToGS}). * * @param {GameObject|string} objOrTsid the game object to find the * responsible game server for, or its TSID * @returns {string} ID of the server managing the object */ function getGsid(objOrTsid) { // locations, geos and groups mapped by their own tsid if (utils.isLoc(objOrTsid) || utils.isGroup(objOrTsid) || utils.isGeo(objOrTsid)) { return config.mapToGS(objOrTsid).gsid; } // for all other classes, we need the actual game object var obj = typeof objOrTsid === 'string' ? pers.get(objOrTsid) : objOrTsid; assert(typeof obj === 'object' && obj !== null, 'cannot map nonexistent game object: ' + objOrTsid); // player mapped by current location if (utils.isPlayer(obj)) { assert(utils.isLoc(obj.location), util.format('invalid location for %s: %s', obj, obj.location)); return getGsid(obj.location); } // items (including bags) mapped by their top container (location or player) if (utils.isItem(obj)) { assert(utils.isLoc(obj.tcont) || utils.isPlayer(obj.tcont), util.format('invalid tcont for %s: %s', obj, obj.tcont)); return getGsid(obj.tcont); } // quests or DCs mapped by their owner (location, player or group) if (utils.isQuest(obj) || utils.isDC(obj)) { assert(utils.isLoc(obj.owner) || utils.isPlayer(obj.owner) || utils.isGroup(obj.owner), util.format('invalid owner for %s: %s', obj, obj.owner)); return getGsid(obj.owner); } throw new Error('invalid game object type: ' + objOrTsid); }
/** * Called by {@link RequestContext#run} after processing a request has finished; * writes object additions and deletions to persistence, and persists and * unloads game objects and connected "child" objects that this GS worker is * not managing anymore after the request (e.g. in case of player movement to * another server). * * @param {object} dlist hash with added or deleted game objects to persist * @param {object} ulist hash containing game objects to persist and release * from the live object cache * @param {string} [logmsg] optional information for log messages * @param {function} [callback] function to be called after persistence * operations have finished */ function postRequestProc(dlist, ulist, logmsg, callback) { if (shuttingDown) { var e = new Error('no more persistence operations allowed (shutdown)'); log.error({err: e}, 'failed to persist %s request', logmsg); if (callback) return callback(e); } var dtsids = Object.keys(dlist); var utsids = []; for (var k in ulist) { if (!(k in cache)) continue; if (utils.isPlayer(k) || utils.isLoc(k) || utils.isGroup(k)) { utsids = utsids.concat(getLoadedRefs(cache[k])); } else { utsids = utsids.concat(k); } } if (!dtsids.length && !utsids.length) { return callback ? callback(null) : undefined; } utsids = _.uniq(utsids); log.trace('objects to release after %s request: %s', logmsg, utsids); // process persistence changes in a safe order (add/modify first, then // delete); this may leave behind orphaned data, but should at least avoid // invalid object references async.series([ postRequestProcStep.bind(undefined, 'write', dtsids, logmsg), postRequestProcStep.bind(undefined, 'write', utsids, logmsg), postRequestProcStep.bind(undefined, 'delete', utsids, logmsg), postRequestProcStep.bind(undefined, 'delete', dtsids, logmsg), ], function cb(err) { // unload objects scheduled to be released from cache (take care not // to load objects here if they are not loaded in the first place) for (var i = 0; i < utsids.length; i++) { try { unload(utsids[i]); } catch (e) { log.error(e, 'failed to unload %s', utsids[i]); } } if (callback) return callback(err); }); }
DataContainer.create = function create(owner) { assert(utils.isLoc(owner) || utils.isItem(owner) || utils.isGroup(owner), util.format('invalid DC owner: %s', owner)); var dc = pers.create(DataContainer, {owner: owner}); return dc; };