Example #1
 * 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)) {
			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) ||
			util.format('invalid owner for %s: %s', obj, obj.owner));
		return getGsid(obj.owner);
	throw new Error('invalid game object type: ' + objOrTsid);
Example #2
 * 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
		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 {
			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;