| 505 | /* ── Public API ───────────────────────────────────────────────── */ |
| 506 | |
| 507 | cbm_layout_result_t *cbm_layout_compute(cbm_store_t *store, const char *project, |
| 508 | cbm_layout_level_t level, const char *center_node, |
| 509 | int radius, int max_nodes) { |
| 510 | if (!store || !project) |
| 511 | return NULL; |
| 512 | max_nodes = clamp_max_nodes(max_nodes); |
| 513 | (void)center_node; |
| 514 | (void)radius; |
| 515 | (void)level; |
| 516 | |
| 517 | /* 1. Query nodes */ |
| 518 | cbm_search_params_t params; |
| 519 | memset(¶ms, 0, sizeof(params)); |
| 520 | params.project = project; |
| 521 | params.limit = max_nodes; |
| 522 | params.min_degree = -1; |
| 523 | params.max_degree = -1; |
| 524 | |
| 525 | cbm_search_output_t search_out; |
| 526 | memset(&search_out, 0, sizeof(search_out)); |
| 527 | if (cbm_store_search(store, ¶ms, &search_out) != CBM_STORE_OK) |
| 528 | return calloc(CBM_ALLOC_ONE, sizeof(cbm_layout_result_t)); |
| 529 | |
| 530 | int n = search_out.count, total_count = search_out.total; |
| 531 | if (n == 0) { |
| 532 | cbm_store_search_free(&search_out); |
| 533 | cbm_layout_result_t *r = calloc(CBM_ALLOC_ONE, sizeof(*r)); |
| 534 | if (r) |
| 535 | r->total_nodes = total_count; |
| 536 | return r; |
| 537 | } |
| 538 | |
| 539 | /* 2. Build sorted node-ID → index map for O(log n) edge filtering */ |
| 540 | node_id_entry_t *id_map = malloc((size_t)n * sizeof(node_id_entry_t)); |
| 541 | if (!id_map) { |
| 542 | cbm_store_search_free(&search_out); |
| 543 | cbm_layout_result_t *r = calloc(CBM_ALLOC_ONE, sizeof(*r)); |
| 544 | if (r) { |
| 545 | r->total_nodes = total_count; |
| 546 | } |
| 547 | return r; |
| 548 | } |
| 549 | for (int i = 0; i < n; i++) { |
| 550 | id_map[i].id = search_out.results[i].node.id; |
| 551 | id_map[i].idx = i; |
| 552 | } |
| 553 | qsort(id_map, (size_t)n, sizeof(node_id_entry_t), cmp_node_id_entry); |
| 554 | |
| 555 | /* 3. Query edges — filter during fetch via binary search (O(e log n)) */ |
| 556 | int *deg = calloc((size_t)n, sizeof(int)); |
| 557 | int mapped = 0; |
| 558 | int edge_cap = CBM_SZ_256; |
| 559 | cbm_edge_t *all_edges = malloc((size_t)edge_cap * sizeof(cbm_edge_t)); |
| 560 | int *es = malloc((size_t)edge_cap * sizeof(int)); |
| 561 | int *ed = malloc((size_t)edge_cap * sizeof(int)); |
| 562 | cbm_schema_info_t schema; |
| 563 | memset(&schema, 0, sizeof(schema)); |
| 564 | if (deg && all_edges && es && ed && |