Hugh, I looked around at some of the options and I think the runtime
class generation is the way to go. However I'm thinking you are the only
person with enough background in creating similar systems that would be
able to implement it properly. Once some foundation code is in place it
will be easy enough for everyone else to get up to speed and start using
it or contributing patches and more ideas.
Ok, great! As you said earlier a huge amount of abstraction has already done, for example in PacketBuilder; this is another step along that same path.
Given the core nature of the functionality to the project, it's probably worth spending a little bit of time agreeing the specifications for this. There are two parts to consider:
- outgoing packet
- incoming packets
For outgoing packets, the way the classes in the packets directory work looks ideal. For example, there is a class called Communication that contains a function called ImprovedInstantMessage. We'd keep the Communication class; we'd keep the ImprovedInstantMessage function. What we'd change is that the function would be empty, and our rpc infrastructure will do the rest:
In libsecondlife:
class Communication
{
public void ImprovedInstantMessage(ProtocolManager protocol, LLUUID targetAgentID,
LLUUID myAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, byte offline,
byte dialog, LLUUID id, uint timestamp, string myAgentName, string message,
byte[] binaryBucket)
{
}
}
In use:
Communication communication = rpccontroller.CreateProxy ( typeof( Communication ) ) as Communication;
communication.ImprovedInstantMessage( protocol, targetid, ... etc..., "some message", ... etc ... );
Note that this adds a requirement to the user to instantiate the proxy before using it. This is fairly standard; it's how Corba, Ice, .Net Remoting etc work; but it is a change of API for the user. We could manually wrap this, use code generation instead or to wrap it, or accept it. To be discussed?
Incoming packets
Incoming packets offer several possibilities, so better to discuss them first prior to implementation?
We can use ImprovedInstantMessage as an example. We can see this class in Avatar.cs for example.
Currently there is a method in Avatar called InstantMessageHandler that is registered with the Network object to receive ImprovedInstantMessage packets. When it receives such a packet it unmarshalls it, then calls OnInstantMessage, passing the data as multiple parameters to the OnInstantMessage call.
Some ways to integrate automatic unmarshalling into this process are:
- we use the callback delegate declaration as the basis for our unmarshalling code. So, in Avatar.InstantMessageHandler, we'd have:
private void InstantMessageHandler(Packet packet, Circuit circuit)
{
if (packet.Layout.Name == "ImprovedInstantMessage")
{
rpccontroller.SendPacketToCallback( packet, OnInstantMessage );
}
}
This is fairly flexible, and seems to fit in well with the way InstantMessageHandler works. Unsure how well it fits in with other examples of incoming packets?
- we create a class called ImprovedInstantMessage that will contain the data from the packet. We can automatically unmarshall the packet to a new instance of ImprovedInstantMessage.
Conserving current OnInstantMessage API:
private void InstantMessageHandler(Packet packet, Circuit circuit)
{
if (packet.Layout.Name == "ImprovedInstantMessage")
{
ImprovedInstantMessage improvedinstantmessage = rpccontroller.Unmarshall( packet ) as improvedinstantmessage;
if( OnInstantMessage != null )
{
OnInstantMessage(
improvedinstantmessage.FromAgentID,
improvedinstantmessage.ToAgentID,
improvedinstantmessage.ParentEstateID,
improvedinstantmessage.RegionID,
improvedinstantmessage.Position,
improvedinstantmessage.Offline,
improvedinstantmessage.Dialog,
improvedinstantmessage.ID,
improvedinstantmessage.Timestamp,
improvedinstantmessage.AgentName,
improvedinstantmessage.Message,
improvedinstantmessage.Bucket
);
}
}
}
There's still a fair amount of manual code here. We can eliminate that by writing the improvedinstantmessage object directly to the OnInstantMessage callback:
private void InstantMessageHandler(Packet packet, Circuit circuit)
{
if (packet.Layout.Name == "ImprovedInstantMessage")
{
ImprovedInstantMessage improvedinstantmessage = rpccontroller.Unmarshall( packet ) as improvedinstantmessage;
if( OnInstantMessage != null )
{
OnInstantMessage( improvedinstantmessage );
}
}
}
Note that we dont need to specify the name or type of the ImprovedInstantMessage class to our Unmarshall function, because it is already contained in the Packet!
- Client.Net handles everything. We can register callbacks to directly received incoming packets. We no longer need Avatar.InstantMessageHandler. Client.Net has the name of the packet so it knows what to do with it, as long as it has the appropriate callback or class definitions available.
There are three ways to do this:
- by defining the incoming packets in delegates
- by defining the incoming packets in classes
This is essentially similar to the two previous scenarios, except that there is no middleman between the user and Client.Net. Client.Net handles it automatically:
Using delegates:
In libsecondlife:
public delegate void ImprovedInstantMessageCallback(LLUUID FromAgentID, LLUUID ToAgentID,
uint ParentEstateID, LLUUID RegionID, LLVector3 Position, byte Offline, byte Dialog,
LLUUID ID, uint Timestamp, string AgentName, string Message, string Bucket);
In user's code:
Client.Net.RegisterCallback( new ImprovedInstantMessageCallback( _ImprovedInstantMessage ) );
public void _ImprovedInstantMessage( LLUUID FromAgentID, LLUUID ToAgentID,
uint ParentEstateID, LLUUID RegionID, LLVector3 Position, byte Offline, byte Dialog,
LLUUID ID, uint Timestamp, string AgentName, string Message, string Bucket)
{
// user's additional code here
}
In libsecondlife:
public class ImprovedInstantMessage
{
public LLUUID FromAgentID = new LLUUID();
public LLUUID ToAgentID = new LLUUID();
public uint ParentEstateID = 0;
public LLUUID RegionID = new LLUUID();
public LLVector3 Position = new LLVector3();
public byte Offline = 0;
public byte Dialog = 0;
public LLUUID ID = new LLUUID();
public uint Timestamp = 0;
public string FromAgentName = "";
public string Message = "";
public string BinaryBucket = "";
public ImprovedInstantMessage()
{
}
}
}
In user's code:
Client.Net.RegisterCallback ( typeof( ImprovedInstantMessage ), new ImprovedInstantMessageCallback( _ImprovedMessageCallback ) );
public void _ImprovedInstantMessage( ImprovedInstantMessage improvedinstantmessage )
{
// user's additional code here
}
- Defining the incoming packets as method calls
This is how this would be done in standard .Net Remoting, Corba etc. It has the advantage that we can use the same packet definition for both outgoing and ingoing packets of the same type, reducing duplication and therefore saving time.
In this scenario we define each packet by writing a function call, exactly as we are doing for outgoing packets. We can group these function calls together, for example in a class CommunicationsInbound, just as we are doing for incoming packets, for example in the class Communications.
We can register these classes with Client.Net , which will store a list of the available function calls and their names, and match these up with incoming packets.
When the name of an incoming packet matches the name of an available function, it is unmarshalled according to the parameters in that function, and that function is called.
Of course, by default, not much is going to happen because there is no code in our CommunicationsInbound class.
The user can add code by deriving their own class from CommunicationsInbound , adding their code to this derived class, and registering it with Client.Net.
We could choose to make CommunicationsInbound a c# interface rather than a class.
Client.Net will automatically route incoming packets to all matching method calls in the classes that have been registered with it.
Note that there is one additional choice here, because we don't have enough choices yet ;-). We can register a class with Client.Net, or we can register a specific object. If we register a class, a new instance of that class will be created for each new packet; if we register an object, the same object will be targeted by each incoming packet.
In libsecondlife:
interface CommunicationsInbound
{
void ImprovedInstantMessage(ProtocolManager protocol, LLUUID targetAgentID,
LLUUID myAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, byte offline,
byte dialog, LLUUID id, uint timestamp, string myAgentName, string message,
byte[] binaryBucket);
}
In user's code:
class MyCommunicationsInbound : CommunicationsInbound
{
void ImprovedInstantMessage(ProtocolManager protocol, LLUUID targetAgentID,
LLUUID myAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, byte offline,
byte dialog, LLUUID id, uint timestamp, string myAgentName, string message,
byte[] binaryBucket)
{
// handle incoming packet here
}
}
MyCommunicationsInbound mycommunicationsinbound;
Client.Net.RegisterCallback( mycommunicationsinbound );
This is a fairly big change in API, on the other hand it makes it fairly standard wrt existing object rpc implementations; and it means that each packet need only be defined once, even if it will be used both for incoming and outgoing messages.
Summarising, for outgoing packets the methodology seems fairly obvious: essentially we make it easier to create the classes in the Packets directory, by automating the marshalling process. We can also choose to write the packets directly to the wire?
For incoming packets, there are a few possibilities. Using callbacks to define the incoming packets is probably a relatively easy way of doing it that fits in with our current implementation? Modelling the packets as method calls means we can use the outgoing packet definitions as our incoming packet definitions, but it does imply a change of API for the user.
_______________________________________________ libsecondlife-dev mailing list libsecondlife-dev@gna.org https://mail.gna.org/listinfo/libsecondlife-dev