gun slinger!

This commit is contained in:
Mark Nadal 2014-09-24 11:08:00 -07:00
parent bfd11b9115
commit cf386e3ced
5 changed files with 248 additions and 67 deletions

View File

@ -1,15 +1,19 @@
console.log("If modules not found, run `npm install` in example/admin folder!"); // git subtree push -P examples/admin heroku master
var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || 8888;
var express = require('express');
var express = require('connect');
var bodyParser = require('body-parser');
var app = express();
var Gun = require('gun');
var gun = Gun({
s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
});
app.use(gun.server)
.use(express.static(__dirname))
app.use(function(req, res, next){
console.log("THIS HIT SEEEERVER", req.url);
next();
})
.use(gun.server)
.use(require('serve-static')(__dirname))
//.use(express.static(__dirname))
app.listen(port);
console.log('Express started on port ' + port + ' with /gun');

View File

@ -29,52 +29,183 @@
.none {
display: none;
}
.fight {
z-index: 9999;
background: brown;
color: white;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.player input, .player button {
font-size: 18pt;
}
</style>
<h2>Gun Duel!</h2>
<span>Old western cowboy style! Two players are needed, whoever can shoot the other first wins!</span>
<span><b>Fastest gun in the west, <span name="fastest">nut'n</span> by <span name="slinger">nobody</span>.</b></span>
<form id="p1" class="assign-player" onsubmit="return false;">
Player 1: <input type="text" name="p1" class="player" placeholder="nickname">
<input type="submit" class="take" value="Join!">
<h2><i>GUNSLINGER</i></h2>
<span>Old West Duel! Two players are needed, whoever can tap the screen first to draw their pistol and shoot wins!</span><br>
<span><b>Fastest gun in the west, <span name="fastest">nut'n</span> seconds, by <span name="slinger">nobody</span>.</b></span>
<form id="p1" class="player" onsubmit="return false;">
Player 1: <input type="text" name="p1" placeholder="nickname"> <button type="submit">Join!</button>
</form>
<form id="p2" class="assign-player" onsubmit="return false;">
Player 2: <input type="text" name="p2" class="player" placeholder="nickname">
<input type="submit" class="take" value="Join!">
<form id="p2" class="player" onsubmit="return false;">
Player 2: <input type="text" name="p2" placeholder="nickname"> <button type="submit">Join!</button>
</form>
<div id="duel">
<div id="duel" class="none fight">
<center>
<h2>GET READY!</h2>
<button id="reset" class="none">
Reset the game!
</button>
</center>
</div>
<span><b>Last duel won by <span name="last">no one</span> in <span name="ended">0</span> seconds against <span name="loser">nobody</span>.</b></span>
<script>
(function(){
var me = {},
gun = Gun(['http://localhost:8888/' + 'gun'])
.load('game/duel', function(game){
console.log(game);
$(document).on('submit', '.assign-player', function(e){
$(function(){
var me = window.me = {},
game = window.game = {},
//gun = window.gun = Gun(['http://localhost:8888/' + 'gun'])
gun = window.gun = Gun(['http://gunduel.t.proxylocal.com/' + 'gun'])
.load('game/duel', function(data){
console.log(data);
$(document).on('submit', '.player', function(e){
e.preventDefault();
var nick = $(this).find('input').val();
if(!nick){ return }
gun.path(this.id).get(function(val){
var nick = $(this).find('input').val(), id = this.id;
if(!nick || me.player){ return }
gun.path(id).get(function(val){
if(val){ return }
gun.path(this.id).set(me.player = nick);
this.set(me.player = nick);
me.took = id;
});
})
Gun.on(game._[Gun.sym.id]).event(function(node){
Gun.on(data._[Gun.sym.id]).event(function(node, local){
if(!node){ return }
console.log("change!", node);
if(node.start){ game.schedule(node.start) }
if(node.dqed){ game.dq(node.dqed) }
if(node.sling){ game.sling(node.sling, local) }
Gun.obj.map(node, game.set);
if(node.p1 || node.p2){
gun.path('p1').get(function(p1){ // start game?
if(!p1){ return }
gun.path('p2').get(function(p2){
if(!p2){ return }
game.play(p2);
})
});
}
game.timeout(true);
});
Gun.obj.map(game, me.set);
Gun.obj.map(data, game.set);
game.timeout(20);
});
me.set = function(val, name){
game.$duel = $("#duel");
game.$duelm = game.$duel.clone();
game.play = function(p2){
if(!me.player){ return }
game.$duel.removeClass("none");
if(me.player == p2){ return }
me.scheduled = (+new Date()) + Math.round(Math.random() * 2000 + 2700); // MIN is the right number, and MAX is the SUM of both numbers.
gun.path('start').set(me.scheduled);
}
game.schedule = function(at){
console.log(" ------------------ START", at);
if(me.started){ return }
me.started = true;
me.scheduled = at;
Gun.schedule(at, function(){
me.shoot = true;
if(me.dqed){ return }
game.$duel.css({background: 'lime'}).find('h2').text("FIRE!");
});
}
game.fire = function(){
if(!me.started || me.fired){ return }
me.fired = (+ new Date());
if(me.fired < me.scheduled){ // DQ
me.dqed = me.player;
gun.path('dqed').set(me.player);
return;
}
gun.path('dqed').get(function(yes){
if(yes){ return }
game.$duel.css({background: 'gold'}).find('h2').text("STOP!");
gun.path('sling').set(me.player);
me.time = (me.fired - me.scheduled) / 1000; // in seconds
});
}
game.sling = function(){
me.count = (me.count || 0) + 1;
if(me.count < 2){ return }
$('#reset').removeClass('none');
gun.path('sling').get(function(sling){
if(!sling){ return }
if(sling == me.player){
game.$duel.css({background: 'red'}).find('h2').text("YOU DIED!!!");
gun.path('loser').set(me.player);
return;
}
game.$duel.css({background: 'skyblue'}).find('h2').text("YOU WON!!!");
if(!me.time || me.fired < me.scheduled || me.time < 0){ return }
gun.path('last').set(me.player);
gun.path('ended').set(me.time);
gun.path('fastest').get(function(time){
if(time <= me.time){ return }
gun.path('fastest').set(me.time);
gun.path('slinger').set(me.player);
});
});
}
game.dq = function(who){
$('#reset').removeClass('none');
if(who == me.player){
return game.$duel.css({background: 'gray'}).find('h2').text("DISQUALIFIED!!!");
}
if(me.dqed){
return game.$duel.find('h2').text("BOTH DISQUALIFIED!");
}
game.$duel.css({background: 'skyblue'}).find('h2').text("YOU WON!!!");
}
game.clear = function(){
me = {};
game.$duel.replaceWith(game.$duel = game.$duelm.clone());
gun.path('p1').set("");
gun.path('p2').set("");
gun.path('sling').set(null);
gun.path('start').set(null);
gun.path('dq').set(null);
game.$duel.addClass('none');
}
game.timeout = function(wait){
if(true === wait){ return clearTimeout(me.timeout) }
wait = wait || 15;
clearTimeout(me.timeout);
me.timeout = setTimeout(game.clear, wait * 1000);
}
game.set = function(val, name){
if(val == game.nothing){ return }
$("[name='" + name + "']").text(val).val(val);
Gun.on("duel-" + name).emit(val, name);
}
me.plock = function(val, id){
console.log("OH?", val, id);
$("#" + id).find('.player').attr("readonly", val? true : false);
$("#" + id).find('.take').val(val? "Taken!" : "Join!").attr("disabled", val? true : false);
game.plock = function(val, id){
$("#" + id).find('input').attr("readonly", val? true : false);
$("#" + id).find('button').text(val? "Taken!" : "Join!").attr("disabled", val? true : false);
}
Gun.on("duel-p1").event(me.plock);
Gun.on("duel-p2").event(me.plock);
Gun.on("duel-p1").event(game.plock);
Gun.on("duel-p2").event(game.plock);
$(document).on('click', '#duel', game.fire);
$(document).on('click', '#reset', game.clear);
}());
</script>
<script>
(function(){
return;
var game = Gun(location + 'gun').load('game/duel');
game.on('change').path('p1').or.path('p2').get(function(val){
$("#" + this.field).find('input').val(val? "Taken!" : "Join!").attr("disabled", val? true : false);
$("#" + this.field).find('button').val(val? "Taken!" : "Join!").attr("disabled", val? true : false);
});
}());
</script>
</body>

79
gun.js
View File

@ -95,8 +95,8 @@
}
Gun.chain.path = function(path){ // The focal point follows the path
var gun = this.chain();
path = path.split('.');
Gun.log("PATH stack trace", gun._.events.trace + 1, 'was it before loaded?', this._);
path = (path||'').split('.');
Gun.log("PATH stack trace", path, gun._.events.trace + 1, 'was it before loaded?', this._);
gun._.events.on(gun._.events.trace += 1).event(function trace(node){
Gun.log("stack at", gun._.events.at);
if(!path.length){ // if the path resolves to another node, we finish here
@ -131,9 +131,9 @@
gun._.events.on(gun._.events.trace += 1).event(function(node){
console.log("BOOM got it", node);
if(gun._.field){
return cb((node||{})[gun._.field]); // copy data first?
return cb.call(gun, (node||{})[gun._.field]); // copy data first?
}
cb(Gun.obj.copy(node)); // we do here.
cb.call(gun, Gun.obj.copy(node)); // we do here.
});
if(gun._.loaded){
gun._.events.at -= 1; // IDK why we are doing this, just trying to get something to work.
@ -428,7 +428,7 @@
var serverState = Gun.time.is();
// add more checks?
var state = HAM(serverState, deltaStates[field], states[field], deltaValue, current[field]);
// Gun.log("HAM:", field, deltaValue, deltaStates[field], current[field], 'the', state, (deltaStates[field] - serverState));
//console.log("HAM:", field, deltaValue, deltaStates[field], current[field], 'the', state, (deltaStates[field] - serverState));
if(state.err){
Gun.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", state.err);
return;
@ -615,7 +615,7 @@
}
}());
Gun.log = function(a, b, c, d, e, f){ //s, l){
console.log(a, b, c, d, e, f);
//console.log(a, b, c, d, e, f);
//console.log.apply(console, arguments);
}
own.sym = Gun.sym = {
@ -637,7 +637,7 @@
tab.server = tab.server || function(req, res, next){
}
window.tab = tab; //window.XMLHttpRequest = null; // for debugging purposes
window.tab = tab; // window.XMLHttpRequest = null; // for debugging purposes
(function(){
tab.store = {};
var store = window.localStorage || {setItem: function(){}, removeItem: function(){}, getItem: function(){}};
@ -659,7 +659,7 @@
}
(function(){
tab.subscribe.sub = (reply.headers || {})['gun-sub'];
tab.subscribe.sub = (reply.headers || {})['gun-sub'] || tab.subscribe.sub;
//console.log("We are sub", tab.subscribe.sub);
var data = reply.body;
if(!data || !data._){ return }
@ -668,23 +668,44 @@
}, {headers: {'Gun-Sub': tab.subscribe.sub || ''}, header: {'Gun-Sub': 1}});
});
}
tab.url = function(nodes){
return;
console.log("urlify delta", nodes);
var s = ''
, uri = encodeURIComponent;
Gun.obj.map(nodes, function(delta, id){
var ham;
if(!delta || !delta._ || !(ham = delta._[Gun.sym.HAM])){ return }
s += uri('#') + '=' + uri(id) + '&';
Gun.obj.map(delta, function(val, field){
if(field === Gun.sym.meta){ return }
s += uri(field) + '=' + uri(Gun.text.ify(val)) + uri('>') + uri(ham[field]) + '&';
})
});
console.log(s);
return s;
}
tab.set = tab.set || function(nodes, cb){
cb = cb || function(){};
// TODO: batch and throttle later.
tab.store.set(respond.id = 'send/' + Gun.text.random(), nodes);
//console.log("localStorage the DELTA", nodes);
tab.store.set(cb.id = 'send/' + Gun.text.random(), nodes);
//tab.url(nodes);
Gun.obj.map(gun.__.opt.peers, function(peer, url){
tab.ajax(url, nodes, respond, {headers: {'Gun-Sub': tab.subscribe.sub || ''}});
});
function respond(err, reply, id){
if(reply && reply.body){
if(reply.body.defer){
tab.set.defer[reply.body.defer] = respond;
tab.ajax(url, nodes, function respond(err, reply, id){
var body = reply && reply.body;
respond.id = respond.id || cb.id;
Gun.obj.del(tab.set.defer, id); // handle err with a retry? Or make a system auto-do it?
if(!body){ return }
if(body.defer){
console.log("deferring post", body.defer);
tab.set.defer[body.defer] = respond;
}
if(reply.body.refed || reply.body.reply){
//console.log("-------post-reply-all--------->", reply, err);
respond(null, {headers: reply.headers, body: reply});
Gun.obj.map(reply.body.refed, function(r, id){
if(body.reply){
respond(null, {headers: reply.headers, body: body.reply });
}
if(body.refed){
console.log("-------post-reply-all--------->", reply, err);
Gun.obj.map(body.refed, function(r, id){
var cb;
if(cb = tab.set.defer[id]){
cb(null, {headers: reply.headers, body: r}, id);
@ -693,11 +714,13 @@
// TODO: should be able to do some type of "checksum" that every request cleared, and if not, figure out what is wrong/wait for finish.
return;
}
console.log('callback complete, now respond', respond.id);
if(body.reply || body.defer || body.refed){ return }
tab.store.del(respond.id);
}
Gun.obj.del(tab.set.defer, id);
}
}, {headers: {'Gun-Sub': tab.subscribe.sub || ''}});
});
Gun.obj.map(nodes, function(node, id){
Gun.on(id).emit(node, true); // TODO: Temporary hack, I want to rebroadcast back to ourselves. IDK if this is always useful, and we shouldn't use global.
});
}
tab.set.defer = {};
tab.subscribe = function(id){ // TODO: BUG!!! ERROR! Handle disconnection (onerror)!!!!
@ -711,10 +734,14 @@
'Gun-Sub': tab.subscribe.sub || ''
}
}, query = tab.subscribe.sub? '' : tab.subscribe.query(tab.subscribe.to);
console.log("subscribing poll", tab.subscribe.sub);
Gun.obj.map(gun.__.opt.peers, function(peer, url){
tab.ajax(url + query, null, function(err, reply){
//console.log("poll", err, reply);
if(!reply || !reply.body){ return } // not interested in any null/0/''/undefined values
if(err || !reply || !reply.body || reply.body.err){ // not interested in any null/0/''/undefined values
console.log(err, reply);
return;
}
console.log("poll", reply);
tab.subscribe.poll();
if(reply.headers){
tab.subscribe.sub = reply.headers['gun-sub'] || tab.subscribe.sub;

View File

@ -1,5 +1,5 @@
{ "name": "gun"
, "version": "0.0.6"
, "version": "0.0.6a"
, "author": "Mark Nadal"
, "description": "Graph engine."
, "engines": {

View File

@ -5,7 +5,8 @@
, url = require('url')
, meta = {};
Gun.on('opt').event(function(gun, opt){
gun.server = gun.server || function(req, res, next){ // this whole function needs refactoring and modularization
gun.server = gun.server || function(req, res, next){ // this whole function needs refactoring and modularization
//console.log("\n\n GUN SERVER!");
next = next || function(){};
if(!req || !res){ return next() }
if(!req.url){ return next() }
@ -66,6 +67,7 @@
s3.prenode = s3.prenode || opt.s3.prenode || '_/nodes/';
gun.__.opt.batch = opt.batch || gun.__.opt.batch || 10;
gun.__.opt.throttle = opt.throttle || gun.__.opt.throttle || 15;
gun.__.opt.disconnect = opt.disconnect || gun.__.opt.disconnect || 5;
if(!gun.__.opt.keepMaxSockets){ require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = Infinity } // WARNING: Document this!
s3.load = s3.load || function(key, cb, opt){
@ -160,11 +162,12 @@
gun.server.transport = (function(){
function tran(req, cb){
//console.log("\n\n\n", req);
//console.log(req);
req.sub = req.headers['gun-sub']; // grab the sub
req.tab = tran.sub.s[req.sub] || {}; // check to see if there already is a tab associated with it, or create one
req.tab.sub = req.sub = req.sub || Gun.text.random(); // Generate a session id if we don't already have one
req.tran = tran.xhr(req, cb) || tran.jsonp(req, cb); // polyfill transport layer
clearTimeout(req.tab.timeout);
// raw test for now, no auth:
if(!req.tran){ return cb({headers: {"Content-Type": tran.json}, body: {err: "No transport layer!"}}) }
if('post' === req.method || 'patch' === req.method){ return tran.post(req, req.tran) } // TODO: Handle JSONP emulated POST via GET
@ -204,7 +207,7 @@
if(context.err){ return cb({body: {err: context.err}}) }
// WARNING! TODO: BUG! Do not send OK confirmation if amnesiaQuaratine is activated! Not until after it has actually been processed!!!
if(Gun.fns.is(gun.__.opt.hooks.set)){
gun.__.opt.hooks.set(context.nodes, function(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved
gun.__.opt.hooks.set(context.nodes, function saved(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved
var body = {};
if(err){
body.err = err ;
@ -217,6 +220,7 @@
}
var now = tran.post.s[req.sub]; // begin our stupid Chrome fix, we should abstract this out into defer (where it belogns) to keep things clean.
if(!now){ return } // utoh we've lost our reply to the tab!
clearTimeout(now.timeout);
now.body = now.body || {}; // make sure we have a body for our multi-response in a single response.
if(req.wait){ // did this request get deferred?
(now.body.refed = now.body.refed || {})[req.wait] = err? {err: err} : defer.map({}, context.nodes, 1); // then reply to it "here".
@ -224,7 +228,7 @@
now.body.reply = err? {err: err} : defer.map({}, context.nodes, 1); // else this is the original POST that had to be upgraded.
}
if(0 < (now.count = ((now.count || 0) - 1))){ // Don't reply till all deferred POSTs have successfully heard back from S3. (Sarcasm: Like counting guarantees that)
return; // TODO: BUG!!! Memory leak, we have no guarantee we'll ever get a reply! So time it out, maybe some multiple of the S3 throttle.
return now.timeout = setTimeout(saved, gun.__.opt.throttle * 2 * 1000); // reply not guaranteed, so time it out, in seconds.
}
if(Gun.fns.is(now)){
now({body: now.body}); // FINALLY reply for ALL the POSTs for that session that accumulated.
@ -278,6 +282,7 @@
//console.log("<-- ", req.sub, req.tran ," -->");
req.tab = tran.sub.s[req.sub];
if(!req.tab){
console.log(req.url.query);
cb({
headers: {'Gun-Sub': ''}
,body: {err: "Please re-initialize sub."}
@ -286,6 +291,7 @@
}
//console.log("\n\n\n THE CURRENT STATUS IS");console.log(req.tab);
if(req.tab.queue && req.tab.queue.length){
tran.clean(req.tab); // We flush their data now, if they don't come back for more within timeout, we remove their session
console.log("_____ NOW PUSHING YOUR DATA ______", req.sub);
cb({ headers: {'Gun-Sub': req.sub} });
while(1 < req.tab.queue.length){
@ -299,7 +305,18 @@
}
}
tran.sub.s = {};
tran.sub.scribe = function(tab, id){ // TODO: BUG!!! Memory leaks, remember to destroy sessions via timeout.
tran.clean = function(tab, mult){
if(!tab){ return }
mult = mult || 1;
clearTimeout(tab.timeout);
tab.timeout = setTimeout(function(){
if(!tab){ return }
if(tab.reply){ tab.reply({body: {err: "Connection timed out"}}) }
console.log("!!!! DISCONNECTING CLIENT !!!!!", tab.sub);
Gun.obj.del(tran.sub.s, tab.sub)
}, gun.__.opt.disconnect * mult * 1000); // in seconds
}
tran.sub.scribe = function(tab, id){
tran.sub.s[tab.sub] = tab;
tab.subs = tab.subs || {};
tab.subs[id] = tab.subs[id] || tran.push.on(id).event(function(req){
@ -307,7 +324,8 @@
if(!tab){ return this.off() } // resolve any dangling callbacks
req.sub = req.sub || req.headers['gun-sub'];
if(req.sub === tab.sub){ return } // do not send back to the tab that sent it
console.log('FROM:', req.sub, "TO:", tab);
console.log('FROM:', req.sub, "TO:", tab.sub);
tran.clean(tab);
if(tab.reply){
tab.reply({
headers: {'Gun-Sub': tab.sub}
@ -318,6 +336,7 @@
}
(tab.queue = tab.queue || []).push(req.body);
});
tran.clean(tab, 2);
}
tran.xhr = function(req, cb){ // Streaming Long Polling
return req.tran || (req.headers['x-requested-with'] === 'XMLHttpRequest'? transport : null);