You are here:   Blog
Register   |  Login

LightSwitch News

Oct 29

Written by: Richard Waddell
10/29/2013 4:00 PM  RssIcon

Part I – Bare Bones Mechanics

Part II – Basic Classes and Interface Implementations

Part III – Game to Demonstrate Derived Classes and more Sophisticated Interface Implementations

The End Result

The end result is not what I set out to do. I wanted to write games, but what I ended up with was so brittle it evolved into a game framework which I hope can be used to create all sorts of games.

Here’s a screen shot of three users logged into the same page and all seeing the same thing but all able to interact with it individually. The intent of the framework is that you easily can plug in your own behavior and rules to control what comes out of that basic functionality.

image

Try It Out

You can try the game online at http://sinalrgame1.lightswitchhelpwebsite.com/HTMLClient/

Adding Classes and Interfaces / Dependency Injection

Wow, this is brutal. I had no intention of writing about dependency injection. In the first post I felt I had to cut everything to the bone in order to demonstrate SignalR without obscuring the details, but the result is too simple to be very compelling. So by the time you read this, there should be three posts. Hopefully the third will be interesting enough to send the reader back to the first one to find out how it’s all put together.

The attraction of programming for ooNaWorld lies in being able to try new ideas for how the various subtypes of ooNaThing behave. So the problem here at the beginning of the second post is if I just derive some ooNaThing subtype and then change a bunch of code to do whatever with it, then I’m going to have to do it all over again for the next subtype and every other variation that comes up, whether in modifying an existing ooNa-based application, or writing a different one.

To avoid that I need dependency injection, and Michael thinks I need to explain what it is and how how I go about it, so here we are.

Multiple Subtypes

There’s more to dependency injection than just supporting multiple subtypes, but it’s a good place to start.

image

Deriving From ooNaThing

The ooNaThing Class gives us the base properties required for nearly any thing that might exist in ooNaWorld.

image

Enough information to draw an ooNaThing on the surface of ooNaWorld, but nothing else. When we add derived classes that demand more we’ll see the shortcomings of the current design.

An obvious property would be Score for an ooNaThing that is a player in a game.

image

The new Class is shown below.

namespace ooNaClasses
{
    public class ooNaGameThing : ooNaThing
    {
        public int Score { get; set; }
        public ooNaGameThing(   string creator, int xLocation, int yLocation, 
                                int height, int width, 
                                string color, string shape) :
            base(   creator, xLocation, yLocation, 
                    height, width, 
                    color, shape)
        {
            Score = 0;
        }
    }
}

The score will be incremented every time the ooNaThing moves.

This will require changes on the Server:

image

Shown below;

// Return a new ooNaThing initialized with the requested values
public ooNaThing CreateOoNaThing
    (string userId, Double xCoordinate, Double yCoordinate, string color, string shape)
{
    int xPoint = Convert.ToInt32(xCoordinate + 0.5);
    int yPoint = Convert.ToInt32(yCoordinate + 0.5);
    // Return an ooNaGameThing
    ooNaThing newThing = new ooNaGameThing(userId, xPoint, yPoint, 30, 30, color, shape);
    _ooNaThings.Add(newThing);
    return newThing;
}
// Return an ooNaThing with its location changed to the requested values
public ooNaThing MoveOoNaThing
    (string userId, Double xCoordinate, Double yCoordinate)
{
    int xPoint = Convert.ToInt32(xCoordinate + 0.5);
    int yPoint = Convert.ToInt32(yCoordinate + 0.5);
    var theThing = _ooNaThings.Where(o => o.Creator == userId).FirstOrDefault();
    if (theThing != null)
    {
        theThing.PreviousLocation.x = theThing.Location.x;
        theThing.PreviousLocation.y = theThing.Location.y;
        theThing.Location.x = xPoint;
        theThing.Location.y = yPoint;
        // If it's an ooNaGameThing, increment the score
        if (theThing is ooNaGameThing)
            ((ooNaGameThing)theThing).Score++;
    }
    return theThing;
}

To display the score we’ll just draw it on the ooNaThing itself.

