Sign In Register

How can we help you today?

Start a new topic
Answered

Concurrency / Locking

I want to implement a simple energy system: Once the player consumes the first point of energy, a timer starts that will restore a point of energy every minute, until the player's energy is full again. I wanted to implement this using the Scheduler:


  This is the function to consume a point of energy: 

function consumeEnergy(characterId, amount) {
    var playerId = Spark.getPlayer().getPlayerId();
    var character = Spark.runtimeCollection("PlayerCharacters").findOne( { playerId: playerId, characterId: characterId } );
    if (character.currentEnergy === character.maxEnergy) {
        Spark.getScheduler().inSeconds("RestoreEnergy", 60, { playerId: playerId, characterId: characterId });
    }
    character.currentEnergy -= amount;
    Spark.runtimeCollection("PlayerCharacters").save(character);
}

   This is the RestoreEnergy module:

var data = Spark.getData();
var character = Spark.runtimeCollection("PlayerCharacters").findOne( { playerId: data.playerId, characterId: data.characterId } );
if (character.currentEnergy < character.maxEnergy) {
    character.currentEnergy += 1;
    Spark.runtimeCollection("PlayerCharacters").save(character);
}
if (character.currentEnergy < character.maxEnergy) {
    Spark.getScheduler().inSeconds("RestoreEnergy", 60, data);
}


The problem with this is concurrency. If the scheduled RestoreEnergy module is triggered at the same time as the consumeEnergy function, they may get into a situation where both decide that there is no need to schedule the next Restore because they think the other one will do this, thereby completely stopping the energy restoration. Or they both schedule a Restore, doubling the restoration rate.


To fix this, I would need some kind of mutex. From what I could find, GameSparks supports locks only for challenges. What is the recommended way to solve this?


Best Answer

Hi Benjamin


In this case I would use mongos atomic findAndModify .


var updatedPlayer = Spark.runtimeCollection("PlayerCharacters").findAndModify( 

    { playerId: data.playerId, characterId: data.characterId, currentEnergy : {"$lt" : maxEnergy} }, 

    { $inc: { quantity: 1 } }

);


In this instance, updatedPlayer will be null if they are already at maxEnergy, so you will know not to schedule another increment.


If an object is returned, then you know they are less than maxEnergy, so you should schedule another.


Does this make sense?


Gabriel







Answer

Hi Benjamin


In this case I would use mongos atomic findAndModify .


var updatedPlayer = Spark.runtimeCollection("PlayerCharacters").findAndModify( 

    { playerId: data.playerId, characterId: data.characterId, currentEnergy : {"$lt" : maxEnergy} }, 

    { $inc: { quantity: 1 } }

);


In this instance, updatedPlayer will be null if they are already at maxEnergy, so you will know not to schedule another increment.


If an object is returned, then you know they are less than maxEnergy, so you should schedule another.


Does this make sense?


Gabriel






In our app, we don't rely on the scheduler to support life (or energy) regeneration. We synchronize the player life property when we need it based on a "nextLife" timestamp property and the current time. This way we don't overload the system with scheduled tasks. The cloud code and the client code must use the same algorithm. The implementation is more complicated but there is no concurrency issue.

The "nextLife" property is kept in a "PlayerLife" collection. There is one document per player. I can give you more implementation details if you need to.

Christian,
I'd personally be quite interested in hearing more details about your implementation, if it's not too much trouble. I've just started switching over to GameSparks from Parse, which allowed me to store DateTime objects as they were, at which point I could simply calculate the difference between the stored "then" values and DateTime.Now. On the GameSparks side of things, I was considering creating a Event and simply storing the string value of the DateTime, then converting it back and comparing the results, but I'm not sure just how efficient or elegant that is and would be grateful to hear how you've done this. For my part, I'm setting up a simple "Lives" timer, where the player's lives take 30 minutes to renew. Nothing fancy :) Thanks!

 

 Kenneth , I switched from Parse to GameSparks too.below code show the logic and implementation.I have a function called "Sync" in the server side and I call it every minute , (because I update the energy that I call bullets in my game every 60 seconds that you can change it as you wish). Gift energy must separate from energy that player buy within the game.In my game maximum energy is 25 and the first time player open the game (which giftPistolBullets is null) is also 25.

 

var lastGiftPistolBulletsPresentTime = matchUser.get("lastGiftPistolBulletsPresentTime");
if(lastGiftPistolBulletsPresentTime==null)
lastGiftPistolBulletsPresentTime==0;

var giftPistolBullets = matchUser.get("giftPistolBullets");
var finalGiftBulletCount = 0;
var currentTime = Math.floor(Date.now() / 1000);
if(giftPistolBullets==null)
finalGiftBulletCount=25;
else{
var passedTimeFromLastGiftPresent = currentTime - lastGiftPistolBulletsPresentTime;
var mustPresentGiftsCount = passedTimeFromLastGiftPresent / 60;
finalGiftBulletCount = giftPistolBullets + mustPresentGiftsCount;
}
if(finalGiftBulletCount>25)
finalGiftBulletCount = 25;

 

Login to post a comment