mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-08-06 22:08:41 +00:00
more integration work, of new block tree eval visualization structure w/ watch views
This commit is contained in:
@@ -539,13 +539,21 @@ ev2_block_tree_from_expr(Arena *arena, EV_View *view, String8 filter, String8 st
|
|||||||
Temp scratch = scratch_begin(&arena, 1);
|
Temp scratch = scratch_begin(&arena, 1);
|
||||||
EV_ViewRuleInfo *default_expand_view_rule_info = ev_view_rule_info_from_string(str8_lit("default"));
|
EV_ViewRuleInfo *default_expand_view_rule_info = ev_view_rule_info_from_string(str8_lit("default"));
|
||||||
|
|
||||||
|
//- rjf: form complete set of view rules
|
||||||
|
EV_ViewRuleList *top_level_view_rules = ev_view_rule_list_copy(arena, view_rules);
|
||||||
|
{
|
||||||
|
E_IRTreeAndType irtree = e_irtree_and_type_from_expr(scratch.arena, expr);
|
||||||
|
String8 auto_view_rule = ev_auto_view_rule_from_type_key(irtree.type_key);
|
||||||
|
ev_view_rule_list_push_string(arena, top_level_view_rules, auto_view_rule);
|
||||||
|
}
|
||||||
|
|
||||||
//- rjf: generate root block
|
//- rjf: generate root block
|
||||||
tree.root = push_array(arena, EV2_Block, 1);
|
tree.root = push_array(arena, EV2_Block, 1);
|
||||||
MemoryCopyStruct(tree.root, &ev2_nil_block);
|
MemoryCopyStruct(tree.root, &ev2_nil_block);
|
||||||
tree.root->key = ev_key_root();
|
tree.root->key = ev_key_root();
|
||||||
tree.root->string = string;
|
tree.root->string = string;
|
||||||
tree.root->expr = expr;
|
tree.root->expr = ev_expr_from_expr_view_rules(arena, expr, top_level_view_rules);
|
||||||
tree.root->view_rules = view_rules;
|
tree.root->view_rules = top_level_view_rules;
|
||||||
tree.root->row_count = 1;
|
tree.root->row_count = 1;
|
||||||
tree.total_row_count += 1;
|
tree.total_row_count += 1;
|
||||||
tree.total_item_count += 1;
|
tree.total_item_count += 1;
|
||||||
@@ -557,16 +565,17 @@ ev2_block_tree_from_expr(Arena *arena, EV_View *view, String8 filter, String8 st
|
|||||||
Task *next;
|
Task *next;
|
||||||
EV2_Block *parent_block;
|
EV2_Block *parent_block;
|
||||||
E_Expr *expr;
|
E_Expr *expr;
|
||||||
EV_Key key;
|
U64 child_id;
|
||||||
EV_ViewRuleList *view_rules;
|
EV_ViewRuleList *view_rules;
|
||||||
U64 split_relative_idx;
|
U64 split_relative_idx;
|
||||||
};
|
};
|
||||||
Task start_task = {0, tree.root, expr, tree.root->key, view_rules, 1};
|
Task start_task = {0, tree.root, expr, 1, top_level_view_rules, 0};
|
||||||
Task *first_task = &start_task;
|
Task *first_task = &start_task;
|
||||||
Task *last_task = first_task;
|
Task *last_task = first_task;
|
||||||
for(Task *t = first_task; t != 0; t = t->next)
|
for(Task *t = first_task; t != 0; t = t->next)
|
||||||
{
|
{
|
||||||
EV_ExpandNode *expand_node = ev_expand_node_from_key(view, t->key);
|
EV_Key key = ev_key_make(ev_hash_from_key(t->parent_block->key), t->child_id);
|
||||||
|
EV_ExpandNode *expand_node = ev_expand_node_from_key(view, key);
|
||||||
|
|
||||||
// rjf: skip if not expanded
|
// rjf: skip if not expanded
|
||||||
if(!expand_node || !expand_node->expanded)
|
if(!expand_node || !expand_node->expanded)
|
||||||
@@ -598,7 +607,7 @@ ev2_block_tree_from_expr(Arena *arena, EV_View *view, String8 filter, String8 st
|
|||||||
MemoryCopyStruct(expansion_block, &ev2_nil_block);
|
MemoryCopyStruct(expansion_block, &ev2_nil_block);
|
||||||
DLLPushBack_NPZ(&ev2_nil_block, t->parent_block->first, t->parent_block->last, expansion_block, next, prev);
|
DLLPushBack_NPZ(&ev2_nil_block, t->parent_block->first, t->parent_block->last, expansion_block, next, prev);
|
||||||
expansion_block->parent = t->parent_block;
|
expansion_block->parent = t->parent_block;
|
||||||
expansion_block->key = t->key;
|
expansion_block->key = key;
|
||||||
expansion_block->split_relative_idx = t->split_relative_idx;
|
expansion_block->split_relative_idx = t->split_relative_idx;
|
||||||
expansion_block->expr = t->expr;
|
expansion_block->expr = t->expr;
|
||||||
expansion_block->view_rules = t->view_rules;
|
expansion_block->view_rules = t->view_rules;
|
||||||
@@ -628,7 +637,7 @@ ev2_block_tree_from_expr(Arena *arena, EV_View *view, String8 filter, String8 st
|
|||||||
SLLQueuePush(first_task, last_task, task);
|
SLLQueuePush(first_task, last_task, task);
|
||||||
task->parent_block = expansion_block;
|
task->parent_block = expansion_block;
|
||||||
task->expr = child_expand.row_exprs[0];
|
task->expr = child_expand.row_exprs[0];
|
||||||
task->key = child->key;
|
task->child_id = child->key.child_num;
|
||||||
task->view_rules = child_view_rules;
|
task->view_rules = child_view_rules;
|
||||||
task->split_relative_idx = split_relative_idx;
|
task->split_relative_idx = split_relative_idx;
|
||||||
}
|
}
|
||||||
@@ -807,7 +816,7 @@ ev2_key_from_num(EV2_BlockRangeList *block_ranges, U64 num)
|
|||||||
Rng1U64 global_range = r1u64(base_num, base_num + range_size);
|
Rng1U64 global_range = r1u64(base_num, base_num + range_size);
|
||||||
if(contains_1u64(global_range, num))
|
if(contains_1u64(global_range, num))
|
||||||
{
|
{
|
||||||
U64 relative_num = (num - base_num) + 1;
|
U64 relative_num = (num - base_num) + n->v.range.min + 1;
|
||||||
U64 child_id = n->v.block->expand_view_rule_info->expr_expand_id_from_num(relative_num, n->v.block->expand_view_rule_info_user_data);
|
U64 child_id = n->v.block->expand_view_rule_info->expr_expand_id_from_num(relative_num, n->v.block->expand_view_rule_info_user_data);
|
||||||
EV_Key block_key = n->v.block->key;
|
EV_Key block_key = n->v.block->key;
|
||||||
key = ev_key_make(ev_hash_from_key(block_key), child_id);
|
key = ev_key_make(ev_hash_from_key(block_key), child_id);
|
||||||
@@ -825,15 +834,17 @@ ev2_num_from_key(EV2_BlockRangeList *block_ranges, EV_Key key)
|
|||||||
U64 base_num = 1;
|
U64 base_num = 1;
|
||||||
for(EV2_BlockRangeNode *n = block_ranges->first; n != 0; n = n->next)
|
for(EV2_BlockRangeNode *n = block_ranges->first; n != 0; n = n->next)
|
||||||
{
|
{
|
||||||
U64 range_size = dim_1u64(n->v.range);
|
|
||||||
U64 hash = ev_hash_from_key(n->v.block->key);
|
U64 hash = ev_hash_from_key(n->v.block->key);
|
||||||
if(hash == key.parent_hash)
|
if(hash == key.parent_hash)
|
||||||
{
|
{
|
||||||
U64 relative_num = n->v.block->expand_view_rule_info->expr_expand_num_from_id(key.child_num, n->v.block->expand_view_rule_info_user_data);
|
U64 relative_num = n->v.block->expand_view_rule_info->expr_expand_num_from_id(key.child_num, n->v.block->expand_view_rule_info_user_data);
|
||||||
result = (base_num - 1) + relative_num;
|
if(contains_1u64(n->v.range, relative_num-1))
|
||||||
break;
|
{
|
||||||
|
result = base_num + (relative_num - 1 - n->v.range.min);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
base_num += range_size;
|
base_num += dim_1u64(n->v.range);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -877,6 +888,10 @@ ev2_windowed_row_list_from_block_range_list(Arena *arena, EV_View *view, String8
|
|||||||
// rjf: sum & advance
|
// rjf: sum & advance
|
||||||
visual_idx_off += block_num_visual_rows;
|
visual_idx_off += block_num_visual_rows;
|
||||||
rows.count_before_visual += num_skipped;
|
rows.count_before_visual += num_skipped;
|
||||||
|
if(block_num_visual_rows != 0 && num_skipped != 0)
|
||||||
|
{
|
||||||
|
rows.count_before_semantic += n->v.block->single_item ? 1 : num_skipped;
|
||||||
|
}
|
||||||
|
|
||||||
// rjf: generate rows before next splitting child
|
// rjf: generate rows before next splitting child
|
||||||
if(block_relative_range__windowed.max > block_relative_range__windowed.min)
|
if(block_relative_range__windowed.max > block_relative_range__windowed.min)
|
||||||
@@ -888,7 +903,7 @@ ev2_windowed_row_list_from_block_range_list(Arena *arena, EV_View *view, String8
|
|||||||
SLLQueuePush(rows.first, rows.last, row);
|
SLLQueuePush(rows.first, rows.last, row);
|
||||||
rows.count += 1;
|
rows.count += 1;
|
||||||
row->block = n->v.block;
|
row->block = n->v.block;
|
||||||
row->key = n->v.block->key;
|
row->key = ev_key_make(ev_hash_from_key(row->block->key), 1);
|
||||||
row->visual_size = n->v.block->single_item ? n->v.block->row_count : 1;
|
row->visual_size = n->v.block->single_item ? n->v.block->row_count : 1;
|
||||||
row->visual_size_skipped = 0; // TODO(rjf)
|
row->visual_size_skipped = 0; // TODO(rjf)
|
||||||
row->visual_size_chopped = 0; // TODO(rjf)
|
row->visual_size_chopped = 0; // TODO(rjf)
|
||||||
@@ -904,7 +919,7 @@ ev2_windowed_row_list_from_block_range_list(Arena *arena, EV_View *view, String8
|
|||||||
EV_ExpandRangeInfo expand_range_info = n->v.block->expand_view_rule_info->expr_expand_range_info(arena, view, filter, n->v.block->expr, n->v.block->expand_view_rule_params, block_relative_range__windowed, n->v.block->expand_view_rule_info_user_data);
|
EV_ExpandRangeInfo expand_range_info = n->v.block->expand_view_rule_info->expr_expand_range_info(arena, view, filter, n->v.block->expr, n->v.block->expand_view_rule_params, block_relative_range__windowed, n->v.block->expand_view_rule_info_user_data);
|
||||||
for EachIndex(idx, expand_range_info.row_exprs_count)
|
for EachIndex(idx, expand_range_info.row_exprs_count)
|
||||||
{
|
{
|
||||||
U64 child_num = block_relative_range.min + idx + 1;
|
U64 child_num = block_relative_range.min + num_skipped + idx + 1;
|
||||||
U64 child_id = n->v.block->expand_view_rule_info->expr_expand_id_from_num(child_num, n->v.block->expand_view_rule_info_user_data);
|
U64 child_id = n->v.block->expand_view_rule_info->expr_expand_id_from_num(child_num, n->v.block->expand_view_rule_info_user_data);
|
||||||
EV2_Row *row = push_array(arena, EV2_Row, 1);
|
EV2_Row *row = push_array(arena, EV2_Row, 1);
|
||||||
SLLQueuePush(rows.first, rows.last, row);
|
SLLQueuePush(rows.first, rows.last, row);
|
||||||
|
|||||||
@@ -7743,7 +7743,7 @@ rd_window_frame(RD_Window *ws)
|
|||||||
{
|
{
|
||||||
Vec4F32 color = rd_rgba_from_theme_color(RD_ThemeColor_Focus);
|
Vec4F32 color = rd_rgba_from_theme_color(RD_ThemeColor_Focus);
|
||||||
color.w *= 0.2f*b->focus_hot_t;
|
color.w *= 0.2f*b->focus_hot_t;
|
||||||
R_Rect2DInst *inst = dr_rect(b->rect, color, 0, 0, 1.f);
|
R_Rect2DInst *inst = dr_rect(b->rect, color, 0, 0, 0.f);
|
||||||
MemoryCopyArray(inst->corner_radii, b->corner_radii);
|
MemoryCopyArray(inst->corner_radii, b->corner_radii);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11166,9 +11166,14 @@ rd_frame(void)
|
|||||||
for EachElement(idx, rd_collection_name_table)
|
for EachElement(idx, rd_collection_name_table)
|
||||||
{
|
{
|
||||||
EV_ViewRuleInfo info = {0};
|
EV_ViewRuleInfo info = {0};
|
||||||
info.string = rd_collection_name_table[idx];
|
info.string = rd_collection_name_table[idx];
|
||||||
info.flags = EV_ViewRuleInfoFlag_Expandable;
|
info.flags = EV_ViewRuleInfoFlag_Expandable;
|
||||||
info.block_prod = rd_collection_block_prod_hook_function_table[idx];
|
info.expr_resolution = EV_VIEW_RULE_EXPR_RESOLUTION_FUNCTION_NAME(identity);
|
||||||
|
info.expr_expand_info = rd_collection_expr_expand_info_hook_function_table[idx];;
|
||||||
|
info.expr_expand_range_info = rd_collection_expr_expand_range_info_hook_function_table[idx];
|
||||||
|
info.expr_expand_id_from_num = EV_VIEW_RULE_EXPR_EXPAND_ID_FROM_NUM_FUNCTION_NAME(identity);
|
||||||
|
info.expr_expand_num_from_id = EV_VIEW_RULE_EXPR_EXPAND_NUM_FROM_ID_FUNCTION_NAME(identity);
|
||||||
|
info.block_prod = rd_collection_block_prod_hook_function_table[idx];
|
||||||
ev_view_rule_info_table_push(scratch.arena, view_rule_info_table, &info);
|
ev_view_rule_info_table_push(scratch.arena, view_rule_info_table, &info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1049,7 +1049,7 @@ rd_watch_view_build(RD_WatchViewState *ewv, RD_WatchViewFlags flags, String8 roo
|
|||||||
{
|
{
|
||||||
MemoryZeroStruct(&block_tree);
|
MemoryZeroStruct(&block_tree);
|
||||||
MemoryZeroStruct(&block_ranges);
|
MemoryZeroStruct(&block_ranges);
|
||||||
ev_key_set_expansion(eval_view, ev_key_zero(), ev_key_root(), 1);
|
ev_key_set_expansion(eval_view, ev_key_root(), ev_key_make(ev_hash_from_key(ev_key_root()), 1), 1);
|
||||||
block_tree = ev2_block_tree_from_string(scratch.arena, eval_view, filter, root_expr, top_level_view_rules);
|
block_tree = ev2_block_tree_from_string(scratch.arena, eval_view, filter, root_expr, top_level_view_rules);
|
||||||
block_ranges = ev2_block_range_list_from_tree(scratch.arena, &block_tree);
|
block_ranges = ev2_block_range_list_from_tree(scratch.arena, &block_tree);
|
||||||
#if 0 // TODO(rjf): @blocks
|
#if 0 // TODO(rjf): @blocks
|
||||||
@@ -1086,7 +1086,7 @@ rd_watch_view_build(RD_WatchViewState *ewv, RD_WatchViewFlags flags, String8 roo
|
|||||||
for(EV2_BlockRangeNode *n = block_ranges.first; n != 0; n = n->next)
|
for(EV2_BlockRangeNode *n = block_ranges.first; n != 0; n = n->next)
|
||||||
{
|
{
|
||||||
UI_ScrollListRowBlock block = {0};
|
UI_ScrollListRowBlock block = {0};
|
||||||
block.row_count = n->v.block->row_count;
|
block.row_count = dim_1u64(n->v.range);
|
||||||
block.item_count = n->v.block->single_item ? 1 : dim_1u64(n->v.range);
|
block.item_count = n->v.block->single_item ? 1 : dim_1u64(n->v.range);
|
||||||
ui_scroll_list_row_block_chunk_list_push(scratch.arena, &row_block_chunks, 256, &block);
|
ui_scroll_list_row_block_chunk_list_push(scratch.arena, &row_block_chunks, 256, &block);
|
||||||
}
|
}
|
||||||
@@ -1859,7 +1859,7 @@ rd_watch_view_build(RD_WatchViewState *ewv, RD_WatchViewFlags flags, String8 roo
|
|||||||
//
|
//
|
||||||
EV2_WindowedRowList rows = {0};
|
EV2_WindowedRowList rows = {0};
|
||||||
{
|
{
|
||||||
rows = ev2_windowed_row_list_from_block_range_list(scratch.arena, eval_view, filter, &block_ranges, r1u64(visible_row_rng.min, visible_row_rng.max + 1));
|
rows = ev2_windowed_row_list_from_block_range_list(scratch.arena, eval_view, filter, &block_ranges, r1u64(visible_row_rng.min, visible_row_rng.max+1));
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////
|
////////////////////////////
|
||||||
@@ -1867,15 +1867,15 @@ rd_watch_view_build(RD_WatchViewState *ewv, RD_WatchViewFlags flags, String8 roo
|
|||||||
//
|
//
|
||||||
ProfScope("build table")
|
ProfScope("build table")
|
||||||
{
|
{
|
||||||
U64 semantic_idx = rows.count_before_semantic;
|
U64 semantic_num = rows.count_before_semantic + 1;
|
||||||
for(EV2_Row *row = rows.first; row != 0; row = row->next, semantic_idx += 1)
|
for(EV2_Row *row = rows.first; row != 0; row = row->next, semantic_num += 1)
|
||||||
{
|
{
|
||||||
////////////////////////
|
////////////////////////
|
||||||
//- rjf: unpack row info
|
//- rjf: unpack row info
|
||||||
//
|
//
|
||||||
U64 row_hash = ev_hash_from_key(row->key);
|
U64 row_hash = ev_hash_from_key(row->key);
|
||||||
U64 row_depth = ev2_depth_from_block(row->block);
|
U64 row_depth = ev2_depth_from_block(row->block);
|
||||||
B32 row_selected = (selection_tbl.min.y <= (semantic_idx+1) && (semantic_idx+1) <= selection_tbl.max.y);
|
B32 row_selected = (selection_tbl.min.y <= semantic_num && semantic_num <= selection_tbl.max.y);
|
||||||
B32 row_expanded = ev_expansion_from_key(eval_view, row->key);
|
B32 row_expanded = ev_expansion_from_key(eval_view, row->key);
|
||||||
E_Eval row_eval = e_eval_from_expr(scratch.arena, row->expr);
|
E_Eval row_eval = e_eval_from_expr(scratch.arena, row->expr);
|
||||||
E_Type *row_type = e_type_from_key(scratch.arena, row_eval.type_key);
|
E_Type *row_type = e_type_from_key(scratch.arena, row_eval.type_key);
|
||||||
@@ -1933,7 +1933,7 @@ rd_watch_view_build(RD_WatchViewState *ewv, RD_WatchViewFlags flags, String8 roo
|
|||||||
palette = ui_build_palette(ui_top_palette(), .background = rd_rgba_from_theme_color(RD_ThemeColor_HighlightOverlay));
|
palette = ui_build_palette(ui_top_palette(), .background = rd_rgba_from_theme_color(RD_ThemeColor_HighlightOverlay));
|
||||||
row_flags |= UI_BoxFlag_DrawBackground;
|
row_flags |= UI_BoxFlag_DrawBackground;
|
||||||
}
|
}
|
||||||
else if(semantic_idx & 1)
|
else if(semantic_num & 1)
|
||||||
{
|
{
|
||||||
palette = ui_build_palette(ui_top_palette(), .background = rd_rgba_from_theme_color(RD_ThemeColor_BaseBackgroundAlt));
|
palette = ui_build_palette(ui_top_palette(), .background = rd_rgba_from_theme_color(RD_ThemeColor_BaseBackgroundAlt));
|
||||||
row_flags |= UI_BoxFlag_DrawBackground;
|
row_flags |= UI_BoxFlag_DrawBackground;
|
||||||
@@ -2223,7 +2223,7 @@ rd_watch_view_build(RD_WatchViewState *ewv, RD_WatchViewFlags flags, String8 roo
|
|||||||
}break;
|
}break;
|
||||||
case RD_WatchViewColumnKind_FrameSelection:
|
case RD_WatchViewColumnKind_FrameSelection:
|
||||||
{
|
{
|
||||||
if(semantic_idx == rd_regs()->unwind_count - rd_regs()->inline_depth)
|
if(semantic_num - 1 == rd_regs()->unwind_count - rd_regs()->inline_depth)
|
||||||
{
|
{
|
||||||
cell_icon = RD_IconKind_RightArrow;
|
cell_icon = RD_IconKind_RightArrow;
|
||||||
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->thread);
|
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->thread);
|
||||||
@@ -2422,7 +2422,15 @@ rd_watch_view_build(RD_WatchViewState *ewv, RD_WatchViewFlags flags, String8 roo
|
|||||||
//- rjf: bump x pixel coordinate
|
//- rjf: bump x pixel coordinate
|
||||||
x_px += col->pct*dim_2f32(rect).x;
|
x_px += col->pct*dim_2f32(rect).x;
|
||||||
|
|
||||||
//- rjf: [DEV] hovering -> tooltips
|
//- rjf: [DEV] hovering -> watch key tooltips
|
||||||
|
if(DEV_eval_watch_key_tooltips && ui_hovering(sig)) UI_Tooltip RD_Font(RD_FontSlot_Code)
|
||||||
|
{
|
||||||
|
ui_labelf("Block Key: {0x%I64x, %I64u}", row->block->key.parent_hash, row->block->key.child_num);
|
||||||
|
ui_labelf("Row Key: {0x%I64x, %I64u}", row->key.parent_hash, row->key.child_num);
|
||||||
|
ui_labelf("Cursor Key: {0x%I64x, %I64u}", ewv->cursor.key.parent_hash, ewv->cursor.key.child_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
//- rjf: [DEV] hovering -> eval system tooltips
|
||||||
if(DEV_eval_compiler_tooltips && x == 0 && ui_hovering(sig)) UI_Tooltip RD_Font(RD_FontSlot_Code)
|
if(DEV_eval_compiler_tooltips && x == 0 && ui_hovering(sig)) UI_Tooltip RD_Font(RD_FontSlot_Code)
|
||||||
{
|
{
|
||||||
local_persist char *spaces = " ";
|
local_persist char *spaces = " ";
|
||||||
|
|||||||
@@ -1453,8 +1453,8 @@ ui_scroll_list_begin(UI_ScrollListParams *params, UI_ScrollPt *scroll_pt, Vec2S6
|
|||||||
//- rjf: determine ranges & limits
|
//- rjf: determine ranges & limits
|
||||||
Rng1S64 visible_row_range = r1s64(scroll_pt->idx + (S64)(scroll_pt->off) + 0 - !!(scroll_pt->off < 0),
|
Rng1S64 visible_row_range = r1s64(scroll_pt->idx + (S64)(scroll_pt->off) + 0 - !!(scroll_pt->off < 0),
|
||||||
scroll_pt->idx + (S64)(scroll_pt->off) + 0 + num_possible_visible_rows + 1);
|
scroll_pt->idx + (S64)(scroll_pt->off) + 0 + num_possible_visible_rows + 1);
|
||||||
visible_row_range.min = clamp_1s64(scroll_row_idx_range, visible_row_range.min);
|
visible_row_range.min = clamp_1s64(params->item_range, visible_row_range.min);
|
||||||
visible_row_range.max = clamp_1s64(scroll_row_idx_range, visible_row_range.max);
|
visible_row_range.max = clamp_1s64(params->item_range, visible_row_range.max);
|
||||||
*visible_row_range_out = visible_row_range;
|
*visible_row_range_out = visible_row_range;
|
||||||
|
|
||||||
//- rjf: store thread-locals
|
//- rjf: store thread-locals
|
||||||
|
|||||||
Reference in New Issue
Block a user