Example #1
0
/**
 * Loads the game object with the given TSID from persistence.
 * Depending on whether the GS is "responsible" for this object, it
 * will be wrapped either in a {@link module:data/persProxy|persProxy}
 * or {@link module:data/rpcProxy|rpcProxy}.
 *
 * @param {string} tsid TSID of the object to load
 * @returns {GameObject|null} the requested object, or `null` if no
 *          object data was found for the given TSID
 */
function load(tsid) {
	assert(pbe, 'persistence back-end not set');
	log.debug('pers.load: %s', tsid);
	var data = pbe.read(tsid);
	if (typeof data !== 'object' || data === null) {
		log.info(new DummyError(), 'no or invalid data for %s', tsid);
		return null;
	}
	orProxy.proxify(data);
	var obj = gsjsBridge.create(data);
	if (!rpc.isLocal(obj)) {
		// wrap object in RPC proxy and add it to request cache
		obj = rpc.makeProxy(obj);
		RC.getContext().cache[tsid] = obj;
		metrics.increment('pers.load.remote');
	}
	else {
		// check if object has been loaded in a concurrent request (fiber) in the meantime
		if (tsid in cache) {
			log.warn('%s already loaded, discarding redundant copy', tsid);
			return cache[tsid];
		}
		// make sure any changes to the object are persisted
		obj = persProxy.makeProxy(obj);
		cache[tsid] = obj;
		// post-construction operations (resume timers/intervals, GSJS onLoad etc.)
		if (obj.gsOnLoad) {
			obj.gsOnLoad();
		}
		metrics.increment('pers.load.local');
	}
	metrics.increment('pers.load');
	return obj;
}
Example #2
0
Session.prototype.send = function send(msg) {
	if (!this.socket) {
		log.debug('socket is gone, dropping %s message', msg.type);
		return;
	}
	if (!this.loggedIn) {
		if (msg.type !== 'login_start' && msg.type !== 'login_end' &&
			msg.type !== 'relogin_start' && msg.type !== 'relogin_end' &&
			msg.type !== 'ping') {
			log.debug('(re)login incomplete, postponing %s message', msg.type);
			this.preLoginBuffer.push(msg);
			return;
		}
		if (msg.type === 'login_end' || msg.type === 'relogin_end') {
			this.loggedIn = true;
		}
	}
	if (log.trace()) {
		log.trace({data: msg, to: this.pc ? this.pc.tsid : undefined},
			'sending %s message', msg.type);
	}
	var data;
	if (this.jsamf) {
		data = amf.js.serializer().writeObject(msg);
	}
	else {
		data = amf.cc.serialize(msg);
	}
	var size = Buffer.byteLength(data, 'binary');
	var buf = new Buffer(4 + size);
	buf.writeUInt32BE(size, 0);
	buf.write(data, 4, size, 'binary');
	this.socket.write(buf);
	metrics.increment('net.amf.tx', 0.01);
};
Example #3
0
	pbe.del(obj, function db(err, res) {
		if (err) {
			log.error(err, 'could not delete: %s', obj.tsid);
			metrics.increment('pers.del.fail');
		}
		if (callback) return callback(err, res);
	});
Example #4
0
	pbe.write(orProxy.refify(obj.serialize()), function cb(err, res) {
		if (err) {
			log.error(err, 'could not write: %s', obj.tsid);
			metrics.increment('pers.write.fail');
		}
		if (callback) return callback(err, res);
	});
Example #5
0
		pbe.del(tsid, (err) => {
			if (err) {
				log.error(err, 'could not delete: %s', tsid);
				metrics.increment('pers.del.fail');
			}
			return cb();
		});
Example #6
0
/**
 * Writes a game object to persistent storage.
 *
 * @param {GameObject} obj game object to write
 * @param {string} logmsg short additional info for log messages
 * @param {function} callback called when write operation has finished,
 *        or in case of errors
 * @private
 */
function write(obj, logmsg, callback) {
	log.debug('pers.write: %s%s', obj.tsid, logmsg ? ' (' + logmsg + ')' : '');
	metrics.increment('pers.write');
	pbe.write(orProxy.refify(obj.serialize()), function cb(err, res) {
		if (err) {
			log.error(err, 'could not write: %s', obj.tsid);
			metrics.increment('pers.write.fail');
		}
		if (callback) return callback(err, res);
	});
}
Example #7
0
/**
 * Loads the game object with the given TSID from persistence.
 * Depending on whether the GS is "responsible" for this object, it
 * may be wrapped in an {@link module:data/rpcProxy|rpcProxy}.
 *
 * @param {string} tsid TSID of the object to load
 * @returns {GameObject|null} the requested object, or `null` if no
 *          object data was found for the given TSID
 */
