From f56cdb95f4858540a55251d59a245295e7887562 Mon Sep 17 00:00:00 2001 From: hkc Date: Fri, 6 Sep 2024 16:06:19 +0300 Subject: [PATCH] Live viewer, attempts at nbdkit stuff, etc --- .gitignore | 2 + Makefile | 21 +++++- src/live.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.c | 47 ++++++++++++- src/obcb-nbd.c | 92 ++++++++++++++++++++++++++ src/packets.h | 69 +++++++++++++++++++ 6 files changed, 401 insertions(+), 5 deletions(-) create mode 100644 src/live.c create mode 100644 src/obcb-nbd.c create mode 100644 src/packets.h diff --git a/.gitignore b/.gitignore index e070ae6..e733775 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ obj/* obcb +obcb-nbd.so +live diff --git a/Makefile b/Makefile index e357a4b..3a9e5fd 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,25 @@ CFLAGS += LDFLAGS := -lm OBJECTS := -obcb: lib - $(CC) $(CFLAGS) $(OBJECTS) src/main.c $(LDFLAGS) -o obcb +all: obcb obcb-nbd.so live -all: obcb +test: obcb + ./obcb + +live-run: live + ./live + +live: ./src/live.c + $(CC) $(CFLAGS) $(OBJECTS) src/live.c $(LDFLAGS) -o live -lmongoose -lraylib -lm + +obcb: lib + $(CC) $(CFLAGS) $(OBJECTS) src/main.c $(LDFLAGS) -o obcb -lmongoose + +nbd: obcb-nbd.so + nbdkit --filter=blocksize-policy -fv ./obcb-nbd.so blocksize-error-policy=error + +obcb-nbd.so: src/obcb-nbd.c + $(CC) -ggdb -fPIC -shared src/obcb-nbd.c -o obcb-nbd.so -lmongoose `pkg-config nbdkit --cflags --libs` lib: $(OBJECTS) diff --git a/src/live.c b/src/live.c new file mode 100644 index 0000000..76ae650 --- /dev/null +++ b/src/live.c @@ -0,0 +1,175 @@ +// x-run: make -C.. live-run +#include +#include +#include +#include +#include +#include +#include "packets.h" + +static void obcb_send_full_state_request(struct mg_connection *c, uint16_t index); + +static uint32_t connected_clients = 0; +static bool running = true; + +struct mg_mgr manager; + +struct chunk { + double last_update; + double last_request; +} chunks[OBCB_CHUNK_COUNT]; + +struct img_and_tex { + Image img; + Texture2D tex; +} blocks[32 * 32]; + + +static void obcb_mg_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_OPEN) { + printf("Connected to WebSocket\n"); + } else if (ev == MG_EV_WS_MSG) { + struct mg_ws_message *wsm = (struct mg_ws_message*)ev_data; + if (wsm->data.buf[0] == OBCB_MSG_HELLO) { + struct obcb_msg_hello hello; memcpy(&hello, &wsm->data.buf[1], sizeof(hello)); + printf("HELLO: v%d.%d. Why hello there, honey~\n", hello.version_major, hello.version_minor); + } else if (wsm->data.buf[0] == OBCB_MSG_STATS) { + struct obcb_msg_stats stats; memcpy(&stats, &wsm->data.buf[1], sizeof(stats)); + printf("STATS: %d clients connected\n", stats.current_clients); + connected_clients = stats.current_clients; + } else if (wsm->data.buf[0] == OBCB_MSG_FULL_STATE_RESPONSE) { + struct obcb_msg_full_state_response state; memcpy(&state, &wsm->data.buf[1], sizeof(state)); + chunks[state.chunk_index].last_update = GetTime(); + for (int i = 0; i < OBCB_CHUNK_SIZE; i++) { + int index = i + state.chunk_index * OBCB_CHUNK_SIZE; + int x = index % 32768, y = index / 32768; + int chunk_x = x / 1024, chunk_y = y / 1024; + int in_chunk_x = x % 1024, in_chunk_y = y % 1024; + uint8_t *img = blocks[chunk_x + chunk_y * 32].img.data; + img[in_chunk_x + in_chunk_y * 1024] = state.bitmap[i >> 3] & (0x80 >> (i & 7)) ? 0xFF : 0x00; + } + } + } +} + +static int mongoose_thread(void *_mgr) { + while (running) mg_mgr_poll((struct mg_mgr *)_mgr, 1000); + mg_mgr_free((struct mg_mgr *)_mgr); + return 0; +} +static unsigned char rendered_canvas[OBCB_BITMAP_SIZE]; + +int main(void) { + mg_mgr_init(&manager); + struct mg_connection *ws = mg_ws_connect(&manager, "ws://bitmap-ws.alula.me/", obcb_mg_handler, 0, 0); + + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + SetTargetFPS(60); + InitWindow(800, 600, "obcb"); + + thrd_t mongoose_manager_thrd; + thrd_create(&mongoose_manager_thrd, mongoose_thread, &manager); + + for (int i = 0; i < 32 * 32; i++) { + blocks[i].img.data = &rendered_canvas[i * 1024 * 1024]; + blocks[i].img.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + blocks[i].img.width = 1024; + blocks[i].img.height = 1024; + blocks[i].img.mipmaps = 1; + blocks[i].tex = LoadTextureFromImage(blocks[i].img); + SetTextureFilter(blocks[i].tex, TEXTURE_FILTER_ANISOTROPIC_16X); + } + + Camera2D cam = { + .offset.x = 400, + .offset.y = 300, + .target.x = 0, + .target.y = 0, + .rotation = 0, + .zoom = 0.125 + }; + + double zoom = 0.5; + for (unsigned long frame = 0; !WindowShouldClose(); frame++) { + double current_time = GetTime(); + if (IsWindowResized()) { + cam.offset.x = (int)(GetScreenWidth() / 2.0); + cam.offset.y = (int)(GetScreenHeight() / 2.0); + } + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { + cam.target = Vector2Subtract(cam.target, Vector2Scale(GetMouseDelta(), 1.0 / cam.zoom)); + } + + zoom += GetMouseWheelMove() * 0.01; + cam.zoom = pow(zoom, 4.0); + + { + + int limit = 32; + for (int i = 0; i < OBCB_CHUNK_COUNT; i++) { + if (((current_time - chunks[i].last_update) > 5 || (current_time - chunks[i].last_request) > 30) && limit > 0) { + chunks[i].last_request = current_time; + chunks[i].last_update = current_time; + obcb_send_full_state_request(ws, i); + limit--; + } + } + + for (int i = 0; i < 32; i++) { + struct img_and_tex *block = &blocks[i + (frame % 32) * 32]; + UpdateTexture(block->tex, block->img.data); + } + } + + BeginDrawing(); + { + ClearBackground(BLACK); + BeginMode2D(cam); + { + DrawRectangle(-32, -32, 32832, 32832, RED); + for (int cy = 0; cy < 32; cy++) { + for (int cx = 0; cx < 32; cx++) { + if (blocks[cx + cy * 32].tex.id != 0) { + DrawTexture(blocks[cx + cy * 32].tex, cx * 1024, cy * 1024, WHITE); + } + } + } + } + EndMode2D(); + + { + for (int cy = 0; cy < 64; cy++) { + for (int cx = 0; cx < 64; cx++) { + double since_last_update = current_time - chunks[cx + cy * 64].last_update; + double since_last_request = current_time - chunks[cx + cy * 64].last_request; + DrawRectangle(cx * 2, cy * 2 + 128, 2, 2, (Color){ + .r = 255 * (5.0 - since_last_update) / 5.0, + .g = 255 * (10.0 - since_last_request) / 10.0, + .b = 0, + .a = 255 + }); + } + } + } + + DrawFPS(8, 8); + DrawText(TextFormat("Clients: %d", connected_clients), 8, 28, 20, WHITE); + DrawText(TextFormat("Camera: %.2f %.2f (1/%.2f) offset:[%.2f %.2f]", + cam.target.x, cam.target.y, 1.0 / cam.zoom, + cam.offset.x, cam.offset.y), 8, 48, 20, WHITE); + } + EndDrawing(); + } + + return 0; +} + +static void obcb_send_full_state_request(struct mg_connection *c, uint16_t index) { + static unsigned char packet[sizeof(struct obcb_msg_full_state_request) + 1]; + struct obcb_msg_full_state_request *msg = (struct obcb_msg_full_state_request *)&packet[1]; + packet[0] = OBCB_MSG_FULL_STATE_REQUEST; + msg->chunk_index = index; + mg_ws_send(c, packet, sizeof(packet), WEBSOCKET_OP_BINARY); +} + diff --git a/src/main.c b/src/main.c index cbcecc0..393f2e2 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,49 @@ +// x-run: make -C.. test +#include #include -#include +#include +#include "packets.h" + +static void obcb_send_full_state_request(struct mg_connection *c, uint16_t index); + +static void obcb_mg_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_OPEN) { + printf("Connected to WebSocket\n"); + } else if (ev == MG_EV_WS_OPEN) { + obcb_send_full_state_request(c, 0); + } else if (ev == MG_EV_WS_MSG) { + struct mg_ws_message *wsm = (struct mg_ws_message*)ev_data; + if (wsm->data.buf[0] == OBCB_MSG_HELLO) { + struct obcb_msg_hello hello; memcpy(&hello, &wsm->data.buf[1], sizeof(hello)); + printf("HELLO: v%d.%d. Why hello there, honey~\n", hello.version_major, hello.version_minor); + } else if (wsm->data.buf[0] == OBCB_MSG_STATS) { + struct obcb_msg_stats stats; memcpy(&stats, &wsm->data.buf[1], sizeof(stats)); + /*stats.current_clients = htonl(stats.current_clients);*/ + printf("STATS: %d clients connected\n", stats.current_clients); + } else if (wsm->data.buf[0] == OBCB_MSG_FULL_STATE_RESPONSE) { + struct obcb_msg_full_state_response state; memcpy(&state, &wsm->data.buf[1], sizeof(state)); + printf("STATE[%d] =", state.chunk_index); + for (int i = 0; i < sizeof(state.bitmap); i++) { + printf(" %02x", state.bitmap[i]); + } + printf("\n"); + } + } +} int main(void) { - printf("Hi!\n"); + struct mg_mgr manager; + mg_mgr_init(&manager); + struct mg_connection *ws = mg_ws_connect(&manager, "ws://bitmap-ws.alula.me/", obcb_mg_handler, 0, 0); + while (1) mg_mgr_poll(&manager, 1000); + mg_mgr_free(&manager); + return 0; +} + +static void obcb_send_full_state_request(struct mg_connection *c, uint16_t index) { + static unsigned char packet[sizeof(struct obcb_msg_full_state_request) + 1]; + struct obcb_msg_full_state_request *msg = (struct obcb_msg_full_state_request *)&packet[1]; + packet[0] = OBCB_MSG_FULL_STATE_REQUEST; + msg->chunk_index = index; + mg_ws_send(c, packet, sizeof(packet), WEBSOCKET_OP_BINARY); } diff --git a/src/obcb-nbd.c b/src/obcb-nbd.c new file mode 100644 index 0000000..5f33343 --- /dev/null +++ b/src/obcb-nbd.c @@ -0,0 +1,92 @@ +#include +#include + +#include +#include +#include + +#define NBDKIT_API_VERSION 2 +#include + +#include "packets.h" + +#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS + +struct mg_mgr mg_manager; +thrd_t manager_thread; +struct mg_connection *obcb_mg_conn; + +static void obcb_nbd_mg_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_OPEN) { + c->is_hexdumping = 1; + nbdkit_debug("Connected to WebSocket"); + } else if (ev == MG_EV_WS_MSG) { + struct mg_ws_message *wsm = (struct mg_ws_message*)ev_data; + if (wsm->data.buf[0] == OBCB_MSG_HELLO) { + struct obcb_msg_hello hello; memcpy(&hello, wsm->data.buf, sizeof(hello)); + nbdkit_debug("HELLO: v%d.%d. Why hello there, honey", hello.version_major, hello.version_minor); + } else if (wsm->data.buf[0] == OBCB_MSG_STATS) { + struct obcb_msg_stats stats; memcpy(&stats, wsm->data.buf, sizeof(stats)); + nbdkit_debug("STATS: %d clients connected", stats.current_clients); + } + } +} + +static int obcb_nbd_mg_thread(void *param) { + nbdkit_debug("Running in a thread now..."); + obcb_mg_conn = mg_ws_connect(&mg_manager, "wss://bitmap-ws.alula.me/", obcb_nbd_mg_handler, 0, 0); + while (1) mg_mgr_poll(&mg_manager, 1000); + return 0; +} + +static void obcb_nbd_load(void) { + nbdkit_debug("Hello from OBCB!"); +} + +static int obcb_nbd_after_fork(void) { + mg_mgr_init(&mg_manager); + // thrd_create(&manager_thread, obcb_nbd_mg_thread, 0); + obcb_nbd_mg_thread(0); + return 0; +} + +static void *obcb_nbd_open(int readonly) { + return NBDKIT_HANDLE_NOT_NEEDED; +} + +static int64_t obcb_nbd_get_size(void *handle) { + return OBCB_BITMAP_SIZE / 8; +} + +static int obcb_nbd_block_size(void *handle, uint32_t *minimum, uint32_t *preferred, uint32_t *maximum) { + *minimum = OBCB_CHUNK_SIZE_BYTES; + *preferred = OBCB_CHUNK_SIZE_BYTES; + *maximum = OBCB_CHUNK_SIZE_BYTES; + return 0; +} + +static int obcb_nbd_pread(void *_handle, void *buf, uint32_t count, uint64_t offset, uint32_t flags) { + if (count != OBCB_CHUNK_SIZE_BYTES || offset % OBCB_CHUNK_SIZE_BYTES) { + nbdkit_error("Invalid block size (should be %d)", OBCB_CHUNK_SIZE_BYTES); + return -1; + } + nbdkit_debug("READ at %ld of %d", offset, count); + memset(buf, 0xAA, count); + return 0; +} + +static void obcb_nbd_close(void *_handle) { +} + +static struct nbdkit_plugin plugin = { + .name = "obcb", + .longname = "One Billion Checkboxes", + .description = "Using One Billion Checkboxes as a storage", + .load = obcb_nbd_load, + .open = obcb_nbd_open, + .get_size = obcb_nbd_get_size, + .block_size = obcb_nbd_block_size, + .pread = obcb_nbd_pread +}; + +NBDKIT_REGISTER_PLUGIN(plugin); diff --git a/src/packets.h b/src/packets.h new file mode 100644 index 0000000..3140eb8 --- /dev/null +++ b/src/packets.h @@ -0,0 +1,69 @@ +#ifndef _PACKETS_H_ +#define _PACKETS_H_ + +#include + +// The size of a single chunk in bits +#define OBCB_CHUNK_SIZE (64 * 64 * 64) + +// The size of a single chunk in bytes +#define OBCB_CHUNK_SIZE_BYTES (OBCB_CHUNK_SIZE / 8) + +// The number of chunks +#define OBCB_CHUNK_COUNT (64 * 64) + +// The size of the entire bitmap in bits +#define OBCB_BITMAP_SIZE (OBCB_CHUNK_SIZE * OBCB_CHUNK_COUNT) + +// The size of a single update chunk in bytes +#define OBCB_UPDATE_CHUNK_SIZE (32) + +enum obcb_message_e { + OBCB_MSG_HELLO = 0x00, + OBCB_MSG_STATS = 0x01, + OBCB_MSG_FULL_STATE_REQUEST = 0x10, + OBCB_MSG_FULL_STATE_RESPONSE = 0x11, + OBCB_MSG_PARTIAL_STATE_UPDATE = 0x12, + OBCB_MSG_TOGGLE_BIT = 0x13, + OBCB_MSG_PARTIAL_STATE_SUBSCRIPTION = 0x14, + OBCB_MSG_PARTIAL_STATE_UNSUBSCRIPTION = 0x15, +}; + +#define OBCB_MSG(NAME, SIDE) __attribute__((__packed__)) obcb_msg_##NAME + +struct OBCB_MSG(hello, SERVER) { + uint16_t version_major; + uint16_t version_minor; +}; + +struct OBCB_MSG(stats, SERVER) { + uint32_t current_clients; + uint8_t reserved[60]; +}; + +struct OBCB_MSG(full_state_request, CLIENT) { + uint16_t chunk_index; +}; + +struct OBCB_MSG(full_state_response, SERVER) { + uint16_t chunk_index; + uint8_t bitmap[OBCB_CHUNK_SIZE_BYTES]; +}; + +struct OBCB_MSG(partial_state_update, SERVER) { + uint16_t offset; // in bytes, global + uint8_t chunk[OBCB_UPDATE_CHUNK_SIZE]; +}; + +struct OBCB_MSG(toggle_bit, CLIENT) { + uint32_t offset; +}; + +struct OBCB_MSG(partial_state_subscription, CLIENT) { + uint16_t chunk_index; +}; + +struct OBCB_MSG(partial_state_unsubscription, CLIENT) { +}; + +#endif