Live viewer, attempts at nbdkit stuff, etc

This commit is contained in:
Casey 2024-09-06 16:06:19 +03:00
parent 3dd8217afe
commit f56cdb95f4
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
6 changed files with 401 additions and 5 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
obj/*
obcb
obcb-nbd.so
live

View File

@ -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)

175
src/live.c Normal file
View File

@ -0,0 +1,175 @@
// x-run: make -C.. live-run
#include <raylib.h>
#include <raymath.h>
#include <stdint.h>
#include <stdio.h>
#include <mongoose.h>
#include <threads.h>
#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);
}

View File

@ -1,6 +1,49 @@
// x-run: make -C.. test
#include <netinet/in.h>
#include <stdio.h>
#include <math.h>
#include <mongoose.h>
#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);
}

92
src/obcb-nbd.c Normal file
View File

@ -0,0 +1,92 @@
#include <stdbool.h>
#include <stdint.h>
#include <mongoose.h>
#include <string.h>
#include <threads.h>
#define NBDKIT_API_VERSION 2
#include <nbdkit-plugin.h>
#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);

69
src/packets.h Normal file
View File

@ -0,0 +1,69 @@
#ifndef _PACKETS_H_
#define _PACKETS_H_
#include <stdint.h>
// 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