I am developing a windows form application that takes a Robot Program generated by other software and modifies it. The process of modification is as follows:
- The StreamReader.ReadLine() is used parse the file line by line
- Regex is used to search for specific keywords in the file. If a match is obtained, the matched string is copied to another string and is replaced with new lines of robot code.
The modified code is saved in a string and is finally written to a new file.
All the collection of matched strings obtained using Regex is also saved in a string and is finally written to a new file.
I have been able to successfully do this
private void Form1_Load(object sender, EventArgs e)
{
string NextLine = null;
string CurrLine = null;
string MoveL_Pos_Data = null;
string MoveL_Ref_Data = null;
string MoveLFull = null;
string ModCode = null;
string TAB = "\t";
string NewLine = "\r\n";
string SavePath = null;
string ExtCode_1 = null;
string ExtCode_2 = null;
string ExtCallMod = null;
int MatchCount = 0;
int NumRoutines = 0;
try
{
// Ask user location of the source file
// Displays an OpenFileDialog so the user can select a Cursor.
OpenFileDialog openFileDialog1 = new OpenFileDialog
{
Filter = "MOD Files|*.mod",
Title = "Select an ABB RAPID MOD File"
};
// Show the Dialog.
// If the user clicked OK in the dialog and
// a .MOD file was selected, open it.
if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
// Assign the cursor in the Stream to the Form's Cursor property.
//this.Cursor = new Cursor(openFileDialog1.OpenFile());
using (StreamReader sr = new StreamReader(openFileDialog1.FileName))
{
// define a regular expression to search for extr calls
Regex Extr_Ex = new Regex(@"\bExtr\(-?\d*.\d*\);", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
Regex MoveL_Ex = new Regex(@"\bMoveL\s+(.*)(z\d.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
Match MoveLString = null;
while (sr.Peek() >= 0)
{
CurrLine = sr.ReadLine();
//Console.WriteLine(sr.ReadLine());
// check if the line is a match
if (Extr_Ex.IsMatch(CurrLine))
{
// Keep a count for total matches
MatchCount++;
// Save extr calls in a string
ExtCode_1 += NewLine + TAB + TAB + Extr_Ex.Match(CurrLine).ToString();
// Read next line (always a MoveL) to get Pos data for TriggL
NextLine = sr.ReadLine();
//Console.WriteLine(NextLine);
if (MoveL_Ex.IsMatch(NextLine))
{
// Next Line contains MoveL
// get matched string
MoveLString = MoveL_Ex.Match(NextLine);
GroupCollection group = MoveLString.Groups;
MoveL_Pos_Data = group[1].Value.ToString();
MoveL_Ref_Data = group[2].Value.ToString();
MoveLFull = MoveL_Pos_Data + MoveL_Ref_Data;
}
// replace Extr with follwing commands
ModCode += NewLine + TAB + TAB + "TriggL " + MoveL_Pos_Data + "extr," + MoveL_Ref_Data;
ModCode += NewLine + TAB + TAB + "WaitDI DI1_1,1;";
ModCode += NewLine + TAB + TAB + "MoveL " + MoveLFull;
ModCode += NewLine + TAB + TAB + "Reset DO1_1;";
//break;
}
else
{
// No extr Match
ModCode += "\r\n" + CurrLine;
}
}
Console.WriteLine($"Total Matches: {MatchCount}");
}
}
// Write modified code into a new output file
string SaveDirectoryPath = Path.GetDirectoryName(openFileDialog1.FileName);
string ModName = Path.GetFileNameWithoutExtension(openFileDialog1.FileName);
SavePath = SaveDirectoryPath + @"\" + ModName + "_rev.mod";
File.WriteAllText(SavePath, ModCode);
//Write Extr matches into new output file
//Prepare module
ExtCallMod = "MODULE ExtruderCalls";
// All extr calls in one routine
//Prepare routines
ExtCallMod += NewLine + NewLine + TAB + "PROC Prg_ExtCall"; // + 1;
ExtCallMod += ExtCode_1;
ExtCallMod += NewLine + NewLine + TAB + "ENDPROC";
ExtCallMod += NewLine + NewLine;
//}
ExtCallMod += "ENDMODULE";
// Write to file
string ExtCallSavePath = SaveDirectoryPath + @"\ExtrCalls.mod";
File.WriteAllText(ExtCallSavePath, ExtCallMod);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
While this helps me achieve what I want, the process is very slow. Since I am new to C# programming, I suspect that the slowness is coming from duplicating the original file contents to a string and NOT replacing content in place (I am not sure if contents in original file can be directly replaced). For an input file of 20,000 rows, the whole process is taking a little over 5 minutes.
I used to get the following error: Message=Managed Debugging Assistant 'ContextSwitchDeadlock' : 'The CLR has been unable to transition from COM context 0xb27138 to COM context 0xb27080 for 60 seconds. The thread that owns the destination context/apartment is most likely either doing a non pumping wait or processing a very long running operation without pumping Windows messages. This situation generally has a negative performance impact and may even lead to the application becoming non responsive or memory usage accumulating continually over time. To avoid this problem, all single threaded apartment (STA) threads should use pumping wait primitives (such as CoWaitForMultipleHandles) and routinely pump messages during long running operations.'
I was able to get past it by disabling 'ContextSwitchDeadlock' settings in debugger settings. This may not be the best practice.
Can anyone help me in improving the performance of my code?
EDIT: I found out that the robot controller had limitations on the number of Lines to be in the MOD file (output file). The maximum number of lines allowed was 32768. I came up with a logic to split the contents of string builder to separate output files as follows:
// Split modCodeBuilder into seperate strings based on final size
const int maxSize = 32500;
string result = modCodeBuilder.ToString();
string[] splitResult = result.Split(new string[] { "\r\n" }, StringSplitOptions.None);
string[] splitModCode = new string[maxSize];
// Setup destination directory to be same as source directory
string destDir = Path.GetDirectoryName(fileNames[0]);
for (int count = 0; ; count++)
{
// Get the next batch of text by skipping the amount
// we've taken so far and then taking the maxSize.
string modName = $"PrgMOD_{count + 1}";
string procName = $"Prg_{count + 1}()";
// Use Array Copy to extract first 32500 lines from modCode[]
int src_start_index = count * maxSize;
int srcUpperLimit = splitResult.GetUpperBound(0);
int dataLength = maxSize;
if (src_start_index > srcUpperLimit) break; // Exit loop when there's no text left to take
if (src_start_index > 1)
{
// Make sure calculate right length so that src index is not exceeded
dataLength = srcUpperLimit - maxSize;
}
Array.Copy(splitResult, src_start_index, splitModCode, 0, dataLength);
string finalModCode = String.Join("\r\n", splitModCode);
string batch = String.Concat("MODULE ", modName, "\r\n\r\n\tPROC ", procName, "\r\n", finalModCode, "\r\n\r\n\tENDPROC\r\n\r\nENDMODULE");
//if (batch.Length == 0) break;
// Generate file name based on count
string fileName = $"ABB_R3DP_{count + 1}.mod";
// Write our file text
File.WriteAllText(Path.Combine(destDir, fileName), batch);
// Write status to output textbox
TxtOutput.AppendText("\r\n");
TxtOutput.AppendText("\r\n");
TxtOutput.AppendText($"Modified MOD File: {fileName} is generated sucessfully! It is saved to location: {Path.Combine(destDir, fileName)}");
}