Discussion:
[Pyogp] Proposal: Change to networking layer
Christian Scholz
2008-08-06 08:11:06 UTC
Permalink
Hi!

Right now we use the networking layer like this:

client = getUtility(IRESTClient)

client.GET(url)
client.POST(url, data)

After a discussion with Locklainn if his UDPClient shouldn't rather be a
connection and store it's socket inside the instance instead of passing
it I was wondering if the same shouldn't be true for the REST client.

So instead writing it like this:

resource = IRESTResource(url)
resource.GET()
resource.POST(data)

(headers are in both cases optional parameters to the call)

This would have the following benefits:

- it models more what REST is about (you call a method on a resource)
- it models how Capabilities in pyogp are used already (they also have
POST() and GET() methods and the capability basically is the resource).

How the resource get's instantiated in the above example is by using an
adapter which adapts to a string (the url) for the IRESTResource
interface. A factory utility maybe would be a different way of doing it:

factory = getUtility(IRESTResourceFactory)
resource = factory(url)

The first solution might be more flexible though if you want to
instantiate a resource from something different than a string as in your
client code you do not need to care. In the second case though if the
url is not a string but some sort of object with an interface you would
need to also change the getUtility call and use some
IRESTResourceFromAnotherObjectFactory.

But what do you think of the general idea? I think it would make usage
of resources and caps similar and you'd only need to learn once how you
do GET and POST and the cap would be more or less a wrapper around the
resource.

-- Christian

PS: Of course this means changing of code but it's pretty clear how to
do that and it might show if all our tests really notice such changes.
And we should try to refactor things where possible IMHO. Merciless
refactoring FTW! :-)
--
Christian Scholz Homepage: http://comlounge.net
COM.lounge blog: http://mrtopf.de/blog
Luetticher Strasse 10 Skype: HerrTopf
52064 Aachen Video Blog: http://comlounge.tv
Tel: +49 241 400 730 0 E-Mail ***@comlounge.net
Fax: +49 241 979 00 850 IRC: MrTopf, Tao_T

neue Show: TOPFt?glich (http://mrtopf.de/blog/category/topf-taglich/)
Tess Chu
2008-08-07 23:46:14 UTC
Permalink
I'm working on https://jira.secondlife.com/browse/SVC-2682, and wanted
to execute the exact urllib commands from the python command line in
order to repro it, but couldn't figure out from these changes where all
the network code went. Where is the actual library code that's sending
the request?

Tess
Post by Christian Scholz
Hi!
client = getUtility(IRESTClient)
client.GET(url)
client.POST(url, data)
After a discussion with Locklainn if his UDPClient shouldn't rather be
a connection and store it's socket inside the instance instead of
passing it I was wondering if the same shouldn't be true for the REST
client.
resource = IRESTResource(url)
resource.GET()
resource.POST(data)
(headers are in both cases optional parameters to the call)
- it models more what REST is about (you call a method on a resource)
- it models how Capabilities in pyogp are used already (they also have
POST() and GET() methods and the capability basically is the resource).
How the resource get's instantiated in the above example is by using
an adapter which adapts to a string (the url) for the IRESTResource
factory = getUtility(IRESTResourceFactory)
resource = factory(url)
The first solution might be more flexible though if you want to
instantiate a resource from something different than a string as in
your client code you do not need to care. In the second case though if
the url is not a string but some sort of object with an interface you
would need to also change the getUtility call and use some
IRESTResourceFromAnotherObjectFactory.
But what do you think of the general idea? I think it would make usage
of resources and caps similar and you'd only need to learn once how
you do GET and POST and the cap would be more or less a wrapper around
the resource.
-- Christian
PS: Of course this means changing of code but it's pretty clear how to
do that and it might show if all our tests really notice such changes.
And we should try to refactor things where possible IMHO. Merciless
refactoring FTW! :-)
Christian Scholz
2008-08-08 06:43:50 UTC
Permalink
Hi!
Post by Tess Chu
I'm working on https://jira.secondlife.com/browse/SVC-2682, and wanted
to execute the exact urllib commands from the python command line in
order to repro it, but couldn't figure out from these changes where all
the network code went. Where is the actual library code that's sending
the request?
It's all in the network package:

http://svn.secondlife.com/trac/linden/browser/projects/2008/pyogp/pyogp.lib.base/trunk/pyogp/lib/base/network/stdlib_client.py

This gets called from caps.py:

http://svn.secondlife.com/trac/linden/browser/projects/2008/pyogp/pyogp.lib.base/trunk/pyogp/lib/base/caps.py

(line 63)

