Мне нужно реализовать таймер ожидания ВНУТРИ цикла обработки веб-сокетов PHP React (возможно, многопоточность?)

У меня есть веб-сокет-приложение, на котором я создаю игру, основанную на Ratchet, которая использует цикл событий React. В начале этого сценария я уже выяснил, как реализовать периодический тимер, чтобы каждую секунду посылать импульсы в игру, а затем выполнять тики и боевые раунды. Это прекрасно работает.

Однако недавно я понял, что мне также нужно будет добавить возможность «отставать» от клиентов или приостанавливать выполнение в функции. Например, если игрок ошеломлен, или я хочу, чтобы NPC подождал 1,5 секунды, прежде чем ответить на триггер для более «реалистичного» разговорного ощущения.

Встроена ли эта функциональность в библиотеку реагирования, или я собираюсь добиться чего-то другим? После некоторого исследования, похоже, что pthreads — это то, что я могу искать, см. Этот вопрос / ответ: Как можно использовать многопоточность в приложениях PHP

Чтобы быть более понятным с тем, что я пытаюсь достичь, возьмите этот код в качестве примера:

    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!``'");
}
}
}
}

Очевидно, это не работает, так как функция sleep (1) остановит весь процесс сервера.

Любое понимание будет с благодарностью. Спасибо!

Обновление: Мой серверный скрипт:

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

Решение

Хорошо, так что я собираюсь предположить, что, поскольку это все еще остается без ответа, не существует «простого» решения, запрограммированного в цикле событий реакции, хотя я бы хотел ошибиться в этом. До тех пор я решил опубликовать свое решение.

Замечания: Я понятия не имею, каковы последствия этого. Я понятия не имею, насколько это масштабируемо. Он не тестировался в реальной среде с несколькими процессами и игроками.

Однако я думаю, что это достойное решение. Моя конкретная игра ориентирована на количество игроков от 20 до 30, поэтому я думаю, что единственная проблема, с которой я могу столкнуться, — это если куча действий в очереди стреляет в одну и ту же секунду.

Коду!

Первое, что я сделал (некоторое время назад), добавил периодический таймер при запуске сервера:

public function __construct(React\EventLoop\LoopInterface $loop)
{
$update = new Update();
$update->doTick();

$loop->addPeriodicTimer(1, function()
{
$this->doBeat();
});
}

У меня также есть некоторые глобальные переменные в моем «мировом» классе:

// 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;

Заметка биения а также process_queue.

Моя функция doBeat () выглядит так:

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();
}
}

Теперь в моем глобальном объекте «Мир» у меня есть пара других функций:

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);
}

Теперь, чтобы добавить процесс в очередь, вот моя новая мобильная команда «onSay ()»:

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);
}
}

Таким образом, если мобильный телефон слышит «привет», «привет» или «привет», функция «toRoom» (которая отправляет строку каждому символу в той же комнате) будет добавлена ​​в очередь процесса и сработает через 2 секунды после оригинальная функция была выполнена.

Я надеюсь, что все это имеет смысл, и если кто-нибудь знает, как лучше реализовать такие вещи в php и в цикле событий, пожалуйста, ответьте / прокомментируйте. Я не отмечаю это как «правильное», как я уже сказал выше, я понятия не имею, насколько эффективно это будет в производстве.

1

Другие решения

Вы можете просто использовать addTimer как ты сделал с addPeriodicTimer, Если вы хотите работать с обещаниями, вы можете создать вспомогательное обещание, которое разрешается сразу после вашего времени паузы.

Amp (другая реализация цикла событий) имеет Amp\Pause который делает именно это. Может быть, вы можете использовать это как вдохновение, если вы хотите выполнить обещание, как упомянуто.

0

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector