0
\$\begingroup\$

I am trying to create a world boss system using PHP. But I am having a problem with how to account for all the damage while updating the health of the boss in real-time.

The current damage process is as follows:

  1. Get boss' current HP from database.
  2. Reduce the HP based on the calculated damage.
  3. Update the boss' HP to database.

Example:

  • Player 1 (P1) attacks the boss. Calculated damage is 10.
  • The boss' current HP is saved to variable $boss_hp which is 100.
  • It will now be $boss_hp -= 10. Which is 90.
  • The boss' HP in database is then updated.

Now, imagine two player attack the boss simultaneously...

  • P1 attacks the boss. Calculated damage is 10.
  • The boss' current HP is saved to variable $boss_hp which is 100.
  • It will now be $boss_hp -= 10. Which is 90.
  • Before P1 updates the boss_hp in database, P2 also damages the boss.
  • P2 saved the boss' current HP to his/her own $boss_hp variable which is 100 at the moment.
  • P2 also performs step 2. $boss_hp -= 10. Which is also 90.
  • P1 now updates the boss_hp in database (=90).
  • P2 also updates the boss_hp in database (=90).

The boss_hp should be 80 but the damage of P1 has been neglected since P2 updates the boss_hp last.

How to avoid this problem?

\$\endgroup\$
  • 1
    \$\begingroup\$ A naive approch could be to insert the damage dealt to the DB and the simply sum up all damage and subsctract this from the boss hp. A far better approch would be to send dmg infos to a central instance (here you can bring everything into order, verify that the damage could be dealt and the calculate the hp. \$\endgroup\$ – user743414 Nov 14 at 12:46
  • 1
    \$\begingroup\$ This is called a race condition. Read more about it for example here stackoverflow.com/questions/34510/what-is-a-race-condition and you can also find suggested solutions/workarounds by reading elsewhere. \$\endgroup\$ – user985366 Nov 16 at 21:39
  • \$\begingroup\$ Does your database support locking rows/records? Or even better, does your database support arithmetic operations inside the update statements? Many databases have built-in protections against race conditions (by having atomic operations). With a correctly written update statement you could leverage that functionality of the database. \$\endgroup\$ – Romen Nov 22 at 22:30
1
\$\begingroup\$

You could either use "SET boss_hp = boss_hp - 10" directly in a MySQL query if your damage calculation is simple enough to be expressed by a simple subtraction. Or look into some of the locking statements supported by MySQL https://dev.mysql.com/doc/refman/8.0/en/sql-syntax-transactions.html

Edit: Sorry, I can see transactions does not handle locking the same way I thought. Here is an example of transactions http://www.mysqltutorial.org/php-mysql-transaction/ your implementation could look something like:

while(true)
  start_transaction(read_write)
  $cached_hp = select hp from boss
  // calc hp after hit
  try
    // verify that boss has not been changed
    select boss where hp = $cached_hp
  catch
    rollback;
    // retry this again
    continue;
  update boss hp -= damage
  commit;
  break;

The $cached_hp will not be guaranteed until the commit; But you are guranteed that all three queries will be executed atomic, so by verifying that the $cached_hp is still equal to the boss hp in database, you guarantee that no hit will be accepted while another is processed.

EditEdit: Yes this means that if it fail it will retry the hit calculations. If this happens too often, you should probably switch to an event driven system instead.

(Personally I would do these kind of calculations from a server supporting multiple connections to a single instance (nodejs is easy to get started with), and only use php as the presentsation layer for the client, sending damage events to the backend server, and let the backend handle the synchronization. I am not sure how easy this would be to do for php, as the language is not designed for real time interaction between users.)

\$\endgroup\$
  • \$\begingroup\$ I have already tried using "SET boss_hp = boss_hp - 10" directly but I don't think it I can use it since the damage calculations has lots of variables located from different tables. Though I will still try some things. In the mean time... how can I "synchronize" the damage events? \$\endgroup\$ – EnyaVil Mar 24 at 14:54
  • \$\begingroup\$ @EnyaVil does the transactions solve your problem? \$\endgroup\$ – Jannek Mar 25 at 20:18
  • \$\begingroup\$ @EnyaVil I would try creating a stored procedure for the damage calculation \$\endgroup\$ – Theraot Nov 21 at 9:07
0
\$\begingroup\$

I think maybe Redis could solve this. Redis is fast and even if there are hundreds of simultaneous requests, redis will work out. Example (its javascript, you can convert this into PHP)

// you query database, calculate random damage etc in this method, to have a final damage.
var player_damage_dealt = calculate_player_damage()

// boss health atomicly reduced, dont need any validation, lock, transaction, wont fail.
$redis.incrby('boss_health', player_damage_dealt * -1);

var left_health = $redis.get('boss_health')

// check if boss is still alive, if not, do database updates accordingly
update_database_if_boss_died(left_health)
return left_health;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.