// Common drawing function. All ooNaThing properties can be specified differently
// from actual ooNaThing except for shape. 
function drawOoNaShape(ooNaThing, color, xCoordinate, yCoordinate, width, height) {
    // Avoiding mysterious unnamed arguments
    var ENDING_ANGLE_OF_FULL_CIRCLE = 2 * Math.PI;
    var STARTING_ANGLE_OF_FULL_CIRCLE = 0;
    var context = _ooNaWorldCanvas.getContext("2d");
    context.beginPath();
    context.fillStyle = color;
    switch (ooNaThing.Shape) {
        case "Circle":
            context.arc(    xCoordinate, yCoordinate, // location
                            width / 2, // radius
                            STARTING_ANGLE_OF_FULL_CIRCLE,
                            ENDING_ANGLE_OF_FULL_CIRCLE);
            context.fill();
            // Hack to avoid drawing the score if this is an erasure
            if (color != _ooNaWorldColor) {
                context.font = "20px Arial";
                context.fillStyle = "Yellow";
                context.fillText("" + ooNaThing.Score, xCoordinate - 5, yCoordinate + 5);
            }
            break;
        default:
            alert("Unknown Shape");
            break;
    }
    context.closePath();
}

The result:

image

Now the other change that should have been required is that ooBerHub has to cast ooNaThing to an ooNaGameThing when shipping it back to the Client for updating the View, but as it turns out the new Score property is visible on the client anyway.  In Part III we’ll be adding core classes that specifically strip out any properties that are not required to update the View, so the casting question becomes moot.

But let’s get back to what’s wrong with this approach to supporting the ooNaGameThing Class, which is, if any other classes are added then the code has to be changed again.

Dependency Injection – What Is It?

I went to Wikipedia to see what the rest of the world thinks Dependency Injection is.  It’s a tough read (Inversion Of Control is even tougher); inevitable in technical explanations. But both were helpful. They convinced me that I wasn’t going to try and put it in my own words because I’d overlook or misstate something in the attempt.

Dependency Injection, I may not have the courage to think I can write a readable and accurate explanation, but I know it when I see it, and I can give examples.

I’ve also been advised by someone I respect deeply that I need charts and diagrams; I objected that there were too many ways to do it, but I have now been browbeaten into it. Smile I’ll provide diagrams for each type as we come to them in the project… well, maybe not all of them.

Now that we’ve seen the bad way to support a new subtype, Here’s what I consider the important part of what I consider to be dependency injection and how it relates to the code we’ve seen so far.

image

Dependency Injection – Deciding What to Create

In the bullet list above, I said that CreateOoNaThing’s caller passes in a method that creates the desired ooNaThing subtype. That’s made possible by delegates.

image

This is illustrated below:

image

Here’s the ooNaThingVariantCreator delegate:

public delegate ooNaThing ooNaThingVariantCreator
    (string creator, Double xLocation, Double yLocation,
        int height, int width,
        string color, string shape);

To create a generic ooNaThing, we can create the CreateGenericOoNaThing method, although we can call it anything we want:

private ooNaThing CreateGenericOoNaThing
        (   string userId, Double xCoordinate, Double yCoordinate, 
            int height, int width, 
            string color, string shape)
{
    int xPoint = Convert.ToInt32(xCoordinate + 0.5);
    int yPoint = Convert.ToInt32(yCoordinate + 0.5);
    return new ooNaThing(userId, xPoint, yPoint, height, width, color, shape);
}

And to create an ooNaGameThing:

private ooNaGameThing CreateGameThing
    (string userId, Double xCoordinate, Double yCoordinate,
        int height, int width,
        string color, string shape)
{
    int xPoint = Convert.ToInt32(xCoordinate + 0.5);
    int yPoint = Convert.ToInt32(yCoordinate + 0.5);
    return new ooNaGameThing(userId, xPoint, yPoint, height, width, color, shape);
}

I’ll pass one of these methods in as a CreateOoNaThing arguments, namely thingCreator, so it can be called at the point where ooBer is currently calling new ooNaThing() or new ooNaGameThing() depending on which way it’s hard-coded.