The serializer used is defined at the bottom of that file (it's looking
for an ISerialization adapter for a dict (payload) in line 53
(ISerialization(payload) and it will get the one defined in line 110
(grok.implements(ISerialization); grok.context(dict)).

The content-type it returns is defined in line 132 ff., serialize() will
simply call llsd.format_xml() for that dict.

But regarding the network layer I am now also confused what is in what
file so maybe we should rename those files to:

stdlib_client => rest_stdlib_client
mockup_client => rest_mockup_client
net => udp_client
mockup_net => udp_mockup_client

I also would suggest to move the mockups to the tests folder as IMHO it
makes sense to have them there as they are not really good for something
useful outside the test environment (my mockup client for rest even uses
the WSGI app defined in tests/ and thus imports it from there).

But I might post this as separate proposal later again.

-- Christian
Post by Tess Chu
Tess
Post by Christian Scholz
Hi!
client = getUtility(IRESTClient)
client.GET(url)
client.POST(url, data)
After a discussion with Locklainn if his UDPClient shouldn't rather be
a connection and store it's socket inside the instance instead of
passing it I was wondering if the same shouldn't be true for the REST
client.
resource = IRESTResource(url)
resource.GET()
resource.POST(data)
(headers are in both cases optional parameters to the call)
- it models more what REST is about (you call a method on a resource)
- it models how Capabilities in pyogp are used already (they also have
POST() and GET() methods and the capability basically is the resource).
How the resource get's instantiated in the above example is by using
an adapter which adapts to a string (the url) for the IRESTResource
factory = getUtility(IRESTResourceFactory)
resource = factory(url)
The first solution might be more flexible though if you want to
instantiate a resource from something different than a string as in
your client code you do not need to care. In the second case though if
the url is not a string but some sort of object with an interface you
would need to also change the getUtility call and use some
IRESTResourceFromAnotherObjectFactory.
But what do you think of the general idea? I think it would make usage
of resources and caps similar and you'd only need to learn once how
you do GET and POST and the cap would be more or less a wrapper around
the resource.
-- Christian
PS: Of course this means changing of code but it's pretty clear how to
do that and it might show if all our tests really notice such changes.
And we should try to refactor things where possible IMHO. Merciless
refactoring FTW! :-)
_______________________________________________
https://lists.secondlife.com/cgi-bin/mailman/listinfo/pyogp
Tess Chu
2008-08-08 14:24:33 UTC
Permalink
This is exactly what I was looking for. Thanks for the explanation!

Tess
Post by Christian Scholz
Hi!
Post by Tess Chu
I'm working on https://jira.secondlife.com/browse/SVC-2682, and
wanted to execute the exact urllib commands from the python command
line in order to repro it, but couldn't figure out from these changes
where all the network code went. Where is the actual library code
that's sending the request?
http://svn.secondlife.com/trac/linden/browser/projects/2008/pyogp/pyogp.lib.base/trunk/pyogp/lib/base/network/stdlib_client.py
http://svn.secondlife.com/trac/linden/browser/projects/2008/pyogp/pyogp.lib.base/trunk/pyogp/lib/base/caps.py
(line 63)
The serializer used is defined at the bottom of that file (it's
looking for an ISerialization adapter for a dict (payload) in line 53
(ISerialization(payload) and it will get the one defined in line 110
(grok.implements(ISerialization); grok.context(dict)).
The content-type it returns is defined in line 132 ff., serialize()
will simply call llsd.format_xml() for that dict.
But regarding the network layer I am now also confused what is in what
stdlib_client => rest_stdlib_client
mockup_client => rest_mockup_client
net => udp_client
mockup_net => udp_mockup_client
I also would suggest to move the mockups to the tests folder as IMHO
it makes sense to have them there as they are not really good for
something useful outside the test environment (my mockup client for
rest even uses the WSGI app defined in tests/ and thus imports it from
there).
But I might post this as separate proposal later again.
-- Christian
Post by Tess Chu
Tess
Post by Christian Scholz
Hi!
client = getUtility(IRESTClient)
client.GET(url)
client.POST(url, data)
After a discussion with Locklainn if his UDPClient shouldn't rather
be a connection and store it's socket inside the instance instead of
passing it I was wondering if the same shouldn't be true for the
REST client.
resource = IRESTResource(url)
resource.GET()
resource.POST(data)
(headers are in both cases optional parameters to the call)
- it models more what REST is about (you call a method on a resource)
- it models how Capabilities in pyogp are used already (they also
have POST() and GET() methods and the capability basically is the
resource).
How the resource get's instantiated in the above example is by using
an adapter which adapts to a string (the url) for the IRESTResource
factory = getUtility(IRESTResourceFactory)
resource = factory(url)
The first solution might be more flexible though if you want to
instantiate a resource from something different than a string as in
your client code you do not need to care. In the second case though
if the url is not a string but some sort of object with an interface
you would need to also change the getUtility call and use some
IRESTResourceFromAnotherObjectFactory.
But what do you think of the general idea? I think it would make
usage of resources and caps similar and you'd only need to learn
once how you do GET and POST and the cap would be more or less a
wrapper around the resource.
-- Christian
PS: Of course this means changing of code but it's pretty clear how
to do that and it might show if all our tests really notice such
changes. And we should try to refactor things where possible IMHO.
Merciless refactoring FTW! :-)
_______________________________________________
https://lists.secondlife.com/cgi-bin/mailman/listinfo/pyogp
_______________________________________________
https://lists.secondlife.com/cgi-bin/mailman/listinfo/pyogp
Locklainn
2008-08-08 18:06:04 UTC
Permalink
So, I believe I have gotten presence to work. I committed a new file
called test_ogp_presence. It essentially just keeps acking the packings
it needs to and responds to a handful (with VERY GENERIC message
handlers) of needed responses (looking at Sai's old code, thanks Sai!).
I put a screenshot on our main page for old-times sake. Now we have our
own screen up there.

The next step for the client library, I believe, would be to somehow
hook up some sort of input so that while presence is established you
could send commands to do (like teleporting). This way, we could see
things working live I think. Maybe this would allow us to see an avatar
teleporting interactively. It may be easy to get an av to walk in a
straight line or something too, but I dunno.

Anyway, I'll talk to you all monday.

TJ
Christian Scholz
2008-08-11 11:50:43 UTC
Permalink
Hi!
Post by Locklainn
So, I believe I have gotten presence to work. I committed a new file
called test_ogp_presence. It essentially just keeps acking the packings
it needs to and responds to a handful (with VERY GENERIC message
handlers) of needed responses (looking at Sai's old code, thanks Sai!).
I put a screenshot on our main page for old-times sake. Now we have our
own screen up there.
Very cool! I need to have a look at that code to see why mine back then
did not work ;-) I suppose I did not send all needed packets.
Post by Locklainn
The next step for the client library, I believe, would be to somehow
hook up some sort of input so that while presence is established you
could send commands to do (like teleporting). This way, we could see
things working live I think. Maybe this would allow us to see an avatar
teleporting interactively. It may be easy to get an av to walk in a
straight line or something too, but I dunno.
I started some client with threads in my sandbox. Well, maybe more an
experiment than a client. But as it uses threads it should as well be
possible to take commands and pass them to the correct threads.

See here:

http://svn.secondlife.com/svn/linden/projects/2008/pyogp/sandbox/trunk/sandbox/mrtopf/example1.py

It's pretty much the original example which does it all manually but
sorted out into threads.

There is also the ADQueue thread which calls the EQG of the AD regularly.

It's not pretty code at the moment though but maybe it's helpful.

cheers,

Christian
--
Christian Scholz Homepage: http://comlounge.net
COM.lounge blog: http://mrtopf.de/blog
Luetticher Strasse 10 Skype: HerrTopf
52064 Aachen Video Blog: http://comlounge.tv
Tel: +49 241 400 730 0 E-Mail ***@comlounge.net
Fax: +49 241 979 00 850 IRC: MrTopf, Tao_T

neue Show: TOPFt?glich (http://mrtopf.de/blog/category/topf-taglich/)
Lawson English
2008-08-11 12:12:51 UTC
Permalink
Post by Christian Scholz
Hi!
Post by Locklainn
So, I believe I have gotten presence to work. I committed a new file
called test_ogp_presence. It essentially just keeps acking the
packings it needs to and responds to a handful (with VERY GENERIC
message handlers) of needed responses (looking at Sai's old code,
thanks Sai!). I put a screenshot on our main page for old-times sake.
Now we have our own screen up there.
Very cool! I need to have a look at that code to see why mine back
then did not work ;-) I suppose I did not send all needed packets.
My recollection is that you just need to ack all incoming packets that
request acks. There are 2-3 packets you have to send first to establish
presence before the circuit is complete. 2 are required plus 1 optional
(3 required+1 optional?) and in order to make sure you have a good EQG
with the sim you have to send those with acks and wait for them to be
acked before using the EQG cap. Otherwise, it works only sometimes. Very
hair-pulling when it doesn't work.
Post by Christian Scholz
Post by Locklainn
The next step for the client library, I believe, would be to somehow
hook up some sort of input so that while presence is established you
could send commands to do (like teleporting). This way, we could see
things working live I think. Maybe this would allow us to see an
avatar teleporting interactively. It may be easy to get an av to walk
in a straight line or something too, but I dunno.
I started some client with threads in my sandbox. Well, maybe more an
experiment than a client. But as it uses threads it should as well be
possible to take commands and pass them to the correct threads.
http://svn.secondlife.com/svn/linden/projects/2008/pyogp/sandbox/trunk/sandbox/mrtopf/example1.py
It's pretty much the original example which does it all manually but
sorted out into threads.
There is also the ADQueue thread which calls the EQG of the AD regularly.
It's not pretty code at the moment though but maybe it's helpful.
cheers,
Moving around on the sim should be possible once you have full presence.
Not sure if ghosting (leaving off the optional presence-packet) allows
movement or not or if that only has to do with Physics. Changing avatar
position and the like would be a good first test for making sure our bot
is truly functional, but I think the most useful thing to test will be
group IM. Getting that to work should reveal more design flaws that
anything since you have to pull in data from UDP AND EQG to get it working.

Lawson
Enus Linden
2008-08-13 13:25:13 UTC
Permalink
moving this to the list. (I took the liberty of pasting this mail's
predecessor below as well)

This is an excellent discussion...
Here is a proposal of mine of how we can change the message system to
incorporate Tao's suggestions (more Pythonic creation of messages) as
well as some other features (having a connection that messages are
sent to)
Overall Design
The user should never have to see any serialized form of a message. He
or she should always use the higher level classes like Packet that are
sent and received. Serialization and deserialization are done not by
the user but by the UDPConnection.
MessageSystem -> UDPConnection
Represents a udp connection that can send and receive packets.
* *
*Responsibilities:*
* Keeping track of all the circuits it is connected to, or have
sent/received messages on (it seems people want the connection to
represent only a single circuit)
* When sending packets, add flags and sequence number, have a
serializer serialize them and send them to the host
* When receiving packets, deserialize them, keeping track of the
flags that were deserialized
+ keeping track of the packets we received that need acks
+ keeping track of the packets we sent that weren?t acked
+ sending acks to those we need to ack
+ resending packets that didn?t get acked
*Changes:*
* No longer has the responsibility of creating messages
* No longer has the responsibility of getting data from messages
Packet
Represents everything about a Packet that is needed to be sent and
received. It also has methods to add data to the packet. The design
says that the user shouldn?t set the flags or sequence number
themselves, but the UDPConnection will do such a thing.
*Responsibilities:*
* Knows the flags it is being sent with
* Knows its sequence number
* Knows its payload
* Methods to add blocks of data to the packet
* Passed to a UDPConnection to be sent, and read from the receive
*Changes:*
* Didn?t exist before. Exists because this design is a
message-centric design that allows the user to have direct access
to messages and manipulation of them.
MessageTemplateReader -> UDPDeserializer
Deserializes a buffer into a UDPPacket?..hmmm?.how does this work?
Whoever is deserializing the buffer MUST know it?s a udp packet, so
this must be done by the UDPConnection.
*Responsibilities:*
* Determines if the buffer is a template message, decodes the data
* Also deserializes the flags and sequence number as well
*Changes:*
* It doesn?t work off of a single message anymore. The data isn?t
gotten by doing a get_data on the reader. Instead, the
deserializer outputs a Packet object which the user has direct
access to.
* Decoding the flags and sequence number wasn?t a responsibility
before
MessageTemplateBuilder -> UDPSerializer
Serializes a Packet into a buffer that can be sent.
*Responsibilities:*
* Serializes a Packet, including its flags and sequence number,
header, and payload.
* Outputs a Packet object.
*Changes:*
* This was previously done by the template builder, which also built
the message (adding data and blocks to the message). These
functionalities are now separated and the Packet is directly
manipulated to add data, and the deserializer is used to put it
into network format.
-------------------------------------------------------------------------
(previous mail - discussion between Tao and Lock, with Tao in blue....)
In my approach all domain knowledge is encapsulated in an abstract
object (abstract not in an OO sense) and the same would be true for
the connection. Right now I am not 100% sure what needs to be done
when sending and receiving but I would assume that the Connection
class will handle this.
I was thinking of a Packet class which holds a message and has a
serializer which will take care of getting all information together.
What sort of flags would that be btw? Who defines which one gets set?
Right now I know about the reliable flag and I assume that this is is
either defined by the template of message.xml. So both could be known
by the message.
The flags are added on by the client. Any packet can be reliable, any
packet can be resent, any packet can have acks added on. Reliable means
the server needs to ack it. Resent means this is a second (or more)
attempt at the packet, and so may be a duplicate if it is received. The
ack flag means that we have attached acks on the end of our message
(saves network traffic). The templates don't define any of this and so
the message itself can't know. These are added on at the time of sending.
The Connection class would then have a list of to be acked packages
and would basically do the same as the message system does.
Similarly, the server can make any packet ackable (by setting the send
flag with the ack flag), and so we must ack it, otherwise the server
gets angry. We don't know which packet we will need to ack, we have to
determine that when we receive one.
It would follow the pattern seen in e.g. smtplib, urllib2 (where the
Request is the message). And most network modules actually have a
connection object, such as ftplib, nntplib, gopherlib etc. Not all
have message classes though because if it's just a file you send, then
there is no need to encapsulate this in a separate object.
http://docs.python.org/lib/message-objects.html
I'm starting to like the idea of a Message. Maybe this Message could be
only the payload of the message, with a Packet class (I think you have
suggested this) having the other necessary fields. Then, a serializer
can serialize the packet and a net framework can send it on a connection.

Also be aware that connections will change during the lifetime of the
client. You don't have a single udp connection. You communication with
neighboring sims, you may switch regions, etc. This causes you to create
a new connection to send on.
But also remember that for udp messages you don't need a connection, you
can simply send the message to any given host. So, it may be extra to
have a connection class doing such a thing because you can use a single
connection to send and receive on. The target we are sending to changes,
but we don't need to change the sockets or anything.
*Current Design: *
This is taken from
http://wiki.secondlife.com/wiki/Pyogp/Documentation/Specification/pyogp.lib.base
messenger = MessageSystem()
host = Host('sim_ip', 'sim_port') #note: these aren't true values, of
course
messenger.new_message("UseCircuitCode")
messenger.next_block("CircuitCode")
messenger.add_data('Code', circuit_code, \
MsgType.MVT_U32)
messenger.add_data('SessionID', \
uuid.UUID(session_id), \
MsgType.MVT_LLUUID)
messenger.add_data('ID', \
uuid.UUID(agent_id), \
MsgType.MVT_LLUUID)
messenger.send_message(host)
_Explanation:_
The thing to know about the current design is that it is encapsulated
into a MessageSystem. Everything from building, reading, sending, and
receiving messages all occurs in the Message System (though each of
the sets of functionality are performed by other objects that the
system HAS).
I think this is a quite good explanation where we differ. As said
before this feels very uncommon in the python world to me.
One concern I also have is all the sort of global state in these long
living objects. It doesn't need to but might lead to problems with
threading or coroutines. I would try to keep locking zones as small as
- You create and send a message in coroutine A
- Sending blocks for whatever reasons
- Coroutine B gets activated, creates a message and sends it. Maybe
with the same message system. It also blocks on sending.
- current_msg is now message of B and this is what A sends.
So this would mean that you need to separate message system per
thread. This also means though that it's only one host you connect to
per message system and thus the host could be in the constructor as
it's quite fixed then.
Yea, I do agree that it is confusing having the message remembered in
state by the system, builders, and readers. I'm starting to like the
idea of outputting a Message that the user adds data to and sends. Maybe
the Message System could remain as the connection you send through and
receive on, which automatically serializes sending packets and
deserializes receiving packets, keep track of all acks and such.
In my approach you of course also just would have one Connection per
thread/coroutine but additionally you could create messages e.g.
outside a thread and pass it into a thread. The send method would also
just have method local variables it works. Packet ID apparently is
something which needs thinking ;-)
For the current design, you don't ever have direct access (handle,
object, reference whatever it may be called) to a Message or a
Connection. Building is delegated to the Message System, which,
underneath the hood, is delegated to the appropriate builder. Sending
is delegated to the Message System, which again, is delegated to the
appropriate sender, this case being a udp_sender. Also note the user
doesn't need to serialize or otherwise perform any functions on the
built message.
One point I experienced in my programmer life was that delegation from
one object to another (and maybe yet to another) makes debugging hard
because if you need to keep in mind which method now was where (esp.
if they are called the same). As I had to debug such systems I feel
more comfortable with calls you perform directly on the object you
actually want to change.
You?ll also notice that the type is given when adding data. This is
not absolutely necessary to have (and can be removed). It is used as
a user-check to make sure the user knows what type of data he or she
is sending. This makes it a bit easier for coders to think their
creation through, as well as other coders who look at it (it may be
confusing to see adding a simple 1 where that 1 can be stored as a
byte, an int, or a long).
In Python you don't care about this. If there is a 1 you mean 1 and
you don't care how it's sent over the wire on a lower level of the
system. Yes, you might run into a problem if you don't know the type
but in my experience this rarely actually leads into problems. Having
no type also makes coding faster as you have to type less and you
don't have to consult the documentation.
So let's get rid of the type-checking, I'm fine with that. It IS just
extra junk I don't feel like typing anyway :)
*Proposed Design 1:*
*A*
conn = UDPConnection(region)
msg = Message('UseCircuitCode',
Block('CircuitCode',
('Code', circuit_code, MsgType.MVT_U32),
('SessionID', uuid.UUID(session_id), MsgType.MVT_LLUUID)),
('ID', uuid.UUID(agent_id),MsgType.MVT_LLUUID)
)
)
conn.send(msg)
* *
*B*
conn = UDPConnection(region)
msg = Message('UseCircuitCode')
block = Block('CircuitCode',
('Code', circuit_code, MsgType.MVT_U32),
('SessionID', uuid.UUID(session_id), MsgType.MVT_LLUUID)),
('ID', uuid.UUID(agent_id),MsgType.MVT_LLUUID)
)
msg.add(block)
conn.send(msg)
* *
BTW, now that I look at it again I think a Message is just a list of
blocks so it could even derive from a list object and add() would be
append. Blocks seem like dicts to me with the exception that they have
a name. But they could be more easily instantiated as
blk = Block('CircuitCode',
Code=circuit_code,
SessionID=sessionid,
ID=agent_id)
msg.append(blk)
_Explanation:_
This takes the code for the current design and makes it more
Pythonic. It essentially makes a wrapper class called Message, which
can handle Pythonic structures, and can create a message like that of
the current design.
In the A version of this design, the constructor takes in all the
blocks and data and then would construct the message completely. The
B version allows users to create blocks separately and add them into
the message. These two methods could be combined, in fact.
You can of course also first create the blocks in separate vars and
then pass them into the Message constructor: Message(name, blk1, blk2,
blk3)
_Pros n Cons:_
This method allows us to keep most of the same design in place, with
an additional layer that wraps the message creation to make it less
sequential and more Pythonic. It cuts out all the calls to
new_message, next_block, and add_data, allowing users to pass in more
Python structured data (form of lists).
This means less typing which to me is always a pro :)
With the above change even less typing.
Messages can have multiple and variable number of blocks with the
same name, so this method would consist of the user passing in a list
of blocks rather than just a single block into the constructor. This
is not too difficult to handle.
Having the constructor take the entire message may be complicated and
visually difficult to parse for the user. It is also prone to syntax
errors.
I actually see this the completely other way, esp. with
msg = Message('UseCircuitCode',
Block('CircuitCode',
Code=circuit_code,
SessionID=sessionid,
ID=agent_id)
)
Of course if the message is more complex you would probably create
blocks separately and then pass them in. But both would be possible.
It would also remove one bit of delegation (add_data) and methods
would only be defined on those classes which they actually implement.
It also refactors the way the Message System, builders, and readers
work. Some messages are template messages, which means the messages
MUST be built according to the template. If they are not, then they
shouldn?t be allowed to be built and sent. The builder makes sure
this doesn?t happen. These designs get rid of a builder and put it
directly into the message, which means the message IS the builder.
When the message is being created, we somehow have to determine what
type of message it is (template or llsd) and use the correct builder
(or at least make sure messages are being built correctly).
I have one superclass Message from which I have derived LLSDXMLMessage
and UDPMessage. There is a MessageFactory utility which can be used to
factory = getUtility(IMessageFactory)
message = factory.new('UseCircuitCode')
You can then look into message.flavor to check the flavor.
To serialize either message you then do
serializer = ISerialization(message)
serializer.serialize()
This is the same pattern as in the rest of the library.
My first though on this was though to create just an LLSDMessage class
which doesn't know about it's final encoding. This is decided on
serialization time. I think this would more follow the protocol
structure as both types are actually equivalent.
The problem was that the message based template was initialized from
the template on instantiation which the XML version not always could
be because not every message is in the template.
I am not sure if the initialization is necessary or just made to have
default values here. I would think it's not necessary as the template
is known in both approaches and you can also check for invalid blocks
when you add new ones (might raise an InvalidBlock exception).
The serialization step in this case would look like above just that
the serializer would consult the MessageDict (which is a utility in my
case).
There might then also be a MessageDispatcher which does the same so it
knows over which channel to send this message (I guess for XML
messages it's simply the cap we have and we do cap.POST(data).
Right, so I'm thinking the Message System could do all this. Maybe the
Message System could be the factory and dispatcher, with all messaging
being sent and received going through it (but BUILDING messages not
going through this).
* *
*msg = api.new_message('PacketAck')
msg.next_block('Packets')
msg.add_data('ID', 0x00000001, MsgType.MVT_U32)
msg.next_block('Packets')
msg.add_data('ID', 0x00000001, MsgType.MVT_U32)
data = api.serialize(msg)
connection = UDPConnection(host)
connection.send(data)
_Explanation:_
The new proposed design has a few differences. One is what the
responsibilities of each of the objects is. You'll notice in this
design you have direct access to the message. The message is also the
builder, so you perform building operations directly on the message
(whereas in the current design you use a builder to add data to the
message). You'll also notice that you have direct access to the
UDPConnection and therefore you direct the message to the connection
you wish to send it to.
Actually I would prefer the design above with Blocks and Messages.
message (up to 500 I believe) will have its own unique class that
will initialize the data attributes.
We would start with the ones we actually use in the library. If
somebody needs to use an additional one he can still use the more low
level version (Message('name', Block(...), Block(...)) ).
We also have to look at every message in the protocol spec anyway and
define it there in detail. When we do this we can go along and define
them in code as well. I am also willing to do that.
A pro here would be that you can put default values in the class so
that you don't have to specify all parameters.
When receiving a message you would have the possibility to attach an
event handler directly to that class using ZCA.
Another pro is that the user of this level doesn't have to know about
blocks and the sequence of these. She only needs to know about the
actual data to be passed in.
Well, we can do this with ZCA without deriving a class for each message.
We can have them all implement an interface and register them with a
certain name. This way we don't have to write each individual class, but
can have a generic Message which can handle them all, with handlers. The
default data can be added in by the Message Factory (which looks into
the template and fills in the message with default values).
I guess this is the problem then. If we have a single Message which
builds itself (add_block methods), we cannot write a Message class which
tests that the data being added is correct and expected. Unless the
message itself is a UDP message derivation and can look at the message
template itself and do the checking.
_Questions:_
The region domain stores the connections, both udp and http. So would
msg.send(region)
region.send(msg)
api.send(msg, region)
conn = UDPConnection(region)
conn.send(msg)
How are the UDP packet flags added onto the packets being sent? They
are apparently not built into the message itself (because they are
only UDP), so need to be added on when actually sending the message.
These depend on how you want to send the message (want an ack) and so
can vary per message, and they are not always the same even on a
single circuit.
Do I get this right that the message type defines the flags needed?
I shortly looked into your code and I think I would do it similarly.
You have all the data in MsgData without those flags and you add them
on sending. I would maybe move some logic from the msgsystem into the
send_flags = ...
packet=Packet(id, message, send_flags)
packet.addAcks(self.acklist) # might be in the constructor as well
serializer = ISerialization(packet)
packetdata = serializer.serialize()
# what defines if it's a reliable packet?
self.udp_client.send_packet(self.socket, packet_data, self.host)
This is just a quick shot without reading the code in detail so it
might be wrong ;-)
The reason there are builders is because the template messages must
have the correct data. The template builder makes sure that blocks
and data being added to messages follow the template?s specification
(LLSD has no format because it is going to be formatted into XML, and
deserialized when being received, and so the arrays and dicts can be
directly accessed). How is this accomplished without going through
the builder? How do we distinguish between creating a template
message (making sure it has the correct data) and an llsd message?
As said above, by the message factory. It gives you one of two classes
of messages.
See
http://svn.secondlife.com/trac/linden/browser/projects/2008/pyogp/pyogp.lib.base/branches/mrtopf-message-refactoring/pyogp/lib/base/message/message.py
in line 46 for the message factory. Message types follow below. This
is not using blocks etc. as in the example above though.
Who does the message maintenance? Meaning, who keeps track of the
packets that need to be acked, the ones we want acked, and resending
messages that weren?t acked? Do we leave this up to the user to
create such a system?
Some Connection class which seems to me similar to your message system
and circuits.
BTW, what actually is a circuit? Is it a connection to a region? Or
can you have many circuits to one region? This part of the protocol is
not that clear to me right now. We probably should write it down if it
isn't somewhere (but it should be part of the spec at some point anyway).
A circuit is a UDP connection. So, it is a UNIQUE connection to ip
address and port combination. Can only have 1 circuit for each ip and
port combination.

