-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1.7] Add getSocketResource
method to enhance socket management
#17
Conversation
This method is a prerequisite to eventually resolving chrome-php/chrome#606
getSocketResource
method to enhance socket management
Exposing this underlying resource feels kinda wrong. What did you need to do with the resource? I wonder if it'd be better to expose a couple of methods here to deal with that, rather than leaking the internals. |
@GrahamCampbell it's a long story, and the TL;DR is that in https://github.com/chrome-php/chrome/blob/1.11/src/Communication/Connection.php I want to replace /**
* Wait before sending next message.
*/
private function waitForDelay(): void
{
if ($this->lastMessageSentTime) {
$currentTime = (int) (\hrtime(true) / 1000 / 1000);
// if not enough time was spent until last message was sent, wait
if ($this->lastMessageSentTime + $this->delay > $currentTime) {
$timeToWait = ($this->lastMessageSentTime + $this->delay) - $currentTime;
\usleep($timeToWait * 1000);
}
}
$this->lastMessageSentTime = (int) (\hrtime(true) / 1000 / 1000);
} with /**
* Wait before sending next message.
*/
private function waitForDelay(): void
{
if ($this->lastMessageSentTime) {
$currentTime = (int) (\hrtime(true) / 1000 / 1000);
// if not enough time was spent until last message was sent, wait
if ($this->lastMessageSentTime + $this->delay > $currentTime) {
$timeToWait = ($this->lastMessageSentTime + $this->delay) - $currentTime;
$seconds = (int)floor($timeToWait);
$nanoseconds = (int)(($timeToWait - $seconds) * 1e9);
$null = null;
$read = [$this->wsClient->getSocketResource()];
socket_select($read, $null, $null, $seconds, $nanoseconds);
}
}
$this->lastMessageSentTime = (int) (\hrtime(true) / 1000 / 1000);
}
That's the short version anyway... The full version is: I wanted to make video recordings with chrome-php/chrome, and at first I tried just spamming screenshot(), and while that kindof works, it is way too slow and resource-intensive to make smooth video recordings of animations. Then I tried using Page.startScreencast + method:Page.screencastFrame instead, and that was a huge improvement over spamming screenshot() ! While much better than screenshot() spam, it still didn't produce 100% smooth recordings of animations. I started investigating why, and I tried replacing stuff like $navigate->waitForNavigation(\HeadlessChromium\Page::NETWORK_IDLE); with $target_time = microtime(true) + 5;
while (microtime(true) < $target_time) {
// calling readData() fast causes smooth video recordings.
// if there is too much delay in sending Page.screencastFrameAck, chromium will drop frames.
$page->getSession()->getConnection()->readData();
} and Yeah! Now i could make completely smooth video recordings! but it was a huge waste of CPU to busy loop like this. Then I started investigating why I needed that loop, and it turns out that if you don't respond to a Page.screencastFrame with a Page.screencastFrameAck fast enough, Chrome will start dropping frames until you respond. That causes un-smooth choppy video recordings. I investigated why waitForNavigation was too slow, and eventually concluded that the culprit is the usleep() in waitForDelay(), and concluded that the correct fix is to replace the usleep() with a socket_select(), and thus this PR. |
Perhaps we can make a PHP-alternative to NodeJS' timecut :) |
I wonder if it would be better to add a public function waitOnData(float $maxSeconds): void
{
$seconds = (int)floor($timeToWait);
$nanoseconds = (int)(($timeToWait - $seconds) * 1e9);
$null = null;
socket_select($this->socket, $null, $null, $seconds, $nanoseconds);
} private function waitForDelay(): void
{
if ($this->lastMessageSentTime) {
$currentTime = \hrtime(true) / 1000 / 1000;
$timeToWait = (float) ($this->lastMessageSentTime + $this->delay) - $currentTime;
// if not enough time was spent until last message was sent, wait
if ($timeToWait > PHP_FLOAT_EPSILON)
$this->wsClient->waitForData($timeToWait);
}
}
$this->lastMessageSentTime = (int) (\hrtime(true) / 1000 / 1000);
} That way we're not breaking the abstraction and calling |
getSocketResource
method to enhance socket managementgetSocketResource
method to enhance socket management
sounds good! Where would it be appropriate to be defined tho? \Wrench\Client or \Wrench\Socket\ClientSocket or \Wrench\Socket\UriSocket or \Wrench\Socket\AbstractSocket ? 🤔 Edit: Just realized a mistake in my example code above, select() takes $seconds + $MICROseconds , not $NANOseconds, so the correct code is $seconds = (int)floor($timeToWait);
$microseconds = (int)(($timeToWait - $seconds) * 1e6);
(...)
socket_select($read, $null, $null, $seconds, $microseconds); |
@GrahamCampbell added a |
Thanks. |
@GrahamCampbell Any ETA for a 1.7 release? kinda itching to make progress on chrome-php/chrome#606 |
You can make progress by using |
This method is a prerequisite for eventually resolving chrome-php/chrome#606
Here is my investigation notes leading up to this PR:
class \HeadlessChromium\Communication\Connection has a protected \HeadlessChromium\Communication\Socket\SocketInterface $wsClient;
\HeadlessChromium\Communication\Socket\SocketInterface has no getSocketResource().
\HeadlessChromium\Communication\Socket\Wrench must then create its own getSocketResource(),
returning the resource from protected \Wrench\Client $client;
\Wrench\Client needs a getSocketResource(), returning it's socket resource from protected \Wrench\Socket\ClientSocket $socket;
\Wrench\Socket\ClientSocket inherits $this->socket property from UriSocket...
\Wrench\Socket\UriSocket inherits $this->socket property from \Wrench\Socket\AbstractSocket...
\Wrench\Socket\AbstractSocket has a public function getResource()
that means UriSocket has it too... that means ClientSocket also has it.. \Wrench\Client needs to expose it publicly (or at least protectedly)
it needs a
public function getSocketResource(){return $this->socket->getResource();}