// Create a new ooNaThing, initialized with the requested values.
// Add it to the Model and return it to the caller
public ooNaThing CreateOoNaThing
    (   ooNaThingVariantCreator thingCreator, string userId, 
        Double xCoordinate, Double yCoordinate, 
        string color, string shape)
{
    int xPoint = Convert.ToInt32(xCoordinate + 0.5);
    int yPoint = Convert.ToInt32(yCoordinate + 0.5);
    // Call a method passed from the caller to create the new ooNaThing
    ooNaThing newThing = thingCreator(userId, xPoint, yPoint, 30, 30, color, shape);
    _ooNaThings.Add(newThing);
    return newThing;
}

If we pass in the CreateGameThing method everything should still work:

// This method is called by a client to create an ooNaThing at the requested coordinates.
public void createDefaultOoNaThing(Double xCoordinate, Double yCoordinate, string shape)
{
...
    // Get the new ooNaThing from ooBer
    ooNaThing newThing = _ooBer.CreateOoNaThing
        (   CreateGameThing, Context.ConnectionId, 
            xCoordinate, yCoordinate, 
            color, shape);
...
}

And it does:

image

Let’s see what happens if we pass in the CreateGenericOoNaThing method

// This method is called by a client to create an ooNaThing at the requested coordinates.
public void createDefaultOoNaThing(Double xCoordinate, Double yCoordinate, string shape)
{
...
    // Get the new ooNaThing from ooBer
    ooNaThing newThing = _ooBer.CreateOoNaThing
        (   CreateGenericOoNaThing, Context.ConnectionId, 
            xCoordinate, yCoordinate, 
            color, shape);
...
}

Instead of a score we see the string “undefined” where the score would be:

image

Dependency Injection – Deciding What to Do

In the Dependency Injection bullet list above, I said that ooNaThing provides a virtual method to carry out the move.

image

This is so fundamental to object oriented programming that it seems strange to call it dependency injection. I just sort of backed into it.

We start by adding a virtual method to ooNaThing that allows it to move itself:

public virtual void Move(int xLocation, int yLocation)
{
    PreviousLocation.x = Location.x;
    PreviousLocation.y = Location.y;
    Location.x = xLocation;
    Location.y = yLocation;
}

Now ooBer can move the ooNaThing by calling that method:

// Return an ooNaThing with its location changed to the requested values
public ooNaThing MoveOoNaThing
    (string userId, Double xCoordinate, Double yCoordinate)
{
    int xPoint = Convert.ToInt32(xCoordinate + 0.5);
    int yPoint = Convert.ToInt32(yCoordinate + 0.5);
    var theThing = _ooNaThings.
                        Where(o => o.Creator == userId).
                        FirstOrDefault();
    if (theThing != null)
    {
        theThing.Move(xPoint, yPoint);
        // If it's an ooNaGameThing, increment the score
        if (theThing is ooNaGameThing)
            ((ooNaGameThing)theThing).Score++;
    }
    return theThing;
}

Next we override the Move method in ooNaGameThing to update the score:

public override void Move(int xLocation, int yLocation)
{
    base.Move(xLocation, yLocation);
    Score++;
}

And now ooBer doesn’t have to care:

// Return an ooNaThing with its location changed to the requested values
public ooNaThing MoveOoNaThing
    (string userId, Double xCoordinate, Double yCoordinate)
{
    int xPoint = Convert.ToInt32(xCoordinate + 0.5);
    int yPoint = Convert.ToInt32(yCoordinate + 0.5);
    var theThing = _ooNaThings.
                        Where(o => o.Creator == userId).
                        FirstOrDefault();
    if (theThing != null)
        theThing.Move(xPoint, yPoint);
    return theThing;
}

ooBer And The Distributed Mind

As more decisions are removed from ooBer’s responsibility, the ooBer class per se will become simpler and simpler until finally It does nothing more than shove ooNaThings in and out of the Model and return them to the caller. It’s pretty close to that now.

But that’s an implementation detail. In the “real'” world, an omnipotent ooBer could deal an infinite number of decisions on how to deal with each denizen of ooNaWorld according to its type and circumstances.

But in the real real world, as we’ve seen, we need to eliminate as many decisions as possible, not just to deal with complexity and code changes, but for performance.

