#ifndef _CONSOLE_H #define _CONSOLE_H /* * z64555's debug console, created for the FreeSpace Source Code project * * Portions of this source code are based on works by Volition, Inc. circa 1999. You may not sell or otherwise * commercially exploit the source or things you created based on the source. */ /** * @file console.h * @brief An overhauled/updated debug console to allow monitoring, testing, and general debugging of new features. * * @details * Of key interest is Volition's DCF macro, which adds the function argument to the available command list in the * debug console. These functions may be defined in the .cpp file that they are related to, but it is recommended * that they be in their own .cpp if they have multiple sub-arguments (ex: Git has its sub-arguments delimited by * a pair of -'s, or --) */ #include "debugconsole/consoleparse.h" #include "globalincs/pstypes.h" #include "globalincs/vmallocator.h" #define DC_MAX_COMMANDS 300 class debug_command; /** * @def DCF * * @brief The potent DCF macro, used to define new debug commands for the console. * * @param function_name[in] The name of the function, as shown in the debug console * @param help_txt[in] The short-help text, as shown as listed from the 'help' command * * @details * Each DCF is responsible for getting data from the debug console's command line. The parsing functions for the * command line have been set up to have similar syntax and usage as the parselo commands. (see consoleparse.h) * Most, if not all, argument and subcommand strings should be parsed by the dc_optional_string functions. * Usage example: * DCF(toggle_it, "description") * { * if (dc_optional_string_either("help", "--help")) { * dc_printf("Usage: sample. Toggles This_var on/off\n"); * * } else if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { * dc_printf("This_var is %s\n", (This_var ? "ON" : "OFF")); * * } else { * This_var = !This_var; * * } * } * * In the console, the command would be listed as 'toggle_it', and dcf_help would display it as: * toggle_it - Usage: sample. Toggles This_var on/off. * Note: The only allowed function type is a void fn( void ) */ #define DCF(function_name, help_text) \ void dcf_##function_name(); \ debug_command dcmd_##function_name(#function_name, help_text, dcf_##function_name); \ void dcf_##function_name() /** * @def Shortcut for debug commands that toggle a bool, such as Show_lightning * * @param [in] function_name Name of the function, as shown in the debug console * @param [in] bool_variable Name of the variable to allow toggling. */ #define DCF_BOOL(function_name, bool_variable) \ void dcf_##function_name(); \ debug_command dcmd_##function_name(#function_name, "Sets or toggles the boolean: "#bool_variable, dcf_##function_name ); \ void dcf_##function_name() { \ bool bool_tmp = bool_variable != 0; \ if (dc_optional_string_either("help", "--help")) { \ dc_printf( "Usage: %s [bool]\nSets %s to true or false. If nothing passed, then toggles it.\n", #function_name, #bool_variable ); \ return; \ } \ if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { \ dc_printf("%s = %s\n", #bool_variable, (bool_variable ? "TRUE" : "FALSE")); \ return; \ } \ if (!dc_maybe_stuff_boolean(&bool_tmp)) { \ if (bool_variable != 0) \ bool_variable = 0; \ else \ bool_variable = 1; \ } else { \ if (bool_tmp) \ bool_variable = 1; \ else \ bool_variable = 0; \ } \ dc_printf("%s set to %s\n", #bool_variable, (bool_variable != 0 ? "TRUE" : "FALSE")); \ } /** * @def Same as DCF_BOOL, but with custom help strings * * @param [in] function_name Name of the function, as shown in the debug console * @param [in] bool_variable Name of the variable to allow toggling. */ #define DCF_BOOL2(function_name, bool_variable, short_help, long_help) \ void dcf_##function_name(); \ debug_command dcmd_##function_name(#function_name, short_help, dcf_##function_name ); \ void dcf_##function_name() { \ if (dc_optional_string_either("help", "--help")) { \ dc_printf( #long_help ); \ return; \ } \ if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { \ dc_printf("%s = %s\n", #function_name, (bool_variable ? "TRUE" : "FALSE")); \ return; \ } \ if (!dc_maybe_stuff_boolean(&bool_variable)) { \ bool_variable = !bool_variable; \ } \ } /** * @def Shortcut for single-variable setters/monitors * * @param [in] function_name * @param [in] float_variable * @param [in] short_help */ #define DCF_FLOAT(function_name, float_variable, short_help) \ void dcf_##function_name(); \ debug_command dcmd_##function_name(#function_name, short_help, dcf_##function_name ); \ void dcf_##function_name() { \ float value; \ if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { \ dc_printf("%s = %f\n", #float_variable, float_variable); \ return; \ } \ dc_stuff_float(&value); \ float_variable = value; \ dc_printf("%s set to %f\n", #float_variable, float_variable); \ } /** * @def Shortcut for single-variable setters/monitors with lower/upper bounds clamping * * @param [in] function_name * @param [in] float_variable * @param [in] lower_bounds * @param [in] upper_bounds * @param [in] short_help */ #define DCF_FLOAT2(function_name, float_variable, lower_bounds, upper_bounds, short_help) \ void dcf_##function_name(); \ debug_command dcmd_##function_name(#function_name, short_help, dcf_##function_name ); \ void dcf_##function_name() { \ float value; \ if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { \ dc_printf("%s = %f\n", #float_variable, float_variable); \ return; \ } \ dc_stuff_float(&value); \ CLAMP(float_variable, lower_bounds, upper_bounds); \ float_variable = value; \ dc_printf("%s set to %f\n", #float_variable, float_variable); \ } /** * @def Shortcut for single-variable setters/monitors * * @param [in] function_name * @param [in] int_variable * @param [in] short_help */ #define DCF_INT(function_name, int_variable, short_help) \ void dcf_##function_name(); \ debug_command dcmd_##function_name(#function_name, short_help, dcf_##function_name ); \ void dcf_##function_name() { \ int value; \ if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { \ dc_printf("%s = %i\n", #int_variable, int_variable); \ return; \ } \ dc_stuff_int(&value); \ int_variable = value; \ dc_printf("%s set to %i\n", #int_variable, int_variable); \ } /** * @def Shortcut for single-variable setters/monitors with lower/upper bounds clamping * * @param [in] function_name * @param [in] int_variable * @param [in] lower_bounds * @param [in] upper_bounds * @param [in] short_help */ #define DCF_INT2(function_name, int_variable, lower_bounds, upper_bounds, short_help) \ void dcf_##function_name(); \ debug_command dcmd_##function_name(#function_name, short_help, dcf_##function_name ); \ void dcf_##function_name() { \ int value; \ if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { \ dc_printf("%s = %i\n", #int_variable, int_variable); \ return; \ } \ dc_stuff_int(&value); \ CLAMP(int_variable, lower_bounds, upper_bounds); \ int_variable = value; \ dc_printf("%s set to %i\n", #int_variable, int_variable); \ } /** * @class debug_command * @brief Class to aggregate a debug command with its name (as shown in the console) and short help. * * @details * Note: Long help, as evoked by ' help', should be handled by the function itself. It is recommended * that arguments that have sub-arguments be in their own function, so as to aide in organization and to keep the * size of the command function down. */ class debug_command { public: const char *name; //!< The name of the command, as shown in the debug console const char *help; //!< The short help string, as shown by 'help ' void (*func)(); //!< Pointer to the function that to run when this command is evoked debug_command(); /** * @brief Adds a debug command to the debug_commands map, if it isn't in there already. * * @details The DCF macro more or less guarantees that a command won't be duplicated on compile time. But no harm in * some extra caution. */ debug_command(const char *name, const char *help, void (*func)()); }; /** * @class is_dcmd * @brief Predicate class used to search for a dcmd by name */ class is_dcmd { public: const char *name; is_dcmd(const char *_name) : name(_name) {} bool operator() (debug_command* dcmd) { return (strcmp(name, dcmd->name) == 0); } }; extern bool Dc_debug_on; extern int dc_commands_size; extern uint lastline; extern SCP_string dc_command_str; // The rest of the command line, from the end of the last processed arg on. /** * @brief Pauses the output of a command and allows user to scroll through the output history. * * @details Returns true if user has pressed Esc, returns false otherwise. Use this in your function to (safely?) break * out of the loop it's presumably in. */ bool dc_pause_output(void); /** * @brief Prints the given char string to the debug console * @details See the doc for std::printf() for formating and more details */ void dc_printf(SCP_FORMAT_STRING const char *format, ...) SCP_FORMAT_STRING_ARGS(1, 2); /** * @brief Opens and processes the debug console. (Blocking call) * @details TODO: Make this a non-blocking call so that the game can still run while the debug console is open. */ void debug_console(void (*func)(void) = NULL); #endif // _CONSOLE_H