From 23db1cead062ebf5aa094150ef694a50c5c1d79f Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 18 Jan 2026 22:40:58 +0100 Subject: [PATCH 1/3] perf(ini): Simplify and unify INI read line buffer implementation --- Generals/Code/GameEngine/Include/Common/INI.h | 10 +- .../Code/GameEngine/Source/Common/INI/INI.cpp | 112 +++++++-------- .../Code/GameEngine/Include/Common/INI.h | 12 +- .../Code/GameEngine/Source/Common/INI/INI.cpp | 127 +++++++++--------- 4 files changed, 125 insertions(+), 136 deletions(-) diff --git a/Generals/Code/GameEngine/Include/Common/INI.h b/Generals/Code/GameEngine/Include/Common/INI.h index 3854f3f0480..59b2e596605 100644 --- a/Generals/Code/GameEngine/Include/Common/INI.h +++ b/Generals/Code/GameEngine/Include/Common/INI.h @@ -392,12 +392,14 @@ class INI void readLine( void ); -// FILE *m_file; ///< file pointer of file currently loading - File *m_file; ///< file pointer of file currently loading + char* m_readBuffer; ///< internal read buffer + unsigned m_readBufferNext; ///< next char in read buffer + unsigned m_readBufferUsed; ///< number of bytes in read buffer + AsciiString m_filename; ///< filename of file currently loading INILoadType m_loadType; ///< load time for current file UnsignedInt m_lineNum; ///< current line number that's been read - char m_buffer[ INI_MAX_CHARS_PER_LINE ]; ///< buffer to read file contents into + char m_buffer[ INI_MAX_CHARS_PER_LINE+1 ];///< buffer to read file contents into const char *m_seps; ///< for strtok parsing const char *m_sepsPercent; ///< m_seps with percent delimiter as well const char *m_sepsColon; ///< m_seps with colon delimiter as well @@ -405,6 +407,6 @@ class INI const char *m_blockEndToken; ///< token to represent end of data block Bool m_endOfFile; ///< TRUE when we've hit EOF #ifdef DEBUG_CRASHING - char m_curBlockStart[ INI_MAX_CHARS_PER_LINE ]; ///< first line of cur block + char m_curBlockStart[ INI_MAX_CHARS_PER_LINE+1 ]; ///< first line of cur block #endif }; diff --git a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp index c9cf9cbee86..06fad6b7412 100644 --- a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp @@ -175,7 +175,9 @@ Bool INI::isValidINIFilename( const char *filename ) INI::INI( void ) { - m_file = nullptr; + m_readBuffer = nullptr; + m_readBufferNext = 0; + m_readBufferUsed = 0; m_filename = "None"; m_loadType = INI_LOAD_INVALID; m_lineNum = 0; @@ -295,7 +297,7 @@ UnsignedInt INI::loadDirectory( AsciiString dirName, INILoadType loadType, Xfer void INI::prepFile( AsciiString filename, INILoadType loadType ) { // if we have a file open already -- we can't do another one - if( m_file != nullptr ) + if( m_readBuffer != nullptr ) { DEBUG_CRASH(( "INI::load, cannot open file '%s', file already open", filename.str() )); @@ -304,8 +306,8 @@ void INI::prepFile( AsciiString filename, INILoadType loadType ) } // open the file - m_file = TheFileSystem->openFile(filename.str(), File::READ); - if( m_file == nullptr ) + File* file = TheFileSystem->openFile(filename.str(), File::READ); + if( file == nullptr ) { DEBUG_CRASH(( "INI::load, cannot open file '%s'", filename.str() )); @@ -313,7 +315,9 @@ void INI::prepFile( AsciiString filename, INILoadType loadType ) } - m_file = m_file->convertToRAMFile(); + m_readBufferNext = 0; + m_readBufferUsed = file->size(); + m_readBuffer = file->readEntireAndClose(); // save our filename m_filename = filename; @@ -326,9 +330,12 @@ void INI::prepFile( AsciiString filename, INILoadType loadType ) //------------------------------------------------------------------------------------------------- void INI::unPrepFile() { - // close the file - m_file->close(); - m_file = nullptr; + // delete the buffer + delete[] m_readBuffer; + m_readBuffer = nullptr; + m_readBufferNext = 0; + m_readBufferUsed = 0; + m_filename = "None"; m_loadType = INI_LOAD_INVALID; m_lineNum = 0; @@ -448,89 +455,72 @@ UnsignedInt INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer ) //------------------------------------------------------------------------------------------------- /** Read a line from the already open file. Any comments will be removed and - * therefore ignored from any given line */ + * therefore ignored from any given line + * + * TheSuperHackers @performance xezon 18/01/2026 The file contents are now read directly from a + * full File Ram buffer into the INI Line Buffer without a third buffer in between. + */ //------------------------------------------------------------------------------------------------- void INI::readLine( void ) { - Bool isComment = FALSE; - // sanity - DEBUG_ASSERTCRASH( m_file, ("readLine(), file pointer is null") ); + DEBUG_ASSERTCRASH( m_readBuffer, ("readLine(), read buffer is null") ); - // if we've reached end of file we'll just keep returning empty string in our buffer - if( m_endOfFile ) + if (m_endOfFile) { - m_buffer[ 0 ] = '\0'; + *m_buffer = 0; } else { - // read up till the newline character or until out of space - Int i = 0; - Bool done = FALSE; - while( !done ) + char *p = m_buffer; + while (p != m_buffer+INI_MAX_CHARS_PER_LINE) { - - // read character - m_endOfFile = (m_file->read(m_buffer + i, 1) == 0); - - // check for end of file - if( m_endOfFile ) + // test end of read buffer + if (m_readBufferNext==m_readBufferUsed) { - - done = TRUE; - m_buffer[ i ] = '\0'; - + m_endOfFile = true; + *p = 0; + break; } + // get next character + *p = m_readBuffer[m_readBufferNext++]; - // check for new line - if( m_buffer[ i ] == '\n' ) - done = TRUE; - - DEBUG_ASSERTCRASH(m_buffer[ i ] != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d",m_filename.str(), getLineNum())); - - // make all whitespace characters actual spaces - if( isspace( m_buffer[ i ] ) ) - m_buffer[ i ] = ' '; - - // if this is a semicolon, that represents the start of a comment - if( m_buffer[ i ] == ';' ) - isComment = TRUE; + // LF? + if (*p == '\n') + { + *p = 0; + break; + } - // if we've set the comment flag, just insert terminators in the place of each character read - if( isComment == TRUE ) - m_buffer[ i ] = '\0'; + DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d", m_filename.str(), getLineNum())); - // - // when we've become done, as the last thing just set the current index position + 1 - // to the string terminator - // - if( done == TRUE && i + 1 < INI_MAX_CHARS_PER_LINE ) - m_buffer[ i + 1 ] = '\0'; + // comment? + if (*p == ';') + *p = 0; - // increase our buffer index, but watch out for the max - if( ++i == INI_MAX_CHARS_PER_LINE ) - done = TRUE; + // whitespace? + else if (*p > 0 && *p < 32) + *p = ' '; + p++; } + *p = 0; + // increase our line count m_lineNum++; // check for at the max - if( i == INI_MAX_CHARS_PER_LINE ) + if ( p == m_buffer+INI_MAX_CHARS_PER_LINE ) { - - DEBUG_ASSERTCRASH( 0, ("Buffer too small (%d) and was truncated, increase INI_MAX_CHARS_PER_LINE", - INI_MAX_CHARS_PER_LINE) ); - + DEBUG_ASSERTCRASH( 0, ("Buffer too small (%d) and was truncated, increase INI_MAX_CHARS_PER_LINE", INI_MAX_CHARS_PER_LINE) ); } } if (s_xfer) { s_xfer->xferUser( m_buffer, sizeof( char ) * strlen( m_buffer ) ); - //DEBUG_LOG(("Xfer val is now 0x%8.8X in %s, line %s", ((XferCRC *)s_xfer)->getCRC(), - //m_filename.str(), m_buffer)); + //DEBUG_LOG(("Xfer val is now 0x%8.8X in %s, line %s", ((XferCRC *)s_xfer)->getCRC(), m_filename.str(), m_buffer)); } } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/INI.h b/GeneralsMD/Code/GameEngine/Include/Common/INI.h index 86437a1b461..7073bb270f6 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/INI.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/INI.h @@ -395,15 +395,9 @@ class INI void readLine( void ); - File *m_file; ///< file pointer of file currently loading - - enum - { - INI_READ_BUFFER = 8192 ///< size of internal read buffer - }; - char m_readBuffer[INI_READ_BUFFER]; ///< internal read buffer - unsigned m_readBufferNext; ///< next char in read buffer - unsigned m_readBufferUsed; ///< number of bytes in read buffer + char* m_readBuffer; ///< internal read buffer + unsigned m_readBufferNext; ///< next char in read buffer + unsigned m_readBufferUsed; ///< number of bytes in read buffer AsciiString m_filename; ///< filename of file currently loading INILoadType m_loadType; ///< load time for current file diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index b91fa9eb3f6..133b0e0ed07 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -179,8 +179,9 @@ Bool INI::isValidINIFilename( const char *filename ) INI::INI( void ) { - m_file = nullptr; - m_readBufferNext=m_readBufferUsed=0; + m_readBuffer = nullptr; + m_readBufferNext = 0; + m_readBufferUsed = 0; m_filename = "None"; m_loadType = INI_LOAD_INVALID; m_lineNum = 0; @@ -300,7 +301,7 @@ UnsignedInt INI::loadDirectory( AsciiString dirName, INILoadType loadType, Xfer void INI::prepFile( AsciiString filename, INILoadType loadType ) { // if we have a file open already -- we can't do another one - if( m_file != nullptr ) + if( m_readBuffer != nullptr ) { DEBUG_CRASH(( "INI::load, cannot open file '%s', file already open", filename.str() )); @@ -309,8 +310,8 @@ void INI::prepFile( AsciiString filename, INILoadType loadType ) } // open the file - m_file = TheFileSystem->openFile(filename.str(), File::READ); - if( m_file == nullptr ) + File* file = TheFileSystem->openFile(filename.str(), File::READ); + if( file == nullptr ) { DEBUG_CRASH(( "INI::load, cannot open file '%s'", filename.str() )); @@ -318,7 +319,9 @@ void INI::prepFile( AsciiString filename, INILoadType loadType ) } - m_file = m_file->convertToRAMFile(); + m_readBufferNext = 0; + m_readBufferUsed = file->size(); + m_readBuffer = file->readEntireAndClose(); // save our filename m_filename = filename; @@ -331,10 +334,12 @@ void INI::prepFile( AsciiString filename, INILoadType loadType ) //------------------------------------------------------------------------------------------------- void INI::unPrepFile() { - // close the file - m_file->close(); - m_file = nullptr; - m_readBufferUsed=m_readBufferNext=0; + // delete the buffer + delete[] m_readBuffer; + m_readBuffer = nullptr; + m_readBufferNext = 0; + m_readBufferUsed = 0; + m_filename = "None"; m_loadType = INI_LOAD_INVALID; m_lineNum = 0; @@ -454,55 +459,57 @@ UnsignedInt INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer ) //------------------------------------------------------------------------------------------------- /** Read a line from the already open file. Any comments will be removed and - * therefore ignored from any given line */ + * therefore ignored from any given line + * + * TheSuperHackers @performance xezon 18/01/2026 The file contents are now read directly from a + * full File Ram buffer into the INI Line Buffer without a third buffer in between. + */ //------------------------------------------------------------------------------------------------- void INI::readLine( void ) { // sanity - DEBUG_ASSERTCRASH( m_file, ("readLine(), file pointer is null") ); - - if (m_endOfFile) - *m_buffer=0; - else - { - char *p=m_buffer; - while (p!=m_buffer+INI_MAX_CHARS_PER_LINE) - { - // get next character - if (m_readBufferNext==m_readBufferUsed) - { - // refill buffer - m_readBufferNext=0; - m_readBufferUsed=m_file->read(m_readBuffer,INI_READ_BUFFER); - - // EOF? - if (!m_readBufferUsed) - { - m_endOfFile=true; - *p=0; - break; - } - } - *p=m_readBuffer[m_readBufferNext++]; - - // CR? - if (*p=='\n') - { - *p=0; - break; - } - - DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d",m_filename.str(), getLineNum())); - - // comment? - if (*p==';') - *p=0; - // whitespace? - else if (*p>0&&*p<32) - *p=' '; - p++; - } - *p=0; + DEBUG_ASSERTCRASH( m_readBuffer, ("readLine(), read buffer is null") ); + + if (m_endOfFile) + { + *m_buffer = 0; + } + else + { + char *p = m_buffer; + while (p != m_buffer+INI_MAX_CHARS_PER_LINE) + { + // test end of read buffer + if (m_readBufferNext==m_readBufferUsed) + { + m_endOfFile = true; + *p = 0; + break; + } + // get next character + *p = m_readBuffer[m_readBufferNext++]; + + // LF? + if (*p == '\n') + { + *p = 0; + break; + } + + DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d", m_filename.str(), getLineNum())); + + // comment? + if (*p == ';') + *p = 0; + + // whitespace? + else if (*p > 0 && *p < 32) + *p = ' '; + + p++; + } + + *p = 0; // increase our line count m_lineNum++; @@ -510,18 +517,14 @@ void INI::readLine( void ) // check for at the max if ( p == m_buffer+INI_MAX_CHARS_PER_LINE ) { - - DEBUG_ASSERTCRASH( 0, ("Buffer too small (%d) and was truncated, increase INI_MAX_CHARS_PER_LINE", - INI_MAX_CHARS_PER_LINE) ); - + DEBUG_ASSERTCRASH( 0, ("Buffer too small (%d) and was truncated, increase INI_MAX_CHARS_PER_LINE", INI_MAX_CHARS_PER_LINE) ); } - } + } if (s_xfer) { s_xfer->xferUser( m_buffer, sizeof( char ) * strlen( m_buffer ) ); - //DEBUG_LOG(("Xfer val is now 0x%8.8X in %s, line %s", ((XferCRC *)s_xfer)->getCRC(), - //m_filename.str(), m_buffer)); + //DEBUG_LOG(("Xfer val is now 0x%8.8X in %s, line %s", ((XferCRC *)s_xfer)->getCRC(), m_filename.str(), m_buffer)); } } From 17fa65e3fe66e02136cdadfbe9ccc963fa447d08 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:13:36 +0100 Subject: [PATCH 2/3] Add curly brackets --- Generals/Code/GameEngine/Source/Common/INI/INI.cpp | 4 ++++ GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp index 06fad6b7412..4408c83d4ea 100644 --- a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp @@ -496,11 +496,15 @@ void INI::readLine( void ) // comment? if (*p == ';') + { *p = 0; + } // whitespace? else if (*p > 0 && *p < 32) + { *p = ' '; + } p++; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index 133b0e0ed07..c904f9db1ec 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -500,11 +500,15 @@ void INI::readLine( void ) // comment? if (*p == ';') + { *p = 0; + } // whitespace? else if (*p > 0 && *p < 32) + { *p = ' '; + } p++; } From a1ed3af275c07f7adeddbcfa5c42d8b90249fcb6 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Tue, 20 Jan 2026 22:03:39 +0100 Subject: [PATCH 3/3] Take the comments from Generals --- Generals/Code/GameEngine/Source/Common/INI/INI.cpp | 8 +++++--- GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp index 4408c83d4ea..1144d47214a 100644 --- a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp @@ -472,6 +472,7 @@ void INI::readLine( void ) } else { + // read up till the newline or semicolon character, or until out of space char *p = m_buffer; while (p != m_buffer+INI_MAX_CHARS_PER_LINE) { @@ -482,10 +483,11 @@ void INI::readLine( void ) *p = 0; break; } + // get next character *p = m_readBuffer[m_readBufferNext++]; - // LF? + // check for new line if (*p == '\n') { *p = 0; @@ -494,13 +496,13 @@ void INI::readLine( void ) DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d", m_filename.str(), getLineNum())); - // comment? + // if this is a semicolon, that represents the start of a comment if (*p == ';') { *p = 0; } - // whitespace? + // make whitespace characters actual spaces else if (*p > 0 && *p < 32) { *p = ' '; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index c904f9db1ec..3dd3e982e18 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -476,6 +476,7 @@ void INI::readLine( void ) } else { + // read up till the newline or semicolon character, or until out of space char *p = m_buffer; while (p != m_buffer+INI_MAX_CHARS_PER_LINE) { @@ -486,10 +487,11 @@ void INI::readLine( void ) *p = 0; break; } + // get next character *p = m_readBuffer[m_readBufferNext++]; - // LF? + // check for new line if (*p == '\n') { *p = 0; @@ -498,13 +500,13 @@ void INI::readLine( void ) DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d", m_filename.str(), getLineNum())); - // comment? + // if this is a semicolon, that represents the start of a comment if (*p == ';') { *p = 0; } - // whitespace? + // make whitespace characters actual spaces else if (*p > 0 && *p < 32) { *p = ' ';