So, in practice, the mind of ooBer is comprised of the ooBer class and classes derived from ooBerLink and ooNaThing:

image

 

Deciding on The Decider

Need To Know – The View

image

There is no concept of View or Model. Just the code necessary to carry out a narrow range of operations. On the Client an ooNaThing is just an object with properties that describe what to draw on the canvas. It could be called anything. It’s just convenient to call it ooNaThing on both Client and Server.

Let’s start by eliminating the call to ooBerHub.createDefaultOoNaThing(). Instead the client will call canvasReady and pass in the dimensions of the canvas.

// Starts the hub and attaches a function to be run when start is complete
function startHubConnection() {
    $.connection.hub.start()
    .done(function () {
        // We can now make calls to the server, so create the ooNaThing
        var canvasRect = _ooNaWorldCanvas.getBoundingClientRect();
        $.connection.ooBerHub.server.canvasReady(canvasRect.width, canvasRect.height);
    })
    .fail(function () {
        alert("Could not Connect! - ensure EnableCrossDomain = true");
    });
}

The decision to create a new ooNaThing in response to the mouse click is now made on the Server.

public void canvasReady(Double canvasWidth, Double canvasHeight)
{
    string color = _ooNaThingColorList[_colorIndex++];
    if (_colorIndex > _colorMax)
        _colorIndex = 0;
    ooNaThing newThing = _ooBer.CreateOoNaThing
        (CreateGameThing, Context.ConnectionId,
            rand.Next(20, Convert.ToInt32(canvasWidth) - 20), rand.Next(20, Convert.ToInt32(canvasHeight) - 20),
            color, "Circle");
    Clients.All.newOoNaThing(newThing);
    // Send a message to the caller
    Clients.Caller.
        notify(string.Format("{0}: Your ooNaThing is {1}", DateTime.Now, newThing.Color));
}

Now the client truly just sends Event notifications and provides an API for the Server to use in updating the View.

Need to Know – The Hub

I’m ok with the names of the two methods on ooBerHub that the Client can call, canvasReady and leftMouseClick. They don’t give anything away.

But the calls to the client in response are another thing. Why should ooBerHub know, for instance, that a leftMouseClick should result in the ooNaThing being moved to that location? If it ever means anything else, ooBerHub will have to be modified. All in all, ooBerHub is kind of a hodge-podge.

Let’s go back to the diagram to see what I mean.

image

ooBerHub is taking calls from the Client. Deciding what each means for the Model, calling ooBer to change the Model, and finally calling functions on the Client to update the View.

Here’s a block diagram that relieves ooBerHub of all but one of those responsibilities.

image

Taking it step by step.

  1. We previously got rid of the ability of the Client to make requests of ooBerHub; other than that the relation between Client and ooBerHub is unchanged.
  2. ooBerHub is relieved of the responsibility for deciding the implications of the Client call. It’s just passed on to ooBerLink. ooBerHub is only responsible for handling communication, which it does through its inheritance from the SignalR Hub Class.
  3. ooBerLink decides how the event changes the Model and calls ooBer to make the change.
  4. ooBer returns the model changes in the form of an ooNaThing object.
  5. ooBerLink passes that ooNaThing object to an IooNaView method appropriate to the change to the Model, such as NewOoNaThing or MoveOoNaThing. ooBerLink is aware that the View is being updated, but is unconcerned with the details.
  6. IooNaView makes the appropriate call to Client methods to update the View. In this case IooNaView will be implemented by an instance of the ooNaSignalRView Class, which has the ability to accept an object from the Hub that will allow it to call the Client functions directly. Without this capability it would be difficult to remove the Client calls from ooBerHub, since the Hub is required to make them.

The “Model Object” is an ooNaThing object that has properties reflecting changes to the model caused by that particular ooNaThing. This provides enough information to draw the appropriate changes on the View.

You might object here that it would make more sense to just call one method on the Client that figures out the details of how to update the View. I’ll remind you that instead of methods on the Client we have JavaScript functions, and the  objective is to use a little JavaScript code as possible. A dispatcher that interprets the model object and decides which local functions to call – no, uh-uh.

