Reputation: 7414
I am working on rewriting a MUD based game engine of mine with one of the primary goals being makin it a bit more modular, decoupling the components. One of the things I've hit a wall with is distributing messages.
When a user sets the engine to run as a MMO, then all communication is done through the server. Objects send and receive messages, execute commands and receive data from the environment through their network Socket.
The revised version of the engine will support single player offline games. Are there patterns in place that I can use for routing messages/data through a central location that can redirect as needed between the server or local client? I don't want to litter the engine with conditional statements to check if if is networked or not. I'd rather have a dispatcher of some kind that handles this communication.
If anyone could point me in the right direction to achieve this I'd appreciate it!
edit: I have been working hard at keeping the engine pretty abstract. I make heavy use of the factory pattern, generics and repositories to prevent tight coupling. I'm wanting to continue that approach with the networking aspect. In 90% of the cases, networked communication will be caused by a command sent from the clients telnet client. So handling receiving their commands and handling based on network state is straight forward. The tricky part comes within the game loop where I have to broadcast messages sent from numerous objects back to the client. All network enabled objects implement a IServerObject interface so the server knows what objects it can and can't communicate with. I'm thinking a central dispatching system makes sense but not sure if there is a pattern I can follow to help guide me.
Upvotes: 0
Views: 122
Reputation: 10548
Disclaimer
This is most definitely not the best way of doing it but it is the one that requires the least effort. In my opinion the server should not be aware of it is broadcasting to a single client or multiple remote ones as to the server all clients are remote. You should just simply not accept any connections that aren't sourced locally IMO
/// <summary>
/// A wrapper around a client instance that is interested in
/// the game events. It is observable, and when you observe the
/// client, you can subscribe to the messages we receive from that
/// client.
/// </summary>
public interface IClient : IObservable<IMessage>, IDisposable
{
/// <summary>
/// Send the client a message.
/// The client is responsible for encoding
/// or decoding the message as necessary
/// </summary>
/// <param name="message"></param>
void Send(IMessage message);
}
/// <summary>
/// Some information that can be sent to either a remote
/// client or a local client. This could be as simple as
/// EventArgs, just make sure you can write a decoder/encoder
/// for each of the types. The message types should derive
/// from this interface
/// </summary>
public interface IMessage
{
}
/// <summary>
/// A class that can serialize or deserialize messages
/// </summary>
/// <typeparam name="TSerializedType"></typeparam>
public interface IMessageSerializer<TSerializedType>
{
TSerializedType Encode(IMessage message);
IMessage Decode(TSerializedType serialized);
}
/// <summary>
/// This is a client located at a remote location (i.e,
/// not at this server and we're connecting to it via a Tcp
/// connection)
/// </summary>
public class RemoteClient : IClient
{
private readonly TcpClient _client;
/// <summary>
/// Creates the Remote client.
/// </summary>
/// <param name="client">The underlying .NET connection</param>
/// <param name="serializer">The instance of IMessageSerializer that can
/// serialize or deserialize messages</param>
public RemoteClient(TcpClient client, IMessageSerializer serializer)
{
....
}
public void Send(IMessage message)
{
var serialized = serializer.Encode(message);
client.Write(serialized);
}
}
/// <summary>
/// This is the class you would use if you were connecting
/// to no one and just wanted to route messages back to yourself
/// </summary>
public class LocalClient : IClient
{
/// <summary>
/// Creates the local client
/// </summary>
/// <param name="messageQueue">An observer that is listening for messages.
/// This is our message queue and it should be pointing at someone who
/// is interested in consuming the messages (the game UI for example)</param>
public LocalClient(IObserver<IMessage> messageQueue)
{
}
public void Send(IMessage message)
{
messageQueue.OnNext(message);
}
}
This is similar to the implementation I use in my Minecraft server. You would create a different type of client depending on whether you are interested in sending to remote clients or not. You could one step further and skip the LocalClient
approach and just broadcast to the IObserver<IMessage>
instead.
The intended usage is that you would create a LocalClient
instead of RemoteClient
s and broadcast to that instead, which will push the message you just told the LocalClient
to receive to an IObserver<IMessage>
- which should be the party that consumes events in your server.
You would change between LocalClient
and RemoteClient
by creating an IClientFactory
, then deriving that to create a LocalClientFactory
and RemoteClientFactory
(omitted for brevity) and use dependency injection to alter what kind of client factory you use.
Upvotes: 1