Thanks for your work, I'm starting to see where we can improve things.
I'll start writing down my new proposal and see if we can get something
working.

PS: I don't like the idea of ZCAifying things like the dictionary just
so that we can register them with ZCA as a global utility. It is an
extra abstraction that is confusing and the reasoning not clearly seen.
Something else we can do?



-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.secondlife.com/pipermail/pyogp/attachments/20080813/ca9e5363/attachment-0001.htm
Christian Scholz
2008-08-14 14:57:59 UTC
Permalink
Hi!
Post by Enus Linden
moving this to the list. (I took the liberty of pasting this mail's
predecessor below as well)
Thanks, Enus :)
And good to see that we seem to be reaching some consensus.

Following are my attempts to answer the questions raised:

[...]
Post by Enus Linden
Similarly, the server can make any packet ackable (by setting the send
flag with the ack flag), and so we must ack it, otherwise the server
gets angry. We don't know which packet we will need to ack, we have to
determine that when we receive one.
Ok, so that seems to be part of what a connection needs to handle.
Post by Enus Linden
It would follow the pattern seen in e.g. smtplib, urllib2 (where the
Request is the message). And most network modules actually have a
connection object, such as ftplib, nntplib, gopherlib etc. Not all
have message classes though because if it's just a file you send, then
there is no need to encapsulate this in a separate object.
http://docs.python.org/lib/message-objects.html
I'm starting to like the idea of a Message. Maybe this Message could be
only the payload of the message, with a Packet class (I think you have
suggested this) having the other necessary fields. Then, a serializer
can serialize the packet and a net framework can send it on a connection.
Right, if you do ISerialization(packet) you will get the packet
serializer which in turn can do a ISerialization(msg) to get the message
serializer. It would then add everything togethet to one binary blob.
Post by Enus Linden
Also be aware that connections will change during the lifetime of the
client. You don't have a single udp connection. You communication with
neighboring sims, you may switch regions, etc. This causes you to create
a new connection to send on.
On a higher level I was thinking about having different Region objects
which all have their UDP and HTTP/Cap connection.
Post by Enus Linden
But also remember that for udp messages you don't need a connection, you
can simply send the message to any given host. So, it may be extra to
have a connection class doing such a thing because you can use a single
connection to send and receive on. The target we are sending to changes,
but we don't need to change the sockets or anything.
To me that would mean I need to know that it's a socket connection. On a
higher level though I might not want or need to know. It's more a
logical connection to that region, whatever the implementation.
Post by Enus Linden
*Current Design: *
This is taken from
http://wiki.secondlife.com/wiki/Pyogp/Documentation/Specification/pyogp.lib.base
messenger = MessageSystem()
host = Host('sim_ip', 'sim_port') #note: these aren't true values, of
course
messenger.new_message("UseCircuitCode")
messenger.next_block("CircuitCode")
messenger.add_data('Code', circuit_code, \
MsgType.MVT_U32)
messenger.add_data('SessionID', \
uuid.UUID(session_id), \
MsgType.MVT_LLUUID)
messenger.add_data('ID', \
uuid.UUID(agent_id), \
MsgType.MVT_LLUUID)
messenger.send_message(host)
_Explanation:_
The thing to know about the current design is that it is encapsulated
into a MessageSystem. Everything from building, reading, sending, and
receiving messages all occurs in the Message System (though each of
the sets of functionality are performed by other objects that the
system HAS).
I think this is a quite good explanation where we differ. As said
before this feels very uncommon in the python world to me.
One concern I also have is all the sort of global state in these long
living objects. It doesn't need to but might lead to problems with
threading or coroutines. I would try to keep locking zones as small as
- You create and send a message in coroutine A
- Sending blocks for whatever reasons
- Coroutine B gets activated, creates a message and sends it. Maybe
with the same message system. It also blocks on sending.
- current_msg is now message of B and this is what A sends.
So this would mean that you need to separate message system per
thread. This also means though that it's only one host you connect to
per message system and thus the host could be in the constructor as
it's quite fixed then.
Yea, I do agree that it is confusing having the message remembered in
state by the system, builders, and readers. I'm starting to like the
idea of outputting a Message that the user adds data to and sends. Maybe
the Message System could remain as the connection you send through and
receive on, which automatically serializes sending packets and
deserializes receiving packets, keep track of all acks and such.
Seems like it's basically doing the work already so I think it makes
sense. Not sure what it should be called but we can discuss this later.
But from a logical view on it it seems mostly in there although I would
move some stuff to the Packet class and it's serializer (what you also
suggested I think).
Post by Enus Linden
In Python you don't care about this. If there is a 1 you mean 1 and
you don't care how it's sent over the wire on a lower level of the
system. Yes, you might run into a problem if you don't know the type
but in my experience this rarely actually leads into problems. Having
no type also makes coding faster as you have to type less and you
don't have to consult the documentation.
So let's get rid of the type-checking, I'm fine with that. It IS just
extra junk I don't feel like typing anyway :)
Cool :-)
Post by Enus Linden
There might then also be a MessageDispatcher which does the same so it
knows over which channel to send this message (I guess for XML
messages it's simply the cap we have and we do cap.POST(data).
Right, so I'm thinking the Message System could do all this. Maybe the
Message System could be the factory and dispatcher, with all messaging
being sent and received going through it (but BUILDING messages not
going through this).
In my branch I made the factory a utility as well but that's just a
proposal. If we see it as MessageSystem and not as connection I guess it
can also implement the message creation. If we see it as a connection
IMHO it's strange to say that a connection produces messages. I would
more think that a connection really just cares about serializing and
deserializing and keeping track of state (acks) but not about how to
create new messages.. They either get created by the system who wants to
send it via that connection or they get somehow created on the other
side of the network and it just converts them back from binary from to
Python structures.

Maybe not too important though.


[...]
Post by Enus Linden
message (up to 500 I believe) will have its own unique class that
will initialize the data attributes.
We would start with the ones we actually use in the library. If
somebody needs to use an additional one he can still use the more low
level version (Message('name', Block(...), Block(...)) ).
We also have to look at every message in the protocol spec anyway and
define it there in detail. When we do this we can go along and define
them in code as well. I am also willing to do that.
A pro here would be that you can put default values in the class so
that you don't have to specify all parameters.
When receiving a message you would have the possibility to attach an
event handler directly to that class using ZCA.
Another pro is that the user of this level doesn't have to know about
blocks and the sequence of these. She only needs to know about the
actual data to be passed in.
Well, we can do this with ZCA without deriving a class for each message.
We can have them all implement an interface and register them with a
certain name. This way we don't have to write each individual class, but
can have a generic Message which can handle them all, with handlers. The
default data can be added in by the Message Factory (which looks into
the template and fills in the message with default values).
I guess this is the problem then. If we have a single Message which
builds itself (add_block methods), we cannot write a Message class which
tests that the data being added is correct and expected. Unless the
message itself is a UDP message derivation and can look at the message
template itself and do the checking.
I would propose that for now we don't think about such high level
classes. You also raised the issue yesterday of how those are
deserialized and this probably needs some thinking.

If we have a structure like Message(Block('name', param1=10, param2=20))
it's maybe readable enough for now.

We might discuss which layers to add later then and such classes should
probably also be part of a separate package.

I might write another mail with some proposals on how to maybe get a
release out and what we should do before that (like cleaning up what we
have, doing logging, exceptions etc.)
Post by Enus Linden
Some Connection class which seems to me similar to your message system
and circuits.
BTW, what actually is a circuit? Is it a connection to a region? Or
can you have many circuits to one region? This part of the protocol is
not that clear to me right now. We probably should write it down if it
isn't somewhere (but it should be part of the spec at some point anyway).
A circuit is a UDP connection. So, it is a UNIQUE connection to ip
address and port combination. Can only have 1 circuit for each ip and
port combination.
Sounds also like some connection then. I wonder then if MessageSystem
and Circuit could be merged somehow and the circuit bit is just an
implementation detail of the Connection class. But I am not into that
code so that might be wrong.
Post by Enus Linden
Thanks for your work, I'm starting to see where we can improve things.
I'll start writing down my new proposal and see if we can get something
working.
Thanks for writing down that proposal! I will also be looking at your
branch once you think it's lookworthy ;-) And we should keep the
discussison going I think to get the best result we can.
Post by Enus Linden
PS: I don't like the idea of ZCAifying things like the dictionary just
so that we can register them with ZCA as a global utility. It is an
extra abstraction that is confusing and the reasoning not clearly seen.
Something else we can do?
Well, this seemed to me as one of the main examples of using utilities
because:

- they are singletons (and thus global)
- zope uses similar utilities a lot (like MessageCatalog/Translation)
- they only get instantiated once and the reading is only done once
- you don't need to pass them around
- you can still change them and override the utility if you think you
need to.


Another approach would be to use a module level variable which holds the
instantiated dictionary. You could import it with

import message_template
tmpldict = message_template.tmpldict

and you could also still override it in your own code by "patching" the
module.

But I would prefer to only use one variant because otherwise it really
gets confusing and you never know which variant is used now (and the
deserializer is also a utility btw. for the same reason of being simply
a global service).

Maybe it needs to be documented more? (I guess the answer to such
question is never No ;-) ).