Let’s start by just putting ooBerLink in the middle.

namespace ooNaClasses
{
    public class ooBerLink
    {
        protected readonly ooBer _ooBer;
        public ooBerLink()
        {
            _ooBer = ooBer.Instance;
        }
        public virtual ooNaThing CanvasReady
            (string creator, Double canvasWidth, Double canvasHeight, string color)
        {
            // Get the new ooNaThing from ooBer
            ooNaThing newThing = _ooBer.CreateOoNaThing
                (CreateGenericOoNaThing, creator,
                    canvasWidth / 2, canvasHeight / 2,
                    color, "Circle");
            return newThing;
        }
        private ooNaThing CreateGenericOoNaThing
                (string userId, Double xCoordinate, Double yCoordinate,
                    int height, int width,
                    string color, string shape)
        {
            int xPoint = Convert.ToInt32(xCoordinate + 0.5);
            int yPoint = Convert.ToInt32(yCoordinate + 0.5);
            return new ooNaThing(userId, xPoint, yPoint, height, width, color, shape);
        }
    }
}

ooBerHub now calls ooBerLink.CanvasReady but otherwise is unchanged

public void canvasReady(Double canvasWidth, Double canvasHeight)
{
    // Assign the next color
    string color = _ooNaThingColorList[_colorIndex++];
    if (_colorIndex > _colorMax)
        _colorIndex = 0;
    ooNaThing newThing = _ooBerLink.CanvasReady(Context.ConnectionId, canvasWidth, canvasHeight, color);
    Clients.All.newOoNaThing(newThing);
    // Send a message to the caller
    Clients.Caller.notify(string.Format("{0}: Your ooNaThing is {1}", DateTime.Now, newThing.Color));
}

That’s half the battle. ooBerHub now calls a method on ooBerLink that is event-driven – “There is a new Client” rather than request-driven “The Client wants an ooNaThing”.  On the Client the call to canvasReady and the resulting callback to newOoNaThing are now decoupled. To do that at the ooBerHub level, we have to remove the response to the client from the class that receives the call from the client.

Single Responsibility Principle / Separation of Concerns – What’s In A Name?

The concern of ooBerHub should be to receive calls from the client and make calls to ooBerLink. That’s two things, but one concern – to process calls from the client. I’m going to decouple the same things we decoupled on the client, the event and the response to the event.

But how are we going to make calls to the client without going through the hub? Turns out it’s not that difficult. We’re going to allow ooBerLink to make the calls by passing a reference to IHubConnectionContext to the constructor. Here’s the new version of ooBerLink:

namespace ooNaClasses
{
    public class ooBerLink
    {
        protected readonly ooBer _ooBer;
        private IHubConnectionContext _Clients;
        public ooBerLink(IHubConnectionContext hubContext)
        {
            _ooBer = ooBer.Instance;
            _Clients = hubContext;
        }
        // Easy way to assign colors for bare-bones demo, not thread-safe however
        private static List<string> _ooNaThingColorList = 
            new List<string>() { "Red", "Green", "Pink", "Blue", "Orange", "Black" };
        private static int _colorMax = _ooNaThingColorList.Count() - 1;
        private static int _colorIndex = 0;
        private static Random rand = new Random();
        public virtual void NewOoNaWorldView
            (string creator, Double canvasWidth, Double canvasHeight)
        {
            // Assign the next color
            string color = _ooNaThingColorList[_colorIndex++];
            if (_colorIndex > _colorMax)
                _colorIndex = 0;
            // Get the new ooNaThing from ooBer
            ooNaThing newThing = _ooBer.CreateOoNaThing
                (   CreateGameThing, creator,
                    rand.Next(20, Convert.ToInt32(canvasWidth) - 20), rand.Next(20, Convert.ToInt32(canvasHeight) - 20),
                    color, "Circle");
            _Clients.All.newOoNaThing(newThing);
            // Send a message to the caller
            _Clients.Client(creator).
                notify(string.Format("{0}: Your ooNaThing is {1}", DateTime.Now, newThing.Color));
        }
        public virtual void LeftMouseClick(string creator, Double xCoordinate, Double yCoordinate)
        {
            ooNaThing movedThing = _ooBer.MoveOoNaThing(creator, xCoordinate, yCoordinate);
            _Clients.All.moveOoNaThing(movedThing);
        }
        private ooNaThing CreateGenericOoNaThing
                (string userId, Double xCoordinate, Double yCoordinate,
                    int height, int width,
                    string color, string shape)
        {
            int xPoint = Convert.ToInt32(xCoordinate + 0.5);
            int yPoint = Convert.ToInt32(yCoordinate + 0.5);
            return new ooNaThing(userId, xPoint, yPoint, height, width, color, shape);
        }
        private ooNaThing CreateGameThing
            (string userId, Double xCoordinate, Double yCoordinate,
                int height, int width,
                string color, string shape)
        {
            int xPoint = Convert.ToInt32(xCoordinate + 0.5);
            int yPoint = Convert.ToInt32(yCoordinate + 0.5);
            return new ooNaGameThing(userId, xPoint, yPoint, height, width, color, shape);
        }
    }
}

Which reduces ooBerHub to:

namespace LightSwitchApplication
{
    public class ooBerHub : Hub
    {
        private readonly ooBerLink _ooBerLink;
        // Constructor initializes the _ooBer reference to point to the ooBer singleton object
        public ooBerHub() 
        {
            _ooBerLink = new ooBerLink(GlobalHost.ConnectionManager.GetHubContext<ooBerHub>().Clients);
        }
        public void newOoNaWorldView(Double canvasWidth, Double canvasHeight)
        {
            _ooBerLink.NewOoNaWorldView(Context.ConnectionId, canvasWidth, canvasHeight);
        }
        public void leftMouseClick(Double xCoordinate, Double yCoordinate)
        {
            _ooBerLink.LeftMouseClick(Context.ConnectionId, xCoordinate, yCoordinate);
        }
    }
}

What about Dependency Injection?

ooBerLink is dependent on ooBerHub to provide methods that ooBerLink can call on the Client. But why do it this way?

If ooBerLink knows what effect each Client event has on the View, ooBerLink should also be responsible for calling the appropriate Client function to update the View accordingly. For instance if ooBerLink decides that left mouse click means the ooNaThing owned by the user who made the click should be moved to that location, then ooBerLink also decides that moveOoNaThing is the Client function to call to update the View.

ooBerHub satisfies the dependency by supplying an IHubConnectionContext object to the ooBerLink constructor. What are the practical implications of this and more specifically do we want ooBerLink to be dependent on ooBerHub?

We need ooBerHub to supply appropriately named methods to be called by the Client and we need the associated IHubConnectionContext if we want to make calls to functions on the Clients. So yes, we do want ooBerLink to be dependent on ooBerHub (technically it’s whatever class calls the ooBerLink constructor) to supply an IHubConnectionContext object implementation. The simplest approach then would be to have ooBerHub create the ooBerLink instance, and just modify each new copy of ooBerHub to creates an ooNaLink subtype appropriate to the ooNaWorld variant you’re creating for that particular ooNa-based application.

If you want to get more indirect than that then you could inject an ooNaLink subtype in ooBerHub, whether through delegate reference, constructor argument, or public property. ooBerHub would set the IHubConnectionContext property on that ooNaLink object instead of passing it as a constructor argument to an ooNaLink it creates itself. Or it could pass it to a delegate reference to be used in the ooNaLink constructor if you went that route. The combinations are not endless, but too many to discuss each one.

What about Separation of Concerns?

Now ooBerLink has two concerns

  1. Receive an event notification from ooBerHub and call the appropriate ooBer method.
  2. Use the model object returned from ooBer to update the View

It doesn’t bother me that ooBerLink knows it has to call specific functions to update the View. In our pattern, the Client provides an API of functions that ooBerLink calls to make the appropriate changes to the View, so decoupling the cause from the effect would actually make the code harder to maintain. I know, because I almost did it in the name of mindless factoring.

What does bother me is that IHubConnectionContext object that ooNaLink is using to update the View. ooNaLink has nothing to do with SignalR and SignalR has nothing to do with ooNaLink. Why  should ooNaLink be making SignalR specific calls? What if the Views are on something other than browsers on the other end of a SignalR connection? What if there’s only a single View in a Windows desktop game application with a human against computer players?

