Skip to content

Instantly share code, notes, and snippets.

@Morabaraba
Created March 12, 2017 07:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Morabaraba/e0ec88c68d10a7c3684e5a1cdda6fc47 to your computer and use it in GitHub Desktop.
Save Morabaraba/e0ec88c68d10a7c3684e5a1cdda6fc47 to your computer and use it in GitHub Desktop.
QICI Engine Message Queue Plugin
/**
* mqclient awake the MQ Plugin.
* You atleast need one attached to a node.
*/
var mqclient = qc.defineBehaviour('qc.engine.mqclient', qc.Behaviour, function() {
// need this behaviour be scheduled in editor
//this.runInEditor = true;
this.hostname = 'localhost';
this.port = 8083;
this.path = '/mqtt';
this.userName = 'guest';
this.password = 'guest';
this.rootTopic = 'mqgameengine/level1';
this.singleton = true;
}, {
hostname: qc.Serializer.STRING,
port: qc.Serializer.INT,
userName: qc.Serializer.STRING,
password: qc.Serializer.STRING,
path: qc.Serializer.STRING,
rootTopic: qc.Serializer.STRING,
singleton: qc.Serializer.BOOLEAN,
});
// Called when the script instance is being loaded.
mqclient.prototype.awake = function() {
if (this.singleton) {
// check in plugins
function findmq(plugin) {
return plugin instanceof PluginMQ;
}
var firstClient = this.game.plugins.plugins.find(findmq);
if (firstClient) {
this.plugin = firstClient;
return;
}
}
var mqclientConfig = this.game.storage.get('mqclientConfig');
if (mqclientConfig) {
this.port = Number(mqclientConfig.port) || this.port;
this.hostname = mqclientConfig.hostname || this.hostname;
this.path = mqclientConfig.path || this.path;
this.rootTopic = mqclientConfig.rootTopic || this.rootTopic;
}
//change client id HACK TODO
this.clientId = 'clientId-Anon' + this.game.phaser.rnd.integer();
// TODO can I have multiple plugins of the same constructor
this.plugin = this.game.plugins.add(PluginMQ, this);
};
/**
* `PluginMQ` is a [QICI plugin](http://docs.qiciengine.com/api/plugin/index.html).
* You will not directly use this, instead add a `qc.engine.mqclient` to a node and use the `qc.engine.mqbehaviour`.
* If you add a `qc.engine.mqbehaviour` to a node remember to add `qc.engine.mqhandler` or your own behaviour to process events.
*
* @constructor
*/
var PluginMQ = function(game, parent) {};
PluginMQ.prototype.constructor = qc.NodeScheduler;
PluginMQ.prototype.utils = {};
PluginMQ.prototype.init = function(behaviour) {
// console.log('init', arguments);
var self = this;
self.rootTopic = behaviour.rootTopic;
self.messages = [];
/** hash of other clients on the channel */
self.clients = {};
/* all the objects we want to sync */
self.sync = {}
var client = new Paho.MQTT.Client(
behaviour.hostname,
behaviour.port,
behaviour.path,
behaviour.clientId);
self.client = client;
// hook our mq callbacks
// HACK to pass self/this to our callbacks instead of `client` as `this`, any better way TODO this?
client.onConnectionLost = function(responseObject) {
self.onConnectionLost.apply(self, [responseObject]);
};
client.onMessageArrived = function(message) {
self.onMessageArrived.apply(self, [message]);
};
var mqttConnectOptions = {
// hook up our chat onConnect and onFailure functions to our mqtt connection options
// HACK for `self`
onSuccess: function() {
self.onConnect.apply(self);
},
onFailure: function() {
self.onFailure.apply(self);
},
//useSSL: false, // atleast it is encrypted
//userName: this.userName, // I know this is insecure
//password: this.password,
keepAliveInterval: 30 // check every 30 sec if we are still allive
};
client.connect(mqttConnectOptions);
self.mqttConnectOptions = mqttConnectOptions;
};
PluginMQ.prototype.findNode = function(node) {
if (node.data.uniqueName && node.data.uniqueName[1]) {
uniqueName = node.data.uniqueName[1];
//console.warn('finding uniqueName node ', uniqueName);
return this.game.nodePool.findByName(uniqueName);
}
else {
//console.warn('finding uuid node ', node.data.uuid);
return this.game.nodePool.find(node.data.uuid);
}
}
PluginMQ.prototype.preUpdate = function() {
var data = this.messages.pop();
var game = this.game;
while (data) {
if (data.rpc) {
// this[data.rpc](data); # YOLO
switch (data.rpc) {
case 'registerClient':
this.registerClientEvent(data);
break;
case 'requestSyncAvail':
this.requestSyncAvailEvent(data);
break;
case 'requestSyncSync':
this.requestSyncSyncEvent(data);
break;
case 'requestSyncData':
this.requestSyncDataEvent(data);
break;
default:
console.error('Unknown rpc call', data.rpc);
}
// finished with rpc, lets do the next msg
data = this.messages.pop();
continue;
}
var sprite = data.args[0];
if (sprite.data) {
var e = data.args[1];
var uuid = sprite.data.uuid;
var uniqueName;
var gameObj = this.findNode(sprite);
}
if (gameObj) {
var behaviour = gameObj.getScript('qc.engine.mqbehaviour');
var eventBehaviour = gameObj.getScript(behaviour.eventBehaviour);
if (!eventBehaviour) {
console.error('Could not find eventBehaviour', behaviour.eventBehaviour, gameObj);
return;
}
eventBehaviour.preUpdateEvent(gameObj, sprite, e, data);
}
else {
console.error('gameObj not found ', data);
}
data = this.messages.pop();
}
};
PluginMQ.prototype.registerClient = function() {
this.send({rpc: 'registerClient', clientId: this.client.clientId});
};
PluginMQ.prototype.registerClientEvent = function(data) {
// if you scream in a open room your gonna hear echos
if (this.client.clientId === data.clientId) { return; };
this.clients[data.clientId] = data;
// register ourself with the new client
// TODO this would be a good time to rebuild client list
this.registerClient();
};
//PluginMQ.prototype.deregisterClient = function() {};
PluginMQ.prototype.requestSync = function() {
var uuid = this.game.math.uuid();
this.requestSyncUuid = uuid;
this.send({rpc: 'requestSync', clientId: this.client.clientId, uuid: uuid});
};
PluginMQ.prototype.requestSyncEvent = function(data) {
if (this.client.clientId === data.clientId) { return; };
this.send({rpc: 'requestSyncAvail',
toClientId: data.clientId,
clientId: this.client.clientId,
uuid: data.uuid,
}, {topic: this.rootTopic + '/' + data.clientId});
};
PluginMQ.prototype.requestSyncAvailEvent = function(data) {
if (this.client.clientId === data.clientId) { return; };
if (this.waitingSync) return;
this.waitingSync = data.uuid;
this.send({rpc: 'requestSyncSync', clientId: this.client.clientId, uuid: data.uuid}, {topic: this.rootTopic + '/' + data.clientId});
};
PluginMQ.prototype.requestSyncSyncEvent = function(data) {
if (this.client.clientId === data.clientId) { return; };
var list = [];
this.sync.forEach(function(node) {
var context = {};
var json = self.game.serializer.buildBundle(arg, context);
json.dependences = self.game.serializer.combineDependence(context);
list.push(json);
})
this.send({rpc: 'requestSyncData', sync: list}, {topic: this.rootTopic + '/' + data.clientId});
};
PluginMQ.prototype.utils.getObjNodePos = function(gameObject, node) {
var meta = this.gameObject.getMeta();
// HACK a phaser object to get _anchoredX and _anchoredY from qici bundle data
var p = {
phaser: {},
setWidth: function() {},
setHeight: function() {},
relayout: function() {}
};
meta.position.set(p, node.data.position);
return p;
}
PluginMQ.prototype.requestSyncDataEvent = function(data) {
if (this.client.clientId === data.clientId) { return; };
data.sync.forEach(function(json) {
var gameObj = this.findNode(json);
var p = this.utils.getObjNodePos(gameObj, json);
gameObj.x = p._anchoredX;
gameObj.y = p._anchoredY;
});
};
//PluginMQ.prototype.availSync = function() {};
PluginMQ.prototype.send = function(payload, opts) {
opts = opts || {};
var encoded = msgpack.encode(payload);
// console.debug('encoded length', encoded.length);
var message = new Paho.MQTT.Message(encoded);
message.qos = opts.qos || 0;
message.destinationName = opts.topic || this.rootTopic;
// console.debug('Sending Message', message);
// this.game.log.trace('Send Message to topic ' + message.destinationName + ' payload length ' + message.payloadBytes.length);
this.client.send(message);
}
// called on succesful connect
PluginMQ.prototype.onConnect = function() {
this.client.subscribe(this.rootTopic + '/' + this.client.clientId);
this.client.subscribe(this.rootTopic);
console.debug('Connected, register and syncing');
this.registerClient();
this.requestSync();
}
// called when client connect fail
PluginMQ.prototype.onFailure = function() {
console.error('Connection Failed', arguments, this);
}
/** @memberof! chat# */
// called when the client loses its connection
PluginMQ.prototype.onConnectionLost = function(responseObject) {
delete this.mqttConnectOptions.mqttVersion;
delete this.mqttConnectOptions.mqttVersionExplicit;
// console.error('onConnectionLost', responseObject, this, this.mqttConnectOptions);
this.client.connect(this.mqttConnectOptions);
}
PluginMQ.prototype.onMessageArrived = function(message) {
var data = msgpack.decode(message.payloadBytes);
// console.debug('onMessageArrived decoded payload', data);
this.messages.push(data);
}
/*
PluginMQ.prototype.update = function() {
console.log('update');
};
PluginMQ.prototype.postUpdate = function() {
console.log('postUpdate');
};
PluginMQ.prototype.render = function() {
console.log('render');
};
PluginMQ.prototype.postRender = function() {
console.log('postRender');
};
PluginMQ.prototype.destroy = function() {
console.log('destroy');
};
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment