Just a hint, I'm a newb. I may be an older and somewhat educated newb, but still a newb so read what I place here with the understanding that I may have no clue what I'm talking about. :p
I'm currently working on a hobby mog client/server.
Its just for learning purposes and not meant to be a fullscale production game.
The client and server are written in C++ and share code (you compile with either #define is_server or #define is_client to build the seperate binaries).
I originally had a working UDP prototype but due to it being blocked by either ATT or my VPS provider I switched to TCP so that I could actually test it over the internet. I coded it in a way so that the TCP parts are encapsulated in an object that can be replaced with a UDP equiv with little hassle.
The network logic makes no assumptions of in-order packet arrival or fragmentation and in fact will multiplex multiple RPC/actions at the same time.
Anyways...
I'm going to describe two important parts:
Data: everything is a record- actually everything inherits from the record class. It doesn't matter what it is- a human character or even a lump of coal in inventory is a record (well actually an object that inherits from record). Basically all records are stored and retrieved using the same logic. When records are loaded there is a collection of record factories that are selected by record type so that the correct record is generated for each blob in the file(s) (or database in the future).
Each record has a record state. Record state is just using the state pattern to encapsulate stuff like monster AI into a hot-swappable object contained within the record. Basically each state will decide which next state to generate and return when that state is ran. This also makes handling game data updates simpler too since the same logic is used regardless of the record type to send the updates to the clients. Also, the client has the exact same code so it saves me from having to duplicate data handling code on the client and server. There are basically only a few differences between client and server. The client side code will have saving updates to disk disabled and it won't broadcast changes to data to the server.
Sessions: the session code is basically the same for the client and server. The client typically only runs one session at a time and sessions pass packets to the corresponding session at the other end of the network. The client has a few things disabled or blocked like data update pushes or attaching sessions to records but the entire session code base is used verbatim on client and server. Sessions contain a list of action receivers and action senders and handle login state.
RPC/Actions: This is where the work gets done. This can best be explained by an example. Lets say that I created an account from the client.
The client will take the input from the GUI and allocate a account_create_sender_action object. This action object will send the first packet to the server. The server will read the packet and get the action type (first it tests to see if this action was already created by comparing id with action id in packet) and find and evoke the correct action receiver factory. The action receiver is then given the packet and performs the work of creating the account. The action sender on the client may persist in cases where the action requires multiple packets of communication between sender and receiver or it can simply ask for itself to be deleted immediately depending on the action. In the case of creating an account, the receiver sends a sucess or fail packet back to the sender (client matches by action id- action id is the same on the server and client for each sender+receiver pair).
The account action receiver then also attaches the involved session to the account record for reasons that will be explained below.
Basically packets are sent and received between action objects. Game logic does not handle any packets directly and everything happens by creating an action sender. The action receiver is just what accepts the incoming packets from the sender (despite the names, both senders and receivers can send and receive packets and both may exist on server and client). Incoming packets are routed directly to the action receiver by action id if exists, or an action receiver is created by matching a factory on action type and then passing in the packet. Action senders are what is created by game logic to initiate the network conversation to evoke the action receiver to perform the necessary action.
Pushing updates to clients: After a data change occurs for any reason, the clients need to be informed. This is acheived by "attaching" sessions to records. When a session is attached, that record will check each session to see when it was last pushed and push a new update after each data change in that record. The record itself actually determines how sessions may be attached. An NPC fox will attach sessions based on proximity to its location. Also, sessions may attach themselves to records like when a player teleports to a new location on the map and many new objects become in close proximity. Some records are attached for other reasons like the account record is attached when a session logs in so that the client will see the account data. Each record object contains a list of record_session objects which handle maintain the attachment and initiating the data push by creating the data_sender_action on that session. Different attachment behaviors exist like permanent, timed (detach after x seconds), range(detach after distance between record's world location and session's location becomes larger than a supplied value) are handled by having different flavors record_session objects that inherit from the abstract one. The sessions don't handle data updates with the clients, the records do (well actually, the record_sessions create the sender action and bla bla bla).
Some of the reasons I went this route are:
I found myself writing the same code over and over for updates to clients and loading and saving data to disk on the server for all of the different entities- now every entity is treated the same in code but have overridden "doProcessing()", "attachSessions()", and "genFirstState()" methods.
I found myself writing duplicate code for the client and server for all entities. Now basically the client and server have the exact same code with a few methods overriden here and there in about 3 classes. There are "client" and "server" objects that override the "session_processor" object basically.
Encapsulating the work inside of "action" objects makes the code cleaner. I forgot to mention above that actions also have states and these state objects actually process and send packets. You override the base action class to override the method called by the default state to create the first state (well, technically second ). Basically everything that isn't AI happens via action objects but even the AI will trigger data updates which invoke actions to push data to the clients.
The actual client side rendering code is not complete. Everything is being tested by a primitive command line I baked up.
The client will eventually have a layer that requests different record types and moves around objects in a rendering layer/scene graph and updates the UI. I wanted to build it from the ground up and get the most important parts working first. If the client/server layer sucks nobody will care about how snazzy your graphics are. I have had one primitive functioning MOG running several years back (way way back) but it was created when I was even more naive than I pretend not to be now.
So basically this is the route I am pursuing. Does it sound like a good idea?
Any constructive criticism is appreciated!
Thanks.