This is the ideal scenario for an interface. We don’t need some abstract base class with different derived classes to support each View platform because we don’t need inheritance. We just need classes that implement the methods and properties defined by the interface. So you’d have one class that utilizes SignalR and another that talks to a Windows application . They don’t inherit from a common class (although they could if there were some reason for doing do) but they both implement the IOoNaView interface. And the class that calls the interface methods does not need to know what class is behind the interface implementation.

image

So the interface becomes:

namespace ooNaClasses
{
    public interface IOoNaView
    {
        void NewOoNaThing(string owner, ooNaThing thing);
        void MoveOoNaThing(string owner, ooNaThing thing);
    }
}

And the implementation:

namespace ooNaClasses
{
    public class ooNaSignalRView : IOoNaView
    {
        private IHubConnectionContext _Clients;
        public ooNaSignalRView(IHubConnectionContext hubContext)
        {
            _Clients = hubContext;
        }
        #region IOoNaView Members
        public void NewOoNaThing(string owner, ooNaThing thing)
        {
            _Clients.All.newOoNaThing(thing);
            // Send a message to the caller
            _Clients.Client(owner).
                notify(string.Format("{0}: Your ooNaThing is {1}", DateTime.Now, thing.Color));
        }
        public void MoveOoNaThing(string owner, ooNaThing thing)
        {
            _Clients.All.moveOoNaThing(thing);
        }
        #endregion
    }
}

Notice that the ooNaSignalRView constructor takes an IHubConnectionContext, which means we’re removing it from the ooBerLink constructor. ooBerLink creation in ooBerHub now looks like this:

public ooBerHub() 
{
    // _ooBerLink responds to the clients directly
    _ooBerLink = new ooBerLink(new ooNaSignalRView(GlobalHost.ConnectionManager.GetHubContext<ooBerHub>().Clients));
}

And the ooBerLink constructor looks like this:

protected readonly ooBer _ooBer;
protected readonly IOoNaView _ooNaView;
public ooBerLink(IOoNaView ooNaView)
{
    _ooBer = ooBer.Instance;
    _ooNaView = ooNaView;
}

Now ooBerLink will call methods on the interface instead of functions on  the clients.

public virtual void NewOoNaWorldView
    (string creator, Double canvasWidth, Double canvasHeight)
{
...
    ooNaThing newThing = _ooBer.CreateOoNaThing
        (   CreateGameThing, creator,
            rand.Next(20, Convert.ToInt32(canvasWidth) - 20), rand.Next(20, Convert.ToInt32(canvasHeight) - 20),
            color, "Circle");
    _ooNaView.NewOoNaThing(creator, newThing);
}
public virtual void LeftMouseClick(string creator, Double xCoordinate, Double yCoordinate)
{
    ooNaThing movedThing = _ooBer.MoveOoNaThing(creator, xCoordinate, yCoordinate);
    _ooNaView.MoveOoNaThing(creator, movedThing);
}

 

And now ooBerLink is consistent. Creating a new thing calls _ooNaView.NewOoNaThing, and moving an existing one calls _ooNaView.MoveOoNaThing.

Now let me point out that you can have the code that implements the IOoNaView interface do any crazy thing you want. So it does accomplish my misguided goal of decoupling cause and effect and you could use it to create some sort of bizarro ooNaWorld, but there are probably better ways to go about it.

Download Code

The project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

Tags:
Categories:

2 comment(s) so far...


Gravatar

Re: LightSwitch based Multi-Player Gaming Pattern using SignalR–Part II

Awesome article!! I've been looking for something like this for a while!! Thank you very much! :D

By Jaime Noguera on   6/21/2014 6:18 AM
Gravatar

Re: LightSwitch based Multi-Player Gaming Pattern using SignalR–Part II

Jamie: Thanks very much, I'm glad you find it useful. There are so many things I'd like to clean up, particularly the client-side code, but I just can't find the time.

By Richard Waddell on   6/21/2014 6:22 AM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 

Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation