pixelflut: Implement alternative flooding strategies

These allow column-wise, traditional scanline, or random pixel flooding.
Depending on the use case and amount of contestion, any of the
strategies may work best, as tested on 37c3.
This commit is contained in:
kleines Filmröllchen 2023-12-30 02:40:45 +01:00 committed by Andrew Kaster
parent fdfd4b5e3d
commit bdd92e881f
Notes: sideshowbarker 2024-07-17 20:19:08 +09:00

View file

@ -6,6 +6,7 @@
#include <AK/Error.h>
#include <AK/NonnullRefPtr.h>
#include <AK/QuickSelect.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Socket.h>
@ -16,19 +17,42 @@
constexpr StringView get_command = "SIZE\n"sv;
// Which strategies to use for flooding the image to the server.
enum class FloodStrategy {
// Send image row by row, like a CRT scanline.
Scanline,
// Send random pixels.
Random,
// Send image column by column.
Columns,
};
static Optional<FloodStrategy> parse_strategy(StringView name)
{
if (name.equals_ignoring_ascii_case("scanline"sv))
return FloodStrategy::Scanline;
if (name.equals_ignoring_ascii_case("random"sv))
return FloodStrategy::Random;
if (name.equals_ignoring_ascii_case("columns"sv))
return FloodStrategy::Columns;
return {};
}
// Client for the Pixelflut protocol
// https://github.com/defnull/pixelflut#pixelflut-protocol
class Client : public RefCounted<Client> {
public:
static ErrorOr<NonnullRefPtr<Client>> create(StringView image_path, StringView server, size_t x, size_t y);
static ErrorOr<NonnullRefPtr<Client>> create(StringView image_path, StringView server, size_t x, size_t y, FloodStrategy strategy);
ErrorOr<void> run();
private:
Client(NonnullOwnPtr<Core::BufferedTCPSocket>, NonnullRefPtr<Gfx::Bitmap>, Gfx::IntSize, Gfx::IntPoint);
Client(NonnullOwnPtr<Core::BufferedTCPSocket>, NonnullRefPtr<Gfx::Bitmap>, Gfx::IntSize, Gfx::IntPoint, FloodStrategy);
ErrorOr<void> send_current_pixel();
void next_pixel();
void next_scanline_pixel();
void next_random_pixel();
void next_column_pixel();
NonnullOwnPtr<Core::BufferedTCPSocket> m_socket;
@ -37,9 +61,10 @@ private:
Gfx::IntPoint const m_image_offset;
Gfx::IntPoint m_current_point { 0, 0 };
FloodStrategy m_strategy;
};
ErrorOr<NonnullRefPtr<Client>> Client::create(StringView image_path, StringView server, size_t x, size_t y)
ErrorOr<NonnullRefPtr<Client>> Client::create(StringView image_path, StringView server, size_t x, size_t y, FloodStrategy strategy)
{
// Extract hostname and port and connect to server.
auto parts = server.split_view(':');
@ -88,20 +113,27 @@ ErrorOr<NonnullRefPtr<Client>> Client::create(StringView image_path, StringView
image = TRY(image->scaled(fitting_scale, fitting_scale));
}
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) Client(move(socket), move(image), canvas_size, Gfx::IntPoint { x, y })));
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) Client(move(socket), move(image), canvas_size, Gfx::IntPoint { x, y }, strategy)));
}
Client::Client(NonnullOwnPtr<Core::BufferedTCPSocket> socket, NonnullRefPtr<Gfx::Bitmap> image, Gfx::IntSize canvas_size, Gfx::IntPoint image_offset)
Client::Client(NonnullOwnPtr<Core::BufferedTCPSocket> socket, NonnullRefPtr<Gfx::Bitmap> image, Gfx::IntSize canvas_size, Gfx::IntPoint image_offset, FloodStrategy strategy)
: m_socket(move(socket))
, m_image(move(image))
, m_canvas_size(canvas_size)
, m_image_offset(image_offset)
, m_strategy(strategy)
{
outln("Connected to server, image {}, canvas size {}", m_image, m_canvas_size);
}
ErrorOr<void> Client::run()
{
Function<void(void)> next_pixel = [this] { this->next_scanline_pixel(); };
if (m_strategy == FloodStrategy::Columns)
next_pixel = [this] { this->next_column_pixel(); };
else if (m_strategy == FloodStrategy::Random)
next_pixel = [this] { this->next_random_pixel(); };
while (true) {
TRY(send_current_pixel());
next_pixel();
@ -127,7 +159,7 @@ ErrorOr<void> Client::send_current_pixel()
return {};
}
void Client::next_pixel()
void Client::next_scanline_pixel()
{
m_current_point.set_x(m_current_point.x() + 1);
if (m_current_point.x() >= m_image->width()) {
@ -140,6 +172,27 @@ void Client::next_pixel()
}
}
void Client::next_column_pixel()
{
m_current_point.set_y(m_current_point.y() + 1);
if (m_current_point.y() >= m_image->height()) {
m_current_point.set_y(0);
m_current_point.set_x(m_current_point.x() + 1);
if (m_current_point.x() >= m_image->width()) {
m_current_point.set_x(0);
m_current_point.set_y(0);
}
}
}
void Client::next_random_pixel()
{
m_current_point = {
AK::random_int(0, m_image->width() - 1),
AK::random_int(0, m_image->height() - 1),
};
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Core::EventLoop loop;
@ -148,11 +201,13 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
size_t x;
size_t y;
StringView server;
StringView strategy_string { "scanline"sv };
Core::ArgsParser args_parser;
args_parser.add_option(image_path, "Image to send to server", "image", 'i', "IMAGE");
args_parser.add_option(x, "Target x coordinate of the image on the server", "x", 'x', "X");
args_parser.add_option(y, "Target y coordinate of the image on the server", "y", 'y', "Y");
args_parser.add_option(strategy_string, "Pixel flooding strategy (scanline, random or column; default: scanline)", "strategy", 'm', "STRATEGY");
args_parser.add_positional_argument(server, "Pixelflut server (hostname:port)", "server");
args_parser.parse(arguments);
@ -161,7 +216,13 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
return 1;
}
auto client = TRY(Client::create(image_path, server, x, y));
auto maybe_strategy = parse_strategy(strategy_string);
if (!maybe_strategy.has_value()) {
warnln("Error: Strategy {} invalid", strategy_string);
return 1;
}
auto client = TRY(Client::create(image_path, server, x, y, maybe_strategy.release_value()));
TRY(client->run());