1: <?php declare(strict_types=1);
2:
3: /**
4: * Clansuite Server Query
5: *
6: * SPDX-FileCopyrightText: 2003-2025 Jens A. Koch
7: * SPDX-License-Identifier: MIT
8: *
9: * For the full copyright and license information, please view
10: * the LICENSE file that was distributed with this source code.
11: */
12:
13: namespace Clansuite\Capture\Worker;
14:
15: use function array_merge;
16: use function count;
17: use function usleep;
18: use Clansuite\ServerQuery\CSQuery;
19: use Clansuite\ServerQuery\Util\UdpClient;
20:
21: /**
22: * Worker class for performing server queries with timeout and retry logic.
23: */
24: final readonly class CaptureWorker
25: {
26: /**
27: * Constructor.
28: */
29: public function __construct(
30: private int $timeout = 5,
31: private int $maxRetries = 2
32: ) {
33: }
34:
35: /**
36: * Query a game server and return the results.
37: *
38: * @param string $protocol The protocol name (e.g., 'source', 'quake3')
39: * @param string $ip Server IP address
40: * @param int $port Server port
41: *
42: * @return array{debug: array<mixed>, server_info: array<string,mixed>} Query results with debug info and server info
43: */
44: public function query(string $protocol, string $ip, int $port): array
45: {
46: $factory = new CSQuery;
47: $server = $factory->createInstance($protocol, $ip, $port);
48:
49: // Set timeout on the UDP client to prevent hanging
50: $udpClient = new UdpClient;
51: $udpClient->setTimeout($this->timeout);
52: $server->setUdpClient($udpClient);
53:
54: // Initialize debug collection
55: $allDebug = [];
56:
57: // Run a single query (may block); worker's lifetime is controlled by the parent process
58: // First attempt: info + players
59: $server->query_server(true, true);
60: $allDebug = array_merge($allDebug, $server->debug);
61:
62: // If no players were returned, retry a players-only query a couple of times.
63: // The parent process controls the worker timeout, so these retries are bounded.
64: if (count($server->players) === 0) {
65: for ($i = 0; $i < $this->maxRetries; $i++) {
66: // try players-only (avoid re-fetching rules to be faster)
67: $server->query_server(true, false);
68: $allDebug = array_merge($allDebug, $server->debug);
69:
70: // small backoff before retrying
71: usleep(200000); // 200ms
72: }
73: }
74:
75: return [
76: 'debug' => $allDebug,
77: 'server_info' => [
78: 'address' => $server->address,
79: 'queryport' => $server->queryport,
80: 'online' => $server->online,
81: 'gamename' => $server->gamename,
82: 'gameversion' => $server->gameversion,
83: 'servertitle' => $server->servertitle,
84: 'mapname' => $server->mapname,
85: 'gametype' => $server->gametype,
86: 'numplayers' => $server->numplayers,
87: 'maxplayers' => $server->maxplayers,
88: 'rules' => $server->rules,
89: 'players' => $server->players,
90: 'errstr' => $server->errstr,
91: ],
92: ];
93: }
94: }
95: