lnvis

nanovg lightning network visualizer
git clone git://jb55.com/lnvis
Log | Files | Refs | README | LICENSE

commit f55fbdf3edb180f0aaf1ca4cf4eab0c6f1b057a1
parent f1ad0a3d06dd0b45cc7ae31fea913ba2d7eb42e4
Author: William Casarin <jb55@jb55.com>
Date:   Sat,  4 Aug 2018 14:19:28 -0700

progress!

- interactivity
- forces and such
- lines

Diffstat:
MMakefile | 1+
Mdefs.h | 3+++
Mdeps/nanovg/nanovg.c | 5++---
Mln.h | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mmain.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mrender.c | 61++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Aupdate.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aupdate.h | 9+++++++++
8 files changed, 302 insertions(+), 20 deletions(-)

diff --git a/Makefile b/Makefile @@ -9,6 +9,7 @@ LDFLAGS = -lglfw -lGL SRCS = main.c +SRCS += update.c SRCS += render.c SRCS += perf.c SRCS += demo.c diff --git a/defs.h b/defs.h @@ -13,5 +13,8 @@ typedef signed short s16; typedef unsigned int u32; typedef signed int s32; +typedef unsigned long long u64; +typedef signed long long s64; + #endif /* LNVIS_DEFS_H */ diff --git a/deps/nanovg/nanovg.c b/deps/nanovg/nanovg.c @@ -845,9 +845,8 @@ void nvgDeleteImage(NVGcontext* ctx, int image) ctx->params.renderDeleteTexture(ctx->params.userPtr, image); } -NVGpaint nvgLinearGradient(NVGcontext* ctx, - float sx, float sy, float ex, float ey, - NVGcolor icol, NVGcolor ocol) +NVGpaint nvgLinearGradient(NVGcontext* ctx, float sx, float sy, float ex, float ey, + NVGcolor icol, NVGcolor ocol) { NVGpaint p; float dx, dy, d; diff --git a/ln.h b/ln.h @@ -3,10 +3,11 @@ #define LNVIS_LN_H #include "nanovg/nanovg.h" +#include "defs.h" struct node { const char *alias; - const char *nodeid; + const char *id; // pubkey union { float rgba[4]; @@ -14,12 +15,76 @@ struct node { float r, g, b, a; }; } color; + + double x, y; + double vx, vy; + double ax, ay; + + float size; +}; + + +// REMOTE: remote node opened the channel with local +// LOCAL: local node opened the channel with remote +enum side { + LOCAL, + REMOTE, + NUM_SIDES +}; + +/* + * type: 256 (`channel_announcement`) + * - [`64`:`node_signature_1`] + * - [`64`:`node_signature_2`] + * - [`64`:`bitcoin_signature_1`] + * - [`64`:`bitcoin_signature_2`] + * - [`2`:`len`] + * - [`len`:`features`] + * - [`32`:`chain_hash`] + * - [`8`:`short_channel_id`] + * - [`33`:`node_id_1`] + * - [`33`:`node_id_2`] + * - [`33`:`bitcoin_key_1`] + * - [`33`:`bitcoin_key_2`] + */ + +/* type: 258 (`channel_update`) + * - [`64`:`signature`] + * - [`32`:`chain_hash`] + * - [`8`:`short_channel_id`] + * - [`4`:`timestamp`] + * - [`2`:`flags`] + * - [`2`:`cltv_expiry_delta`] + * - [`8`:`htlc_minimum_msat`] + * - [`4`:`fee_base_msat`] + * - [`4`:`fee_proportional_millionths`] + */ + +struct channel { + struct node *nodes[2]; + + u64 our_msatoshi; + /* Statistics for min and max our_msatoshi. */ + u64 msatoshi_to_us_min; + u64 msatoshi_to_us_max; + + u32 last_update; }; struct ln { NVGcontext *vg; + + int clicked; + int mdown; + double mx, my; + + struct node *drag_target; + struct node *nodes; - int node_count; + u32 node_count; + + struct channel *channels; + u32 channel_count; }; #endif /* LNVIS_LN_H */ diff --git a/main.c b/main.c @@ -14,6 +14,7 @@ #include "perf.h" #include "demo.h" #include "render.h" +#include "update.h" #include "ln.h" void errorcb(int error, const char *desc) @@ -35,6 +36,36 @@ static void key(GLFWwindow *window, int key, int scancode, int action, int mods) premult = !premult; } + +static double rand_0to1() { + return (double) rand() / RAND_MAX; +} + +static double mx, my; +static int mdown = 0; +static int mclicked = 0; + +void mouse_pos(GLFWwindow *win, double x, double y) +{ + (void)win; + mx = x; + my = y; +} + +void mouse_click(GLFWwindow *win, int button, int action, int mods) +{ + (void)win; + (void)button; + (void)action; + (void)mods; + + mclicked = action == 1 && button == 0; + + if (button == 0) + mdown = action; +} + + int main() { GLFWwindow *window; @@ -44,14 +75,45 @@ int main() double prevt = 0; struct ln ln; - struct node test_nodes[] = { { - .alias = "@jb55", - .color = { { 1.0, 0, 0, 1.0 } }, - } }; + struct node test_nodes[] = { + { + .alias = "@jb55", + .color = { { 1.0, 0, 0, 1.0 } }, + }, + { + .alias = "SuperHub", + .color = { { 0.0, 1.0, 0.0, 1.0 } }, + }, + { + .alias = "satoshis.place", + .color = { { 0.0, 0.5, 1.0, 1.0 } }, + }, + }; + + struct channel test_channels[] = { + { + .nodes = { &test_nodes[0], &test_nodes[1] }, + }, + { + .nodes = { &test_nodes[1], &test_nodes[2] }, + } + }; ln.nodes = test_nodes; ln.node_count = ARRAY_SIZE(test_nodes); + ln.channels = test_channels; + ln.channel_count = ARRAY_SIZE(test_channels); + + for (u32 i = 0; i < ln.node_count; ++i) { + struct node *n = &ln.nodes[i]; + n->x = 100.0 * (i+1); + n->y = 100.0 * (i+1); + n->vx = 200.0 * rand_0to1(); + n->vy = 200.0 * rand_0to1(); + n->size = 20; + } + if (!glfwInit()) { printf("Failed to init GLFW."); return -1; @@ -95,6 +157,7 @@ int main() } ln.vg = vg; + ln.clicked = 0; if (loadDemoData(vg, &data) == -1) return -1; @@ -104,8 +167,13 @@ int main() glfwSetTime(0); prevt = glfwGetTime(); + glfwSetMouseButtonCallback(window, mouse_click); + glfwSetCursorPosCallback(window, mouse_pos); + while (!glfwWindowShouldClose(window)) { - double mx, my, t, dt; + if (ln.clicked) + ln.clicked = 0; + double t, dt; int winWidth, winHeight; int fbWidth, fbHeight; float pxRatio; @@ -115,6 +183,13 @@ int main() prevt = t; updateGraph(&fps, dt); + ln.clicked = mclicked; + mclicked = 0; + + ln.mx = mx; + ln.my = my; + ln.mdown = mdown; + glfwGetCursorPos(window, &mx, &my); glfwGetWindowSize(window, &winWidth, &winHeight); glfwGetFramebufferSize(window, &fbWidth, &fbHeight); @@ -127,13 +202,15 @@ int main() if (premult) glClearColor(0, 0, 0, 0); else // base16-onedark bg color ;) - glClearColor(0x28 / 255.0, 0x2c / 255.0, 0x34 / 255.0, 1.0f); + glClearColor(0x28 / 255.0, 0x2c / 255.0, 0x34 / 255.0, + 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); nvgBeginFrame(vg, winWidth, winHeight, pxRatio); /* renderDemo(vg, mx, my, winWidth, winHeight, t, 1, &data); */ + update(&ln, dt); render_ln(&ln); renderGraph(vg, 5, 5, &fps); diff --git a/render.c b/render.c @@ -3,10 +3,48 @@ #include <stdio.h> #include "nanovg/nanovg.h" +void draw_channel(NVGcontext *vg, struct channel *channel) +{ + const struct node *n1 = channel->nodes[0]; + const struct node *n2 = channel->nodes[1]; + static const float stroke = 2.0f; + + const float sx = n1->x; + const float sy = n1->y; + + const float ex = n2->x; + const float ey = n2->y; + + NVGcolor n1_color = + nvgRGBAf(n1->color.r, + n1->color.g, + n1->color.b, + n1->color.a); + + NVGcolor n2_color = + nvgRGBAf(n2->color.r, + n2->color.g, + n2->color.b, + n2->color.a); + + NVGpaint linear_grad = + nvgLinearGradient(vg, sx, sy, ex, ey, n1_color, n2_color); + + nvgSave(vg); + nvgStrokeWidth(vg, stroke); + + nvgStrokePaint(vg, linear_grad); + nvgBeginPath(vg); + nvgMoveTo(vg, n1->x, n1->y); + nvgLineTo(vg, n2->x, n2->y); + nvgStroke(vg); + nvgRestore(vg); +} + void draw_node(NVGcontext *vg, struct node *node) { - const float r = 20.0; - const float pos = 500.0; + const float r = node->size; + /* const float pos = 500.0; */ /* const float h = r; */ /* const float w = r; */ @@ -14,13 +52,8 @@ void draw_node(NVGcontext *vg, struct node *node) /* const float y = pos; */ /* const float kr = (int)(h * 0.35f); */ /* const float cy = y + (int)(h * 0.5f); */ - - NVGpaint bg; - nvgSave(vg); - nvgTranslate(vg, pos, pos); - NVGcolor node_color = nvgRGBAf(node->color.r, node->color.g, node->color.b, node->color.a); @@ -28,6 +61,9 @@ void draw_node(NVGcontext *vg, struct node *node) NVGcolor blend = nvgRGBAf(0, 0, 0, 0.5); + nvgSave(vg); + nvgTranslate(vg, node->x, node->y); + const float light = 2.0f; bg = nvgRadialGradient(vg, -light, -light, 0, r+2.0, node_color, blend); nvgBeginPath(vg); @@ -54,6 +90,13 @@ void draw_node(NVGcontext *vg, struct node *node) void render_ln(struct ln *ln) { - for (int i = 0; i < ln->node_count; i++) - draw_node(ln->vg, &ln->nodes[i]); + u32 i; + NVGcontext *vg = ln->vg; + + // render channels first + for (i = 0; i < ln->channel_count; i++) + draw_channel(vg, &ln->channels[i]); + + for (i = 0; i < ln->node_count; i++) + draw_node(vg, &ln->nodes[i]); } diff --git a/update.c b/update.c @@ -0,0 +1,85 @@ + +#include "ln.h" +#include <math.h> +#include <stdio.h> + +struct node *hit_node(struct ln *ln) { + for (u32 i = 0; i < ln->node_count; ++i) + { + struct node *n = &ln->nodes[i]; + + const double dx = fabs(n->x - ln->mx); + const double dy = fabs(n->y - ln->my); + + const double d = sqrt(dx*dx + dy*dy); + + /* printf("%s's dx %f dy %f d %f\n", n->alias, dx, dy, d); */ + + if (d <= n->size) + return n; + } + + return NULL; +} + +// force graph update logic +void update(struct ln *ln, double dt) +{ + + // click detection + if (ln->clicked) { + struct node *hit = hit_node(ln); + + if (hit != NULL) + printf("hit %s\n", hit->alias); + + ln->drag_target = hit; + } + + // stop dragging + if (!ln->mdown && ln->drag_target) + ln->drag_target = NULL; + + // drag + if (ln->mdown && ln->drag_target) { + ln->drag_target->x = ln->mx; + ln->drag_target->y = ln->my; + } + + u32 i; + static const double friction = 0.3; + + for (i = 0; i < ln->channel_count; ++i) { + struct channel *channel = &ln->channels[i]; + + struct node *n1 = channel->nodes[0]; + struct node *n2 = channel->nodes[1]; + + double dx = n2->x - n1->x; + double dy = n2->y - n1->y; + + n1->vx += dx * 0.001; + n1->vy += dy * 0.001; + } + + for (u32 i = 0; i < ln->node_count; i++) { + struct node *node = &ln->nodes[i]; + + // semi-implicit euler + + // update node position + node->vx += node->ax * dt; + node->vy += node->ay * dt; + + // forces (testing) + if (node->vx > 0.0) + node->vx -= friction; + + if (node->vy > 0.0) + node->vy -= friction; + + node->x += node->vx * dt; + node->y += node->vy * dt; + + } +} diff --git a/update.h b/update.h @@ -0,0 +1,9 @@ + +#ifndef LNVIS_UPDATE_H +#define LNVIS_UPDATE_H + +#include "ln.h" + +void update(struct ln *ln, double dt); + +#endif /* LNVIS_UPDATE_H */