Speaking of docs, I will try to put some more stuff on the wiki this
weekend, like explaining how to use grok, how to use exceptions, the
logger etc.

cheers,

Christian
--
Christian Scholz Homepage: http://comlounge.net
COM.lounge blog: http://mrtopf.de/blog
Luetticher Strasse 10 Skype: HerrTopf
52064 Aachen Video Blog: http://comlounge.tv
Tel: +49 241 400 730 0 E-Mail ***@comlounge.net
Fax: +49 241 979 00 850 IRC: MrTopf, Tao_T

neue Show: TOPFt?glich (http://mrtopf.de/blog/category/topf-taglich/)
Locklainn
2008-08-15 17:19:31 UTC
Permalink
Hello all,
I have been working on a proposal for the messaging system refactoring
that takes into consideration some of the things that Tao has suggested
and would like to see.
the branch is at:
pyogp/pyogp.lib.base/brances/locklainn-message-refactoring

For examples of how this is used, go into one of the test_udp* files in
the client test folder.

Mainly, the changes include direct access to the messages so that you
directly build the messages instead of going through a builder or
reader. In essence, the builders and readers no longer store the
messages in their "state".

Also, the method by which packets are serialized and deserialized is
more in the form of ZCA and is a much better design than before.

The Message System has been renamed to UDPConnection, which suggests
that it no longer serves the purposes it did before (handles the
connection between both llsd tcp and template udp messages). It serves
only for udp connections, manages many different circuits, handles all
acking, and resending unacked.

Here is an example of use:
msg = Message('PacketAck',
Block('Packets', ID=0x00000003, ID=0x00000002)
)
packet = IPacket(msg)
buf = self.udpconnection.send_message(packet, self.host)


Some things to consider:
Tao and I were talking about the sockets. There are two options we see:
1) each udp connection has its own socket
this means that the udp socket is connected directly to its remote
address and doesn't receive any data from anything else, or send data to
anywhere else. This could have problems if there are too many sockets
created in situations like, large draw distances and so connections with
many sims, many agents (and so sockets are multiplied) or something of
the sort
2) there is a 1 level-higher than the circuit object that has a single
socket which dispatches to the circuit. This is how the message system
currently works. The benefit is a single socket. The disadvantage is
having the higher level object that we need to use.
Christian Scholz
2008-08-18 06:10:45 UTC
Permalink
Hi!
Post by Locklainn
Hello all,
I have been working on a proposal for the messaging system refactoring
that takes into consideration some of the things that Tao has suggested
and would like to see.
pyogp/pyogp.lib.base/brances/locklainn-message-refactoring
Thanks for working on this :-)

