Showing posts with label Photon. Show all posts
Showing posts with label Photon. Show all posts

Thursday, June 16, 2011

Foray into Photon - Part 6 Strategy/State Pattern

So far I've really only been covering the code and how to build up to where the demo is. I'm going to take a step back to my original post and discuss a few things on why they were done.

As I stated toward the end of my post on SmartFoxServer, there are a few patterns or philosophies I try to adhere to. One of them is to keep only what a class needs to do in the class that does it. The folks at Exit Games follow this same philosophy. They have an Operator class which only contains the data that operations need. The same holds for Events. The only place you will find a send is in the PhotonPeer and the only place you get events is in the PhotonPeerListener.

Would you be able to agree with me if I told you that we want to do the same with how we handle messages? I hope so. In the demo on the client portion you will find a series of classes in the GameStateStrategies. They all base themselves off of the IGameLogicStrategy. This class follows the Design Patterns called State and Strategy. If you are serious into programming and want to make headway into being a better programmer I would recommend you pick up Design Patterns: Elements of Reusable Object-Oriented Software. This book shows you 23 design patterns you will frequently find in programming and one of these is the Strategy pattern.

The book defines the state pattern like this: "Allow an object to alter its behavior when its internal state changes. The object will appear to change its class." Lets take a look at it in terms of how Exit Games used it. The object that appears to change its class is the Game.cs class. You have several behaviors that all do the same thing: Disconnected, WaitingForConnect, Connected, and WorldEntered. It appears to change its class because based on which state its in, is how the game reacts when it gets an operation response or an event, checks its status in the call back, or sends an operation. In disconnected or waiting for connect it doesn't send operations, and it reports errors when it receives events and operationresponses. But connected is able to listen for a few things like when the world is entered and the worldEntered state is there for when they are totally in the game.

So lets think about our what we did in our SmartFoxServer program. We would have several states. Disconnected for when they haven't clicked login. WaitingForConnect would be used when they have hit the login button but we aren't talking to the server. CharacterSelect would be used when they are at the character select screen. CharacterCreate would be used when they are on the character creation screen. Lastly, WorldEntered would be used when they have selected a character and are fully in the game. Each of these states would handle only specific messages and would report all others as errors. So when you are on world entered you would report an error if you got the operation stating that you are creating a character. Also you couldn't send an operation to perform a craft while waiting for the connection.

Next time we will create a set of classes: IGameState that will contain the same as the IGameLogicStrategy. Then we will set up the barebones for these states and modify our game to use these states.

Friday, June 10, 2011

Foray into Photon - Part 5 Operation Dispatcher

So Photon has some really nice features built into it for us to use. They have pre-built encryption, the Operation Dispatcher, and Operations and Events to base our code off of. This segment we will be covering the Operation Dispatcher. Its job is to use reflection to determine what functions get which operations. This reduces the amount of work compared to what we had to do with SmartFoxServer. At first it seems like the same thing. SmartFoxServer allows us to add handlers which then pass us the user and the datapacket. This is exactly the same thing Photon does except that we only have to add the class that holds each of the handler functions instead of adding a new handlers for each message. Lets take a look. All of this code will be going into AegisBornPeer. First we add 2 new properties to our class:


        private static readonly OperationMethodInfoCache Operations = new OperationMethodInfoCache();


        private readonly OperationDispatcher _dispatcher;

The cache provides all the function lookup data and the dispatcher makes the calls and passes the information necessary.

Next we need to add a static constructor that will instantiate our cache and add all the functions that belong to each class that will handle our operations:

        static AegisBornPeer()
        {
            Operations = new OperationMethodInfoCache();

            try
            {
                Operations.RegisterOperations(typeof(AegisBornPeer));
            }
            catch (Exception e)
            {
                ErrorHandler.OnUnexpectedException(Operations, e);
            }
        }

The cool thing about this setup is that you can create handlers in groups. I can create a class called CraftingHandler and put all crafting operations in it. We'll have to explore this option later.

Next we will add a the dispatcher creation code to the AegisBornPeer(PhotonPeer) constructor immediately above the SetCurrentOperationHandler call:

            _dispatcher = new OperationDispatcher(Operations, this);

The last thing we need to do is call the dispatcher when we receive an Operation. So drop down to our "We get signal" call and add the following above it:

            OperationResponse result;
            if (_dispatcher.DispatchOperationRequest(peer, operationRequest, out result))
            {
                return result;
            }

What this does is attempt to find a function that has our operation code to handle the operation. if it doesn't find it, it will call our "We get signal" return. If it does handle it, it returns the result of the function call. Thats all there is to it. Now we can start adding operations and handling them without adding much more code.

So that's it for this segment. The next segment we are going to take a look at how the demo handles connection states. This is a piece of the client to handle specific things and will lead us into the server version of the same thing.

Thursday, June 9, 2011

Foray into Photon - Part 1

I would really recommend you watch the YouTube video Server Setup Part 1 and watch the first 2 minutes as it shows you how to install photon, create a server project in the deploy folder, add references and create the application class. You can watch the rest of the video as it does walk you through creating a console client and connecting to the server, but we are using unity and so we will do things a bit differently.

I'm going to assume you have created a project folder under Photon/deploy and created your project inside that folder along with adding the references and renaming the class. For ease of use, I'm still calling my application AegisBorn so update any namespaces or class names to be your Game's class names. I will keep as much of it as generic as possible.

So we have an empty class. Lets start by inheriting from Application. Don't forget that we need to add the using statement for the Application class. After that you can click on Application, go to the little box that appears under the A and hit "implement abstract class Application" and you will see three functions appear, CreatePeer, Setup, and TearDown. Delete the 3 NotImplementedException calls. In Create peer we want to create our Peer object. The following code should be your entire class.


using System;
using Photon.SocketServer;


namespace AegisBorn
{
    public class AegisBornApplication : Application
    {
        protected override IPeer CreatePeer(PhotonPeer photonPeer, InitRequest initRequest)
        {
            return new AegisBornPeer(photonPeer);
        }


        protected override void Setup()
        {
           
        }


        protected override void TearDown()
        {
            
        }
    }
}

And with that our Application is complete. Next we need to implement our Peer so go ahead and create a new class file in your project and call it GameNamePeer.

This class is our connection to our client, much like our User reference in SmartFoxServer. It contains the functions necessary to pass events back to the client and get operations from the client to have the server process.

We need to inherit from Peer and IOperationHandler. Then you can do like we did before and implement interface IOperationHandler. Again remove the NotImplementedExceptions. From here we will borrow the code that the MMO Demo uses for these functions:

using Photon.SocketServer;
using Photon.SocketServer.Rpc;

namespace AegisBorn
{
    class AegisBornPeer : Peer, IOperationHandler
    {
        public AegisBornPeer(PhotonPeer photonPeer)
            : base(photonPeer)
        {
            this.SetCurrentOperationHandler(this);
        }

        public void OnDisconnect(Peer peer)
        {
            this.SetCurrentOperationHandler(OperationHandlerDisconnected.Instance);
            this.Dispose();
        }

        public void OnDisconnectByOtherPeer(Peer peer)
        {
            peer.ResponseQueue.EnqueueAction(() => peer.RequestQueue.EnqueueAction(() => peer.PhotonPeer.Disconnect()));
        }

        public OperationResponse OnOperationRequest(Peer peer, OperationRequest operationRequest)
        {
            return new OperationResponse(operationRequest, 0, "Ok");
        }
    }
}

Now I'll explain each function. The constructor tells the application that this class will handle all operations.

OnDisconnect sets our handler to be an instance of the disconnected handler. This is done so that if we still receive operations from the client, they will be logged and errors reported back to the client saying they are disconnected. You might be wondering why the server would need to tell us that and it is because we are using UDP which doesn't have a live connection like TCP so we could still send messages even if we are supposed to be disconnected among other things that UDP gives us.

OnDisconnectByOtherPeer is called when another client has said this client needs to be disconnected. this could be done by an admin running on the same server. It queues up an action that causes the peer to disconnect itself.

OnOperationRequest is the function that does everything. Any time the client sends an operation, this function gets called and decides what should be done with that operation. For now we are doing nothing more than sending a message back stating that we received the operation and moves along.

From here you can build the project and then watch Starting Photon to see how you start Photon. The last thing you will need to do is modify the PhotonServer.config and add the application as a server application.

You can follow along with Server Setup Part 1 from the 12:40 mark and they will show you what to modify. For reference, my setup is as follows:



<Applications Default="AegisBorn">
<!-- Lite Application -->
<Application
Name="AegisBorn"
BaseDirectory="AegisBorn"
Assembly="AegisBorn"
Type="AegisBorn.AegisBornApplication"
EnableAutoRestart="true"
WatchFiles="dll;config"
ExcludeFiles="log4net.config">
</Application>
</Applications>



With all this in place, you can build your project and go to the Photon Control and start the server. Thats all we need to do server side.

Foray into Photon - Outline

So I took the last several days to take a break from SmartFoxServer 2x and decided to have a look at the Photon Server built by Exit Games.

I had previously downloaded the server and client code and pulled it up, looked through the files, and decided to put it back down again. I decided to pull it back up and try and break it back down like I did with SmartFoxServer. I did this with the MMO demo that was put together. It takes some time to figure out exactly what is going on and so I have decided to rebuild their demo from scratch in an effort to explain how things work and why the designs were chosen. Feel free to follow along for the next several posts while we proceed through the following steps:


  1. Build the blank server and simple client
  2. Build a basic operation and operation handlers (only because I hate seeing 15 functions in a class that a switch statement calls based on a message type)
  3. Send events and build an event handler 
  4. Build a State class that will handle only the Events applicable to the client based on its current state.
  5. Setup NHibernate so we can build models and effectively access a database.
This is just a rough outline of what I'd like to accomplish over the coming weeks. Stay tuned as we begin our journey.