Reputation: 121
I'm developing a competitive turn based game. When an anonymous visitor visits a page, he is automatically subscribing to a non-full instance of the game he can then either just observe or join the action. There are limited number of spots in each game instance. When joining the game (taking up a spot), the old subscription is stopped and new one is created that also includes private information based on his chosen spot. So far so good.
Now I want to make the server free up a spot whenever a player doesn't complete his turn in reasonable amount of time.
The question is how can I make sure the player kicked out of his spot no longer receives updates that are now intended to go to someone else occupying his spot? Obviously this has to happen on server side as clients cannot be trusted. Ideally the kicked out user would seamlessly become the observer again.
I know there is a method stop() that can be called inside publish(), but how to use it to stop a published subscription for one particular client when a callback set by Meteor.setTimeout() gets called on the server?
Some heavily modified code of what I'm trying to do (not meant to work, only to give you an idea)
if Meteor.isClient
publicGameHandle = Meteor.subscribe 'GameInstances'
join = (gameInstanceId, spot) ->
Meteor.call "join", gameInstanceId, spot, (err, guestId) ->
Session.set("guestId", guestId)
privateGameHandle = Meteor.subscribe 'GameInstances',
gameInstanceId, spot, guestId, ->
publicGameHandle.stop()
if Meteor.isServer
privateSubscriptions = {}
Meteor.publish 'GameInstances', (gameInstanceId, spot, guestId) ->
if gameInstanceId
GameInstances.find {_id: gameInstanceId}
privateSubscriptions[guestId] = @
else
secretFields = {spots.guestId:false, spots.privateGameInfo:false}
GameInstances.find {openSpots: {$gt: 0}}, {fields: secretFields}
Meteor.methods({
join: (gameInstanceId, spot) ->
guestId = Random.id()
gameInstances[gameInstanceId].addPlayer(spot, guestId)
guestId
completePlayerTurn: (gameInstanceId, spot, guestId) ->
gameInstance = gameInstances[gameInstanceId]
Meteor.clearTimeout(gameInstance.timer)
nextPlayer = gameInstance.getNextPlayer()
kick = () ->
privateSubcriptions[nextPlayer.guestId].stop()
gameInstance.removePlayer(nextPlayer.guestId)
gameinstance.timer = Meteor.setTimeout(kick, 60000)
Upvotes: 1
Views: 1047
Reputation: 9779
Create two separate subscriptions that both return game data. One subscription should return public game data, the other should return game data that is viewable by the currentUser. They will be merged into one collection on the client side. Games the user has joined will contain private info AND public info, games they haven't joined will only contain public info. Then on the client side, a liveQuery like Game.findOne(thisGame.id)
will automatically receive the private information when the player joins the game and cause your template to be re-rendered, etc.
Pseudo-code for illustrative purposes:
if Meteor.isClient
Meteor.subscribe 'GameInstances'
Meteor.subscribe 'MyGameInstances', guestId
join = (gameInstanceId, spot) ->
Meteor.call "join", gameInstanceId, spot, (err, guestId) ->
Session.set("guestId", guestId)
if Meteor.isServer
Meteor.publish 'GameInstances', () ->
secretFields = {spots.guestId:false, spots.privateGameInfo:false}
GameInstances.find {openSpots: {$gt: 0}}, {fields: secretFields}
Meteor.publish 'MyGameInstances', (guestId) ->
GameInstances.find {'spots.guestId': guestId}, {fields: secretFields}
Meteor.methods({
join: (gameInstanceId, spot) ->
guestId = Random.id()
gameInstances[gameInstanceId].addPlayer(spot, guestId)
guestId
completePlayerTurn: (gameInstanceId, spot, guestId) ->
gameInstance = gameInstances[gameInstanceId]
Meteor.clearTimeout(gameInstance.timer)
nextPlayer = gameInstance.getNextPlayer()
kick = () ->
gameInstance.removePlayer(nextPlayer.guestId)
gameinstance.timer = Meteor.setTimeout(kick, 60000)
It sounds like you might be including many players private info on one Game Instance. So that will be harder to filter the fields that are allowed (and only publish the CURRENT guest's private info, without publishing other players' private info).
In that case, you might consider either:
https://gist.github.com/colllin/8321227
In this code, I'm observing one collection and publishing data that will be merged into the Meteor.users
collection. You can see that the subscription is publishing data like {_id: userId, wins: 25}
, which makes a wins
field available on user models on the client side. You will probably want to observe()
the Games collection but manually filter the private data based on the current user, then publish that private data into the Games collection to be merged with the public data. You can pass in the guestId
to the publish function like you did above.
Upvotes: 1