binding visibility
[vg.git] / vg_input.h
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
2 #ifndef VG_INPUT_H
3 #define VG_INPUT_H
4
5 #include "common.h"
6 #include "vg/vg_loader.h"
7
8 #define VG_MAX_CONTROLLERS 4
9
10 static float controller_deadzone = 0.05f;
11 typedef u32 vg_input_op;
12 typedef vg_input_op *vg_input_program;
13
14 enum vg_input_type {
15 k_vg_input_type_button_u8,
16 k_vg_input_type_axis_f32,
17 k_vg_input_type_joy_v2f
18 };
19
20 enum vg_input_op {
21 /* data source */
22 vg_keyboard,
23 vg_mouse,
24 vg_joy_button,
25 vg_joy_axis,
26 vg_joy_ls,
27 vg_joy_rs,
28
29 /* modes */
30 vg_mode_mul,
31 vg_mode_sub,
32 vg_mode_add,
33 vg_mode_absmax,
34 vg_mode_max,
35
36 /* control */
37 vg_index,
38 vg_end,
39 vg_gui_visible,
40
41 /* math */
42 vg_normalize
43 };
44
45 struct{
46 const u8 *sdl_keys;
47 u32 sdl_mouse;
48
49 struct vg_controller{
50 SDL_GameController *handle; /* handle for controller. NULL if unused */
51 SDL_JoystickID instance_id; /* uid used in events */
52
53 float axises[ SDL_CONTROLLER_AXIS_MAX ];
54 u32 buttons[ SDL_CONTROLLER_BUTTON_MAX ];
55 }
56 controllers[4];
57
58 int active_controller_index; /* most recent controller (by button press)
59 will be -1 if no controllers active */
60
61 /* what the user is currently using. the keyboard and controller are still
62 * active simultaneously, but this reflects what the UI should show */
63 enum input_method{
64 k_input_method_kbm,
65 k_input_method_controller
66 }
67 display_input_method;
68 SDL_GameControllerType display_input_type;
69 }
70 static vg_input = { .active_controller_index = -2 };
71
72 static u8 vg_getkey( SDL_Keycode kc )
73 {
74 SDL_Scancode sc = SDL_GetScancodeFromKey( kc );
75 return vg_input.sdl_keys[sc];
76 }
77
78 /*
79 * takes SDL device index, and tries to open that on any free channel
80 */
81 static int vg_open_gamecontroller( Sint32 index )
82 {
83 struct vg_controller *controller = NULL;
84 int vg_id = 0;
85 const char *name = SDL_GameControllerNameForIndex( index );
86 SDL_JoystickID instance_id = SDL_JoystickGetDeviceInstanceID( index );
87
88 if( instance_id == -1 ){
89 vg_error( ". Invalid device index (vg_open_gamecontroller)\n" );
90 return -1;
91 }
92
93 for( int j=0; j<VG_MAX_CONTROLLERS; j++ ){
94 struct vg_controller *esta = &vg_input.controllers[j];
95
96 if( esta->handle ){
97 if( esta->instance_id == instance_id ){
98 vg_warn( " . SDL_JoystickID[%d] is already in open at index #%d\n",
99 esta->instance_id, j );
100 return -1;
101 }
102 }
103 else{
104 if( !controller ){
105 controller = &vg_input.controllers[j];
106 vg_id = j;
107 }
108 }
109 }
110
111 if( controller ){
112 controller->handle = SDL_GameControllerOpen( index );
113 controller->instance_id = instance_id;
114
115 if( controller->handle ){
116 vg_success(
117 " . opened SDL_JoystickID[%d] as controller '%s' at index #%d\n",
118 instance_id, name, vg_id );
119
120 for( u32 i=0; i< SDL_CONTROLLER_BUTTON_MAX; i++ )
121 controller->buttons[i] = 0;
122
123 for( u32 i=0; i< SDL_CONTROLLER_AXIS_MAX; i++ )
124 controller->axises[i] = 0.0f;
125
126 if( vg_input.active_controller_index == -2 ){
127 vg_input.active_controller_index = vg_id;
128 vg_input.display_input_method = k_input_method_controller;
129 vg_input.display_input_type =
130 SDL_GameControllerGetType( controller->handle );
131 }
132
133 return vg_id;
134 }
135 else{
136 vg_error( ". Failed to attach game controller '%s'. Reason: %s\n",
137 name, SDL_GetError() );
138 return -1;
139 }
140 }
141 else{
142 vg_error( ". Too many controllers open! ignoring '%s'\n", name );
143 return -1;
144 }
145 }
146
147 static void vg_input_device_event( SDL_Event *ev )
148 {
149 if( ev->type == SDL_CONTROLLERDEVICEADDED ){
150 int is_controller = SDL_IsGameController( ev->cdevice.which );
151 const char *name = SDL_JoystickNameForIndex( ev->cdevice.which );
152
153 Sint32 index = ev->cdevice.which;
154 SDL_JoystickID instance_id = SDL_JoystickGetDeviceInstanceID( index );
155 vg_info( "SDL_CONTROLLERDEVICEADDED | device index: %d, name: '%s'\n",
156 index, name );
157
158 if( is_controller ){
159 vg_open_gamecontroller( index );
160 }
161 }
162 else if( ev->type == SDL_CONTROLLERDEVICEREMOVED ){
163 vg_info( "SDL_CONTROLLERDEVICEREMOVED | instance_id: %d\n",
164 ev->cdevice.which );
165
166 for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
167 struct vg_controller *controller = &vg_input.controllers[i];
168
169 if( controller->handle ){
170 if( controller->instance_id == ev->cdevice.which ){
171 vg_info( " . closing controller at index #%d\n", i );
172 SDL_GameControllerClose( controller->handle );
173 controller->handle = NULL;
174 controller->instance_id = -1;
175
176 if( vg_input.active_controller_index == i ){
177 vg_input.active_controller_index = -1;
178 vg_input.display_input_method = k_input_method_kbm;
179 vg_info( "display_input: k_input_method_kbm\n" );
180 }
181 break;
182 }
183 }
184 }
185 }
186 }
187
188 static void vg_input_controller_event( SDL_Event *ev )
189 {
190 if( ev->type == SDL_CONTROLLERAXISMOTION ){
191 for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
192 struct vg_controller *esta = &vg_input.controllers[i];
193
194 if( ev->caxis.which == esta->instance_id ){
195 float value = (float)ev->caxis.value / 32767.0f;
196
197 if( ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTX ||
198 ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTY ||
199 ev->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX ||
200 ev->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY )
201 {
202 float deadz = vg_clampf( controller_deadzone, 0.0f, 0.999f ),
203 high = vg_maxf( 0.0f, fabsf(value) - deadz );
204
205 value = vg_signf(value) * (high / (1.0f-deadz));
206 }
207
208 esta->axises[ ev->caxis.axis ] = value;
209 break;
210 }
211 }
212 }
213 else if( ev->type == SDL_CONTROLLERBUTTONDOWN ){
214 struct vg_controller *active = NULL;
215
216 if( vg_input.active_controller_index >= 0 )
217 active = &vg_input.controllers[vg_input.active_controller_index];
218
219 if( !active || (ev->cbutton.which != active->instance_id) ){
220 active = NULL;
221 vg_input.active_controller_index = -1;
222 vg_input.display_input_method = k_input_method_kbm;
223
224 for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
225 if( vg_input.controllers[i].instance_id == ev->cbutton.which ){
226 active = &vg_input.controllers[i];
227 vg_input.active_controller_index = i;
228 vg_input.display_input_type =
229 SDL_GameControllerGetType(active->handle);
230 break;
231 }
232 }
233
234 if( active ){
235 vg_info( "Switching active controller index to #%d\n",
236 vg_input.active_controller_index );
237 }
238 else{
239 vg_error( "Input out of range (SDL_JoystickID#%d)\n",
240 ev->cbutton.which );
241 }
242 }
243
244 if( active ){
245 if( vg_input.display_input_method != k_input_method_controller ){
246 vg_input.display_input_method = k_input_method_controller;
247 vg_info( "display_input: k_input_method_controller\n" );
248 }
249 active->buttons[ ev->cbutton.button ] = 1;
250 }
251 }
252 else if( ev->type == SDL_CONTROLLERBUTTONUP ){
253 for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
254 struct vg_controller *esta = &vg_input.controllers[i];
255
256 if( ev->cbutton.which == esta->instance_id ){
257 esta->buttons[ ev->cbutton.button ] = 0;
258 break;
259 }
260 }
261 }
262 }
263
264 static void vg_process_inputs(void)
265 {
266 int count;
267 vg_input.sdl_keys = SDL_GetKeyboardState( &count );
268 vg_input.sdl_mouse = SDL_GetMouseState(NULL,NULL);
269
270 if( vg_input.display_input_method != k_input_method_kbm ){
271 /* check for giving keyboard priority */
272 for( int i=0; i<count; i++ ){
273 if( vg_input.sdl_keys[i] ){
274 vg_input.display_input_method = k_input_method_kbm;
275 vg_info( "display_input: k_input_method_kbm (keyboard %d)\n", i );
276 break;
277 }
278 }
279
280 /* check for giving mouse priority */
281 if( vg_input.sdl_mouse &
282 (SDL_BUTTON(SDL_BUTTON_LEFT)|SDL_BUTTON(SDL_BUTTON_RIGHT)|
283 SDL_BUTTON(SDL_BUTTON_MIDDLE)) )
284 {
285 vg_input.display_input_method = k_input_method_kbm;
286 vg_info( "display_input: k_input_method_kbm (mouse)\n" );
287 }
288 }
289 }
290
291 static void async_vg_input_init( void *payload, u32 size )
292 {
293 VG_VAR_F32( controller_deadzone, flags=VG_VAR_PERSISTENT );
294
295 vg_info( "Checking for controllers\n" );
296 SDL_GameControllerAddMappingsFromFile( "gamecontrollerdb.txt" );
297
298 int joy_count = SDL_NumJoysticks();
299 for( int i=0; i<joy_count; i++ ) {
300 const char *name = SDL_JoystickNameForIndex( i );
301 int is_controller = SDL_IsGameController(i);
302
303 vg_info( "%d: %s [controller: %d]\n", i, name, is_controller );
304
305 if( is_controller ){
306 vg_open_gamecontroller( i );
307 }
308 }
309 }
310
311 static void vg_input_init(void)
312 {
313 vg_async_call( async_vg_input_init, NULL, 0 );
314 }
315
316 static void vg_input_free(void)
317 {
318 for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
319 struct vg_controller *controller = &vg_input.controllers[i];
320
321 if( controller->handle ){
322 SDL_GameControllerClose( controller->handle );
323 controller->handle = NULL;
324 }
325 }
326 }
327
328 struct vg_controller *vg_active_controller(void){
329 if( vg_input.active_controller_index >= 0 )
330 return &vg_input.controllers[vg_input.active_controller_index];
331 else
332 return NULL;
333 }
334
335 static u8 vg_controller_button( SDL_GameControllerButton button ){
336 struct vg_controller *c = vg_active_controller();
337 if( c ) return c->buttons[ button ];
338 else return 0;
339 }
340
341 static f32 vg_controller_axis( SDL_GameControllerAxis axis ){
342 struct vg_controller *c = vg_active_controller();
343 if( c ) return c->axises[ axis ];
344 else return 0;
345 }
346
347 static void vg_input_apply_to_u8( vg_input_op mode, u8 data, u8 *inout_result ){
348 if ( mode == vg_mode_absmax ) *inout_result |= data;
349 else if( mode == vg_mode_mul ) *inout_result &= data;
350 else vg_fatal_error( "mode not supported for destination type (%d)", mode );
351 }
352
353 static void vg_input_apply_to_f32( vg_input_op mode, f32 data,
354 f32 *inout_result ){
355 if ( mode == vg_mode_absmax ){
356 if( fabsf(data) > fabsf(*inout_result) )
357 *inout_result = data;
358 }
359 else if( mode == vg_mode_max ) *inout_result = vg_maxf(*inout_result,data);
360 else if( mode == vg_mode_mul ) *inout_result *= (f32)data;
361 else if( mode == vg_mode_sub ) *inout_result -= (f32)data;
362 else if( mode == vg_mode_add ) *inout_result += (f32)data;
363 else vg_fatal_error( "mode not supported for destination type (%d)", mode );
364 }
365
366 /*
367 * Run an input program. out_result must point to memory with sufficient
368 * storage respective to the size set by type.
369 */
370 static void vg_exec_input_program( enum vg_input_type type, vg_input_op *ops,
371 void *out_result ){
372 u8 *out_button = NULL;
373 f32 *out_joy = NULL;
374
375 if( type == k_vg_input_type_button_u8 ){
376 out_button = out_result;
377 *out_button = 0;
378 }
379 else if( type == k_vg_input_type_axis_f32 ){
380 out_joy = out_result;
381 out_joy[0] = 0.0f;
382 }
383 else if( type == k_vg_input_type_joy_v2f ){
384 out_joy = out_result;
385 out_joy[0] = 0.0f;
386 out_joy[1] = 0.0f;
387 }
388
389 /* computer state */
390 vg_input_op mode = vg_mode_absmax;
391 u32 pc = 0, index = 0;
392
393 next_code:;
394 vg_input_op op = ops[ pc ++ ];
395
396 if( (op >= vg_mode_mul) && (op <= vg_mode_max) )
397 mode = op;
398 else if( (op == vg_keyboard) || (op == vg_mouse) || (op == vg_joy_button) ){
399 u8 state = 0;
400
401 if( op == vg_keyboard )
402 state = vg_getkey(ops[pc ++]);
403 else if( op == vg_mouse )
404 state = (vg_input.sdl_mouse & SDL_BUTTON(ops[pc ++]))?1:0;
405 else
406 state = vg_controller_button(ops[pc ++]);
407
408 if( type == k_vg_input_type_button_u8 )
409 vg_input_apply_to_u8( mode, state, out_button );
410 else
411 vg_input_apply_to_f32( mode, (f32)state, &out_joy[index] );
412 }
413 else if( op == vg_joy_axis ){
414 f32 state = vg_controller_axis( ops[pc ++] );
415 if( type == k_vg_input_type_button_u8 )
416 vg_input_apply_to_u8( mode, state>0.5f?1:0, out_button );
417 else
418 vg_input_apply_to_f32( mode, state, &out_joy[index] );
419 }
420 else if( (op == vg_joy_ls) || (op == vg_joy_rs) ){
421 if( type == k_vg_input_type_joy_v2f ){
422 vg_input_apply_to_f32( mode,
423 vg_controller_axis( op==vg_joy_ls? SDL_CONTROLLER_AXIS_LEFTX:
424 SDL_CONTROLLER_AXIS_RIGHTX),
425 &out_joy[0] );
426 vg_input_apply_to_f32( mode,
427 vg_controller_axis( op==vg_joy_ls? SDL_CONTROLLER_AXIS_LEFTY:
428 SDL_CONTROLLER_AXIS_RIGHTY),
429 &out_joy[1] );
430 }
431 }
432 else if( op == vg_index )
433 index = ops[pc ++];
434 else if( op == vg_end )
435 return;
436 else if( op == vg_normalize )
437 v2_normalize( out_joy );
438 else if( op == vg_gui_visible )
439 pc ++;
440 else
441 vg_fatal_error( "unknown op\n" );
442
443 goto next_code;
444 }
445
446 /*
447 * Get vendor specific button glyphs based on SDL button ID
448 */
449 static const char *controller_button_str( SDL_GameControllerButton button ){
450 static const char *controller_glyphs[ SDL_CONTROLLER_BUTTON_MAX ][2] = {
451 /* xbox/generic playstation */
452 [ SDL_CONTROLLER_BUTTON_A ] = { "\x1e\x85","\x1e\x82" },
453 [ SDL_CONTROLLER_BUTTON_B ] = { "\x1e\x86","\x1e\x81" },
454 [ SDL_CONTROLLER_BUTTON_X ] = { "\x1e\x83","\x1e\x7f" },
455 [ SDL_CONTROLLER_BUTTON_Y ] = { "\x1e\x84","\x1e\x80" },
456 [ SDL_CONTROLLER_BUTTON_LEFTSTICK ] = { "\x87", "\x87" },
457 [ SDL_CONTROLLER_BUTTON_RIGHTSTICK ] = { "\x8b", "\x8b" },
458 [ SDL_CONTROLLER_BUTTON_LEFTSHOULDER ] = { "\x91", "\x91" },
459 [ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ]= { "\x92", "\x92" },
460 [ SDL_CONTROLLER_BUTTON_DPAD_LEFT ] = { "\x1e\x93","\x1e\x93" },
461 [ SDL_CONTROLLER_BUTTON_DPAD_UP ] = { "\x1e\x94","\x1e\x94" },
462 [ SDL_CONTROLLER_BUTTON_DPAD_RIGHT ] = { "\x1e\x95","\x1e\x95" },
463 [ SDL_CONTROLLER_BUTTON_DPAD_DOWN ] = { "\x1e\x96","\x1e\x96" },
464 [ SDL_CONTROLLER_BUTTON_GUIDE ] = { "\x91", "\x91" },
465 };
466
467 if( vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS3 ||
468 vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS4 ||
469 vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS5 )
470 {
471 return controller_glyphs[ button ][ 1 ];
472 }
473 else if( vg_input.display_input_type ==
474 SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO ||
475 vg_input.display_input_type ==
476 SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT ||
477 vg_input.display_input_type ==
478 SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR ||
479 vg_input.display_input_type ==
480 SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT )
481 {
482 return NULL;
483 }
484 else
485 return controller_glyphs[ button ][ 0 ];
486 }
487
488 /*
489 * Cat keyboard key string. special_glyphs include SR glyphs
490 */
491 static void vg_keyboard_key_string( vg_str *str, u32 key, int special_glyphs ){
492 if( (key >= SDLK_a) && (key <= SDLK_z) ){
493 key = (key-SDLK_a)+(u32)'A';
494
495 if( special_glyphs ){
496 vg_strcatch( str, '\x1f' );
497 vg_strcatch( str, key );
498 vg_strcatch( str, ' ' );
499 }
500 else
501 vg_strcatch( str, key );
502 }
503 else if( (key == SDLK_LSHIFT) || (key == SDLK_RSHIFT) )
504 vg_strcat( str, special_glyphs? "\x9e": "shift" );
505 else if( (key == SDLK_LCTRL) || (key == SDLK_RCTRL) )
506 vg_strcat( str, special_glyphs? "\x9f": "ctrl" );
507 else if( (key == SDLK_LALT) || (key == SDLK_RALT) )
508 vg_strcat( str, special_glyphs? "\xa0": "alt" );
509 else if( key == SDLK_SPACE )
510 vg_strcat( str, special_glyphs? "\xa1": "space" );
511 else if( (key == SDLK_RETURN) || (key == SDLK_RETURN2) )
512 vg_strcat( str, special_glyphs? "\xa2": "return" );
513 else if( key == SDLK_ESCAPE )
514 vg_strcat( str, special_glyphs? "\xa3": "escape" );
515 else if( key == SDLK_RIGHT )
516 vg_strcat( str, special_glyphs? "\x1f\x95 ": "right" );
517 else if( key == SDLK_LEFT )
518 vg_strcat( str, special_glyphs? "\x1f\x93 ": "left" );
519 else if( key == SDLK_UP )
520 vg_strcat( str, special_glyphs? "\x1f\x94 ": "up" );
521 else if( key == SDLK_DOWN )
522 vg_strcat( str, special_glyphs? "\x1f\x96 ": "down" );
523 else {
524 vg_strcat( str, "keyboard key #" );
525 vg_strcati32( str, key );
526 }
527 }
528
529 /*
530 * Cat mouse button string. special_glyphs include SR glyphs
531 */
532 static void vg_mouse_button_string( vg_str *str, u32 button,
533 int special_glyphs ){
534 if ( button == SDL_BUTTON_LEFT )
535 vg_strcat( str, special_glyphs? "\x99": "left mouse" );
536 else if( button == SDL_BUTTON_RIGHT )
537 vg_strcat( str, special_glyphs? "\x9a": "right mouse" );
538 else if( button == SDL_BUTTON_MIDDLE )
539 vg_strcat( str, special_glyphs? "\x9c": "middle mouse" );
540 else{
541 vg_strcat( str, "mouse button #" );
542 vg_strcati32( str, button );
543 }
544 }
545
546 /*
547 * Cat string represeinting single axis
548 */
549 static void vg_joy_axis_string( vg_str *str,
550 SDL_GameControllerAxis axis, int special_glyphs ){
551 if( axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT )
552 vg_strcat( str, special_glyphs?"\x8f":"left trigger" );
553 else if( axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT )
554 vg_strcat( str, special_glyphs?"\x90":"right trigger" );
555 else if( axis == SDL_CONTROLLER_AXIS_LEFTX )
556 vg_strcat( str, special_glyphs?"\x88":"left stick horizontal" );
557 else if( axis == SDL_CONTROLLER_AXIS_LEFTY )
558 vg_strcat( str, special_glyphs?"\x89":"left stick vertical" );
559 else if( axis == SDL_CONTROLLER_AXIS_RIGHTX )
560 vg_strcat( str, special_glyphs?"\x8c":"right stick horizontal" );
561 else if( axis == SDL_CONTROLLER_AXIS_RIGHTY )
562 vg_strcat( str, special_glyphs?"\x8d":"right stick vertical" );
563 else{
564 vg_strcat( str, "axis " );
565 vg_strcati32( str, axis );
566 }
567 }
568
569 /*
570 * Cat string represeinting whole joystick
571 */
572 static void vg_joy_string( vg_str *str, vg_input_op op, int special_glyphs ){
573 if( op == vg_joy_ls )
574 vg_strcat( str, special_glyphs? "\x87": "left stick" );
575 else
576 vg_strcat( str, special_glyphs? "\x8b": "right stick" );
577 }
578
579 /*
580 * Convert an input program into a readable string
581 */
582 static void vg_input_string( vg_str *str, vg_input_op *ops, int glyphs ){
583 u32 pc = 0;
584 int applicable = 0, visible = 1;
585
586 next_code:;
587 vg_input_op op = ops[ pc ++ ];
588
589 if( (op == vg_keyboard) || (op == vg_mouse) ){
590 if( (vg_input.display_input_method == k_input_method_kbm) && visible ){
591 applicable = 1;
592
593 if( op == vg_keyboard )
594 vg_keyboard_key_string( str, ops[pc], glyphs );
595 else
596 vg_mouse_button_string( str, ops[pc], glyphs );
597 }
598 else applicable = 0;
599 pc ++;
600 }
601 else if( (op == vg_joy_button) || (op == vg_joy_axis) ){
602 if( (vg_input.display_input_method == k_input_method_controller)
603 && visible ){
604 applicable = 1;
605
606 if( op == vg_joy_button )
607 vg_strcat( str, controller_button_str(ops[pc]) );
608 else
609 vg_joy_axis_string( str, ops[pc], glyphs );
610 }
611 else applicable = 0;
612 pc ++;
613 }
614 else if( (op == vg_joy_ls) || (op == vg_joy_rs) ){
615 if( (vg_input.display_input_method == k_input_method_controller)
616 && visible ){
617 applicable = 1;
618 vg_joy_string( str, op, glyphs );
619 }
620 else applicable = 0;
621 }
622 else if( op == vg_mode_mul ){
623 if( applicable && visible )
624 vg_strcat( str, " + " );
625 }
626 else if( op == vg_index )
627 pc ++;
628 else if( op == vg_gui_visible )
629 visible = ops[pc++];
630 else if( op == vg_end )
631 return;
632
633 goto next_code;
634 }
635
636 #endif