|
-
Sep 28th, 2000, 07:44 AM
#1
Thread Starter
Fanatic Member
I'm attempting to write a chat program that allows multiple users over telnet. The program works relatively well, it's object oriented (and still in the progress of converting
the code). There are player objects which contain their own sockets, and command parsers (to help eliminate confusion and conflicts). For now, the objects are stored in
an Array (IE Player() as new oPlayer).
What I'm doing is using a large case statement to deal with commands, parse out the data that's to be sent to the other users in the chat program, and grab other
parameters from the incoming data.
Currently, when someone sends something that's seen by everyone, the server will cycle through the players who are online with a for next loop. (previously it was set to go
to the maximum number of users each time, which slowed it down a bit). For instance:
Code:
For x = 1 to playercollection.count
y = playercollection.iIndex(x)
player(y).dataout = Message
next x
As you can see, this would get rather boggy if more people logged on and there was alot of chatting going on. Going to maximum users all the time is a rather long
process in itself.
What I'd like to know, is if there is a way to utilize an event handler (this project has just modules and class modules, no forms) to take care of this problem. My thinking? If
I can use events for commands, then i do not have to worry about problems dealing with invalid player objects stuck in the player collection. If this will work, all I have to
do is raise the event Player_Chat or Player_Action and have the objects handle it themselves instead of cycling through a for next loop every time something happens.
As I understand it, an object cannot handle an event that is declared within itself, but I can add an Event Handler object that contains the events to be triggered. I've tried
to set up the error handler in a class module called PlayerEvents. Here is an example of my implementation, which didn't do what I needed it to.
In class module PlayerEvents.cls:
Code:
Public Event Command(Command As String, Player As Index)
Public Sub parseCommand(pCommand As String, pPlayer as Integer)
RaiseEvent Command(pCommand,pPlayer)
End Sub
In class module Player.cls:
Code:
Private WithEvents Socket as WinSock
Private WithEvents ehPlayer as PlayerEvents
Private Sub Class_Initialize()
Set ehPlayer = New PlayerEvents
'The code breaks without the above line
Set Socket = New WinSock
End Sub
Private Sub Socket_DataArrival(...)
.....
Select Case LCase(left(sCommand,instr(1,sCommand," ")-1))
Case "chat"
ehPlayer.parseCommand(sCommand,SocketIndex)
End Select
Private Sub ehPlayer_Command(Command As String, Index as Integer)
Socket.SendData parseit(Command)
End Sub
The above code is a representation, I've obviously left out variables and such, and just showed how I was working with the events. With the above code, the event works,
but only for the person who raised the event. I think it has something to do with Set ... = New ... statement. I don't remember where I saw it, but I thought I read something
that stated that there is a problem with mulitple objects that contain an instance of the event handler object not being able to see the events generated by the other
copies or some such... As you can see from this last statement, events have somewhat confused me heh
The question now is, even though i can get a user to deal with his own events, is there a way to trigger an event in the other user objects so that when one person sends a
command, the appropriate event will fire for all the others so they can recieve the message? A quick example of what I'm asking follows:
There are 4 players: A, B, C, & D
1 -- Player A sends the command: chat how is everyone.
2 -- The player object recives this, and sends this command to the event handler object which raises the event PlayerChat.
3 -- Having raised the event, the following happens.
4 -- The Player objects for players B, C, & D see that the PlayerChat event has been triggered, in each object, the Handler_PlayerChat sub fires, sending: PlayerA: how is
everyone. to Socket.SendData
5 -- The player object for Player A sees the event has been triggered and the Handler_PlayerChat sub fires, sending: You Chat: how is everyone. to Socket.SendData
I'm hoping someone can help me out as it's rather unstable to 1-rely on for ... next loops, especially if more players are logged on, and 2-there has been quite a bit of
instability on behalf of the collection and for ... next loops which causes the server to crash.
If I can get the event handler working, without causing conflicts with input and output, I will worry less about having to loop through everything as much as I am now.
Update: I tried to use a Globally declared EventHandler to see if that would work and it didn't.
Does _anyone_ have an idea they might suggest?
Thanks for reading my book,
Excalibur
-
Sep 28th, 2000, 11:35 AM
#2
Thread Starter
Fanatic Member
Doesn't anyone know if this can be done?
One Event class shared with multiple objects. One event triggered all objects recieve the event (not just the originating object).
Any information would be greatly helpful.
-
Sep 28th, 2000, 08:27 PM
#3
Thread Starter
Fanatic Member
Does anyone know of any other forums/message boards I could go to that might actually answer this question or offer some possible assistance? Thankee
-
Sep 28th, 2000, 09:08 PM
#4
Hyperactive Member
I will answer you
It will take some time to read your entire post however.
Regards
-
Sep 28th, 2000, 09:16 PM
#5
Hello there,
There is a sample app in VB that will raise events on all the client machines. Look on the MSDN site for VB DCOM Samples. I know it's there, I look on my machine but I can't find the demo.
If you can't find it let me know here and I will see if I can locate it.
Best,
-
Sep 28th, 2000, 09:32 PM
#6
Hyperactive Member
Short answer or long answer?
Short answer is : NO.
Long answer is : Sort of.
In effect you are asking for something for free. What you
are asking for is a way to have subs or functions in your
runtime objects fired (simultaneously) when some other
event occurs.
But even if there was a black box answer for you, wherein
you do not have to keep an array or collection, SOMETHING
else will have to. This is the nature of things. You can't
get something for nothing...
Hence the short answer of NO.
To work with this, you should forget about Events and
trying to fit in with the RaiseEvent method. Use Interfaces
instead. If you need help on that post another message.
You will still have to parse the message and then you will
be able to call the appropriate Interface method on each of
your objects. At times, your objects will simply have no
implementation or they will have a fast exit in their code
for the given action.
If you envisage thousands of users, and speed is more
important that memory, then have lots of collections or
arrays to hold the appropriate lists of players to perform
the actions on. It is far faster to go through a collection
of 500 "interested" players than to go through all 1000
calling the same method on each (even if they have fast
exits in their code).
Now on to the long answer, which is shorter in text because
I have not had a huge amount of experience in this area.
USing the API, you can create things called Events (see the
CreateEvent API and others). Using this in conjunction
with something called anonymous pipes, you could have your
main app spawn many child processes - each one with a pipe
to the main app. Each of the children are told to
WaitForMultipleObjects which effectively puts them to sleep
until some event wakes them up again.
Now the main app gets something from a player and using the
Event that was created, calls SetEvent to tell the children
that an event has occured. Only those children interested
would wake up because they were waiting for this particular
event.
Upon waking, the children call PeekNamedPipe and WriteFile
to read/write data to the parent.
How this will help you - once you get over the learning
curve - is that it will be more of a true multi-tasking
application so that your OS will be able to handle the load
far better (especially if running on NT). It will mean
that you do not need to have any lists of objects at all
(because the API has done this for you) and it will mean
that only those objects that are truly interested will
respond.
Phew, I hope that was in some way useful..
One final note here is that I would assume you can not get
up to the thousands of users anyhow, because you would run
out of sockets on the server end. How many do you envisage
having?
Cheers
-
Sep 28th, 2000, 10:10 PM
#7
Thread Starter
Fanatic Member
Thanks for the exhaustive reply you just gave, Paul.
What this application is is actually a MUD or Multi-User Dungeon. There is going to be alot of activity going on due to Monsters roaming around, weather changes, and player actions.
I see approximately 20 people on at one time possibly as high as 50 or so (I still have to stress test what I have so far).
I'm currently using for next loops to send data to the other players, each of which are objects. Each player object contain their own command parser, color parser, socket, etc.
There are no forms associated with the server yet, I will be setting up a form that will be shown for minor status information, but everything runs from Sub Main.
The mud is actually very dynamic, being able to change nearly the entire look and feel of the mud without having to reload, restart, or recompile it. There are some minor hardcoded points, but those are being worked on.
For the most part, the server works very well, mainly as a Chat World of types. There are over 100 rooms to start, several communication channels, and a level system in place (for security to sensitive commands & chat lines).
I'm really trying to kill off the for next loop idea because it seems to me like a hindrance and pretty quirky bug and I've already ran into several instances where the mud hasn't worked properly because a socket disconnected improperly (still looking for that little freak of a bug though).
I figured using events would be the best remedy for that because, as I understood it, I wouldn't need to worry as much about the dependence of a player's index in a collection, array, or whatnot. If the player is there, the event handlers would be listening and pick up on anything they needed.
I'll look more into the API (gotta get a good api/oop book for vb, there isn't a good bookstore within 45 minutes of here) and look into that.
I did, however, get an event to fire off, but it only worked for the player who triggered the event (bleh) ... The other player completly ignored the event ...
If you have any further suggestions, I'd be glad to hear them.
Again, thanks for replying.
-Excalibur
-
Sep 28th, 2000, 10:29 PM
#8
Thread Starter
Fanatic Member
Did I forget to mention that players who are on the game connect via Telnet? There is no client outside of standard windows telnet (minimum needed to connect ... a basic telnet application). So raising an event on another persons computer is pretty much a moot point Though, if I had written a client, the event that told the client something is there or someone did something would be: Socket_DataArrival and a large case statement *heh*
Let me know if you want to see what I have so far (in terms of connecting to the server and taking a look around.) by emailing me.
-
Sep 28th, 2000, 10:54 PM
#9
Hyperactive Member
Mismatch
You are thinking in terms of the Event which the API has,
but the Event which VB has is not the same.
but, if you wish to pursue this, in your Class_Initialize()
for the player, drop the line
Code:
Set ehPlayer = New PlayerEvents
Instead, replace it with
Code:
Set ehPlayer = gPlayerEvent
Of course gPlayerEvent is a global you have already created. This would be better though as an explicit sub so that you choose which events your objects respond to.
The attached example will show you how to do what you are trying to do properly.
Code:
' myPlayer.cls
Option Explicit
Private WithEvents anEvent1 As myEvent1
Private WithEvents anEvent2 As myEvent2
Private mName As String
Public Sub SetName(newName As String)
mName = newName
End Sub
Private Sub anEvent1_SomethingHappened()
Debug.Print "in the event1 handler for " & mName
End Sub
Private Sub anEvent2_SomethingHappened()
Debug.Print "in the event2 handler for " & mName
End Sub
Public Sub SetEventHandler1(newEventHandler As myEvent1)
Set anEvent1 = newEventHandler
End Sub
Public Sub SetEventHandler2(newEventHandler As myEvent2)
Set anEvent2 = newEventHandler
End Sub
Code:
'myEvent1.cls and myEvent2.cls (two different classes but
same code so just copy / paste it twice into seperate
class modules)
Option Explicit
Public Event SomethingHappened()
Public Sub Test()
RaiseEvent SomethingHappened
End Sub
Code:
' module1.bas
Option Explicit
Sub Main()
Dim lEvent1 As myEvent1
Dim lEvent2 As myEvent2
Set lEvent1 = New myEvent1
Set lEvent2 = New myEvent2
Dim mCol As Collection
Set mCol = New Collection
Dim player As myPlayer
Dim c As Integer
For c = 1 To 5
Set player = New myPlayer
player.SetName "Player " & Str(c)
' the first set of players will respond to event 1
player.SetEventHandler1 lEvent1
mCol.Add player
Next
For c = 6 To 10
Set player = New myPlayer
player.SetName "Player " & Str(c)
' the second set of players will respond to event 2
player.SetEventHandler2 lEvent2
mCol.Add player
Next
Debug.Print "Testing Event 1"
lEvent1.Test
Debug.Print "Testing Event 2"
lEvent2.Test
End Sub
With this, you will be able to go ahead with what you were doing. It will be just as good as the API solution in many ways. There is something I have against VB's Events though - I just can't recall at present 
Cheers
-
Sep 28th, 2000, 11:40 PM
#10
Thread Starter
Fanatic Member
I can see that something is happening, but let me see if I can figure this out right. Using your code as an example.
What's happening in Module1:
We create lEvent1 & lEvent2 as Event1 and Event2 types.
We then set them as new instances of Event1 & Event2. And they are not created using the WithEvents flag (that is taken care of in the MyPlayer object).
We then create the collection which holds the player objects, which in turn are derived from the MyPlayer object.
(I never really liked collections heh I used an array)
We create 5 instances of MyPlayer (1-5), set the players names to 'Player #' then we set each player's eventhandler1 to point to lEvent1, leaving the second eventhandler empty.
We then add the player to the collection.
We create another 5 instances of MyPlayer (6-10), set their names, then set their eventhandler2 to lEvent2, leaving eventhandler1 empty. then the player is added to the collection.
We then test the events.
lEvent1.Test Triggers the somethinghappened Event in MyEvent1.
Which in turn triggers anEvent1_somethinghappened in players 1-5.
lEvent2.test triggers the somethinghappened event in myevent2.
Which in turn triggers anEvent2_somethinghappened in players 6-10.
Now, this is where I get a little bit muddled. Don't you have to declare anevent1/anevent2 as New? or am I correct that that was causing my initial problems?
From what I can tell, we create the events initially in SubMain, which is the Global Event handler.
Now, this is where I run into the problem ... Sub Main() is not where the events actually trigger from. Sub Main() has a loop that it processes until it recieves a shutdown_server command (boolean) (do while shutdown_server <> true). All operation takes place within the player object itself with no assistance from the main program (aside from setting up the initial socket (player(0)) for listen, after that everything is thrown on the player socket).
I *just* tested the code you gave me by triggering the event from within the MyPlayer object, which did trigger off successfully, though *whew* what a recursion 
Do you think this method is succeptable to lag, as I think I read something that nothing can happen after the event is over for a given object.
Thanks for the reply! I certainly hope this will work (I'll be testing it out by tomorrow night in the mud proper (with 1 command to start)).
Really appreciate the help!
-Excalibur
-
Sep 29th, 2000, 12:38 AM
#11
Hyperactive Member
Your summary is right
You can raise the event from anywhere if you make sure you
call a method on the event object as per my example.
I haven't heard of any problems, but then, I haven't looked
either. I don't really use this myself.
The order of processing is simple. Just imagine that each
object registered as being interested in these events is in
a long list. The event "triggering" is going to call the
event method in each object one after the other.
This works because each object registered with myEvent1 is
looking at the very same event object. In your original
code, you were creating a different object for each player,
hence it would never work.
Remember that when dealing with classes (actually instances
of classes), we only ever pass by reference. So player in
the example had it's SetEventHandler1 (or 2) method called,
and the reference to the lEvent1 object was passed.
Again, I hope it helps.
-
Sep 29th, 2000, 01:15 AM
#12
Thread Starter
Fanatic Member
I hope so too. I'm going to be implementing this in the next few days and will post on the success/failure of my attempt heh (only putting it in will see if it works with my current code). But, if it does, then HoooooYAH 
Thanks again for the help!
-Excalibur
-
Sep 29th, 2000, 08:29 PM
#13
Thread Starter
Fanatic Member
Hey hey!
Well, I tried the code. It was a little tricky at first, but I found a way to do it. It now works and sets off events for every player when someone does a certain command I have to flesh out parameters and such, and I had to create a new sub in sub main called SetPlayer that took care of assigning indexes and such. Hopefully now I can create one parser and just pass the data to the socket.senddata!
Woo Good bye For Next Loops!!!
Thanks again, Paul.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|