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" );
219 vg_error( "Cannot open cfg/auto.conf\n" );
223 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
224 struct vg_var
*cv
= &vg_console
.vars
[i
];
226 if( cv
->flags
& VG_VAR_PERSISTENT
){
227 switch( cv
->data_type
){
228 case k_var_dtype_i32
:
229 fprintf( fp
, "%s %d\n", cv
->name
, *(i32
*)(cv
->data
) );
231 case k_var_dtype_u32
:
232 fprintf( fp
, "%s %u\n", cv
->name
, *(u32
*)(cv
->data
) );
234 case k_var_dtype_f32
:
235 fprintf( fp
, "%s %.5f\n", cv
->name
, *(float *)(cv
->data
) );
244 static void _vg_console_free(void)
246 _vg_console_write_persistent();
250 * splits src into tokens and fills out args as pointers to those tokens
251 * returns number of tokens
252 * dst must be as long as src
254 static int vg_console_tokenize( const char *src
, char *dst
,
255 const char *args
[8] )
260 for( int i
=0;; i
++ ){
262 if( src
[i
] == ' ' || src
[i
] == '\t' ){
275 args
[ arg_count
++ ] = &dst
[i
];
289 static vg_var
*vg_console_match_var( const char *kw
)
291 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
292 struct vg_var
*cv
= &vg_console
.vars
[ i
];
293 if( !strcmp( cv
->name
, kw
) ){
301 static vg_cmd
*vg_console_match_cmd( const char *kw
)
303 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
304 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
305 if( !strcmp( cmd
->name
, kw
) ){
313 static void vg_execute_console_input( const char *cmd
)
317 int arg_count
= vg_console_tokenize( cmd
, temp
, args
);
322 vg_var
*cv
= vg_console_match_var( args
[0] );
323 vg_cmd
*fn
= vg_console_match_cmd( args
[0] );
325 assert( !(cv
&& fn
) );
328 /* Cvar Matched, try get value */
329 if( arg_count
>= 2 ){
330 if( cv
->flags
& VG_VAR_CHEAT
){
331 if( !vg_console
.cheats
){
332 vg_error( "variable is cheat protected\n" );
337 if( (cv
->data_type
== k_var_dtype_u32
) ||
338 (cv
->data_type
== k_var_dtype_i32
) )
341 *ptr
= atoi( args
[1] );
343 if( cv
->flags
& VG_VAR_CHEATTOGGLE
){
345 _ccmd_vg_console_defaults( 0, NULL
);
349 else if( cv
->data_type
== k_var_dtype_f32
){
350 float *ptr
= cv
->data
;
351 *ptr
= atof( args
[1] );
355 if( cv
->data_type
== k_var_dtype_i32
)
356 vg_info( "= %d\n", *((int *)cv
->data
) );
357 else if( cv
->data_type
== k_var_dtype_u32
)
358 vg_info( "= %u\n", *((u32
*)cv
->data
) );
359 else if( cv
->data_type
== k_var_dtype_f32
)
360 vg_info( "= %.4f\n", *((float *)cv
->data
) );
366 fn
->function( arg_count
-1, args
+1 );
370 vg_error( "No command/var named '%s'. Use 'list' to view all\n", args
[0] );
373 u32
str_lev_distance( const char *s1
, const char *s2
){
374 u32 m
= strlen( s1
),
380 assert( n
+1 <= 256 );
384 for( u32 k
=0; k
<=n
; k
++ )
388 for( u32 i
=0; i
<m
; i
++ ){
393 for( u32 j
=0; j
<n
; j
++ ){
394 u32 upper
= costs
[j
+1];
397 costs
[ j
+1 ] = corner
;
399 u32 t
= (upper
< corner
)? upper
: corner
;
400 costs
[j
+1] = ((costs
[j
] < t
)? costs
[j
]: t
) + 1;
410 u32
str_lcs( const char *s1
, const char *s2
){
411 u32 m
= VG_MIN( 31, strlen( s1
) ),
412 n
= VG_MIN( 31, strlen( s2
) );
417 for( int i
=0; i
<=m
; i
++ ){
418 for( int j
=0; j
<=n
; j
++ ){
419 if( i
== 0 || j
== 0 )
421 else if( s1
[i
-1] == s2
[j
-1] ){
422 suff
[i
][j
] = suff
[i
-1][j
-1] + 1;
423 result
= VG_MAX( result
, suff
[i
][j
] );
433 /* str must not fuckoff ever! */
434 static void console_suggest_score_text( const char *str
, const char *input
,
437 /* filter duplicates */
438 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ )
439 if( !strcmp( vg_console
.suggestions
[i
].str
, str
) )
443 u32 score
= str_lcs( str
, input
);
445 if( score
< minscore
)
448 int best_pos
= vg_console
.suggestion_count
;
449 for( int j
=best_pos
-1; j
>=0; j
-- )
450 if( score
> vg_console
.suggestions
[j
].lev_score
)
453 /* insert if good score */
454 if( best_pos
< vg_list_size( vg_console
.suggestions
) ){
455 int start
= VG_MIN( vg_console
.suggestion_count
,
456 vg_list_size( vg_console
.suggestions
)-1 );
457 for( int j
=start
; j
>best_pos
; j
-- )
458 vg_console
.suggestions
[j
] = vg_console
.suggestions
[j
-1];
460 vg_console
.suggestions
[ best_pos
].str
= str
;
461 vg_console
.suggestions
[ best_pos
].len
= strlen( str
);
462 vg_console
.suggestions
[ best_pos
].lev_score
= score
;
464 if( vg_console
.suggestion_count
<
465 vg_list_size( vg_console
.suggestions
) )
466 vg_console
.suggestion_count
++;
470 static void console_update_suggestions(void)
472 if( vg_ui
.focused_control_type
!= k_ui_control_textbox
||
473 vg_ui
.textbuf
!= vg_console
.input
)
476 vg_console
.suggestion_count
= 0;
477 vg_console
.suggestion_select
= -1;
478 vg_console
.suggestion_maxlen
= 0;
481 * - must be typing something
482 * - must be at the end
483 * - prev char must not be a whitespace
484 * - cursors should match
487 if( vg_ui
.textbox
.cursor_pos
== 0 ) return;
488 if( vg_ui
.textbox
.cursor_pos
!= vg_ui
.textbox
.cursor_user
) return;
489 if( vg_console
.input
[ vg_ui
.textbox
.cursor_pos
] != '\0' ) return;
491 if( (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == ' ') ||
492 (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == '\t') )
498 int token_count
= vg_console_tokenize( vg_console
.input
, temp
, args
);
499 if( !token_count
) return;
500 vg_console
.suggestion_pastepos
= args
[token_count
-1]-temp
;
502 /* Score all our commands and cvars */
503 if( token_count
== 1 ){
504 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
505 vg_var
*cvar
= &vg_console
.vars
[i
];
506 console_suggest_score_text( cvar
->name
, args
[0], 1 );
509 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
510 vg_cmd
*cmd
= &vg_console
.functions
[i
];
511 console_suggest_score_text( cmd
->name
, args
[0], 1 );
515 vg_cmd
*cmd
= vg_console_match_cmd( args
[0] );
516 vg_var
*var
= vg_console_match_var( args
[0] );
518 assert( !( cmd
&& var
) );
521 if( cmd
->poll_suggest
)
522 cmd
->poll_suggest( token_count
-1, &args
[1] );
525 /* some post processing */
526 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
527 vg_console
.suggestion_maxlen
= VG_MAX( vg_console
.suggestion_maxlen
,
528 vg_console
.suggestions
[i
].len
);
530 if( vg_console
.suggestions
[i
].lev_score
<
531 vg_console
.suggestions
[0].lev_score
/2 )
533 vg_console
.suggestion_count
= i
;
540 * Suggestion controls
542 static void _console_fetch_suggestion(void)
544 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
546 if( vg_console
.suggestion_select
== -1 ){
547 strcpy( target
, vg_console
.input_copy
);
548 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
549 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
553 vg_console
.suggestions
[ vg_console
.suggestion_select
].str
,
554 vg_list_size( vg_console
.input
)-1 );
556 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
557 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
558 _ui_textbox_put_char( ' ' );
562 static void _console_suggest_store_normal(void)
564 if( vg_console
.suggestion_select
== -1 ){
565 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
566 strcpy( vg_console
.input_copy
, target
);
570 static void _console_suggest_next(void)
572 if( vg_console
.suggestion_count
){
573 _console_suggest_store_normal();
575 vg_console
.suggestion_select
++;
577 if( vg_console
.suggestion_select
>= vg_console
.suggestion_count
)
578 vg_console
.suggestion_select
= -1;
580 _console_fetch_suggestion();
584 static void _console_suggest_prev(void)
586 if( vg_console
.suggestion_count
){
587 _console_suggest_store_normal();
589 vg_console
.suggestion_select
--;
591 if( vg_console
.suggestion_select
< -1 )
592 vg_console
.suggestion_select
= vg_console
.suggestion_count
-1;
594 _console_fetch_suggestion();
598 static void _vg_console_on_update( char *buf
, u32 len
)
600 if( buf
== vg_console
.input
){
601 console_update_suggestions();
605 static void console_history_get( char* buf
, int entry_num
)
607 if( !vg_console
.history_count
)
610 int offset
= VG_MIN( entry_num
, vg_console
.history_count
-1 ),
611 pick
= (vg_console
.history_last
- offset
) %
612 vg_list_size( vg_console
.history
);
613 strcpy( buf
, vg_console
.history
[ pick
] );
616 static void _vg_console_on_up( char *buf
, u32 len
)
618 if( buf
== vg_console
.input
){
619 vg_console
.history_pos
=
625 vg_console
.history_pos
+1,
628 vg_list_size( vg_console
.history
),
629 vg_console
.history_count
- 1
634 console_history_get( vg_console
.input
, vg_console
.history_pos
);
635 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
636 &vg_ui
.textbox
.cursor_pos
,
637 vg_list_size(vg_console
.input
)-1, 1);
641 static void _vg_console_on_down( char *buf
, u32 len
)
643 if( buf
== vg_console
.input
){
644 vg_console
.history_pos
= VG_MAX( 0, vg_console
.history_pos
-1 );
645 console_history_get( vg_console
.input
, vg_console
.history_pos
);
647 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
648 &vg_ui
.textbox
.cursor_pos
,
649 vg_list_size(vg_console
.input
)-1, 1 );
653 static void _vg_console_on_enter( char *buf
, u32 len
)
655 if( buf
== vg_console
.input
){
656 if( !strlen( vg_console
.input
) )
659 vg_info( "%s\n", vg_console
.input
);
661 if( strcmp( vg_console
.input
,
662 vg_console
.history
[ vg_console
.history_last
]) )
664 vg_console
.history_last
= ( vg_console
.history_last
+ 1) %
665 vg_list_size(vg_console
.history
);
666 vg_console
.history_count
=
667 VG_MIN( vg_list_size( vg_console
.history
),
668 vg_console
.history_count
+ 1 );
669 strcpy( vg_console
.history
[ vg_console
.history_last
],
673 vg_console
.history_pos
= -1;
674 vg_execute_console_input( vg_console
.input
);
675 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
676 &vg_ui
.textbox
.cursor_pos
, -10000, 1 );
678 vg_console
.input
[0] = '\0';
679 console_update_suggestions();
683 static void _vg_console_draw(void)
685 if( !vg_console
.enabled
) return;
687 SDL_AtomicLock( &log_print_sl
);
689 int ptr
= vg_log
.log_line_current
;
690 int const fh
= vg_ui
.font
->line_height
, log_lines
= 32;
691 int console_lines
= VG_MIN( log_lines
, vg_log
.log_line_count
);
693 ui_rect rect_log
= { 0, 0, vg
.window_x
, log_lines
*fh
},
694 rect_input
= { 0, log_lines
*fh
+ 1, vg
.window_x
, fh
*2 },
695 rect_line
= { 0, 0, vg
.window_x
, fh
};
700 u32 bg_colour
= (ui_colour( k_ui_bg
)&0x00ffffff)|0x9f000000;
702 ui_fill( rect_log
, bg_colour
);
703 rect_line
[1] = rect_log
[1]+rect_log
[3]-fh
;
705 for( int i
=0; i
<console_lines
; i
++ ){
708 if( ptr
< 0 ) ptr
= vg_list_size( vg_log
.log
)-1;
710 ui_text( rect_line
, vg_log
.log
[ptr
], 1, k_ui_align_left
, 0 );
718 struct ui_textbox_callbacks callbacks
= {
719 .up
= _vg_console_on_up
,
720 .down
= _vg_console_on_down
,
721 .change
= _vg_console_on_update
,
722 .enter
= _vg_console_on_enter
,
724 ui_textbox( rect_input
, NULL
,
725 vg_console
.input
, vg_list_size(vg_console
.input
), 1,
726 UI_TEXTBOX_AUTOFOCUS
, &callbacks
);
731 if( vg_console
.suggestion_count
){
732 ui_rect rect_suggest
;
733 rect_copy( rect_input
, rect_suggest
);
735 rect_suggest
[0] += 6 + vg_ui
.font
->spacing
*vg_console
.suggestion_pastepos
;
736 rect_suggest
[1] += rect_input
[3];
737 rect_suggest
[2] = vg_ui
.font
->spacing
* vg_console
.suggestion_maxlen
;
738 rect_suggest
[3] = vg_console
.suggestion_count
* fh
;
740 ui_fill( rect_suggest
, bg_colour
);
742 rect_suggest
[3] = fh
;
744 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
746 if( i
== vg_console
.suggestion_select
){
747 ui_fill( rect_suggest
, ui_colour( k_ui_orange
) );
748 text_colour
= ui_colourcont( k_ui_orange
);
750 else text_colour
= ui_colourcont( k_ui_bg
);
752 ui_text( rect_suggest
, vg_console
.suggestions
[i
].str
, 1,
753 k_ui_align_left
, text_colour
);
755 rect_suggest
[1] += fh
;
759 SDL_AtomicUnlock( &log_print_sl
);
763 #endif /* VG_CONSOLE_H */