1 /* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */
10 #include "vg/vg_imgui.h"
11 #include "vg/vg_log.h"
13 #define VG_VAR_F32( NAME, ... ) \
14 { u32 flags=0x00; __VA_ARGS__ ;\
15 vg_console_reg_var( #NAME, &NAME, k_var_dtype_f32, flags ); }
17 #define VG_VAR_I32( NAME, ... ) \
18 { u32 flags=0x00; __VA_ARGS__ ;\
19 vg_console_reg_var( #NAME, &NAME, k_var_dtype_i32, flags ); }
21 #define VG_VAR_PERSISTENT 0x1
22 #define VG_VAR_CHEAT 0x2
23 #define VG_VAR_CHEATTOGGLE 0x4
25 typedef struct vg_var vg_var
;
26 typedef struct vg_cmd vg_cmd
;
41 union{ u32 _u32
; f32 _f32
; i32 _i32
; } defaults
;
46 int (*function
)( int argc
, char const *argv
[] );
47 void (*poll_suggest
)( int argc
, char const *argv
[] );
60 int suggestion_select
,
64 u32 var_count
, function_count
;
70 int history_last
, history_pos
, history_count
;
76 static void _vg_console_draw( void );
77 void _vg_console_println( const char *str
);
78 static int _vg_console_list( int argc
, char const *argv
[] );
79 static void _vg_console_init(void);
80 static void _vg_console_write_persistent(void);
81 static void _vg_console_free(void);
82 static void vg_execute_console_input( const char *cmd
);
87 static void console_history_get( char* buf
, int entry_num
);
88 static int _vg_console_enabled(void);
89 static void console_proc_key( SDL_Keysym ev
);
94 static int _vg_console_enabled(void){
95 return vg_console
.enabled
;
99 void vg_console_reg_var( const char *alias
, void *ptr
, enum vg_var_dtype type
,
102 if( vg_console
.var_count
> vg_list_size(vg_console
.vars
) )
103 vg_fatal_error( "Too many vars registered" );
105 vg_var
*var
= &vg_console
.vars
[ vg_console
.var_count
++ ];
108 var
->data_type
= type
;
111 if ( type
== k_var_dtype_f32
) var
->defaults
._f32
= *((f32
*)ptr
);
112 else if( type
== k_var_dtype_i32
) var
->defaults
._i32
= *((i32
*)ptr
);
113 else if( type
== k_var_dtype_u32
) var
->defaults
._u32
= *((u32
*)ptr
);
115 vg_info( "Console variable '%s' registered\n", alias
);
119 void vg_console_reg_cmd( const char *alias
,
120 int (*function
)(int argc
, const char *argv
[]),
121 void (*poll_suggest
)(int argc
, const char *argv
[]) )
123 if( vg_console
.function_count
> vg_list_size(vg_console
.functions
) )
124 vg_fatal_error( "Too many functions registered" );
126 vg_cmd
*cmd
= &vg_console
.functions
[ vg_console
.function_count
++ ];
128 cmd
->function
= function
;
129 cmd
->poll_suggest
= poll_suggest
;
132 vg_info( "Console function '%s' registered\n", alias
);
135 static int _vg_console_list( int argc
, char const *argv
[] ){
136 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
137 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
138 vg_info( "* %s\n", cmd
->name
);
141 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
142 struct vg_var
*cv
= &vg_console
.vars
[ i
];
143 vg_info( "%s\n", cv
->name
);
149 int _test_break( int argc
, const char *argv
[] ){
150 vg_fatal_error( "Test crash from main, after loading (console)" );
154 int _vg_console_exec( int argc
, const char *argv
[] ){
155 if( argc
< 1 ) return 0;
158 strcpy( path
, "cfg/" );
159 strncat( path
, argv
[0], 250 );
161 FILE *fp
= fopen( path
, "r" );
165 while( fgets( line
, sizeof( line
), fp
) ){
166 line
[ strcspn( line
, "\r\n#" ) ] = 0x00;
168 if( line
[0] != 0x00 ){
169 vg_execute_console_input( line
);
176 vg_error( "Could not open '%s'\n", path
);
182 int _ccmd_vg_console_defaults( int argc
, const char *argv
[] ){
183 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
184 struct vg_var
*cv
= &vg_console
.vars
[i
];
186 enum vg_var_dtype type
= cv
->data_type
;
187 void *ptr
= cv
->data
;
189 if ( type
== k_var_dtype_f32
) *((f32
*)ptr
) = cv
->defaults
._f32
;
190 else if( type
== k_var_dtype_i32
) *((i32
*)ptr
) = cv
->defaults
._i32
;
191 else if( type
== k_var_dtype_u32
) *((u32
*)ptr
) = cv
->defaults
._u32
;
197 static void _vg_console_init(void){
198 vg_console_reg_cmd( "list", _vg_console_list
, NULL
);
199 vg_console_reg_cmd( "crash", _test_break
, NULL
);
200 vg_console_reg_cmd( "exec", _vg_console_exec
, NULL
);
201 vg_console_reg_cmd( "defaults", _ccmd_vg_console_defaults
, NULL
);
202 vg_console_reg_var( "cheats", &vg_console
.cheats
, k_var_dtype_i32
,
211 static void vg_console_load_autos(void){
212 _vg_console_exec( 1, (const char *[]){ "auto.conf" } );
215 static void _vg_console_write_persistent(void){
216 FILE *fp
= fopen( "cfg/auto.conf", "w" );
218 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
219 struct vg_var
*cv
= &vg_console
.vars
[i
];
221 if( cv
->flags
& VG_VAR_PERSISTENT
){
222 switch( cv
->data_type
){
223 case k_var_dtype_i32
:
224 fprintf( fp
, "%s %d\n", cv
->name
, *(i32
*)(cv
->data
) );
226 case k_var_dtype_u32
:
227 fprintf( fp
, "%s %u\n", cv
->name
, *(u32
*)(cv
->data
) );
229 case k_var_dtype_f32
:
230 fprintf( fp
, "%s %.5f\n", cv
->name
, *(float *)(cv
->data
) );
239 static void _vg_console_free(void)
241 _vg_console_write_persistent();
245 * splits src into tokens and fills out args as pointers to those tokens
246 * returns number of tokens
247 * dst must be as long as src
249 static int vg_console_tokenize( const char *src
, char *dst
,
250 const char *args
[8] )
255 for( int i
=0;; i
++ ){
257 if( src
[i
] == ' ' || src
[i
] == '\t' ){
270 args
[ arg_count
++ ] = &dst
[i
];
284 static vg_var
*vg_console_match_var( const char *kw
)
286 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
287 struct vg_var
*cv
= &vg_console
.vars
[ i
];
288 if( !strcmp( cv
->name
, kw
) ){
296 static vg_cmd
*vg_console_match_cmd( const char *kw
)
298 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
299 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
300 if( !strcmp( cmd
->name
, kw
) ){
308 static void vg_execute_console_input( const char *cmd
)
312 int arg_count
= vg_console_tokenize( cmd
, temp
, args
);
317 vg_var
*cv
= vg_console_match_var( args
[0] );
318 vg_cmd
*fn
= vg_console_match_cmd( args
[0] );
320 assert( !(cv
&& fn
) );
323 /* Cvar Matched, try get value */
324 if( arg_count
>= 2 ){
325 if( cv
->flags
& VG_VAR_CHEAT
){
326 if( !vg_console
.cheats
){
327 vg_error( "variable is cheat protected\n" );
332 if( (cv
->data_type
== k_var_dtype_u32
) ||
333 (cv
->data_type
== k_var_dtype_i32
) )
336 *ptr
= atoi( args
[1] );
338 if( cv
->flags
& VG_VAR_CHEATTOGGLE
){
340 _ccmd_vg_console_defaults( 0, NULL
);
344 else if( cv
->data_type
== k_var_dtype_f32
){
345 float *ptr
= cv
->data
;
346 *ptr
= atof( args
[1] );
350 if( cv
->data_type
== k_var_dtype_i32
)
351 vg_info( "= %d\n", *((int *)cv
->data
) );
352 else if( cv
->data_type
== k_var_dtype_u32
)
353 vg_info( "= %u\n", *((u32
*)cv
->data
) );
354 else if( cv
->data_type
== k_var_dtype_f32
)
355 vg_info( "= %.4f\n", *((float *)cv
->data
) );
361 fn
->function( arg_count
-1, args
+1 );
365 vg_error( "No command/var named '%s'. Use 'list' to view all\n", args
[0] );
368 u32
str_lev_distance( const char *s1
, const char *s2
){
369 u32 m
= strlen( s1
),
375 assert( n
+1 <= 256 );
379 for( u32 k
=0; k
<=n
; k
++ )
383 for( u32 i
=0; i
<m
; i
++ ){
388 for( u32 j
=0; j
<n
; j
++ ){
389 u32 upper
= costs
[j
+1];
392 costs
[ j
+1 ] = corner
;
394 u32 t
= (upper
< corner
)? upper
: corner
;
395 costs
[j
+1] = ((costs
[j
] < t
)? costs
[j
]: t
) + 1;
405 u32
str_lcs( const char *s1
, const char *s2
){
406 u32 m
= VG_MIN( 31, strlen( s1
) ),
407 n
= VG_MIN( 31, strlen( s2
) );
412 for( int i
=0; i
<=m
; i
++ ){
413 for( int j
=0; j
<=n
; j
++ ){
414 if( i
== 0 || j
== 0 )
416 else if( s1
[i
-1] == s2
[j
-1] ){
417 suff
[i
][j
] = suff
[i
-1][j
-1] + 1;
418 result
= VG_MAX( result
, suff
[i
][j
] );
428 /* str must not fuckoff ever! */
429 static void console_suggest_score_text( const char *str
, const char *input
,
432 /* filter duplicates */
433 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ )
434 if( !strcmp( vg_console
.suggestions
[i
].str
, str
) )
438 u32 score
= str_lcs( str
, input
);
440 if( score
< minscore
)
443 int best_pos
= vg_console
.suggestion_count
;
444 for( int j
=best_pos
-1; j
>=0; j
-- )
445 if( score
> vg_console
.suggestions
[j
].lev_score
)
448 /* insert if good score */
449 if( best_pos
< vg_list_size( vg_console
.suggestions
) ){
450 int start
= VG_MIN( vg_console
.suggestion_count
,
451 vg_list_size( vg_console
.suggestions
)-1 );
452 for( int j
=start
; j
>best_pos
; j
-- )
453 vg_console
.suggestions
[j
] = vg_console
.suggestions
[j
-1];
455 vg_console
.suggestions
[ best_pos
].str
= str
;
456 vg_console
.suggestions
[ best_pos
].len
= strlen( str
);
457 vg_console
.suggestions
[ best_pos
].lev_score
= score
;
459 if( vg_console
.suggestion_count
<
460 vg_list_size( vg_console
.suggestions
) )
461 vg_console
.suggestion_count
++;
465 static void console_update_suggestions(void)
467 if( vg_ui
.focused_control_type
!= k_ui_control_textbox
||
468 vg_ui
.textbuf
!= vg_console
.input
)
471 vg_console
.suggestion_count
= 0;
472 vg_console
.suggestion_select
= -1;
473 vg_console
.suggestion_maxlen
= 0;
476 * - must be typing something
477 * - must be at the end
478 * - prev char must not be a whitespace
479 * - cursors should match
482 if( vg_ui
.textbox
.cursor_pos
== 0 ) return;
483 if( vg_ui
.textbox
.cursor_pos
!= vg_ui
.textbox
.cursor_user
) return;
484 if( vg_console
.input
[ vg_ui
.textbox
.cursor_pos
] != '\0' ) return;
486 if( (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == ' ') ||
487 (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == '\t') )
493 int token_count
= vg_console_tokenize( vg_console
.input
, temp
, args
);
494 if( !token_count
) return;
495 vg_console
.suggestion_pastepos
= args
[token_count
-1]-temp
;
497 /* Score all our commands and cvars */
498 if( token_count
== 1 ){
499 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
500 vg_var
*cvar
= &vg_console
.vars
[i
];
501 console_suggest_score_text( cvar
->name
, args
[0], 1 );
504 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
505 vg_cmd
*cmd
= &vg_console
.functions
[i
];
506 console_suggest_score_text( cmd
->name
, args
[0], 1 );
510 vg_cmd
*cmd
= vg_console_match_cmd( args
[0] );
511 vg_var
*var
= vg_console_match_var( args
[0] );
513 assert( !( cmd
&& var
) );
516 if( cmd
->poll_suggest
)
517 cmd
->poll_suggest( token_count
-1, &args
[1] );
520 /* some post processing */
521 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
522 vg_console
.suggestion_maxlen
= VG_MAX( vg_console
.suggestion_maxlen
,
523 vg_console
.suggestions
[i
].len
);
525 if( vg_console
.suggestions
[i
].lev_score
<
526 vg_console
.suggestions
[0].lev_score
/2 )
528 vg_console
.suggestion_count
= i
;
535 * Suggestion controls
537 static void _console_fetch_suggestion(void)
539 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
541 if( vg_console
.suggestion_select
== -1 ){
542 strcpy( target
, vg_console
.input_copy
);
543 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
544 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
548 vg_console
.suggestions
[ vg_console
.suggestion_select
].str
,
549 vg_list_size( vg_console
.input
)-1 );
551 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
552 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
553 _ui_textbox_put_char( ' ' );
557 static void _console_suggest_store_normal(void)
559 if( vg_console
.suggestion_select
== -1 ){
560 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
561 strcpy( vg_console
.input_copy
, target
);
565 static void _console_suggest_next(void)
567 if( vg_console
.suggestion_count
){
568 _console_suggest_store_normal();
570 vg_console
.suggestion_select
++;
572 if( vg_console
.suggestion_select
>= vg_console
.suggestion_count
)
573 vg_console
.suggestion_select
= -1;
575 _console_fetch_suggestion();
579 static void _console_suggest_prev(void)
581 if( vg_console
.suggestion_count
){
582 _console_suggest_store_normal();
584 vg_console
.suggestion_select
--;
586 if( vg_console
.suggestion_select
< -1 )
587 vg_console
.suggestion_select
= vg_console
.suggestion_count
-1;
589 _console_fetch_suggestion();
593 static void _vg_console_on_update( char *buf
, u32 len
)
595 if( buf
== vg_console
.input
){
596 console_update_suggestions();
600 static void console_history_get( char* buf
, int entry_num
)
602 if( !vg_console
.history_count
)
605 int offset
= VG_MIN( entry_num
, vg_console
.history_count
-1 ),
606 pick
= (vg_console
.history_last
- offset
) %
607 vg_list_size( vg_console
.history
);
608 strcpy( buf
, vg_console
.history
[ pick
] );
611 static void _vg_console_on_up( char *buf
, u32 len
)
613 if( buf
== vg_console
.input
){
614 vg_console
.history_pos
=
620 vg_console
.history_pos
+1,
623 vg_list_size( vg_console
.history
),
624 vg_console
.history_count
- 1
629 console_history_get( vg_console
.input
, vg_console
.history_pos
);
630 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
631 &vg_ui
.textbox
.cursor_pos
,
632 vg_list_size(vg_console
.input
)-1, 1);
636 static void _vg_console_on_down( char *buf
, u32 len
)
638 if( buf
== vg_console
.input
){
639 vg_console
.history_pos
= VG_MAX( 0, vg_console
.history_pos
-1 );
640 console_history_get( vg_console
.input
, vg_console
.history_pos
);
642 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
643 &vg_ui
.textbox
.cursor_pos
,
644 vg_list_size(vg_console
.input
)-1, 1 );
648 static void _vg_console_on_enter( char *buf
, u32 len
)
650 if( buf
== vg_console
.input
){
651 if( !strlen( vg_console
.input
) )
654 vg_info( "%s\n", vg_console
.input
);
656 if( strcmp( vg_console
.input
,
657 vg_console
.history
[ vg_console
.history_last
]) )
659 vg_console
.history_last
= ( vg_console
.history_last
+ 1) %
660 vg_list_size(vg_console
.history
);
661 vg_console
.history_count
=
662 VG_MIN( vg_list_size( vg_console
.history
),
663 vg_console
.history_count
+ 1 );
664 strcpy( vg_console
.history
[ vg_console
.history_last
],
668 vg_console
.history_pos
= -1;
669 vg_execute_console_input( vg_console
.input
);
670 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
671 &vg_ui
.textbox
.cursor_pos
, -10000, 1 );
673 vg_console
.input
[0] = '\0';
674 console_update_suggestions();
678 static void _vg_console_draw(void)
680 if( !vg_console
.enabled
) return;
682 SDL_AtomicLock( &log_print_sl
);
684 int ptr
= vg_log
.log_line_current
;
685 int const fh
= vg_ui
.font
->line_height
, log_lines
= 32;
686 int console_lines
= VG_MIN( log_lines
, vg_log
.log_line_count
);
688 ui_rect rect_log
= { 0, 0, vg
.window_x
, log_lines
*fh
},
689 rect_input
= { 0, log_lines
*fh
+ 1, vg
.window_x
, fh
*2 },
690 rect_line
= { 0, 0, vg
.window_x
, fh
};
695 u32 bg_colour
= (ui_colour( k_ui_bg
)&0x00ffffff)|0x9f000000;
697 ui_fill( rect_log
, bg_colour
);
698 rect_line
[1] = rect_log
[1]+rect_log
[3]-fh
;
700 for( int i
=0; i
<console_lines
; i
++ ){
703 if( ptr
< 0 ) ptr
= vg_list_size( vg_log
.log
)-1;
705 ui_text( rect_line
, vg_log
.log
[ptr
], 1, k_ui_align_left
, 0 );
713 struct ui_textbox_callbacks callbacks
= {
714 .up
= _vg_console_on_up
,
715 .down
= _vg_console_on_down
,
716 .change
= _vg_console_on_update
,
717 .enter
= _vg_console_on_enter
719 ui_textbox( rect_input
, NULL
,
720 vg_console
.input
, vg_list_size(vg_console
.input
), 1,
721 UI_TEXTBOX_AUTOFOCUS
, &callbacks
);
726 if( vg_console
.suggestion_count
){
727 ui_rect rect_suggest
;
728 rect_copy( rect_input
, rect_suggest
);
730 rect_suggest
[0] += 6 + vg_ui
.font
->spacing
*vg_console
.suggestion_pastepos
;
731 rect_suggest
[1] += rect_input
[3];
732 rect_suggest
[2] = vg_ui
.font
->spacing
* vg_console
.suggestion_maxlen
;
733 rect_suggest
[3] = vg_console
.suggestion_count
* fh
;
735 ui_fill( rect_suggest
, bg_colour
);
737 rect_suggest
[3] = fh
;
739 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
741 if( i
== vg_console
.suggestion_select
){
742 ui_fill( rect_suggest
, ui_colour( k_ui_orange
) );
743 text_colour
= ui_colourcont( k_ui_orange
);
745 else text_colour
= ui_colourcont( k_ui_bg
);
747 ui_text( rect_suggest
, vg_console
.suggestions
[i
].str
, 1,
748 k_ui_align_left
, text_colour
);
750 rect_suggest
[1] += fh
;
754 SDL_AtomicUnlock( &log_print_sl
);
758 #endif /* VG_CONSOLE_H */