function load(tsid) {
	assert(pbe, 'persistence back-end not set');
	log.debug('pers.load: %s', tsid);
	var data = pbe.read(tsid);
	if (data === null && (utils.isGeo(tsid) || utils.isLoc(tsid))) {
		log.info('no data for %s, using temp location data instead', tsid);
		data = pbe.read(utils.isGeo(tsid) ? 'GKZ8WU4WGMQME7CXXX' : 'LKZ8WU4WGMQME7CXXX');
		if (data) data.tsid = tsid;
	}
	if (!_.isObject(data)) {
		log.info(new DummyError(), 'no or invalid data for %s', tsid);
		return null;
	}
	orProxy.proxify(data);
	var obj = gsjsBridge.create(data);
	if (!rpc.isLocal(obj)) {
		// wrap object in RPC proxy and add it to request cache
		obj = rpc.makeProxy(obj);
		RC.getContext().cache[tsid] = obj;
		metrics.increment('pers.load.remote');
	}
	else {
		// check if object has been loaded in a concurrent request (fiber) in the meantime
		if (tsid in cache) {
			log.warn('%s already loaded, discarding redundant copy', tsid);
			return cache[tsid];
		}
		cache[tsid] = obj;
		// post-construction operations (resume timers/intervals, GSJS onLoad etc.)
		try {
			if (obj.gsOnLoad) obj.gsOnLoad();
		}
		catch (err) {
			log.error(err, 'failed to process onLoad event');
		}
		metrics.increment('pers.load.local');
	}
	metrics.increment('pers.load');
	return obj;
}
Example #8
0
Session.prototype.enqueueMessage = function enqueueMessage(msg) {
	log.trace({data: msg}, 'queueing %s request', msg.type);
	metrics.increment('net.amf.rx', 0.01);
	if (msg.type === 'ping') {
		this.processRequest(msg);
	}
	else {
		var rq = this.pc ? this.pc.getRQ() : RQ.getGlobal('prelogin');
		rq.push(msg.type, this.processRequest.bind(this, msg),
			this.handleAmfReqError.bind(this, msg),
			{session: this, obj: this.pc, timerTag: msg.type});
	}
};
Example #9
0
/**
 * Permanently deletes a game object from persistent storage. Also
 * removes the object from the live object cache.
 *
 * @param {GameObject} obj game object to remove
 * @param {string} logmsg short additional info for log messages
 * @param {function} callback called when delete operation has
 *        finished, or in case of errors
 * @private
 */
function del(obj, logmsg, callback) {
	log.debug('pers.del: %s%s', obj.tsid, logmsg ? ' (' + logmsg + ')' : '');
	metrics.increment('pers.del');
	obj.suspendGsTimers();
	delete cache[obj.tsid];
	pbe.del(obj, function db(err, res) {
		if (err) {
			log.error(err, 'could not delete: %s', obj.tsid);
			metrics.increment('pers.del.fail');
		}
		if (callback) return callback(err, res);
	});
}
Example #10
0
/**
 * Creates a new game object of the given type. Also calls the object's
 * GSJS `onCreate` handler, if there is one.
 *
 * @param {function} modelType the desired game object model type (i.e.
 *        a constructor like `Player` or `Geo`)
 * @param {object} [data] additional properties for the object
 * @param {boolean} [upsert] when `true`, allows replacing existing objects
 * @returns {object} the new object, wrapped in a persistence proxy
 */
function create(modelType, data, upsert) {
	log.debug('pers.create: %s%s', modelType.name,
		_.isObject(data) && data.tsid ? '#' + data.tsid : '');
	data = data || {};
	var obj = gsjsBridge.create(data, modelType);
	if (!upsert) {
		assert(!(obj.tsid in cache), 'object already exists: ' + obj.tsid);
	}
	cache[obj.tsid] = obj;
	RC.getContext().setDirty(obj);
	obj.gsOnCreate();
	metrics.increment('pers.create');
	return obj;
}
Example #11
0
/**
 * Creates a new game object of the given type and adds it to
 * persistence. The returned object is wrapped in a ({@link
 * module:data/persProxy|persProxy}) to make sure all future changes
 * to the object are automatically persisted.
 * Also calls the object's GSJS `onCreate` handler, if there is one.
 *
 * @param {function} modelType the desired game object model type (i.e.
 *        a constructor like `Player` or `Geo`)
 * @param {object} [data] additional properties for the object
 * @returns {object} the new object, wrapped in a persistence proxy
 */
