Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #ifndef UTILS_CONSOLE_HPP__
- #define UTILS_CONSOLE_HPP__ 1
- #include "singleton.hpp"
- #if defined( _WIN32 ) || defined( _WIN64 )
- #include "winapi.hpp"
- #endif
- #include <cstdio>
- #include <cstdarg>
- #include <mutex>
- namespace utils
- {
- class ConsoleClass
- {
- private:
- typedef std::recursive_mutex mutex_t;
- typedef std::lock_guard< mutex_t > lock_t;
- private:
- mutex_t m_mutex;
- FILE* m_file;
- bool m_newline;
- #if defined( _WIN32 ) || defined( _WIN64 )
- HANDLE m_inputhandle;
- bool m_inputisconsole;
- HANDLE m_outputhandle;
- bool m_outputisconsole;
- #endif
- // Sends the string to stdout, possibly converting it from UTF-8 to something other
- void writeconsole( char const* str, int length );
- // Sends the string to the global log file
- void writefile( char const* str, int length );
- void linestart();
- public:
- ConsoleClass();
- ~ConsoleClass();
- ConsoleClass( ConsoleClass const& ) = delete;
- ConsoleClass( ConsoleClass&& ) = delete;
- ConsoleClass& operator=( ConsoleClass const& ) = delete;
- ConsoleClass& operator=( ConsoleClass&& ) = delete;
- // Accepts a UTF-8 string and sends it to both stdout and log file, by sending it to writeconsole and writefile
- // Additionally, in the log file, each newline is prepended with a time returned by clock()
- void writeraw( char const* str );
- // Construct the string according to the format and send it to writeraw
- __attribute__(( __format__( gnu_printf, 2, 0 ) ))
- void vwrite( char const* format, va_list args );
- __attribute__(( __format__( gnu_printf, 2, 3 ) ))
- void write( char const* format, ... );
- // Construct the string, send it, append a newline at the end and flush both streams
- __attribute__(( __format__( gnu_printf, 2, 0 ) ))
- void vwriteln( char const* format, va_list args );
- __attribute__(( __format__( gnu_printf, 2, 3 ) ))
- void writeln( char const* format, ... );
- // Sends a newline and flushes both streams
- void writeln();
- void flush();
- void lock();
- void unlock();
- // Attempts to read a single character from stdin and writes it as a zero-terminated UTF-8 string in the given buffer
- // str must be able to safely hold at least 5 bytes
- // On failure, str[ 0 ] == 0
- void getchar( char* str );
- };
- extern utils::Singleton< ConsoleClass > Console;
- }
- // My debugging tool #1
- #define LOG( format, ... ) \
- utils::Console()->writeln( \
- "[%48s:%24s@%4i]\t" format, \
- __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__ )
- #endif
- ////////////////////////////////////////////////////////////////////////////////
- #include "console.hpp"
- #if defined( _WIN32 ) || defined( _WIN64 )
- // hello again, http://pastebin.com/iXMm3B7t
- #include "encoding.hpp"
- #endif
- #include <cstring>
- #include <ctime>
- namespace utils
- {
- void ConsoleClass::writeconsole( char const* str, int length )
- {
- // Windows plz
- #if defined( _WIN32 ) || defined( _WIN64 )
- if( m_outputhandle == 0 )
- {
- return;
- }
- if( m_outputisconsole )
- {
- while( length > 0 )
- {
- // cmd.exe can work with legacy codepages and UTF-16
- // Supporting all the pages is even more pain, so we stick to *W versions of WinAPI functions
- wchar_t buffer[ 256 ];
- translation_t trstruct =
- {
- &encoding::utf8, // source encoding
- &encoding::utf16, // target encoding
- str, // source buffer
- buffer, // dest buffer
- size_t( length ), // source size
- sizeof( buffer ), // dest size
- 0xfffd, // 'REPLACEMENT CHARACTER', this little thing: �
- };
- int trresult = utils::translatestr( &trstruct );
- DWORD wcresult;
- if( !WriteConsoleW(
- m_outputhandle, buffer, trstruct.destresult / 2, &wcresult, 0 ) )
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- str += trstruct.sourceresult;
- length -= trstruct.sourceresult;
- switch( trresult )
- {
- case translate_success:
- case translate_failure_source_overrun:
- // We have finished our work with the given chunk
- return;
- case translate_failure_dest_unsupported:
- // Something unbelievable: UTF-8 decoder retrieved something UTF-16 encoder refuses to understand
- // But in case it happens anyway, skip the problematic character and go on
- {
- size_t pointlength;
- encoding::utf8.decode( str, 0, length, &pointlength );
- str += pointlength;
- length -= pointlength;
- break;
- }
- case translate_failure_dest_overrun:
- // This just means we got the buffer filled, this is normal operation
- break;
- }
- }
- }
- else
- {
- // If stdout is redirected to a file, we don't apply any conversions, therefore the file becomes the same encoding as the rest of the engine, which is UTF-8
- DWORD result;
- if( !WriteFile( m_outputhandle, str, length, &result, 0 ) )
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- }
- #else
- printf( "%.*s", str, length );
- #endif
- }
- void ConsoleClass::writefile( char const* str, int length )
- {
- fprintf( m_file, "%.*s", length, str );
- }
- ConsoleClass::ConsoleClass()
- : m_newline( true )
- {
- #ifdef __ANDROID__
- // I used CCTools to compile the engine for Android, and its run_na placed CWD in some abyss that is not even accessible with a file manager, so I use a full path here
- m_file = fopen( "/storage/sdcard0/projects/output/last.log", "w" );
- #else
- m_file = fopen( "last.log", "wb" );
- #endif
- #if defined( _WIN32 ) || defined( _WIN64 )
- #if defined( DISABLE_CONSOLE )
- m_inputhandle = 0;
- m_outputhandle = 0;
- #else
- m_inputhandle = GetStdHandle( STD_INPUT_HANDLE );
- if( m_inputhandle == INVALID_HANDLE_VALUE )
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- DWORD imode;
- if( m_inputhandle == 0 )
- {
- // There is no stdin
- }
- else if( GetConsoleMode( m_inputhandle, &imode ) )
- {
- // stdin is provied by cmd.exe
- m_inputisconsole = true;
- }
- else
- {
- BY_HANDLE_FILE_INFORMATION fi;
- // This will probably break if stdin is actually a pipe, but that's the best test I can think of for now
- if( GetFileInformationByHandle( m_inputhandle, &fi ) )
- {
- m_inputisconsole = false;
- }
- else
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- }
- m_outputhandle = GetStdHandle( STD_OUTPUT_HANDLE );
- if( m_outputhandle == INVALID_HANDLE_VALUE )
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- // The same set of tests
- if( m_outputhandle == 0 )
- {
- }
- else if( GetConsoleMode( m_outputhandle, &imode ) )
- {
- m_outputisconsole = true;
- }
- else
- {
- BY_HANDLE_FILE_INFORMATION fi;
- if( GetFileInformationByHandle( m_outputhandle, &fi ) )
- {
- m_outputisconsole = false;
- }
- else
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- }
- #endif
- #endif
- }
- ConsoleClass::~ConsoleClass()
- {
- writeraw( "\nLog finished\n" );
- fclose( m_file );
- }
- void ConsoleClass::linestart()
- {
- if( m_newline )
- {
- m_newline = false;
- char buffer[ 32 ];
- int length = snprintf( buffer, sizeof( buffer ), "[%10u] ", uint32_t( clock() * 1000 / CLOCKS_PER_SEC ) );
- writefile( buffer, length - 1 );
- }
- }
- void ConsoleClass::writeraw( char const* str )
- {
- // The point of m_newline flag is that the times that will appear before every log line will be measured when that line is started. If it was output immediately after '\n', then it will show the time the previous line was finished, which can happen much earlier, and the result would be misleading
- lock_t lock( m_mutex );
- while( str[ 0 ] )
- {
- linestart();
- char const* npos = strchr( str, '\n' );
- if( npos )
- {
- writefile( str, npos - str + 1 );
- writeconsole( str, npos - str + 1 );
- m_newline = true;
- str = npos + 1;
- }
- else
- {
- int len = strlen( str );
- writefile( str, len );
- writeconsole( str, len );
- break;
- }
- }
- }
- void ConsoleClass::vwrite( char const* format, va_list args )
- {
- lock_t lock( m_mutex );
- va_list args2;
- va_copy( args2, args );
- int length = vsnprintf( 0, 0, format, args2 );
- if( length < 1024 )
- {
- char buffer[ 1024 ];
- vsnprintf( buffer, sizeof( buffer ), format, args );
- writeraw( buffer );
- }
- else
- {
- char* buffer = new char[ length ];
- vsnprintf( buffer, length, format, args );
- writeraw( buffer );
- delete[] buffer;
- }
- va_end( args2 );
- }
- void ConsoleClass::write( char const* format, ... )
- {
- va_list args;
- va_start( args, format );
- vwrite( format, args );
- va_end( args );
- }
- void ConsoleClass::vwriteln( char const* format, va_list args )
- {
- lock_t lock( m_mutex );
- vwrite( format, args );
- writeraw( "\n" );
- flush();
- }
- void ConsoleClass::writeln( char const* format, ... )
- {
- va_list args;
- va_start( args, format );
- vwriteln( format, args );
- va_end( args );
- }
- void ConsoleClass::writeln()
- {
- lock_t lock( m_mutex );
- writeraw( "\n" );
- flush();
- }
- void ConsoleClass::flush()
- {
- lock_t lock( m_mutex );
- fflush( m_file );
- #if defined( _WIN32 ) || defined( _WIN64 )
- if( m_outputhandle != 0 && !m_outputisconsole )
- {
- // There is no need to flush a console screen
- if( !FlushFileBuffers( m_outputhandle ) )
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- }
- #endif
- }
- void ConsoleClass::lock()
- {
- m_mutex.lock();
- }
- void ConsoleClass::unlock()
- {
- m_mutex.unlock();
- }
- void ConsoleClass::getchar( char* str )
- {
- size_t pointlength;
- // Windows plz
- #if defined( _WIN32 ) || defined( _WIN64 )
- if( m_inputhandle == 0 )
- {
- str[ 0 ] = 0;
- return;
- }
- // Windows likes to think that "newline" is "newline and carriage return"
- // I like to think it is not
- static uint32_t newline_consumed = 0;
- uint32_t charcode;
- if( m_inputisconsole )
- {
- // Remember that a single wchar_t is not a char yet
- wchar_t buffer[ 2 ] = { 0 };
- int length = 0;
- while( true )
- {
- DWORD result;
- if( !ReadConsoleW( m_inputhandle, buffer + length, 1, &result, 0 ) )
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- length += 1;
- size_t pointlength;
- encoding::utf16.decode(
- buffer, &charcode, length * 2, &pointlength );
- if( pointlength != 0 )
- {
- break;
- }
- }
- }
- else
- {
- str[ 0 ] = 0;
- int length = 0;
- while( true )
- {
- DWORD result;
- if( !ReadFile( m_inputhandle, str + length, 1, &result, 0 ) )
- {
- fprintf( stderr, "%i\n", __LINE__ );
- winerror();
- }
- length += 1;
- size_t pointlength;
- encoding::utf8.decode(
- str, &charcode, length, &pointlength );
- if( pointlength != 0 )
- {
- // If this code stumbles upon invalid UTF-8 sequence, depending on how exactly it is invalid, it may decide to eat the next byte as well
- // No, I am not handling this. This engine is neither web-browser nor Windows to encourage such bad habits as making some bullshit and thinking it will be handled consistently.
- break;
- }
- }
- }
- if( charcode == '\n' || charcode == '\r' )
- {
- if( newline_consumed && newline_consumed != charcode )
- {
- newline_consumed = 0;
- getchar( str );
- return;
- }
- else
- {
- newline_consumed = charcode;
- charcode = '\n';
- }
- }
- else
- {
- newline_consumed = 0;
- }
- if( !encoding::utf8.encode( str, charcode, 4, &pointlength ) )
- {
- str[ 0 ] = 0;
- }
- else
- {
- // As "str" is actually a string of bytes, it makes sense to zero-terminate it
- str[ pointlength ] = 0;
- }
- #else
- int length = 0;
- while( true )
- {
- scanf( "%c", str + length );
- length += 1;
- if( !encoding::utf8.decode( str, 0, length, &pointlength ) )
- {
- if( pointlength != 0 )
- {
- // The same issue: non-conformant UTF-8 makes the engine to tell you to go home and cry in a corner
- str[ 0 ] = 0;
- break;
- }
- }
- else
- {
- str[ pointlength ] = 0;
- break;
- }
- }
- #endif
- // All the characters read are echoed to the log file
- linestart();
- writefile( str, pointlength );
- if( str[ 0 ] == '\n' )
- {
- m_newline = true;
- }
- }
- utils::Singleton< ConsoleClass > Console;
- }
- // Conclusion:
- // #ifdef _WIN32
- // a couple of screens of code
- // #else
- // a single POSIX/stdc call
- // #endif
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement