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.