function create(modelType, data) {
	log.debug('pers.create: %s%s', modelType.name,
		(typeof data === 'object' && data.tsid) ? ('#' + data.tsid) : '');
	data = data || {};
	var obj = gsjsBridge.create(data, modelType);
	assert(!(obj.tsid in cache), 'object already exists: ' + obj.tsid);
	obj = persProxy.makeProxy(obj);
	cache[obj.tsid] = obj;
	obj = orProxy.wrap(obj);
	RC.getContext().setDirty(obj, true);
	if (typeof obj.onCreate === 'function') {
		obj.onCreate();
	}
	metrics.increment('pers.create');
	return obj;
}
Example #12
0
/**
 * Sends an RPC request to another game server instance, taking care of
 * proper argument and return value (de)serialization.
 * Returns the result either via callback or synchronously using
 * {@link https://github.com/luciotato/waitfor|wait.for/fibers}.
 *
 * @param {string} gsid ID of the game server to forward the call to
 * @param {string} rpcFunc RPC function to call (must be `obj`, `api`,
 *        `admin` or `gs`)
 * @param {array} args function arguments; the obligatory source GS ID
 *        parameter (required for any RPC function) is prepended here
 * @param {function} [callback]
 * ```
 * callback(err, res)
 * ```
 * called with the function result (`res`) or an error (`err`) when the
 * RPC returns; if not supplied, the function behaves synchronously and
 * returns the result (or throws an exception)
 * @returns {*} result of the remote function call if no callback was
 *          supplied (`undefined` otherwise)
 * @throws {RpcError} in case something bad happens during the RPC and
 *         no callback was supplied
 */
function sendRequest(gsid, rpcFunc, args, callback) {
	assert(gsid !== config.getGsid(), 'RPC to self');
	if (shuttingDown) {
		// hack - see above (preShutdown)
		log.info('shutdown in progress, dropping %s RPC request', rpcFunc);
		return;
	}
	var client = clients[gsid];
	if (!client) {
		var err = new RpcError(util.format('no RPC client found for "%s"', gsid));
		if (callback) return callback(err);
		throw err;
	}
	// argument marshalling (replace objref proxies with actual objrefs)
	args = orProxy.refify(args);
	var logmsg = util.format('%s(%s) @%s', rpcFunc, args.join(', '), gsid);
	log.debug('calling %s', logmsg);
	metrics.increment('net.rpc.tx');
	var rpcArgs = [config.getGsid()].concat(args);
	if (callback) {
		client.request(rpcFunc, rpcArgs, function cb(err, res) {
			log.trace('%s returned', logmsg);
			// wrapping to handle the special case where res is an objref itself
			var wrap = {res: res};
			orProxy.proxify(wrap);
			callback(err, res);
		});
	}
	else {
		try {
			var res = wait.forMethod(client, 'request', rpcFunc, rpcArgs);
			// wrapping to handle the special case where res is an objref itself
			var wrap = {res: res};
			orProxy.proxify(wrap);
			return wrap.res;
		}
		catch (e) {
			throw new RpcError('error calling ' + logmsg, e);
		}
	}
}
Example #13
0
/**
 * Server-side RPC request handler. Executes a function on an object
 * specified by TSID (within a separate request context) and returns
 * the result to the remote caller.
 *
 * @param {string} callerId ID of the component requesting the function
 *        call (for logging)
 * @param {object} obj the object on which a function should be called
 * @param {string|null} tag ID tag of ongoing request process this RPC belongs
 *        to (if any)
 * @param {string} fname name of the function to call on the object
 * @param {array} args function call arguments
 * @param {function} callback
 * ```
 * callback(error, result)
 * ```
 * callback for the RPC library, returning the result (or errors) to
 * the remote caller
 */
function handleRequest(callerId, obj, tag, fname, args, callback) {
	metrics.increment('net.rpc.rx');
	if (!obj || !_.isFunction(obj[fname])) {
		var msg = util.format('no such function: %s.%s', obj, fname);
		return callback(new RpcError(msg));
	}
	orProxy.proxify(args);  // unmarshal arguments
	var logtag = util.format('%s.%s.%s', callerId, obj.tsid ? obj.tsid : obj, fname);
	log.debug('%s(%s)', logtag, _.isArray(args) ? args.join(', ') : args);
	var rpcReq = function rpcReq() {
		var ret = obj[fname].apply(obj, args);
		// convert <undefined> result to <null> so RPC lib produces a valid
		// response (it just omits the <result> property otherwise)
		if (ret === undefined) ret = null;
		return ret;
	};
	var rpcCallback = function rpcCallback(err, res) {
		if (err) {
			log.error(err, 'exception in %s', logtag);
		}
		if (!_.isFunction(callback)) {
			log.error('%s called without a valid callback', logtag);
		}
		else {
			log.trace('%s finished', logtag);
			res = orProxy.refify(res);  // marshal return value
			return callback(err, res);
		}
	};
	try {
		var rq = RQ.getGlobal('rpc');
		if (obj.tsid && isLocal(obj) && _.isFunction(obj.getRQ)) {
			rq = obj.getRQ();
		}
		rq.push(tag, rpcReq, rpcCallback, {waitPers: true});
	}
	catch (err) {
		return rpcCallback(err);
	}
}