Subhuman
asked on
WinSock, DataArrival and AnsiToRtf
I'm working on a Mud client (isn't everyone?), and after lots of frigging around I've created some subs/functions to convert an ANSI formatted string to an RTF formatted string. I've made a little test app which just takes ANSI formatted text in a TextBox and when a button is pressed converts it and writes it to a RichTextBox, and it works a treat.
Now I've added it to the mud client, and it works ok for small sections of data but when a large amount is sent in a large piece (I'm not sure if it's sent as one string or if it's several smaller strings and that's what's choking it up), it first displays the text correctly, the displays it again after the correct text with all the RTF formatting showing. This doesn't happen for smaller blocks of text (such as room descriptions, etc). If I turn off page-pausing in my mud character's configuration, then the whole lot coming down the tube at once causes the program to crash with a "Runtime Error 5: Invalid procedure call or argument."
I suspect these problems are the result of how I'm using the Winsock control rather than problems with my AnsiToRtf converter, since if I use an older version of the client (pre-ansi support) to do the same command then copy the ansi text to my test app, it handles it superbly.
Here are the relevant subs/functions (if you need to see more, just holler):
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
Dim DataArrived As String
Winsock1.GetData DataArrived, vbString
If Not paused Then
OutputText ParseData(DataArrived)
DoTriggers DataArrived
Else
stringBuffer = stringBuffer & DataArrived
End If
End Sub
Public Function ParseData(ByVal theText As String) As String
Dim temp As String
Dim i As Integer
For i = 1 To Len(theText)
Select Case Asc(Mid(theText, i, 1))
Case 0 'null
Case 1 'echo
If processCommand Then
If theCommand = 251 Then
localEcho = False
processCommand = False
theCommand = -1
ElseIf theCommand = 252 Then
localEcho = True
processCommand = False
theCommand = -1
End If
End If
Case 7 'beep
Beep
Case 8 'backspace
Case 9 'horizontal tab
temp = temp & Chr(9)
Case 10 'LF
temp = temp & vbCrLf
Case 11 'vertical tab
temp = temp & vbCrLf & vbCrLf
Case 12 'form feed
Case 13 'CR
Case 251 'will (option code)
If processCommand Then theCommand = 251
Case 252 'won't (option code)
If processCommand Then theCommand = 252
Case 255 'interpret as command (then one or two chars)
If processCommand Then ' if processCommand is already set,
temp = temp & Chr(255) ' then it's an escape sequence for
processCommand = False ' chr(255), so add it to the string.
Else
processCommand = True ' otherwise, it's an escape seq for
End If ' a command.
Case Else
temp = temp & Mid(theText, i, 1)
processCommand = False
theCommand = -1
End Select
Next i
ParseData = temp
End Function
Public Sub OutputText(ByVal theText As String)
theText = "[m" & theText
theText = AnsiToRtf(theText)
OutputBox.SelStart = Len(OutputBox.Text)
OutputBox.SelRTF = theText
OutputBox.SelStart = Len(OutputBox.Text)
End Sub
Private Sub DoTriggers(ByVal theText As String)
Dim i As Integer
For i = 0 To numTriggers - 1
If InStr(1, theText, GetTriggerByI(i).trigger, vbTextCompare) > 0 Then
If GetTriggerByI(i).enabled Then SendData GetTriggerByI(i).action
End If
Next i
End Sub
Public Function AnsiToRtf(ByVal str As String) As String
Dim s, c, a, r, cs As String
Dim i, j, cc, csc As Integer
Dim ac As AnsiCodeList
Dim b As Boolean
b = False
cs = "{{\colortbl;\red255\green 255\blue25 5;"
csc = 2
For i = 1 To Len(str) ' do strings start at 0 or 1?
r = "{\cf1 "
c = Mid(str, i, 1)
If c = Chr(27) Then
r = "{" ' stop rtf code building up over loop
j = InStr(i, str, "m", vbTextCompare) ' get index of end of string
'MsgBox "j=" & j
a = Mid(str, i, (j - i) + 1) ' get ansi code from string
ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
cc = GetColour(ac)
'MsgBox "cc=" & cc
'r = ' rtf code equivalent to 'a'
If cc >= 0 And cc <= 15 Then ' is it a valid colour?
cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
r = r & "\cf" & csc & " "
csc = csc + 1
If b Then s = s & "}" ' closes previous rtf code
End If
s = s & r ' appends rtf code to new string
i = j ' go to next char after the code. the Next i line
' will add 1 to this.
b = True
ElseIf c = Chr(13) Then
If b Then
s = s & "\par" ' if it's inside {} it needs \par instead of vbcrlf
End If
ElseIf c = "\" Then
c = "{\\}"
s = s & c
Else
s = s & c ' if it's a normal character just append it to the new string
End If
Next i
cs = cs & "}" ' close colortbl string
'if b then
s = s & "}}" ' if rtf has been opened, close it
AnsiToRtf = cs & s ' the new string
End Function
-=-=-=-=-=-=-=-=-=-=-=-=-= -=-=-=-=-= -=-=-
I suspect that the problem is the DataArrival event being triggered faster than the data can be processed, but I have no idea how to test this, and for all I know the Winsock control deals with it automatically. It's just a suspicion of mine.
What do I need to do to stop long and/or rapid DataArrival events and subsequent actions from dropping dead?
Now I've added it to the mud client, and it works ok for small sections of data but when a large amount is sent in a large piece (I'm not sure if it's sent as one string or if it's several smaller strings and that's what's choking it up), it first displays the text correctly, the displays it again after the correct text with all the RTF formatting showing. This doesn't happen for smaller blocks of text (such as room descriptions, etc). If I turn off page-pausing in my mud character's configuration, then the whole lot coming down the tube at once causes the program to crash with a "Runtime Error 5: Invalid procedure call or argument."
I suspect these problems are the result of how I'm using the Winsock control rather than problems with my AnsiToRtf converter, since if I use an older version of the client (pre-ansi support) to do the same command then copy the ansi text to my test app, it handles it superbly.
Here are the relevant subs/functions (if you need to see more, just holler):
Private Sub Winsock1_DataArrival(ByVal
Dim DataArrived As String
Winsock1.GetData DataArrived, vbString
If Not paused Then
OutputText ParseData(DataArrived)
DoTriggers DataArrived
Else
stringBuffer = stringBuffer & DataArrived
End If
End Sub
Public Function ParseData(ByVal theText As String) As String
Dim temp As String
Dim i As Integer
For i = 1 To Len(theText)
Select Case Asc(Mid(theText, i, 1))
Case 0 'null
Case 1 'echo
If processCommand Then
If theCommand = 251 Then
localEcho = False
processCommand = False
theCommand = -1
ElseIf theCommand = 252 Then
localEcho = True
processCommand = False
theCommand = -1
End If
End If
Case 7 'beep
Beep
Case 8 'backspace
Case 9 'horizontal tab
temp = temp & Chr(9)
Case 10 'LF
temp = temp & vbCrLf
Case 11 'vertical tab
temp = temp & vbCrLf & vbCrLf
Case 12 'form feed
Case 13 'CR
Case 251 'will (option code)
If processCommand Then theCommand = 251
Case 252 'won't (option code)
If processCommand Then theCommand = 252
Case 255 'interpret as command (then one or two chars)
If processCommand Then ' if processCommand is already set,
temp = temp & Chr(255) ' then it's an escape sequence for
processCommand = False ' chr(255), so add it to the string.
Else
processCommand = True ' otherwise, it's an escape seq for
End If ' a command.
Case Else
temp = temp & Mid(theText, i, 1)
processCommand = False
theCommand = -1
End Select
Next i
ParseData = temp
End Function
Public Sub OutputText(ByVal theText As String)
theText = "[m" & theText
theText = AnsiToRtf(theText)
OutputBox.SelStart = Len(OutputBox.Text)
OutputBox.SelRTF = theText
OutputBox.SelStart = Len(OutputBox.Text)
End Sub
Private Sub DoTriggers(ByVal theText As String)
Dim i As Integer
For i = 0 To numTriggers - 1
If InStr(1, theText, GetTriggerByI(i).trigger, vbTextCompare) > 0 Then
If GetTriggerByI(i).enabled Then SendData GetTriggerByI(i).action
End If
Next i
End Sub
Public Function AnsiToRtf(ByVal str As String) As String
Dim s, c, a, r, cs As String
Dim i, j, cc, csc As Integer
Dim ac As AnsiCodeList
Dim b As Boolean
b = False
cs = "{{\colortbl;\red255\green
csc = 2
For i = 1 To Len(str) ' do strings start at 0 or 1?
r = "{\cf1 "
c = Mid(str, i, 1)
If c = Chr(27) Then
r = "{" ' stop rtf code building up over loop
j = InStr(i, str, "m", vbTextCompare) ' get index of end of string
'MsgBox "j=" & j
a = Mid(str, i, (j - i) + 1) ' get ansi code from string
ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
cc = GetColour(ac)
'MsgBox "cc=" & cc
'r = ' rtf code equivalent to 'a'
If cc >= 0 And cc <= 15 Then ' is it a valid colour?
cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
r = r & "\cf" & csc & " "
csc = csc + 1
If b Then s = s & "}" ' closes previous rtf code
End If
s = s & r ' appends rtf code to new string
i = j ' go to next char after the code. the Next i line
' will add 1 to this.
b = True
ElseIf c = Chr(13) Then
If b Then
s = s & "\par" ' if it's inside {} it needs \par instead of vbcrlf
End If
ElseIf c = "\" Then
c = "{\\}"
s = s & c
Else
s = s & c ' if it's a normal character just append it to the new string
End If
Next i
cs = cs & "}" ' close colortbl string
'if b then
s = s & "}}" ' if rtf has been opened, close it
AnsiToRtf = cs & s ' the new string
End Function
-=-=-=-=-=-=-=-=-=-=-=-=-=
I suspect that the problem is the DataArrival event being triggered faster than the data can be processed, but I have no idea how to test this, and for all I know the Winsock control deals with it automatically. It's just a suspicion of mine.
What do I need to do to stop long and/or rapid DataArrival events and subsequent actions from dropping dead?
Just a guess- try adding some error checking in your data arrival routine. Perhaps you are getting an error and handling it elsewhere with a resume. In particular, if you get an error 126, ignore it and do not process the data.
ASKER
What's error 126?
And how would I check for this particular error? When I've needed error checking I've generally just given it an On Error Goto [label], then whacked the label at the bottom of the sub so it drops out (or if it's a function, give it some default values to return then drop out), since the errors that occur generally do so because of empty strings or nonexistant config files, and I always check array/string subscripts before trying to access them.
And how would I check for this particular error? When I've needed error checking I've generally just given it an On Error Goto [label], then whacked the label at the bottom of the sub so it drops out (or if it's a function, give it some default values to return then drop out), since the errors that occur generally do so because of empty strings or nonexistant config files, and I always check array/string subscripts before trying to access them.
ASKER
I should mention that I'm using VB5 here.
I was mixed up on error 126 - that applies to send data not data arrival - sorry! What you wrote about error checking sounds right to me. I was just suggesting that you make sure you aren't getting an error in your data arrival routine that might be the cause. Now that I've steered you wrong, I'm going to look at it some more to see if I can spot anything.
ASKER
*laughs* okeydoke. :)
Its a little late to be asking this but are you using TCP or UDP?
Disregard previous question - you must be using UDP. I know you can't tell by my inane comments but I really have done this before. More to come...
ASKER
Umm, TCP I guess. I don't recall what I specified but since I don't really know anything about UDP I probably went with TCP. Whatever it is, it connects and transfers data alright (excepting this problem).
OK, this is very interesting to me. I've got my test TCP server and client working. Can I ask how is numTriggers set and what is the definition of GetTriggerByI? BTW, I'm using VB6 but hopefully I can replicate the problem.
ASKER
Public Type trigger
trigger As String ' this is the text that will set the trigger off
action As String ' this is send back to server when the trigger is set off
enabled As Boolean ' whether the trigger can be set off or not
End Type
Dim triggers() As trigger ' stores all the users triggers (dynamic array)
Public numTriggers As Integer ' stores the current size of the array (incremented by AddTrigger function)
Public Function GetTriggerByI(ByVal i As Integer) As trigger
If i >= LBound(triggers) And i <= UBound(triggers) Then
GetTriggerByI = triggers(i)
Else ' invalid trigger, return default values
GetTriggerByI.trigger = ""
GetTriggerByI.action = ""
GetTriggerByI.enabled = False
End If
End Function
I also have a GetTriggerByS function which does the same thing, except it takes a String parameter and returns the first (and only; the add method checks for uniqueness) trigger with that string as it's .trigger value.
trigger As String ' this is the text that will set the trigger off
action As String ' this is send back to server when the trigger is set off
enabled As Boolean ' whether the trigger can be set off or not
End Type
Dim triggers() As trigger ' stores all the users triggers (dynamic array)
Public numTriggers As Integer ' stores the current size of the array (incremented by AddTrigger function)
Public Function GetTriggerByI(ByVal i As Integer) As trigger
If i >= LBound(triggers) And i <= UBound(triggers) Then
GetTriggerByI = triggers(i)
Else ' invalid trigger, return default values
GetTriggerByI.trigger = ""
GetTriggerByI.action = ""
GetTriggerByI.enabled = False
End If
End Function
I also have a GetTriggerByS function which does the same thing, except it takes a String parameter and returns the first (and only; the add method checks for uniqueness) trigger with that string as it's .trigger value.
ASKER
If you want to see the exact data that is screwing up my program, get a mud client (or plain telnet if you want a headache) and connect to realms.game.org:4000.
Set up a character on there, then once you're through the newbie area (takes about 15 mins) you'll be able to access these commands, which are the ones that screw it up:
config (displays current configuration options)
score (your stats. should be able to get this in the newbie zone too)
who <race> (lists all players of <race>. I use 'who pixie' because that's what I play as)
prac (shows what skills/spells you have at what level)
There are probably others too, but these are the ones I've noticed because they're ones I use fairly often. They all involve lots of line breaks and lots of colour switching (but if I paste the raw ansi code into my test app it handles all the colour fine), which is what leads me to think it's a problem with how I'm catching and handling data arrivals.
Set up a character on there, then once you're through the newbie area (takes about 15 mins) you'll be able to access these commands, which are the ones that screw it up:
config (displays current configuration options)
score (your stats. should be able to get this in the newbie zone too)
who <race> (lists all players of <race>. I use 'who pixie' because that's what I play as)
prac (shows what skills/spells you have at what level)
There are probably others too, but these are the ones I've noticed because they're ones I use fairly often. They all involve lots of line breaks and lots of colour switching (but if I paste the raw ansi code into my test app it handles all the colour fine), which is what leads me to think it's a problem with how I'm catching and handling data arrivals.
That was a great idea. I modified my FTP test client and connected to realms.game.org:4000. I started the logon process but wasn't given pixie as a choice, so I'm thinking about selecting vampire instead. Any idea why I don't get pixie as a choice? Do you think it will make any difference in replicating the problem? Anyway, I'll take it up again when I get home from work. My next step will be to add the AnsiToRtf formatting logic that you've posted. Thanks for working with me - I've already learned a lot.
jeez, I'm doing it again. I meant TCP client, not FTP client. And I meant newbie process, not logon process.
Hi again back to this again eh ?
What you really need to do here is to try and make your code as asynchronous as possible.
On method of doing this in VB (I've done this before and it works) is to pass all your data onto a global queue. Something like a collection. You then use a timer or timers to rip data of the tail of the queue.
Just as an example:
Public gQueue as Collection
In your form load or somewhere:
Set gQueue = New Collection
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
Dim DataArrived As String
Winsock1.GetData DataArrived, vbString
If Not paused Then
gQueue.Add DataArrived
Else
stringBuffer = stringBuffer & DataArrived
End If
End Sub
'Set the timer interval to 1
Private Sub Timer1_Timer()
Dim strData as String
strData = gQueue.Item(1)
gQueue.Remove 1
OutputText ParseData(strData)
DoTriggers strData
End Sub
Alternatively Call something like this from Sub Main.
Public Sub ProcessData()
Do While DoEvents()
Dim strData as String
'Anything in the queue?
if gQueue.Count <> 0 then
strData = gQueue.Item(1)
gQueue.Remove 1
OutputText ParseData(strData)
DoTriggers strData
End If
Loop
End Sub
Hope this is of some help.
Vin.
What you really need to do here is to try and make your code as asynchronous as possible.
On method of doing this in VB (I've done this before and it works) is to pass all your data onto a global queue. Something like a collection. You then use a timer or timers to rip data of the tail of the queue.
Just as an example:
Public gQueue as Collection
In your form load or somewhere:
Set gQueue = New Collection
Private Sub Winsock1_DataArrival(ByVal
Dim DataArrived As String
Winsock1.GetData DataArrived, vbString
If Not paused Then
gQueue.Add DataArrived
Else
stringBuffer = stringBuffer & DataArrived
End If
End Sub
'Set the timer interval to 1
Private Sub Timer1_Timer()
Dim strData as String
strData = gQueue.Item(1)
gQueue.Remove 1
OutputText ParseData(strData)
DoTriggers strData
End Sub
Alternatively Call something like this from Sub Main.
Public Sub ProcessData()
Do While DoEvents()
Dim strData as String
'Anything in the queue?
if gQueue.Count <> 0 then
strData = gQueue.Item(1)
gQueue.Remove 1
OutputText ParseData(strData)
DoTriggers strData
End If
Loop
End Sub
Hope this is of some help.
Vin.
ASKER
VincentLawlor: I was thinking of doing something like that, but had no idea how. I'll give it a whirl when I get home (currently on campus - tee hee).
> 'Set the timer interval to 1
adg: I doubt it would make any difference in replicating the problem; I have a test mud set up on the computer I dev on, and I get the same problems regardless of race, class, alignment, etc.
If you're going to use the AnsiToRtf method I've got above, perhaps I should send you the module I've got it in, as well as a couple of other modules it's dependant on (Tokenizer, StringFuncs) for functionality that VB5 doesn't provide. I know VB6 has a Split() function, but I don't know whether it works the same as the Tokenizer I've got, and I don't know if my custom Replace() function works the same as the VB6 Replace() function either.
> 'Set the timer interval to 1
adg: I doubt it would make any difference in replicating the problem; I have a test mud set up on the computer I dev on, and I get the same problems regardless of race, class, alignment, etc.
If you're going to use the AnsiToRtf method I've got above, perhaps I should send you the module I've got it in, as well as a couple of other modules it's dependant on (Tokenizer, StringFuncs) for functionality that VB5 doesn't provide. I know VB6 has a Split() function, but I don't know whether it works the same as the Tokenizer I've got, and I don't know if my custom Replace() function works the same as the VB6 Replace() function either.
VincentLawlor: That is a very clever suggestion.
Subhuman: Thanks, I would like to see the code. Do you want to post it here? I really want to recreate the problem and/or see the problem resolved.
Subhuman: Thanks, I would like to see the code. Do you want to post it here? I really want to recreate the problem and/or see the problem resolved.
ASKER
I have my email address in my profile here; send me an email and I'll reply and attach a zip of the project.
ASKER
Vincent: Added your code. Hasn't made any significant changes that I've picked up on, and it hasn't fixed the problem of doubling up of long text segments, but I imagine it's improved data handling a lot.
adg: Sent you the files.
adg: Sent you the files.
Doubling up of long text segments as far as I recall happens when using UDP. The host looks like it is using User Datagram Packets to multicast the information.
Just out of interest here's a bit of an Explanation of UDP:
UDP provides a connectionless, unreliable transport service. Connectionless means that a communication session between hosts is not established before exchanging data. UDP is often used for one-to-many communications that use broadcast or multicast IP datagrams. The UDP connectionless datagram delivery service is unreliable because it does not guarantee data packet delivery and no notification is sent if a packet is not delivered. Also, UDP does not guarantee that packets are delivered in the same order in which they were sent.
Because delivery of UDP datagrams is not guaranteed, applications using UDP must supply their own mechanisms for reliability, if needed. Although UDP appears to have some limitations, it is useful in certain situations. For example, Winsock IP multicasting is implemented with UDP datagram type sockets. UDP is very efficient because of low overhead.
Vin.
Just out of interest here's a bit of an Explanation of UDP:
UDP provides a connectionless, unreliable transport service. Connectionless means that a communication session between hosts is not established before exchanging data. UDP is often used for one-to-many communications that use broadcast or multicast IP datagrams. The UDP connectionless datagram delivery service is unreliable because it does not guarantee data packet delivery and no notification is sent if a packet is not delivered. Also, UDP does not guarantee that packets are delivered in the same order in which they were sent.
Because delivery of UDP datagrams is not guaranteed, applications using UDP must supply their own mechanisms for reliability, if needed. Although UDP appears to have some limitations, it is useful in certain situations. For example, Winsock IP multicasting is implemented with UDP datagram type sockets. UDP is very efficient because of low overhead.
Vin.
ASKER
Hrm, interesting.
How would I work around that? Change the winsock control to use UDP?
How would I work around that? Change the winsock control to use UDP?
You could try changing it to UDP (don't think it will make much difference though).
UDP Peer To Peer Example.
UDP Basics
Creating a UDP application is even simpler than creating a TCP application because the UDP protocol doesn't require an explicit connection. In the TCP application above, one Winsock control must explicitly be set to "listen," while the other must initiate a connection with the Connect method.
In contrast, the UDP protocol doesn't require an explicit connection. To send data between two controls, three steps must be completed (on both sides of the connection):
Set the RemoteHost property to the name of the other computer.
Set the RemotePort property to the LocalPort property of the second control.
Invoke the Bind method specifying the LocalPort to be used. (This method is discussed in greater detail below.)
Because both computers can be considered "equal" in the relationship, it could be called a peer-to-peer application. To demonstrate this, the code below creates a "chat" application that allows two people to "talk" in real time to each other:
To create a UDP Peer
Create a new Standard EXE project.
Change the name of the default form to frmPeerA.
Change the caption of the form to "Peer A."
Draw a Winsock control on the form and name it udpPeerA.
On the Properties page, click Protocol and change the protocol to UDPProtocol.
Add two TextBox controls to the form. Name the first txtSend, and the second txtOutput.
Add the code below to the form.
Private Sub Form_Load()
' The control's name is udpPeerA
With udpPeerA
' IMPORTANT: be sure to change the RemoteHost
' value to the name of your computer.
.RemoteHost= "PeerB"
.RemotePort = 1001 ' Port to connect to.
.Bind 1002 ' Bind to the local port.
End With
frmPeerB.Show ' Show the second form.
End Sub
Private Sub txtSend_Change()
' Send text as soon as it's typed.
udpPeerA.SendData txtSend.Text
End Sub
Private Sub udpPeerA_DataArrival _
(ByVal bytesTotal As Long)
Dim strData As String
udpPeerA.GetData strData
txtOutput.Text = strData
End Sub
To create a second UDP Peer
Add a standard form to the project.
Change the name of the form to frmPeerB.
Change the caption of the form to "Peer B."
Draw a Winsock control on the form and name it udpPeerB.
On the Properties page, click Protocol and change the protocol to UDPProtocol.
Add two TextBox controls to the form. Name the TextBox txtSend, and the second txtOutput.
Add the code below to the form.
Private Sub Form_Load()
' The control's name is udpPeerB.
With udpPeerB
' IMPORTANT: be sure to change the RemoteHost
' value to the name of your computer.
.RemoteHost= "PeerA"
.RemotePort = 1002 ' Port to connect to.
.Bind 1001 ' Bind to the local port.
End With
End Sub
Private Sub txtSend_Change()
' Send text as soon as it's typed.
udpPeerB.SendData txtSend.Text
End Sub
Private Sub udpPeerB_DataArrival _
(ByVal bytesTotal As Long)
Dim strData As String
udpPeerB.GetData strData
txtOutput.Text = strData
End Sub
To try the example, press F5 to run the project, and type into the txtSend TextBox on either form. The text you type will appear in the txtOutput TextBox on the other form.
Vin.
UDP Peer To Peer Example.
UDP Basics
Creating a UDP application is even simpler than creating a TCP application because the UDP protocol doesn't require an explicit connection. In the TCP application above, one Winsock control must explicitly be set to "listen," while the other must initiate a connection with the Connect method.
In contrast, the UDP protocol doesn't require an explicit connection. To send data between two controls, three steps must be completed (on both sides of the connection):
Set the RemoteHost property to the name of the other computer.
Set the RemotePort property to the LocalPort property of the second control.
Invoke the Bind method specifying the LocalPort to be used. (This method is discussed in greater detail below.)
Because both computers can be considered "equal" in the relationship, it could be called a peer-to-peer application. To demonstrate this, the code below creates a "chat" application that allows two people to "talk" in real time to each other:
To create a UDP Peer
Create a new Standard EXE project.
Change the name of the default form to frmPeerA.
Change the caption of the form to "Peer A."
Draw a Winsock control on the form and name it udpPeerA.
On the Properties page, click Protocol and change the protocol to UDPProtocol.
Add two TextBox controls to the form. Name the first txtSend, and the second txtOutput.
Add the code below to the form.
Private Sub Form_Load()
' The control's name is udpPeerA
With udpPeerA
' IMPORTANT: be sure to change the RemoteHost
' value to the name of your computer.
.RemoteHost= "PeerB"
.RemotePort = 1001 ' Port to connect to.
.Bind 1002 ' Bind to the local port.
End With
frmPeerB.Show ' Show the second form.
End Sub
Private Sub txtSend_Change()
' Send text as soon as it's typed.
udpPeerA.SendData txtSend.Text
End Sub
Private Sub udpPeerA_DataArrival _
(ByVal bytesTotal As Long)
Dim strData As String
udpPeerA.GetData strData
txtOutput.Text = strData
End Sub
To create a second UDP Peer
Add a standard form to the project.
Change the name of the form to frmPeerB.
Change the caption of the form to "Peer B."
Draw a Winsock control on the form and name it udpPeerB.
On the Properties page, click Protocol and change the protocol to UDPProtocol.
Add two TextBox controls to the form. Name the TextBox txtSend, and the second txtOutput.
Add the code below to the form.
Private Sub Form_Load()
' The control's name is udpPeerB.
With udpPeerB
' IMPORTANT: be sure to change the RemoteHost
' value to the name of your computer.
.RemoteHost= "PeerA"
.RemotePort = 1002 ' Port to connect to.
.Bind 1001 ' Bind to the local port.
End With
End Sub
Private Sub txtSend_Change()
' Send text as soon as it's typed.
udpPeerB.SendData txtSend.Text
End Sub
Private Sub udpPeerB_DataArrival _
(ByVal bytesTotal As Long)
Dim strData As String
udpPeerB.GetData strData
txtOutput.Text = strData
End Sub
To try the example, press F5 to run the project, and type into the txtSend TextBox on either form. The text you type will appear in the txtOutput TextBox on the other form.
Vin.
Your email hasn't arrived - can you send it again?
If you switch to UDP then I think your MUD client won't be compatible with other MUD's because they are using TCP. But I'm not sure.
If you switch to UDP then I think your MUD client won't be compatible with other MUD's because they are using TCP. But I'm not sure.
ASKER
Hrm, ok, will do.
*shrugs* guess I'll find out soon enough.
*shrugs* guess I'll find out soon enough.
Thanks, I got it. I just noticed something interesting that might be a clue. If I run my TCP client and server from the IDE and send 9000 bytes from the server, the client has two data arrivals, the first one is 8192 and the second is 808, as expected. But if I compile the code and run the client and server from EXE's, when I send 9000 bytes the two data arrivals have length 8192 and 9000. Are you noticing any difference in the problem when running from IDE versus EXE?
Are you saying that two blocks of data one of 8192 and the other of 9000 are being received or is the total number of bytes 8192 on the first data arrival and 9000 on the second.
Weird...
Vin.
Weird...
Vin.
Vin, that is an excellent question.
In my previous comment I was referring to the value of the bytesTotal field, not the actual length of the received data. In order to answer Vin's question more precisely, I modified my DataArrival routine as follows and it seems to be much more stable in behavior. Note the handling of error code zero and the resetting of strData to empty after it has been used.
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
On Error GoTo ErrorHandler
Dim strData As String
Winsock1.GetData strData, vbString
Text1.Text = strData
strData = ""
Exit Sub
ErrorHandler:
If Err.Number = 0 Then Resume
Err.Raise Err.Number
End Sub
I hope that making these changes might make a difference.
BTW, I received a second copy of your email so I guess there is some kind of delay happening.
In my previous comment I was referring to the value of the bytesTotal field, not the actual length of the received data. In order to answer Vin's question more precisely, I modified my DataArrival routine as follows and it seems to be much more stable in behavior. Note the handling of error code zero and the resetting of strData to empty after it has been used.
Private Sub Winsock1_DataArrival(ByVal
On Error GoTo ErrorHandler
Dim strData As String
Winsock1.GetData strData, vbString
Text1.Text = strData
strData = ""
Exit Sub
ErrorHandler:
If Err.Number = 0 Then Resume
Err.Raise Err.Number
End Sub
I hope that making these changes might make a difference.
BTW, I received a second copy of your email so I guess there is some kind of delay happening.
Now that I look at it, maybe there should be an "on error goto 0" after the "if err.number = 0 then resume". Otherwise a non-zero error might cause an unwanted loop.
ASKER
I changed my DataArrival method to this, but the problem persists:
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
On Error Goto DataArrivalError
Dim DataArrived As String
Winsock1.GetData DataArrived, vbString
If Not paused Then
gQueue.Add DataArrived
Else
stringBuffer = stringBuffer & DataArrived
End If
DataArrived = ""
Exit Sub
DataArrivalError:
Err.Raise Err.Number
End Sub
Also tried playing with UDP, and after screwing around with some settings and commenting out a few bits (like the close connection if Connect event doesn't happen with 10 seconds of opening connection) and could retrieve data from the server - but it balked when it got to Winsock1.SendData with the following error:
Run-time error '10047': Address Family is not supported.
Digging through the documentation was fruitless; it didn't say anything about how to send data while using the UDP protocol, so I couldn't actually send the offending commands to check the results.
Private Sub Winsock1_DataArrival(ByVal
On Error Goto DataArrivalError
Dim DataArrived As String
Winsock1.GetData DataArrived, vbString
If Not paused Then
gQueue.Add DataArrived
Else
stringBuffer = stringBuffer & DataArrived
End If
DataArrived = ""
Exit Sub
DataArrivalError:
Err.Raise Err.Number
End Sub
Also tried playing with UDP, and after screwing around with some settings and commenting out a few bits (like the close connection if Connect event doesn't happen with 10 seconds of opening connection) and could retrieve data from the server - but it balked when it got to Winsock1.SendData with the following error:
Run-time error '10047': Address Family is not supported.
Digging through the documentation was fruitless; it didn't say anything about how to send data while using the UDP protocol, so I couldn't actually send the offending commands to check the results.
Darn, I was hoping that would help. I've been looking at the program you sent - that's very ambitious work! I'll try to get something working based on a simplified version and see if that helps.
In the meanwhile, here is a UDP test peer that works on my machine. In order to use it, it has to be run twice. I compile it and then run two exe instances or run one exe instance and one ide instance. It automatically sets up the ports. You can see how much simplier UDP is that TCP. But I think that in any communication both sides must be using the same protocol.
Option Explicit
Private Sub Form_Load()
txtSend.Text = ""
txtReceive.Text = ""
txtReceive.Locked = True
On Error GoTo ErrorHandler
With Winsock1
.Close
.RemoteHost = "localhost"
.Bind 2000
.RemotePort = 2001
End With
Exit Sub
ErrorHandler:
If Err.Number = sckAddressInUse Then
With Winsock1
.Close
.RemoteHost = "localhost"
.Bind 2001
.RemotePort = 2000
End With
End If
On Error GoTo 0
End Sub
Private Sub Form_Resize()
cmdClear.Left = Me.Width - cmdClear.Width - 200
cmdSend.Left = cmdClear.Left
txtSend.Width = Me.Width - cmdClear.Width - 500
txtReceive.Width = txtSend.Width
End Sub
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
'MsgBox "DataArrival Len = " + CStr(bytesTotal)
Dim strData As String
Winsock1.GetData strData
If txtReceive.Text = "" Then
txtReceive.Text = strData + " (" + CStr(Now) + ")"
Else
txtReceive.Text = txtReceive + vbCrLf + strData + " (" + CStr(Now) + ")"
End If
End Sub
Private Sub cmdSend_Click()
On Error GoTo ErrorHandler
Winsock1.SendData txtSend.Text
txtSend.Text = ""
Exit Sub
ErrorHandler:
If Err.Number = 126 Then Resume Next
MsgBox CStr(Err.Number) + Err.Description + Err.Source
End Sub
Private Sub cmdClear_Click()
txtReceive.Text = ""
End Sub
Private Sub Winsock1_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)
MsgBox "Winsock Error: " + CStr(Number) + " " + Description
End Sub
In the meanwhile, here is a UDP test peer that works on my machine. In order to use it, it has to be run twice. I compile it and then run two exe instances or run one exe instance and one ide instance. It automatically sets up the ports. You can see how much simplier UDP is that TCP. But I think that in any communication both sides must be using the same protocol.
Option Explicit
Private Sub Form_Load()
txtSend.Text = ""
txtReceive.Text = ""
txtReceive.Locked = True
On Error GoTo ErrorHandler
With Winsock1
.Close
.RemoteHost = "localhost"
.Bind 2000
.RemotePort = 2001
End With
Exit Sub
ErrorHandler:
If Err.Number = sckAddressInUse Then
With Winsock1
.Close
.RemoteHost = "localhost"
.Bind 2001
.RemotePort = 2000
End With
End If
On Error GoTo 0
End Sub
Private Sub Form_Resize()
cmdClear.Left = Me.Width - cmdClear.Width - 200
cmdSend.Left = cmdClear.Left
txtSend.Width = Me.Width - cmdClear.Width - 500
txtReceive.Width = txtSend.Width
End Sub
Private Sub Winsock1_DataArrival(ByVal
'MsgBox "DataArrival Len = " + CStr(bytesTotal)
Dim strData As String
Winsock1.GetData strData
If txtReceive.Text = "" Then
txtReceive.Text = strData + " (" + CStr(Now) + ")"
Else
txtReceive.Text = txtReceive + vbCrLf + strData + " (" + CStr(Now) + ")"
End If
End Sub
Private Sub cmdSend_Click()
On Error GoTo ErrorHandler
Winsock1.SendData txtSend.Text
txtSend.Text = ""
Exit Sub
ErrorHandler:
If Err.Number = 126 Then Resume Next
MsgBox CStr(Err.Number) + Err.Description + Err.Source
End Sub
Private Sub cmdClear_Click()
txtReceive.Text = ""
End Sub
Private Sub Winsock1_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)
MsgBox "Winsock Error: " + CStr(Number) + " " + Description
End Sub
ASKER
So EE is back up again.
Typed 'config' in two version of the client; one an older one with no ansi parsing, the other the newer version.
This is the data sent by the server including ansi codes and everything.
http://www.djh.f2s.com/dumpage/ansicode.gif
This is how it is appearing:
http://www.djh.f2s.com/dumpage/rtf.gif
Notice it displays twice, fine the first time, then spits out the (incorrect) rtf code the second time.
This is the current metamorphosis of the AnsiToRtf function:
-=-=-=-=-=-=-=-=-=-=-=-=-= -
Public Function AnsiToRtf(ByVal str As String) As String
Dim s, c, a, r, cs As String
Dim i, j, cc, csc As Integer
Dim ac As AnsiCodeList
Dim b, cr As Boolean
b = False
cr = False
If midc Then
j = InStr(1, str, "m", vbTextCompare)
MsgBox "midc j=" & j
str = Right(str, Len(str) - j)
End If
midc = False
cs = "{{\colortbl;\red255\green 255\blue25 5;"
csc = 2
CurrColour = 1
For i = 1 To Len(str) ' do strings start at 0 or 1?
r = "{\cf" & CurrColour & " "
c = Mid(str, i, 1)
If c = Chr(27) Then
midc = False
r = "{" ' stop rtf code building up over loop
j = InStr(i, str, "m", vbTextCompare) ' get index of end of string
'MsgBox "j=" & j & " i=" & i
If j = 0 Then
j = Len(str)
midc = True
End If
a = Mid(str, i, (j - i) + 1) ' get ansi code from string
ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
cc = GetColour(ac)
'MsgBox "cc=" & cc
'r = ' rtf code equivalent to 'a'
If cc >= 0 And cc <= 15 Then ' is it a valid colour?
cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
r = r & "\cf" & csc & " "
csc = csc + 1
If b Then s = s & "}" ' closes previous rtf code
End If
s = s & r ' appends rtf code to new string
i = j ' go to next char after the code. the Next i line
' will add 1 to this.
b = True
cr = False
ElseIf c = Chr(13) Then
'MsgBox "cr"
If b Then
s = s & "\par" ' if it's inside {} it needs \par instead of vbcrlf
Else
s = s & vbCrLf
End If
cr = True
' ElseIf c = Chr(10) Then
' 'MsgBox "lf"
' If b And Not cr Then
' s = s & "\par"
' ElseIf Not cr Then
' s = s & vbCrLf
' End If
' cr = False
ElseIf c = "\" Then
c = "{\\}"
s = s & c
cr = False
Else
s = s & c ' if it's a normal character just append it to the new string
cr = False
End If
Next i
cs = cs & "}" ' close colortbl string
'if b then
s = s & "}}" ' if rtf has been opened, close it
AnsiToRtf = cs & s ' the new string
CurrColour = cc
End Function
-=-=-=-=-=-=-=-=-=-=-=-=-= -=-
midc is a boolean declared outside the function in order to be global (and thus remember it's value between calls) It is used to filter out and ignore ansi codes that have been split up between data arrivals.
Typed 'config' in two version of the client; one an older one with no ansi parsing, the other the newer version.
This is the data sent by the server including ansi codes and everything.
http://www.djh.f2s.com/dumpage/ansicode.gif
This is how it is appearing:
http://www.djh.f2s.com/dumpage/rtf.gif
Notice it displays twice, fine the first time, then spits out the (incorrect) rtf code the second time.
This is the current metamorphosis of the AnsiToRtf function:
-=-=-=-=-=-=-=-=-=-=-=-=-=
Public Function AnsiToRtf(ByVal str As String) As String
Dim s, c, a, r, cs As String
Dim i, j, cc, csc As Integer
Dim ac As AnsiCodeList
Dim b, cr As Boolean
b = False
cr = False
If midc Then
j = InStr(1, str, "m", vbTextCompare)
MsgBox "midc j=" & j
str = Right(str, Len(str) - j)
End If
midc = False
cs = "{{\colortbl;\red255\green
csc = 2
CurrColour = 1
For i = 1 To Len(str) ' do strings start at 0 or 1?
r = "{\cf" & CurrColour & " "
c = Mid(str, i, 1)
If c = Chr(27) Then
midc = False
r = "{" ' stop rtf code building up over loop
j = InStr(i, str, "m", vbTextCompare) ' get index of end of string
'MsgBox "j=" & j & " i=" & i
If j = 0 Then
j = Len(str)
midc = True
End If
a = Mid(str, i, (j - i) + 1) ' get ansi code from string
ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
cc = GetColour(ac)
'MsgBox "cc=" & cc
'r = ' rtf code equivalent to 'a'
If cc >= 0 And cc <= 15 Then ' is it a valid colour?
cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
r = r & "\cf" & csc & " "
csc = csc + 1
If b Then s = s & "}" ' closes previous rtf code
End If
s = s & r ' appends rtf code to new string
i = j ' go to next char after the code. the Next i line
' will add 1 to this.
b = True
cr = False
ElseIf c = Chr(13) Then
'MsgBox "cr"
If b Then
s = s & "\par" ' if it's inside {} it needs \par instead of vbcrlf
Else
s = s & vbCrLf
End If
cr = True
' ElseIf c = Chr(10) Then
' 'MsgBox "lf"
' If b And Not cr Then
' s = s & "\par"
' ElseIf Not cr Then
' s = s & vbCrLf
' End If
' cr = False
ElseIf c = "\" Then
c = "{\\}"
s = s & c
cr = False
Else
s = s & c ' if it's a normal character just append it to the new string
cr = False
End If
Next i
cs = cs & "}" ' close colortbl string
'if b then
s = s & "}}" ' if rtf has been opened, close it
AnsiToRtf = cs & s ' the new string
CurrColour = cc
End Function
-=-=-=-=-=-=-=-=-=-=-=-=-=
midc is a boolean declared outside the function in order to be global (and thus remember it's value between calls) It is used to filter out and ignore ansi codes that have been split up between data arrivals.
Well, the rtf code looks OK to my eyeball but I'm 90% sure that it's not. Can you make both strings available (ANSI and RTF) so I can use them in my testing? I think maybe a rewrite of AnsiToRTF is in order. Where did you get it from (or did you write it)?
ASKER
Woohoo, I fixed it!
The problem was the default return value in the GetColour function - it was returning a value that was out of range, and the AnsiToRtf was then checking that the value was in range before using the colour. But, if the value was out of range, then as well as not using the colour, it also failed to close the open brace for that colour code.
I modified it so that if a code that didn't contain a colour value showed up, it would keep using the previous colour (and then add/remove 'bright' as appropriate), then removed the if-check in AnsiToRtf so the colour is always added.
This thread has kinda wandered away from the original question, hasn't it? Hehe. :)
Here's the new code for AnsiToRtf and GetColour:
-=-=-=-=-=-=-=-=-=-=-=-
Public Function AnsiToRtf(ByVal str As String) As String
Dim s, c, a, r, cs As String
Dim i, j, cc, csc As Integer
Dim ac As AnsiCodeList
Dim b, cr As Boolean
b = False
cr = False
If midc Then
j = InStr(1, str, "m", vbTextCompare)
MsgBox "midc j=" & j
str = Right(str, Len(str) - j)
End If
midc = False
cs = "{{\colortbl;\red" & GetColourByI(15).Red & "\green" & GetColourByI(15).Green & "\blue" & GetColourByI(15).Blue & ";"
csc = 2
CurrColour = 1
For i = 1 To Len(str)
' r = "{\cf" & CurrColour & " "
c = Mid(str, i, 1)
If c = Chr(27) Then
midc = False
r = "{" ' stop rtf code building up over loop
j = InStr(i, str, "m", vbTextCompare) ' get index of end of string
'MsgBox "j=" & j & " i=" & i
If j = 0 Then
j = Len(str)
midc = True
' Else
End If
a = Mid(str, i, (j - i) + 1) ' get ansi code from string
ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
cc = GetColour(ac)
'MsgBox "cc=" & cc
' End If
'r = ' rtf code equivalent to 'a'
' If cc >= 0 And cc <= 15 Then ' is it a valid colour?
gcc = cc Mod 8 ' raw colour value without bright attribute
cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
r = r & "\cf" & csc & " "
csc = csc + 1
If b Then s = s & "}" ' closes previous rtf code
' End If
s = s & r ' appends rtf code to new string
i = j ' go to next char after the code. the Next i line
' will add 1 to this.
b = True
cr = False
ElseIf c = Chr(13) Then
'MsgBox "cr"
If b Then
s = s & "\par" ' if it's inside {} it needs \par instead of vbcrlf
Else
s = s & vbCrLf
End If
cr = True
' ElseIf c = Chr(10) Then
' 'MsgBox "lf"
' If b And Not cr Then
' s = s & "\par"
' ElseIf Not cr Then
' s = s & vbCrLf
' End If
' cr = False
ElseIf c = "\" Then
c = "{\\}"
s = s & c
cr = False
Else
s = s & c ' if it's a normal character just append it to the new string
cr = False
End If
Next i
cs = cs & "}" ' close colortbl string
'if b then
s = s & "}}" ' if rtf has been opened, close it
AnsiToRtf = cs & s ' the new string
CurrColour = cc
End Function
Private Function GetColour(ac As AnsiCodeList) As Integer
Dim i As Integer
GetColour = -1
If ac.Count <= 0 Then Exit Function
For i = 0 To ac.Count - 1
If ac.AnsiCodes(i) >= 30 And ac.AnsiCodes(i) <= 37 Then
GetColour = ac.AnsiCodes(i)
Exit For
End If
Next i
If GetColour = -1 Then GetColour = gcc
If HasValue(ac, 0) Then IsBright = False
If HasValue(ac, 1) Or IsBright Then
GetColour = GetColour + 8
IsBright = True
End If
GetColour = GetColour - 30
If GetColour < 0 Or GetColour > 15 Then GetColour = 15
' MsgBox "Colours = " & GetColour
End Function
-=-=-=-=-=-=-=-=-=-=-=-=-
If you want me to email the whole new project, just say so.
I'm still getting some weirdness when ansi codes are split across two DataArrivals. The second part of each split code is still being output (at the start of the next DataArrival), even though I've included this code to theoretically remove it:
...
If midc Then
j = InStr(1, str, "m", vbTextCompare)
MsgBox "midc j=" & j
str = Right(str, Len(str) - j)
End If
...
I'd like to either a) stop the bit of code displaying, or b) re-connect split ansi codes and parse them as normal. Any suggestions?
The problem was the default return value in the GetColour function - it was returning a value that was out of range, and the AnsiToRtf was then checking that the value was in range before using the colour. But, if the value was out of range, then as well as not using the colour, it also failed to close the open brace for that colour code.
I modified it so that if a code that didn't contain a colour value showed up, it would keep using the previous colour (and then add/remove 'bright' as appropriate), then removed the if-check in AnsiToRtf so the colour is always added.
This thread has kinda wandered away from the original question, hasn't it? Hehe. :)
Here's the new code for AnsiToRtf and GetColour:
-=-=-=-=-=-=-=-=-=-=-=-
Public Function AnsiToRtf(ByVal str As String) As String
Dim s, c, a, r, cs As String
Dim i, j, cc, csc As Integer
Dim ac As AnsiCodeList
Dim b, cr As Boolean
b = False
cr = False
If midc Then
j = InStr(1, str, "m", vbTextCompare)
MsgBox "midc j=" & j
str = Right(str, Len(str) - j)
End If
midc = False
cs = "{{\colortbl;\red" & GetColourByI(15).Red & "\green" & GetColourByI(15).Green & "\blue" & GetColourByI(15).Blue & ";"
csc = 2
CurrColour = 1
For i = 1 To Len(str)
' r = "{\cf" & CurrColour & " "
c = Mid(str, i, 1)
If c = Chr(27) Then
midc = False
r = "{" ' stop rtf code building up over loop
j = InStr(i, str, "m", vbTextCompare) ' get index of end of string
'MsgBox "j=" & j & " i=" & i
If j = 0 Then
j = Len(str)
midc = True
' Else
End If
a = Mid(str, i, (j - i) + 1) ' get ansi code from string
ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
cc = GetColour(ac)
'MsgBox "cc=" & cc
' End If
'r = ' rtf code equivalent to 'a'
' If cc >= 0 And cc <= 15 Then ' is it a valid colour?
gcc = cc Mod 8 ' raw colour value without bright attribute
cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
r = r & "\cf" & csc & " "
csc = csc + 1
If b Then s = s & "}" ' closes previous rtf code
' End If
s = s & r ' appends rtf code to new string
i = j ' go to next char after the code. the Next i line
' will add 1 to this.
b = True
cr = False
ElseIf c = Chr(13) Then
'MsgBox "cr"
If b Then
s = s & "\par" ' if it's inside {} it needs \par instead of vbcrlf
Else
s = s & vbCrLf
End If
cr = True
' ElseIf c = Chr(10) Then
' 'MsgBox "lf"
' If b And Not cr Then
' s = s & "\par"
' ElseIf Not cr Then
' s = s & vbCrLf
' End If
' cr = False
ElseIf c = "\" Then
c = "{\\}"
s = s & c
cr = False
Else
s = s & c ' if it's a normal character just append it to the new string
cr = False
End If
Next i
cs = cs & "}" ' close colortbl string
'if b then
s = s & "}}" ' if rtf has been opened, close it
AnsiToRtf = cs & s ' the new string
CurrColour = cc
End Function
Private Function GetColour(ac As AnsiCodeList) As Integer
Dim i As Integer
GetColour = -1
If ac.Count <= 0 Then Exit Function
For i = 0 To ac.Count - 1
If ac.AnsiCodes(i) >= 30 And ac.AnsiCodes(i) <= 37 Then
GetColour = ac.AnsiCodes(i)
Exit For
End If
Next i
If GetColour = -1 Then GetColour = gcc
If HasValue(ac, 0) Then IsBright = False
If HasValue(ac, 1) Or IsBright Then
GetColour = GetColour + 8
IsBright = True
End If
GetColour = GetColour - 30
If GetColour < 0 Or GetColour > 15 Then GetColour = 15
' MsgBox "Colours = " & GetColour
End Function
-=-=-=-=-=-=-=-=-=-=-=-=-
If you want me to email the whole new project, just say so.
I'm still getting some weirdness when ansi codes are split across two DataArrivals. The second part of each split code is still being output (at the start of the next DataArrival), even though I've included this code to theoretically remove it:
...
If midc Then
j = InStr(1, str, "m", vbTextCompare)
MsgBox "midc j=" & j
str = Right(str, Len(str) - j)
End If
...
I'd like to either a) stop the bit of code displaying, or b) re-connect split ansi codes and parse them as normal. Any suggestions?
Congratulations! Thats great news. I don't think the question has wandered too far. In your original question, you mentioned that you thought it was the winsock and not the AnsiToRtf. It seems at this point that it was the AnsiToRtf but we're still on the same topic.
Off the top of my head, I like option b. Can you do something like a deque structure where text can be pushed and popped off either end? As new text arrives you push it onto the bottom of the deque and when you are ready to process text you pop it off the top of the deque. So far, this is similar to what you've already done based on a previous suggestion. The new part would be that ansitortf would recognize when it has an incomplete remainder of text and instead of translating it, would push it back onto the top of the deque to await further processing. I'm not sure if that is possible given the current design of the ansitortf code - just an idea.
Off the top of my head, I like option b. Can you do something like a deque structure where text can be pushed and popped off either end? As new text arrives you push it onto the bottom of the deque and when you are ready to process text you pop it off the top of the deque. So far, this is similar to what you've already done based on a previous suggestion. The new part would be that ansitortf would recognize when it has an incomplete remainder of text and instead of translating it, would push it back onto the top of the deque to await further processing. I'm not sure if that is possible given the current design of the ansitortf code - just an idea.
ASKER
Once again I go ask a question then go off and start coding without waiting to see what comes of it. ;)
I've changed the ansitortf function a tad, so that when it finds an unterminated ansi code, rather than dropping it, it stores it in an external variable 'mids' (which is then accessible next time the ansitortf function is called). Then when the ansitortf runs next time it checks the boolean 'midc' to determine if a code was chopped in half, and if so it prepends the 'mids' variable to the string parameter before it starts parsing, and resets mids and midc.
It seems to work ok, except that in one case (on my local mud) there is a line that is sent where the code is broken in half, and there is a line break before the start of the code. The line break doesn't parse for some reason I can't figure out.
New AnsiToRtf code:
-=-=-=-=-=-=-=-=-=-=-
Public Function AnsiToRtf(ByVal str As String) As String
Dim s, c, a, r, cs As String
Dim i, j, cc, csc As Integer
Dim ac As AnsiCodeList
Dim b, cr As Boolean
b = False
cr = False
If midc Then
str = vbCrLf & mids & str
'MsgBox str
Else
str = "[m" & str
End If
midc = False
mids = ""
cs = "{{\colortbl;\red" & GetColourByI(15).Red & "\green" & GetColourByI(15).Green & "\blue" & GetColourByI(15).Blue & ";"
csc = 2
CurrColour = 1
For i = 1 To Len(str)
c = Mid(str, i, 1)
If c = Chr(27) Then
midc = False
r = "{" ' stop rtf code building up over loop
j = InStr(i, str, "m", vbTextCompare) ' get index of end of string
'MsgBox "j=" & j & " i=" & i
If j = 0 Then
midc = True
mids = Mid(str, i, Len(str))
mids = Tokenize(mids, vbCrLf).Tokens(0)
'MsgBox mids
j = Len(str)
End If
a = Mid(str, i, (j - i) + 1) ' get ansi code from string
ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
cc = GetColour(ac)
'MsgBox "cc=" & cc
gcc = cc Mod 8
cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
r = r & "\cf" & csc & " "
csc = csc + 1
If b Then s = s & "}" ' closes previous rtf code
s = s & r ' appends rtf code to new string
i = j ' go to next char after the code. the Next i line
' will add 1 to this.
b = True
cr = False
ElseIf c = Chr(13) Then
'MsgBox "cr"
If b Then
s = s & "\par" ' if it's inside {} it needs \par instead of vbcrlf
Else
s = s & vbCrLf
End If
cr = True
' ElseIf c = Chr(10) Then
' 'MsgBox "lf"
' If b And Not cr Then
' s = s & "\par"
' ElseIf Not cr Then
' s = s & vbCrLf
' End If
' cr = False
ElseIf c = "\" Then
c = "{\\}"
s = s & c
cr = False
Else
s = s & c ' if it's a normal character just append it to the new string
cr = False
End If
Next i
cs = cs & "}" ' close colortbl string
'if b then
s = s & "}}" ' if rtf has been opened, close it
AnsiToRtf = cs & s ' the new string
CurrColour = cc
End Function
-=-=-=-=-=-=-=-=-=-=-
If we can sort that out, great. If not, it's tolerable so I'll probably just leave it be. But might as well at least give it a whack. :)
I've changed the ansitortf function a tad, so that when it finds an unterminated ansi code, rather than dropping it, it stores it in an external variable 'mids' (which is then accessible next time the ansitortf function is called). Then when the ansitortf runs next time it checks the boolean 'midc' to determine if a code was chopped in half, and if so it prepends the 'mids' variable to the string parameter before it starts parsing, and resets mids and midc.
It seems to work ok, except that in one case (on my local mud) there is a line that is sent where the code is broken in half, and there is a line break before the start of the code. The line break doesn't parse for some reason I can't figure out.
New AnsiToRtf code:
-=-=-=-=-=-=-=-=-=-=-
Public Function AnsiToRtf(ByVal str As String) As String
Dim s, c, a, r, cs As String
Dim i, j, cc, csc As Integer
Dim ac As AnsiCodeList
Dim b, cr As Boolean
b = False
cr = False
If midc Then
str = vbCrLf & mids & str
'MsgBox str
Else
str = "[m" & str
End If
midc = False
mids = ""
cs = "{{\colortbl;\red" & GetColourByI(15).Red & "\green" & GetColourByI(15).Green & "\blue" & GetColourByI(15).Blue & ";"
csc = 2
CurrColour = 1
For i = 1 To Len(str)
c = Mid(str, i, 1)
If c = Chr(27) Then
midc = False
r = "{" ' stop rtf code building up over loop
j = InStr(i, str, "m", vbTextCompare) ' get index of end of string
'MsgBox "j=" & j & " i=" & i
If j = 0 Then
midc = True
mids = Mid(str, i, Len(str))
mids = Tokenize(mids, vbCrLf).Tokens(0)
'MsgBox mids
j = Len(str)
End If
a = Mid(str, i, (j - i) + 1) ' get ansi code from string
ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
cc = GetColour(ac)
'MsgBox "cc=" & cc
gcc = cc Mod 8
cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
r = r & "\cf" & csc & " "
csc = csc + 1
If b Then s = s & "}" ' closes previous rtf code
s = s & r ' appends rtf code to new string
i = j ' go to next char after the code. the Next i line
' will add 1 to this.
b = True
cr = False
ElseIf c = Chr(13) Then
'MsgBox "cr"
If b Then
s = s & "\par" ' if it's inside {} it needs \par instead of vbcrlf
Else
s = s & vbCrLf
End If
cr = True
' ElseIf c = Chr(10) Then
' 'MsgBox "lf"
' If b And Not cr Then
' s = s & "\par"
' ElseIf Not cr Then
' s = s & vbCrLf
' End If
' cr = False
ElseIf c = "\" Then
c = "{\\}"
s = s & c
cr = False
Else
s = s & c ' if it's a normal character just append it to the new string
cr = False
End If
Next i
cs = cs & "}" ' close colortbl string
'if b then
s = s & "}}" ' if rtf has been opened, close it
AnsiToRtf = cs & s ' the new string
CurrColour = cc
End Function
-=-=-=-=-=-=-=-=-=-=-
If we can sort that out, great. If not, it's tolerable so I'll probably just leave it be. But might as well at least give it a whack. :)
Sounds like you're making great progress!
ASKER
Ok, interesting bit of weirdness has come up here. I sent a copy of the program to a friend who also plays RoD, so he could give it a shot, and he's getting line breaks after prompts but before the command he sends.
This is a screenshot of the output for me (as it's supposed to be):
http://www.djh.f2s.com/dumpage/gumboots.gif
This is a screenshot of how it's coming out for him:
http://www.djh.f2s.com/dumpage/gumboots.jpg
Notice how when he types a command like 'sleep' or 'whois self', the command is echoed on the line following the prompt, rather than the same line as it is for me.
I have no idea what might be causing this. It looks almost as if the mud was sending the line breaks in different places for him than it was for me, although I don't see why it would do this.
Any thoughts?
This is a screenshot of the output for me (as it's supposed to be):
http://www.djh.f2s.com/dumpage/gumboots.gif
This is a screenshot of how it's coming out for him:
http://www.djh.f2s.com/dumpage/gumboots.jpg
Notice how when he types a command like 'sleep' or 'whois self', the command is echoed on the line following the prompt, rather than the same line as it is for me.
I have no idea what might be causing this. It looks almost as if the mud was sending the line breaks in different places for him than it was for me, although I don't see why it would do this.
Any thoughts?
ASKER
We both get the same line breaks using GMud, which seems to suggest it's not the mud doing it (although it might just be it was out of synch a little, I'll have to get him to check it again).
http://www.djh.f2s.com/dumpage/gmud.gif
http://www.djh.f2s.com/dumpage/gmud.gif
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Yeah, the logging sounds like a good idea, I'll have to play with that. I know we were using the same executable, because I'd only just sent it to him and was using the same one I sent. :)
ASKER
Stupid EE downtime.
Thanks for the help. :)
Didn't figure out the weird line breaks on win2k, might work on that some other time.
Thanks for the help. :)
Didn't figure out the weird line breaks on win2k, might work on that some other time.
I just realized you'd accepted my answer. Thank you and I'm sorry I wasn't more help. As you mentioned, the ee down time made it difficult.