I need to implement an INSIDE PHP React websocket event loop wait timer (maybe multithreading?)

I have a web application in which I create a game built on Ratchet that uses a React event loop. At the beginning of this script, I already figured out how to implement the frequency, send an impulse to the game every second, and then perform ticks and combat rounds. This works great.

However, I recently realized that I would also need to add the ability to "lag" behind clients or pause the function. For example, if a player is stunned, or I want the NPC to wait 1.5 seconds before responding to a trigger for more “realistic” communication.

Is this functionality built into the reaction library, or is it something I need to achieve using other means? After some research, it seems that maybe pthreads is what I can find, see this question / answer: How to use multithreading in PHP applications

To better understand what I'm trying to achieve, take this code as an example:

function onSay($string) { global $world; $trigger_words = array( 'hi', 'hello', 'greetings' ); $triggered = false; foreach($trigger_words as $trigger_word) { if(stristr($string, $trigger_word)) { $triggered = true; } } if($triggered) { foreach($world->players as $player) { if($player->pData->in_room === $this->mobile->in_room) { sleep(1); $this->toChar($player, $this->mobile->short . " says '`kOh, hello!``'"); } } } } 

Obviously, this does not work, since the sleep (1) function will stop the entire server process.

Any insight would be very helpful. Thanks!

Update: My server script:

 require 'vendor/autoload.php'; require 'src/autoload.php'; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use React\Socket\Server as Reactor; use React\EventLoop\Factory as LoopFactory;; $world = new WorldInterface(); class Server implements MessageComponentInterface { public function __construct(React\EventLoop\LoopInterface $loop) { $update = new Update(); $update->doTick(); $loop->addPeriodicTimer(1, function() { $this->doBeat(); }); } public function onOpen(ConnectionInterface $ch) { global $world; $world->connecting[$ch->resourceId] = $ch; $ch->CONN_STATE = "GET_NAME"; $ch->pData = new stdClass(); $ch->send("Who dares storm our wayward path? "); } public function onMessage(ConnectionInterface $ch, $args) { if($ch->CONN_STATE == "CONNECTED") { $ch->send("> " . $args . "\n"); $interpreter = new Interpreter($ch); $interpreter->interpret($args); } else { $ch->send($args); $login = new Login($ch, $args); $login->start(); } } public function onClose(ConnectionInterface $ch) { global $world; if(isset($ch->pData->name)) { if(isset($world->players[$ch->pData->name])) { echo "Player {$ch->pData->name} has disconnected\n"; unset($world->players->{$ch->pData->name}); } } if(isset($world->connecting->{$ch->resourceId})) { echo "Connection " . $ch->resourceId . " has disconnected."; unset($world->connecting->{$ch->resourceId}); } } public function onError(ConnectionInterface $conn, \Exception $e) { echo "An error has occurred: {$e->getMessage()}\n"; $conn->close(); } public function doBeat() { global $world; ++$world->beats; foreach($world->process_queue as $trigger_beat => $process_array) { // if the beat # that the function should fire on is less than, // or equal to the current beat, fire the function. if($trigger_beat <= $world->beats) { foreach($process_array as $process) { $class = new $process->class(); call_user_func_array(array($class, $process->function), $process->params); } // remove it from the queue unset($world->process_queue[$trigger_beat]); } // else, the beat # the function should fire on is greater than the current beat, // so break out of the loop. else { break; } } if($world->beats % 2 === 0) { $update = new Update(); $update->doBeat(); } } } $loop = LoopFactory::create(); $socket = new Reactor($loop); $socket->listen(9000, 'localhost'); $server = new IoServer(new HttpServer(new WsServer(new Server($loop))), $socket, $loop); $server->run(); 
+6
source share
2 answers

Well, that’s why I’m going to assume that since this has not yet answered, there is no “easy” solution baked in the loop of reacting events, although I would like to be mistaken in that. Until then, I decided that I would publish my decision.

Note. I have no idea what the consequences of this are. I have no idea how scalable it is. It has not been tested in a live environment with multiple processes and players.

I think this is a worthy solution. My particular game is focused on a player base, perhaps 20-30, so I think that the only problem that I can run into is that at that very moment empty lines fired.

To the code!

The first thing I did (some time ago) was to add a periodic timer at server startup:

 public function __construct(React\EventLoop\LoopInterface $loop) { $update = new Update(); $update->doTick(); $loop->addPeriodicTimer(1, function() { $this->doBeat(); }); } 

I also have some global variables in my "world" class:

 // things in the world public $beats = 0; public $next_tick = 45; public $connecting = array(); public $players = array(); public $mobiles = array(); public $objects = array(); public $mobs_in_rooms = array(); public $mobs_in_areas = array(); public $in_combat = array( 'mobiles' => array(), 'players' => array() ); public $process_queue; 

Note also removes process_queue .

My doBeat () function looks like this:

 public function doBeat() { global $world; ++$world->beats; foreach($world->process_queue as $trigger_beat => $process_array) { // if the beat # that the function should fire on is less than, // or equal to the current beat, fire the function. if($trigger_beat <= $world->beats) { foreach($process_array as $process) { $class = new $process->class(); call_user_func_array(array($class, $process->function), $process->params); } // remove it from the queue unset($world->process_queue[$trigger_beat]); } // else, the beat # the function should fire on is greater than the current beat, // so break out of the loop. else { break; } } print_r(array_keys($world->process_queue)); if($world->beats % 2 === 0) { $update = new Update(); $update->doBeat(); } } 

Now, on my global “World” object, I have a couple of other functions:

 function addToProcessQueue($process_obj) { //adds the process object to an array of the beat # //when it should be triggered on process_queue. $this->process_queue[(int)$process_obj->trigger_beat][] = $process_obj; ksort($this->process_queue); } function createProcessObject($array) { $process_obj = new stdClass(); if(isset($array['function'])) { $process_obj->function = $array['function']; } else { echo "All process requests must define a function to call defined as a key named 'function' on the array you pass."; } if(isset($array['class'])) { $process_obj->class = $array['class']; } else { echo "All process requests must define a class to call defined as a key named 'class' on the array you pass."; } if(isset($array['params'])) { $process_obj->params = $array['params']; } else { $process_obj->params = array(); } if(isset($array['char'])) { $process_obj->char = $array['char']; } else { $process_obj->char = false; } if(isset($array['trigger_beat']) && is_numeric($array['trigger_beat'])) { $process_obj->trigger_beat = $array['trigger_beat']; } else { echo "All process requests must define a trigger_beat. \n" . "Use world->beats to get current beat and add your wait time onto it. \n" . "Trigger beat MUST be an integer. \n"; } $this->addToProcessQueue($process_obj); } 

Now, to add the process to the queue, here is my new mobile onSay () command:

 function onSay($string) { global $world; $trigger_words = array( 'hi', 'hello', 'greetings' ); $triggered = false; foreach($trigger_words as $trigger_word) { if(stristr($string, $trigger_word)) { $triggered = true; } } if($triggered) { $process_array = array( 'trigger_beat' => $world->beats + 2, 'function' => 'toRoom', 'class' => 'PlayerInterface', 'params' => array($this->mobile->in_room, $this->mobile->short . " says '`kOh, hello!``'") ); $world->createProcessObject($process_array); } } 

So, if a mobile phone hears “hello”, “hello” or “greetings”, the “toRoom” function (which sends a line to each character in the same room) will be added to the process queue and will work 2 seconds from the moment the original function was executed .

I hope this all makes sense, and if anyone knows how best to do this in php and inside the event loop, please reply / comment. I do not consider this “right”, as I said above, I do not know how effective it is in production.

+1
source

You can simply use addTimer , as you did with addPeriodicTimer . If you want to work with promises, you can create an auxiliary promise that resolves immediately after a pause.

Amp (another implementation of the event loop) has Amp\Pause , which does just that. Perhaps you can use this as inspiration if you want to fulfill the promise that was mentioned.

0
source

Source: https://habr.com/ru/post/1015324/


All Articles