Operation Responses and Events

Options
Kalagaraz
edited January 2012 in DotNet
I notice in the bootcamp demo to discern from different operation responses and events it does a switch and then checks agiasnt each opcode. I find this ugly, and was wondering if there is a better cleaner approach? Just imagine how that will begin to look in like an MMO with 100+ different operations and events.

Comments

  • dreamora
    Options
    You don't use PUN / Lite for MMOs, you use the MMO application which works fundamentally different (full scale states basically)
  • dreamora wrote:
    You don't use PUN / Lite for MMOs, you use the MMO application which works fundamentally different (full scale states basically)

    What I'm saying is, it does the exact same thing in the MMO application server AND client. There has got to be a better way than these switch statements. There are only so many states you can break a MMO down into. Disconnected, LoggingIn, SelectingCharacter. Sure those will be fine with a switch statement is there are a limited subset of events and operations. But WorldEntered, that is the main game, there will be hundreds of operations that could be expected at any time.

    From MmoPeer.cs in server
    public OperationResponse OnOperationRequest(PeerBase peer, OperationRequest operationRequest, SendParameters sendParameters)
            {
                switch ((OperationCode)operationRequest.OperationCode)
                {
                    case OperationCode.CreateWorld:
                        {
                            return this.OperationCreateWorld(peer, operationRequest);
                        }
    
                    case OperationCode.EnterWorld:
                        {
                            return this.OperationEnterWorld(peer, operationRequest, sendParameters);
                        }
    
                    case OperationCode.RadarSubscribe:
                        {
                            return OperationRadarSubscribe(peer, operationRequest, sendParameters);
                        }
    
                    case OperationCode.SubscribeCounter:
                        {
                            return CounterOperations.SubscribeCounter(peer, operationRequest);
                        }
    
                    case OperationCode.UnsubscribeCounter:
                        {
                            return CounterOperations.UnsubscribeCounter(peer, operationRequest);
                        }
    
                    case OperationCode.AddInterestArea:
                    case OperationCode.AttachInterestArea:
                    case OperationCode.DestroyItem:
                    case OperationCode.DetachInterestArea:
                    case OperationCode.ExitWorld:
                    case OperationCode.GetProperties:
                    case OperationCode.Move:
                    case OperationCode.MoveInterestArea:
                    case OperationCode.RaiseGenericEvent:
                    case OperationCode.RemoveInterestArea:
                    case OperationCode.SetProperties:
                    case OperationCode.SetViewDistance:
                    case OperationCode.SpawnItem:
                    case OperationCode.SubscribeItem:
                    case OperationCode.UnsubscribeItem:
                        {
                            return InvalidOperation(operationRequest);
                        }
                }
    
                return new OperationResponse(operationRequest.OperationCode)
                    {
                       ReturnCode = (int)ReturnCode.OperationNotSupported, DebugMessage = "OperationNotSupported: " + operationRequest.OperationCode 
                    };
            }
    


    From WorldEntered.cs in client
    public void OnEventReceive(Game game, EventData eventData)
            {
                switch ((EventCode)eventData.Code)
                {
                    case EventCode.RadarUpdate:
                        {
                            HandleEventRadarUpdate(eventData.Parameters, game);
                            return;
                        }
    
                    case EventCode.ItemMoved:
                        {
                            HandleEventItemMoved(game, eventData.Parameters);
                            return;
                        }
    
                    case EventCode.ItemDestroyed:
                        {
                            HandleEventItemDestroyed(game, eventData.Parameters);
                            return;
                        }
    
                    case EventCode.ItemProperties:
                        {
                            HandleEventItemProperties(game, eventData.Parameters);
                            return;
                        }
    
                    case EventCode.ItemPropertiesSet:
                        {
                            HandleEventItemPropertiesSet(game, eventData.Parameters);
                            return;
                        }
    
                    case EventCode.ItemSubscribed:
                        {
                            HandleEventItemSubscribed(game, eventData.Parameters);
                            return;
                        }
    
                    case EventCode.ItemUnsubscribed:
                        {
                            HandleEventItemUnsubscribed(game, eventData.Parameters);
                            return;
                        }
    
                    case EventCode.WorldExited:
                        {
                            game.SetConnected();
                            return;
                        }
                }
    
                game.OnUnexpectedEventReceive(eventData);
            }
    
  • dreamora
    Options
    if you have hundreds to thousands of operations you have done something fundamentally wrong normally

    For example spells have 3 different operations basically: CastedAtGroundTarget, CastedAtActorTarget, CastedAtArea
    Anything beyond that is matter of a spell id + spell caster id within the operation thats used to look up its information relevant to execute it.

    interaction with npcs requires 1 operation, thats NPCInteraction (if you want to go a step further, you could create a second operation for trader interaction used for traders, bank boxes, auction houses etc, stuff beyond simple conversation)

    if you get thousands of ops and alike the whole thing was likely not planned out the slightest bit at all and every new abbility and alike gets pushed into a new operation which is very bad as that does not allow you to optimize the stuff reasonably nor maintain it at all.
  • dreamora wrote:
    if you have hundreds to thousands of operations you have done something fundamentally wrong normally

    For example spells have 3 different operations basically: CastedAtGroundTarget, CastedAtActorTarget, CastedAtArea
    Anything beyond that is matter of a spell id + spell caster id within the operation thats used to look up its information relevant to execute it.

    interaction with npcs requires 1 operation, thats NPCInteraction (if you want to go a step further, you could create a second operation for trader interaction used for traders, bank boxes, auction houses etc, stuff beyond simple conversation)

    if you get thousands of ops and alike the whole thing was likely not planned out the slightest bit at all and every new abbility and alike gets pushed into a new operation which is very bad as that does not allow you to optimize the stuff reasonably nor maintain it at all.

    Well in that example I'd just you a single operation and just have a target variable passed in hashtable. But then that just shifts the problem, now you have a switch statement on the hashtable target variable, spell id, etc.... But you will still end up with a ton of operations. Pick a big MMO, say world of warcraft, now think of all the actions you can perform in that game:

    Trading with players,
    look up auctions on market,
    use a skill, spell, etc...
    Chat,
    SendMail,
    etc...

    There are probably 100+ distict actions that cannot be grouped in a operation, and if you do find a way, you are just shifting the problem to a different portition of the code. You still end up with a bunch of switch statements on the parameters.

    There must be an easier way to extend your code without going through and adding another "case" to the switch statement. A way to automatically handle it. Something with reflection, I don't know.

    If you wanted to you could just define 1 operation to service the entire game. Call it perform action and then just pass it another code as a variable that defines the action. But that does not eliminate the issue here, cause it just shifts the switch statement onto the action code rather than the operation code.
  • dreamora
    Options
    100+ operations is fine

    And that you have masses of switches is totally unavoidable without doing the whole thing hell inefficient (the reason you use switch and byte values is that nothing is faster than jump tables, not matter what you use).

    Keep in mind that what Photon offers you is no MMO sdk to plugin a few own things but the MMO backend and base (interest management and 'getting into the world'), all the rest (the whole game and world logic) is up to you so if you want to use some dirty or strange object instance / static class based way for your game logic thats fully possible. Photon does not force you to do more than checking that one additional operation type to forward it to your handling layer.

    but as you realized thats only going to shift the problem, in the end you will end in switches or a thing performing worse (which means its much worse as cpu time is the most valuable good as ram is on a MMO backend)

    also I fail to see the problem actually. #region is there for a reason and the possibility to call functions for whole 'operation groups' to structure the handling into more focused functions is there for exactly this kind of reasons.

    if you really think that this is going to be a problem when writing MMO code you will more sooner than later find out that you totally underestimated the effort and the scale MMO code has cause these switches are nice, simple, clean and easily maintainable and 'idiot proof' compared to the rest of the code you will write and the man months you will spend to make them run fast and ram efficient enough to operate a live world with that code.
  • Photon.SocketServer.RPC.Reflection.OperationDispatcher

    ^ That is what I was looking for, it uses reflection to automatically handle adding operations to the list. No need to add a switch statement t look for it, it automatically does it. It uses reflection to look through your code, find operation definitions, and build a table from the opcodes those definitions use.

    That way all I do is define my operations and I'm done. I create the operation handler, and the operationdispatcher automatically adds it to list.