glob2-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Networking (was Re: [glob2-devel] Glob2 salvage proposal)


From: Andrew Sayers
Subject: Networking (was Re: [glob2-devel] Glob2 salvage proposal)
Date: Sun, 23 Oct 2005 04:30:45 +0100
User-agent: Mutt/1.5.11

I've had a look at Order.h (and a quick look around some other
networking-related files) today.  Could you tell me how accurate the
following statements are:

Communication over the network consists *only* of orders being sent back
and forth between systems.

Orders are all defined in Order.h/Order.cpp, all descend from the
"Order" class, and are executed in Game.executeOrder() and
GameGUI.executeOrder() (or not executed at all);

Orders go through the following stages:
* They are created with a call to "new myOrderType", which can happen
  anywhere in the program.
* They are serialised and transmitted to other computers
* <Some magic happens whereby transmission is confirmed, a whole tick's
  worth of orders are grouped, etc.>
* A tick's worth of orders are reconstructed by code somewhere deep in
  NetGame.cpp, and by the setData() function for each order.
* Later, GameGui.getOrder() is called, which in turn calls
  Game.getOrder().  These two functions process the orders.  No other
  function has anything to do with executing orders.


If I'm understanding this right, I'd say the system has the following
pros and cons:

+ It's quite self-documenting.  You can guess what's happening when you
  see OrderCancelConstruction() in the program.
+ The system can be extended infinitely, albeit somewhat clunkily
- All the code has to be stored in files nothing to do with the job they
  do.   OrderCancelConstruction() should be in Building.h, not spread
  across Order.h and Game.cpp
- Orders are stored after construction but before serialisation, and
  after reconstruction but before handling.  This increases the
  complexity of the code without increasing functionality.

I've been trying to think of an implementation that gets around the
flaws without losing the benefits.  The solution I'm thinking of at the
minute is:

Incoming orders are handled by handler functions, which I will explain
more about later.  When the program starts, different modules register
handlers for the orders they want to handle, and get back handlerID
values:

const handlerID myHandlerID = registerOrder(myHandler);

A handlerID is a unique identifier for the handler function, which can
be safely transmitted over the network.  To ensure handlerIDs are
registered in the same order every time, handlerIDs should be stored in
static members of classes (or failing that, global variables).  The most
likely implementation for registerOrder() is:

std::vector<handler_type> orderHandlers;
typedef std::vectior<handler_type>::size_type handlerID;
const handlerID registerOrder(handler_type h)
{
        orderHandlers.push_back(h);
        return orderHandlers.size()-1;
};

Somewhere, there is an obstringstream (i.e. a Glob2 binary output
stream) holding the set of orders for the current tick:

obstringstream orderStream;

When a function in a class wants to send out an order, it simply writes
the handlerID and associated data to the stream.  Authors are
recommended to make this obvious from the function name:

void myClass::orderSomethingToHappen()
{
        // Do various things

        orderStream
                << myHandlerID
                << title("foo") << foo
                << title("bar") << bar;

        // Do some more things
};

(Note that titles are ignored in binary streams - they'd only be used if
we wanted to produce a text version of the stream during debugging)

This stream represents the serialised order, which can then be magically
transmitted, confirmed, etc.  Later, a tick's worth of orders will need
to be processed:

void handleOrders(ibstringstream& handledOrderStream)
{
        handlerID h;
        while (handledOrderStream)
        {
                handledOrderStream >> h;
                orderHandlers[h](handledOrderStream);
        };
};

Finally, handlers can be defined like this:

void myHandler(ibstringstream& orderStream)
{
        int foo, bar;
        orderStream
                >> title("foo") >> foo
                >> title("bar") >> bar;
        // handle order
};

Assuming all handlers read back the data they wrote out, we can be
certain that the handler will always leave the stream at either the
start of the next handler, or at EOF.

This is just a draft proposal, but what do you all think about it?

        - Andrew




reply via email to

[Prev in Thread] Current Thread [Next in Thread]