Wednesday, August 6, 2008

SL-aware server prototype in ruby...

It's no big doubt that Ruby is a high-level language. Probably opensim svn still has my stupid ruby scripts which pretend to do something with the protocol - in effect just being more or less compact "plugs" for testing.

One of these evenings I did not feel like doing anything productive, so I decided to hack up a prototype packetserver in ruby.

So, the below set of files allows me to "login" (in quotes because the login info is not really checked :) with the viewer.



$ ls -al
total 228
drwxr-xr-x 2 dalien dalien 4096 Aug 6 04:46 .
drwxr-xr-x 12 dalien dalien 4096 Jul 31 22:23 ..
-rw-r--r-- 1 dalien dalien 183208 Jul 28 17:44 1.18.3.5.txt
-rwxr-xr-x 1 dalien dalien 8629 Jul 28 17:36 login_server.rb
-rwxr-xr-x 1 dalien dalien 14901 Jul 29 04:27 parse_template.rb
-rw-r--r-- 1 dalien dalien 3710 Jul 31 22:27 pkt_server.rb
-rw-r--r-- 1 dalien dalien 1719 Jul 28 21:59 str_hex.rb
$



The main cleverness is in parse_template.rb, which sucks in the 1.18.3.5.txt being the message template, and generates a bunch of classes, courtesy of Ruby being an interpreted language, something like this:

t = SLPacketTemplateFile.new("1.18.3.5.txt");
t.pt.each { |x| SLMessage.register_message(x, eval(x.to_ruby)) }


So, this defines a bunch of classes with the exact same names as the messages in the message templates, and allows to write a message handler in a nice and conscise way as a class method for a "client handler" class:


def message(msg)
send_packet_ack(@client_ip, @client_port, msg.sequence)
handler = (msg.class.to_s + "_handler")
if self.respond_to? handler
send(handler, msg)
else
pp msg
end
end


And, as soon as I define a "_handler" method within that class, it gets automagically hooked up! Cool, huh ?
For example, something like this:


def MoneyBalanceRequest_handler(msg)
pkt = MoneyBalanceReply.new
md = pkt.MoneyData[0]
md.AgentID = msg.AgentData[0].AgentID
md.TransactionID = msg.MoneyData[0].TransactionID
md.TransactionSuccess = 1;
md.Description = "Test\00"
md.MoneyBalance = 1234;

send_to_client(pkt)
end


where "send_to_client" is again dead simple:


def send_to_client(pkt)
@sock.send(pkt.to_bytes, 0, @client_ip, @client_port)
end


Oh, and you'll probably be amused with this totally stupid-looking message loop:



def run
print "Started server...\n"
loop {
data, from = @sock.recvfrom(8192)
port = from[1]
host = from[2]
rdr = SLDataReader.new(data)
msg = SLMessage.from_bytes(rdr)
if msg.class == UseCircuitCode
print "New client connection!\n"
@clients[host.to_s + ":" + port.to_s] = ClientHandler.new(self, msg, host, port)
else
clt = @clients[host.to_s + ":" + port.to_s]
if clt
clt.message(msg)
else
print "Unknown client:\n";
pp msg
end
end
}
end



Of course, the CPU usage of all this horrific mess is terrible - given that even the simple members like U32 are objects in themselves, but it's fun nonetheless.

The interesting thing that parse message template + generate the code + dynamically evaluating it (creating message classes) takes around2 seconds - which I think is not too bad.

There is one downside however, the debugging of the autogenerated code is a pain. I need to so how to do it better.

Oh yes, and get rid of the braces in the loop {} construct, they do look ugly and out of place.

2 comments:

Anonymous said...

Looks cool. You wouldn't feel like translating this into Python, would you? :-)

Dalien said...

Might be interesting. Certainly could be something more practical, performance-wise.

Although it would be certainly very very alpha stuff. If you are interested to hack around on things like that, drop me an email at dalienta.at-gmail.

Even though now pretty much I have zero time, but maybe if there is someone else interested, maybe could find some :-)