There is a typo in the path though, here is the full URL:

http://svn.secondlife.com/svn/linden/projects/2008/pyogp/pyogp.lib.base/branches/locklainn-message-refactoring
Post by Locklainn
For examples of how this is used, go into one of the test_udp* files in
the client test folder.
Mainly, the changes include direct access to the messages so that you
directly build the messages instead of going through a builder or
reader. In essence, the builders and readers no longer store the
messages in their "state".
Also, the method by which packets are serialized and deserialized is
more in the form of ZCA and is a much better design than before.
The Message System has been renamed to UDPConnection, which suggests
that it no longer serves the purposes it did before (handles the
connection between both llsd tcp and template udp messages). It serves
only for udp connections, manages many different circuits, handles all
acking, and resending unacked.
After spending the weekend with experimenting with network code I wonder
if it shouldn't better be called UDPMultiplexer or so because the actual
connections are the circuits I think. And this class handles more than
one and needs to dispatch the packets to the individual circuits.
Post by Locklainn
msg = Message('PacketAck',
Block('Packets', ID=0x00000003, ID=0x00000002)
)
packet = IPacket(msg)
buf = self.udpconnection.send_message(packet, self.host)
1) each udp connection has its own socket
this means that the udp socket is connected directly to its remote
address and doesn't receive any data from anything else, or send data to
anywhere else. This could have problems if there are too many sockets
created in situations like, large draw distances and so connections with
many sims, many agents (and so sockets are multiplied) or something of
the sort
I tested this the weekend on my machine. I wrote a simple server based
on SocketServer (python stdlib) with the ThreadingMixin. It was a simple
echo server with a sleep before replying. Then I wrote a client which
instatiated 1500 connections which all then hang because of the sleep.
After instantiating 380 of them the server went down because it couldn't
create enough threads. I changed it to use eventlet. Then it stayed up
and the client broke down after about 1000 connections saying that he
has too many open files.
So 1000 sockets can be open on Linux at least. I couldn't find out how
many these are on other OSes via Google but I might run this script as
well on windows and mac later (there probably is some info somewhere).

So unless you run 100s of avatars I guess there is no real problem. And
if you run 100s of avatars you can still increase this limit in your OS.
Post by Locklainn
2) there is a 1 level-higher than the circuit object that has a single
socket which dispatches to the circuit. This is how the message system
currently works. The benefit is a single socket. The disadvantage is
having the higher level object that we need to use.
After playing around with different network approaches (eventlet,
threads, twisted) I am even more unsure how to solve this ;-)
E.g. with twisted you cannot have a receive() method which waits for
something to come in from the socket. Instead this is done already by
the event loop. The event loop might then call a high level object to
dispatch the data to the right place (and keep track of acks and such).

So I am not sure if our idea of a lower level networking layer actually
works here and I am also not sure if it works for the TCP part (if you
e.g. also want to do it with twisted).

So I couldn't come very close to a solution and I also didn't think
about the event queue for now. But in general I think it should be
possible for the application to decide which networking approach to use.
This maybe means that it needs to do the low level networking stuff
itself maybe with the help of some helper classes.
But I at least need more thinking and maybe more experimentation. The
problem here really is that for this to solve properly we need to know
more about how the lib might be used. So I would actually expect this to
change in the future depending on what we learn.

What we definitely need of course is the circuits which keep track of
acks, know which packet id is next and so on. So the question here is
mostly what to do with the higher level object.

We also need to think about where threads might make problems, like
where things might be not threadsafe. Here I am also not sure how this
could be implemented because at some point you have the sending and
receiving of messages e.g. in some Region class and don't do it
directly. How can we put hooks in so that things can be locked in the
right spot? Or maybe we can work around this and make sure no locking is
needed? The only spot where I think it's needed right now might be the
incrementation of the packet id. But += should be atomic so no locking
should be needed. There is still the possibility if 2 threads do this at
the same time, that id 15 is sent before 14 but this is possible with
UDP anyway and shouldn't break things.

But I will try to post a more detailed report on my experiments later.
You can also check out my sandbox networking stuff I checked in:

http://svn.secondlife.com/svn/linden/projects/2008/pyogp/sandbox/trunk/sandbox/mrtopf/networktest/

But I am not sure it makes sense without further explanations.
The README in there was written before the tests btw and it's most
likely not fitting the results ;-)

You can find the twisted version of the client in 2/ though.

cheers,

Christian
--
Christian Scholz Homepage: http://comlounge.net
COM.lounge blog: http://mrtopf.de/blog
Luetticher Strasse 10 Skype: HerrTopf
52064 Aachen Video Blog: http://comlounge.tv
Tel: +49 241 400 730 0 E-Mail ***@comlounge.net
Fax: +49 241 979 00 850 IRC: MrTopf, Tao_T

neue Show: TOPFt?glich (http://mrtopf.de/blog/category/topf-taglich/)
Locklainn
2008-08-18 17:30:45 UTC
Permalink
Hey Tao,
I just looked at the code you wrote over the weekend.
Your UDPDispatcher does exactly what our current MessageSystem (branch
calls it UDPConnection) does. So, would it be better if we just kept
this design but renamed UDPConnection to UDPDispatcher? I've been
thinking about it for hours now and it really seems to be the only way.

You could achieve 1-1 socket per circuit by having a UDPDispatcher per
circuit.

The receive_from (or check_receive) function can be used in a callback
or can be threaded (oh the glories of Python).

The problems:
Receiving - we want something to keep track of the messages that we
received that need to be acked
- need a way to handle messages, not just tracking acks
Sending - we want something to keep track of the messages that we send
that we want to be acked
acking - need a way to send out acks at periodic times (who
determines when?)


I think anything more advanced than this is just too complicated for the
time being. I think we are over-designing this at this point. When an
issue comes up, then we can refactor it accordingly. I believe my
branched design solves most of the problems that you brought up, and can
also work with twisted, eventlet, or threading. It is essentially the
same as what you have in your branch.
Christian Scholz
2008-08-20 17:37:19 UTC
Permalink
Hi!
Post by Locklainn
I just looked at the code you wrote over the weekend.
Your UDPDispatcher does exactly what our current MessageSystem (branch
calls it UDPConnection) does. So, would it be better if we just kept
this design but renamed UDPConnection to UDPDispatcher? I've been
thinking about it for hours now and it really seems to be the only way.
Yep, it's not too different as my approach was also to support a shared
socket so you need some sort of dispatcher.
Post by Locklainn
You could achieve 1-1 socket per circuit by having a UDPDispatcher per
circuit.
Yep. But we should maybe also allow a user to eventually create their
own dispatcher and move all the circuit related parts into the circuit
or the packet class. I think we discussed this before and this seemed ok.
Post by Locklainn
The receive_from (or check_receive) function can be used in a callback
or can be threaded (oh the glories of Python).
well, the difference to now is that you don't wait inside the
receive_check but rather give it either a already decoded packet or
binary data and the check does the decoding.

It would then find the right circuit to handle it and hand it over.
So the circuit can do the ack management and such things. A user could
then also choose to directly use circuits and just give it some packet
to handle.

The question is what will happen after the housekeeping stuff, e.g.
reacting on the packet which comes in. The application needs to know
about it. We probably can use some callback in form of an event for that.
Post by Locklainn
Receiving - we want something to keep track of the messages that we
received that need to be acked
- need a way to handle messages, not just tracking acks
Sending - we want something to keep track of the messages that we send
that we want to be acked
acking - need a way to send out acks at periodic times (who determines
when?)
As for sending I see the main problem on who is going to actually send
it. The circuit maybe shouldn't do that but only return data and address
where it needs to be sent to. I think the dispatcher should only be used
for receiving as with a shared socket this is where the problem is and
where it's needed. For sending in application code I'd rather call a
method on some object which represents the endpoint, e.g.

region.send(msg)

Internally this would have a circuit attached and can send it via that
circuit. Which brings us back to the problem of how the circuit will
route the packet to the socket. So if we say the circuit only returns
the binary data (or some packet which needs to be serialized) then the
next higher level needs take care of sending.

So this might need some sort of endpoint object where it can feed data
to. It would also take the address to send it to from the circuit so
that it could look like this:

packet = circuit.prepare(msg)
adr = circuit.address # (host, port)

endpoint.send(packet,adr)

which looks like your message system again and which might mean that
this can be the UDPDispatcher as well.

As you said, for individual sockets we can have individual UDPDispatcher
objects although of course giving it the address then looks strange. But
I doubt that this is a big problem.
Post by Locklainn
I think anything more advanced than this is just too complicated for the
time being. I think we are over-designing this at this point. When an
issue comes up, then we can refactor it accordingly. I believe my
branched design solves most of the problems that you brought up, and can
also work with twisted, eventlet, or threading. It is essentially the
same as what you have in your branch.
Well, my branch does not solve the twisted problem yet as I didn't went
that far back then. I only worked on the message creation part.

But you are right, maybe it's good to have something working now which
we then can test in RL situations. What would be good in general is:

- Message creation can be used without the rest (done, I think)
- Circuits should be usable without a message system (some stuff needs
to be moved in there).

Then we should be rather flexible. If some problem comes up with the
dispatcher you only would need to replace this in your own code which
seems pretty ok. The dispatcher then really would be just a map of
addresses/circuits to sockets. A circuit then maybe should just return
data and do not any actual sending.

Does that sound ok?

-- Christian
Timothy Loughlin (Locklainn Linden)
2008-08-18 18:15:59 UTC
Permalink
First, I won't be around for Tues and Wed. I have to take care of some
training for grad school. This means you guys have 3 days to review this
stuff and make a decision! w00t!

Here is the design proposal for the message system refactoring. After
writing this up and looking at my branch code and Tao's, it seems what
we have in our branches is the only good solution thus far. Also, we
have essentially the same design, just with some different naming. They
perform the same functions in almost the same way.

*Similarities:
*what Tao calls EndPoint I call NetUDPClient (which is ZCAified so it
can be swapped)
what Tao calls UDPDispatcher I call MessageSystem (or UDPConnection in
my branch)

*Differences:
*Tao's sending and receiving functionality is split apart. All receiving
is through the UDPDispatcher, which then dispatches to a circuit to
handle (exactly the same as Lock's). However, sending is different in
that in Tao's branch you send a message by having direct access to a
circuit. The circuit is hard-wired to a single remote host, and so you
don't pass in the destination when sending. With Lock's, every time you
send a message, you tell it which destination it will go to.

For example, here is the difference in sending and receiving (putting
the terms into Lock's terms to show the similarity and differences easily)
*
Receiving:
*Both:
MessageSystem:
receive() (also called handle() ):
size = 10000
data, address = self.udp_client.recvfrom(size)
if address not in self.circuit_list:
error

circuit = find_circuit(address)
circuit.process(data)

NOTES:
This call, receive(), can be wrapped in any way the user wants. It can
be threaded, used as a callback, non-blocking IO, whatever.
The difference between Tao's code and Lock's for receive is how a
circuit gets added to the Message System's circuit list. In Tao's, one
must manually call message_system.add_circuit(circuit). In Lock's a new
circuit is created when the user sends to a new address/port or when it
gets a message (receives) from a new address/port. The user doesn't use
connection directly, then, but they are simply used as ways to keep
track of packet acks. Note that this is the same in both as well,
because even in Tao's code the circuit delegates sending and receiving
to some udp client (called EndPoint).


*Sending:*
Tao:
message_system= MessageSystem()
conn = Connection(message_system, (address, port))
msg = Message(...)
conn.send(msg)

Lock:
message_system= MessageSystem ()
msg = Message(...)
message_system.send(msg, (address, port))

NOTES:
The differences lie in the fact that in Tao's branch you create a
connection explicitly, tie the connection with an address/port, and send
the message through the connection. In Lock's way, you send a message to
the target address/port all the time, never having access to the circuit
itself. The message system determines if the combination is new, if so,
creates a new circuit for it automatically.

*Decision:*
My vote is that we keep the design in my branch because it is mostly
finished and functional (with packet flags and all that already
working). I vote this because our designs are almost identical, with
only a naming difference. So, let's come up with the names for these
things and we will have our refactor design.

For the real differences, we must decide whether or not the Message
System does the sending, or if the circuit does the sending (which
delegates to some udp client). My thoughts are that because in Tao's
branch the circuit is doing the sending and the message system doing the
receiving, we need a single udp_client, or socket at the lowest level,
and so this will be shared among the circuit and the message system
(with message system having circuits as well). We then have to pass this
socket (or EndPoint) to both the MessageSystem and the Circuit, which
makes it 1 or 3 steps more to set up sending. With Lock's design, the
EndPoint, socket, or udp_client is automatically the same for sending
and receiving.

Note that the socket issue, whether or not a single socket is used per
connection or if a single socket is used for all connections, is exactly
the same for both designs. In both designs you have to create a new
MessageSystem, or UDPDispatcher (which also needs a new EndPoint to be
created as well).

Also note that it isn't currently possible to have both designs (one
that has access to connections and one that doesn't). This is because in
my design the connections are under the hood of the message system.We
COULD, however, add this functionality to the message system, which
means having the message system be a way to create circuits:
message_system = MessageSystem()
conn1 = message_system.create_circuit(address, port)
msg = Message(...)
conn1.send(msg)


THIS STUFF COULD GO IN THE DESIGN DOC I'M SENDING
-------------- next part --------------
A non-text attachment was scrubbed...
Name: MessageSystemDecision.doc
Type: application/msword
Size: 52736 bytes
Desc: not available
Url : http://lists.secondlife.com/pipermail/pyogp/attachments/20080818/379e8b84/MessageSystemDecision-0001.doc
Enus Linden
2008-08-20 14:10:14 UTC
Permalink
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


Thanks for the clear details around these issues. I think each approach
toward each point has advantages. I do agree, we have a refactoring
design done, and it would be beneficial to complete this and to move
forward from here with what we have. We can always revisit later as we
encounter situations which warrant further discussion... This would free
us up to continue with other work, part of which I would like to see
happening is an exploration of next steps.

Timothy Loughlin (Locklainn Linden) wrote:
| First, I won't be around for Tues and Wed. I have to take care of some
training for grad school. This means you guys have 3 days to review this
stuff and make a decision! w00t!
|
| Here is the design proposal for the message system refactoring. After
writing this up and looking at my branch code and Tao's, it seems what
we have in our branches is the only good solution thus far. Also, we
have essentially the same design, just with some different naming. They
perform the same functions in almost the same way.
|
| *Similarities:
| *what Tao calls EndPoint I call NetUDPClient (which is ZCAified so it
can be swapped)
| what Tao calls UDPDispatcher I call MessageSystem (or UDPConnection in
my branch)
|
| *Differences:
| *Tao's sending and receiving functionality is split apart. All
receiving is through the UDPDispatcher, which then dispatches to a
circuit to handle (exactly the same as Lock's). However, sending is
different in that in Tao's branch you send a message by having direct
access to a circuit. The circuit is hard-wired to a single remote host,
and so you don't pass in the destination when sending. With Lock's,
every time you send a message, you tell it which destination it will go to.
|
| For example, here is the difference in sending and receiving (putting
the terms into Lock's terms to show the similarity and differences easily)
| *
| Receiving:
| *Both:
| MessageSystem:
| receive() (also called handle() ):
| size = 10000
| data, address = self.udp_client.recvfrom(size)
| if address not in self.circuit_list:
| error
|
| circuit = find_circuit(address)
| circuit.process(data)
|
| NOTES:
| This call, receive(), can be wrapped in any way the user wants. It can
be threaded, used as a callback, non-blocking IO, whatever.
| The difference between Tao's code and Lock's for receive is how a
circuit gets added to the Message System's circuit list. In Tao's, one
must manually call message_system.add_circuit(circuit). In Lock's a new
circuit is created when the user sends to a new address/port or when it
gets a message (receives) from a new address/port. The user doesn't use
connection directly, then, but they are simply used as ways to keep
track of packet acks. Note that this is the same in both as well,
because even in Tao's code the circuit delegates sending and receiving
to some udp client (called EndPoint).
|
|
| *Sending:*
| Tao:
| message_system= MessageSystem()
| conn = Connection(message_system, (address, port))
| msg = Message(...)
| conn.send(msg)
|
| Lock:
| message_system= MessageSystem ()
| msg = Message(...)
| message_system.send(msg, (address, port))
|
| NOTES:
| The differences lie in the fact that in Tao's branch you create a
connection explicitly, tie the connection with an address/port, and send
the message through the connection. In Lock's way, you send a message to
the target address/port all the time, never having access to the circuit
itself. The message system determines if the combination is new, if so,
creates a new circuit for it automatically.
|
| *Decision:*
| My vote is that we keep the design in my branch because it is mostly
finished and functional (with packet flags and all that already
working). I vote this because our designs are almost identical, with
only a naming difference. So, let's come up with the names for these
things and we will have our refactor design.
|
| For the real differences, we must decide whether or not the Message
System does the sending, or if the circuit does the sending (which
delegates to some udp client). My thoughts are that because in Tao's
branch the circuit is doing the sending and the message system doing the
receiving, we need a single udp_client, or socket at the lowest level,
and so this will be shared among the circuit and the message system
(with message system having circuits as well). We then have to pass this
socket (or EndPoint) to both the MessageSystem and the Circuit, which
makes it 1 or 3 steps more to set up sending. With Lock's design, the
EndPoint, socket, or udp_client is automatically the same for sending
and receiving.
|
| Note that the socket issue, whether or not a single socket is used per
connection or if a single socket is used for all connections, is exactly
the same for both designs. In both designs you have to create a new
MessageSystem, or UDPDispatcher (which also needs a new EndPoint to be
created as well).
|
| Also note that it isn't currently possible to have both designs (one
that has access to connections and one that doesn't). This is because in
my design the connections are under the hood of the message system.We
COULD, however, add this functionality to the message system, which
means having the message system be a way to create circuits:
| message_system = MessageSystem()
| conn1 = message_system.create_circuit(address, port)
| msg = Message(...)
| conn1.send(msg)
|
|
| THIS STUFF COULD GO IN THE DESIGN DOC I'M SENDING
|
| -------------------------
|
| _______________________________________________
| Click here to unsubscribe or manage your list subscription:
| https://lists.secondlife.com/cgi-bin/mailman/listinfo/pyogp

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.8 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkisQeQACgkQ32gDDkJcPPiXTQCggBUuFIc1MXTasUe3auRaqYEl
tUoAn0jakqEZQ9JVSYf2AQBlIZoOLUvV
=ANbM
-----END PGP SIGNATURE-----

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.secondlife.com/pipermail/pyogp/attachments/20080820/df2968d0/attachment.htm
Christian Scholz
2008-08-20 17:50:38 UTC
Permalink
Post by Timothy Loughlin (Locklainn Linden)
First, I won't be around for Tues and Wed. I have to take care of some
training for grad school. This means you guys have 3 days to review this
stuff and make a decision! w00t!
Here is the design proposal for the message system refactoring. After
writing this up and looking at my branch code and Tao's, it seems what
we have in our branches is the only good solution thus far. Also, we
have essentially the same design, just with some different naming. They
perform the same functions in almost the same way.
*what Tao calls EndPoint I call NetUDPClient (which is ZCAified so it
can be swapped)
what Tao calls UDPDispatcher I call MessageSystem (or UDPConnection in
my branch)
As I mentioned somewhere I am actually not sure if the way I initially
did the TCP part works that well with e.g. twisted. So maybe the network
layer at all wasn't such a good idea as it assumes that this is the
lowest layer and e.g. circuits use it.

I'd rather think that the networking layer is in the application and it
mainly passes data in and out to be handled. Also I wonder what
possibilities there are right now to exchange the NetUDPClient. It
cannot be replaced right now to use twisted. The same would be true for
the HTTPClient. (with right now I mean trunk).

So having it like discussed in the earlier mail might work better then.
Once this is refactored I would then look into HTTP and maybe build some
example twisted app which does presence or so.
Post by Timothy Loughlin (Locklainn Linden)
*Tao's sending and receiving functionality is split apart. All receiving
is through the UDPDispatcher, which then dispatches to a circuit to
handle (exactly the same as Lock's). However, sending is different in
that in Tao's branch you send a message by having direct access to a
circuit. The circuit is hard-wired to a single remote host, and so you
don't pass in the destination when sending. With Lock's, every time you
send a message, you tell it which destination it will go to.
I want to note that this wasn't finished and I was far from being happy
with it. My brain was just at it's recursion depth limit ;-)
As for the address thing you are right. But I put in the endpoint you
give to the connection constructor (which is the circuit) which can be a
shared socket (or wraps it rather). But you are right that it didn't
know the address which was a problem I found hard to solve back then.
(at least so that the API is the same for all cases)
Post by Timothy Loughlin (Locklainn Linden)
For example, here is the difference in sending and receiving (putting
the terms into Lock's terms to show the similarity and differences easily)
*
size = 10000
data, address = self.udp_client.recvfrom(size)
error
circuit = find_circuit(address)
circuit.process(data)
This call, receive(), can be wrapped in any way the user wants. It can
be threaded, used as a callback, non-blocking IO, whatever.
Well, it can't be a callback as you cannot pass data in you want to be
handled. In twisted you get some data from a transport and you'd need to
pass it to receive. The receivefrom is already done (or rather done in a
non-blocking fashion).
Post by Timothy Loughlin (Locklainn Linden)
The difference between Tao's code and Lock's for receive is how a
circuit gets added to the Message System's circuit list. In Tao's, one
must manually call message_system.add_circuit(circuit). In Lock's a new
circuit is created when the user sends to a new address/port or when it
gets a message (receives) from a new address/port. The user doesn't use
connection directly, then, but they are simply used as ways to keep
track of packet acks. Note that this is the same in both as well,
because even in Tao's code the circuit delegates sending and receiving
to some udp client (called EndPoint).
I personally would prefer some wrapping like this:

Region(Circuit())

So the region knows it's connection and can instantiate it when it's
created itself (e.g. after place_avatar). This way each object knows
directly the one it's using.
The problem is of course the shared socket or some twisted code. This is
why I think that the networking layer actually needs to move up as it
seems to be the main driving force of the whole application.
Post by Timothy Loughlin (Locklainn Linden)
*Sending:*
message_system= MessageSystem()
conn = Connection(message_system, (address, port))
msg = Message(...)
conn.send(msg)
message_system= MessageSystem ()
msg = Message(...)
message_system.send(msg, (address, port))
The differences lie in the fact that in Tao's branch you create a
connection explicitly, tie the connection with an address/port, and send
the message through the connection. In Lock's way, you send a message to
the target address/port all the time, never having access to the circuit
itself. The message system determines if the combination is new, if so,
creates a new circuit for it automatically.
*Decision:*
My vote is that we keep the design in my branch because it is mostly
finished and functional (with packet flags and all that already
working). I vote this because our designs are almost identical, with
only a naming difference. So, let's come up with the names for these
things and we will have our refactor design.
I would vote for slightly moving things from the message system to the
circuit so that you can also use it without the message system. Like
adding flags and such. This wouldn't change anything to the outside API.
Post by Timothy Loughlin (Locklainn Linden)
For the real differences, we must decide whether or not the Message
System does the sending, or if the circuit does the sending (which
delegates to some udp client). My thoughts are that because in Tao's
branch the circuit is doing the sending and the message system doing the
receiving, we need a single udp_client, or socket at the lowest level,
and so this will be shared among the circuit and the message system
(with message system having circuits as well). We then have to pass this
socket (or EndPoint) to both the MessageSystem and the Circuit, which
makes it 1 or 3 steps more to set up sending. With Lock's design, the
EndPoint, socket, or udp_client is automatically the same for sending
and receiving.
But I am not sure it works with twisted. You need to pass something to
use for sending. In twisted you don't use sockets directly. That was the
reason for my endpoint. It's not only about single or shared socket.
So the endpoint needs to come from the application and needs to be passed.
Post by Timothy Loughlin (Locklainn Linden)
Note that the socket issue, whether or not a single socket is used per
connection or if a single socket is used for all connections, is exactly
the same for both designs. In both designs you have to create a new
MessageSystem, or UDPDispatcher (which also needs a new EndPoint to be
created as well).
Also note that it isn't currently possible to have both designs (one
that has access to connections and one that doesn't). This is because in
my design the connections are under the hood of the message system.We
COULD, however, add this functionality to the message system, which
message_system = MessageSystem()
conn1 = message_system.create_circuit(address, port)
msg = Message(...)
conn1.send(msg)
I think it is possible to give the user the possibility to not use the
message system but something else. Then we could keep most of the stuff
which is there and only would need to move things slightly.

I guess we should discuss this tomorrow :)

-- Christian
Timothy Loughlin (Locklainn Linden)
2008-08-21 19:29:56 UTC
Permalink
Hello again,
After talking the redesign over a bit, there have been some changes made
to the design.

The refactoring changes things as follows:

Circuits
It's responsibilities now include handling the ack upkeep, that is,
determining if the packet needs acking, based on the flags set in the
packet.

MessageSystem/UDPConnection now called UDPDispatcher
It's responsibilities include receiving a message from an EXTERNAL
source, deserializing it, and having it handled by circuits and packet
handlers.
It simply receives the data, finds the circuit it came in over, and
dispatches the packet to the circuit. This allows a single socket to be
used (can group circuits together).
It still handles the sending of messages as well. In any case, it seems
like the design will need to use Tao's EndPoint, that is passed to the
Dispatcher, and is passed to each circuit. I don't personally like this
model of passing an endpoint around, but if that is how we want to do it
then I'll code that up.
SO (note, this ISN'T how it is currently done):
endpoint = EndPoint()
circuit = Circuit(endpoint)
circuit.send(packet) - where the circuit's send function will
delegate to the endpoint

dispatcher = UDPDispatcher(endpoint)
dispatcher.add_circuit(circuit)
endpoint.set_handler(dispatcher)
endpoint.receive() - where the endpoint's receive function will get
data from teh socket and hand it off to the dispatcher

Message
There is no a Message class which wraps the old code so that we can use
more Pythonic creation of Messages.

Serializer/Deserializer
These put the message data into or out of raw character strings to be
sent over a network.
Locklainn
2008-08-22 11:48:09 UTC
Permalink
I've been thinking about the sending and receiving part. Tao, I'm not
sure how you want to handle this. I know you want to do something like:
circuit.send(msg)
but this seems to make things confusing because you send from the
circuit but then receive from the dispatcher:
dispatcher.receive() (which will call the circuit's handle event message)

So, with sending and receiving in different areas, I think it makes the
api harder. I'm not sure the solution, but it seems they should go in
the same place.
Post by Christian Scholz
Post by Timothy Loughlin (Locklainn Linden)
First, I won't be around for Tues and Wed. I have to take care of
some training for grad school. This means you guys have 3 days to
review this stuff and make a decision! w00t!
Here is the design proposal for the message system refactoring. After
writing this up and looking at my branch code and Tao's, it seems
what we have in our branches is the only good solution thus far.
Also, we have essentially the same design, just with some different
naming. They perform the same functions in almost the same way.
*what Tao calls EndPoint I call NetUDPClient (which is ZCAified so it
can be swapped)
what Tao calls UDPDispatcher I call MessageSystem (or UDPConnection
in my branch)
As I mentioned somewhere I am actually not sure if the way I initially
did the TCP part works that well with e.g. twisted. So maybe the
network layer at all wasn't such a good idea as it assumes that this
is the lowest layer and e.g. circuits use it.
I'd rather think that the networking layer is in the application and
it mainly passes data in and out to be handled. Also I wonder what
possibilities there are right now to exchange the NetUDPClient. It
cannot be replaced right now to use twisted. The same would be true
for the HTTPClient. (with right now I mean trunk).
So having it like discussed in the earlier mail might work better
then. Once this is refactored I would then look into HTTP and maybe
build some example twisted app which does presence or so.
Post by Timothy Loughlin (Locklainn Linden)
*Tao's sending and receiving functionality is split apart. All
receiving is through the UDPDispatcher, which then dispatches to a
circuit to handle (exactly the same as Lock's). However, sending is
different in that in Tao's branch you send a message by having direct
access to a circuit. The circuit is hard-wired to a single remote
host, and so you don't pass in the destination when sending. With
Lock's, every time you send a message, you tell it which destination
it will go to.
I want to note that this wasn't finished and I was far from being
happy with it. My brain was just at it's recursion depth limit ;-)
As for the address thing you are right. But I put in the endpoint you
give to the connection constructor (which is the circuit) which can be
a shared socket (or wraps it rather). But you are right that it didn't
know the address which was a problem I found hard to solve back then.
(at least so that the API is the same for all cases)
Post by Timothy Loughlin (Locklainn Linden)
For example, here is the difference in sending and receiving (putting
the terms into Lock's terms to show the similarity and differences easily)
*
size = 10000
data, address = self.udp_client.recvfrom(size)
error
circuit = find_circuit(address)
circuit.process(data)
This call, receive(), can be wrapped in any way the user wants. It
can be threaded, used as a callback, non-blocking IO, whatever.
Well, it can't be a callback as you cannot pass data in you want to be
handled. In twisted you get some data from a transport and you'd need
to pass it to receive. The receivefrom is already done (or rather done
in a non-blocking fashion).
Post by Timothy Loughlin (Locklainn Linden)
The difference between Tao's code and Lock's for receive is how a
circuit gets added to the Message System's circuit list. In Tao's,
one must manually call message_system.add_circuit(circuit). In
Lock's a new circuit is created when the user sends to a new
address/port or when it gets a message (receives) from a new
address/port. The user doesn't use connection directly, then, but
they are simply used as ways to keep track of packet acks. Note that
this is the same in both as well, because even in Tao's code the
circuit delegates sending and receiving to some udp client (called
EndPoint).
Region(Circuit())
So the region knows it's connection and can instantiate it when it's
created itself (e.g. after place_avatar). This way each object knows
directly the one it's using.
The problem is of course the shared socket or some twisted code. This
is why I think that the networking layer actually needs to move up as
it seems to be the main driving force of the whole application.
Post by Timothy Loughlin (Locklainn Linden)
*Sending:*
message_system= MessageSystem()
conn = Connection(message_system, (address, port))
msg = Message(...)
conn.send(msg)
message_system= MessageSystem ()
msg = Message(...)
message_system.send(msg, (address, port))
The differences lie in the fact that in Tao's branch you create a
connection explicitly, tie the connection with an address/port, and
send the message through the connection. In Lock's way, you send a
message to the target address/port all the time, never having access
to the circuit itself. The message system determines if the
combination is new, if so, creates a new circuit for it automatically.
*Decision:*
My vote is that we keep the design in my branch because it is mostly
finished and functional (with packet flags and all that already
working). I vote this because our designs are almost identical, with
only a naming difference. So, let's come up with the names for these
things and we will have our refactor design.
I would vote for slightly moving things from the message system to the
circuit so that you can also use it without the message system. Like
adding flags and such. This wouldn't change anything to the outside API.
Post by Timothy Loughlin (Locklainn Linden)
For the real differences, we must decide whether or not the Message
System does the sending, or if the circuit does the sending (which
delegates to some udp client). My thoughts are that because in Tao's
branch the circuit is doing the sending and the message system doing
the receiving, we need a single udp_client, or socket at the lowest
level, and so this will be shared among the circuit and the message
system (with message system having circuits as well). We then have to
pass this socket (or EndPoint) to both the MessageSystem and the
Circuit, which makes it 1 or 3 steps more to set up sending. With
Lock's design, the EndPoint, socket, or udp_client is automatically
the same for sending and receiving.
But I am not sure it works with twisted. You need to pass something to
use for sending. In twisted you don't use sockets directly. That was
the reason for my endpoint. It's not only about single or shared socket.
So the endpoint needs to come from the application and needs to be passed.
Post by Timothy Loughlin (Locklainn Linden)
Note that the socket issue, whether or not a single socket is used
per connection or if a single socket is used for all connections, is
exactly the same for both designs. In both designs you have to create
a new MessageSystem, or UDPDispatcher (which also needs a new
EndPoint to be created as well).
Also note that it isn't currently possible to have both designs (one
that has access to connections and one that doesn't). This is because
in my design the connections are under the hood of the message
system.We COULD, however, add this functionality to the message
system, which means having the message system be a way to create
message_system = MessageSystem()
conn1 = message_system.create_circuit(address, port)
msg = Message(...)
conn1.send(msg)
I think it is possible to give the user the possibility to not use the
message system but something else. Then we could keep most of the
stuff which is there and only would need to move things slightly.
I guess we should discuss this tomorrow :)
-- Christian
_______________________________________________
https://lists.secondlife.com/cgi-bin/mailman/listinfo/pyogp
Locklainn
2008-08-08 18:10:35 UTC
Permalink
PS: this is presence only for sim, not the agent domain. That is
probably also on the to-do list, that is, maintain presence with agent
domain via event queue long-polling (or some other means).
Loading...