The Case for Message Passing Architectures

Shared by: HC12080702313
Categories
Tags
-
Stats
views:
1
posted:
8/6/2012
language:
English
pages:
40
Document Sample
scope of work template
							The Case for Message Passing
       Architectures
         Dave Weinstein
     Red Storm Entertainment
             Ubisoft
            Introduction
Message passing architectures have been used in
game development for over a decade, and while
many developers assume that networking needs
drive this style, it is my contention that the
benefits in terms of overall code clarity, and
reduction of critical path dependencies make
message passing architectures useful even for
single player games.
Message Passing for Network
          Traffic
Message passing architectures lend themselves
particularly well to network games.

One of the biggest risks for any multiplayer game
is the degree to which the single player component
and the multiplayer component differ. The more
the code paths diverge, the more likely it is that
fixes in one area will break another, and the less
benefit either gets from QA testing.
 Message Passing for Network
Traffic: Dividing Logical Tasks
In single player games, it has been common for
the code that determines whether an action is
desired, whether an action is valid, and what the
results are to be intertwined.
These are three discreet steps that any networked
game requires separated, and message passing
architectures are no different. However, if the
game messages are divided into request, confirm,
and report steps, the code path of the single player
game and the code path of the multiplayer game
can be almost identical.
 Message Passing for Network
  Traffic: Fail Soft Solutions
If game flow is broken up into multiplayer friendly
discreet steps, however, multiplayer gameplay for
new features becomes almost automatic.

A generic “fail soft” enables any new feature to
work by default in a multiplayer environment, even
if the network traffic involved is higher than
optimal. By keeping the networking engineers from
having to play catch-up with the rest of the
engineering team, the amount of time that new
features during development breaks the multiplayer
           component is greatly reduced.
Message Passing for Network
  Traffic: Optimizations
The generic case for any message
(regardless of implementation) will almost
always be less efficient in terms of
bandwidth than a specialized version. The
more that can be assumed about the
contents of a message, the tighter the actual
network traffic can be.
               Message Passing for Network
                 Traffic: Example, Tom
                 Clancy’s Ghost Recon
void
IkeMsg_GunshotMissed::WriteBin( std::ostream& outStream )const
{
  ASSERT( mGameMessage );
  mGameMessage->WritePayloadWithoutType( 0, outStream );    // GameObjectID
  mGameMessage->WritePayloadWithoutType( 1, outStream );    // InventoryItemIndex

    // compress direction vector
    RSFloat2* compVec = RSFloat2::Create();
    RSPayloadUtils::CompressUnitVector( (RSFloat3*)mGameMessage->GetPayloadObject( 2 ), compVec );
    compVec->WriteBin( outStream );

    compVec->Release();
}

Source, Tom Clancy’s Ghost Recon, copyright Red Storm Entertainment/Ubisoft,
used with permission
Message Passing as Abstraction
           Layer
With the introduction of Object Oriented
programming methodologies, developers
became used to deferring the actual
implementation of a logical concept.

Message passing architectures allow us to
use the same basic principles for the flow of
the application.
Message Passing as Abstraction
      Layer: Genericity
Example: C++ object oriented application
pUnit = gObjectManager.GetObject( unitID );
if( pUnit )
{
              pUnit->ApplyDamage( damageAmount );
}



Example: Message passing interface
Send( kMsgID_UnitDamaged, unitID, damageAmount );
Message Passing as Abstraction
      Layer: Genericity
Note that these two approaches are not
contradictory. It would be entirely
reasonable to see the object oriented
approach as the implementation of the
response to the damage message at the
simulation level.
Message Passing as Abstraction
      Layer: Genericity
The message passing system works, even in a
single player architecture, for the same reasons
that we use virtual function interfaces at the class
level.

It provides a generic interface that provides a
significant abstraction layer between modules, and
from a project management standpoint, between
     developers.
Message Passing as Abstraction
           Layer
In the following example source code, taken
from Force 21, the User Interface is
handling the damage message, and using the
information in it to adjust the display.
           Message Passing as Abstraction
             Layer: Example, Force 21
case kDamageMsg:
{
   for( int i=0; i < Unit::kMaxVehicles; i++ )
   {
      SetVehicleDamage( i );
   }

    // Find unit damaged and blink that button.
    GameObject* obj = NULL;

    if ( target.Type() == MessageArg::kMAObject )
    {
       obj = target;
    }
    else if ( target.Type() == MessageArg::kMAUnsignedLong )
    {
       obj = theGame.mObjDB->FindObject( ObjId(target) );
    }

    if ( obj && ISA(obj, Actor) )
    {
       ObjId unitId = ((Actor*)obj)->GetUnitId();

       Unit *pU = theGame.mObjDB->FindUnit( unitId );

       Force* force = theGame.mRulesEngine->GetForce( theGame.mRulesEngine->WhoDoIControl() );

       if ( pU && force && unitId == force->mUnits[ (int)pU->GetForceIndex() ] )
       {
                if ( pU == theGame.mArbiter->GetDisplayedUnit() )
                {
                   int damagedVehicle = (int)pU->GetVehicleIndex( obj->GetId() );

                    if ( mVehicles[ damagedVehicle ] )
                       mVehicles[ damagedVehicle ]->Blink();
                }
       }
}

Source, Force 21, copyright Red Storm Entertainment/Ubisoft, used with permission
         Message Passing as
         Abstraction Layer
By using messages as a generic abstract
interface between modules, the architecture
allows for:
• Removal of other modules from the critical
  path
• Clean separation of implementation from
  semantics
• Easier integration of automated testbeds
     Mechanisms of Message
            Passing
Message passing mechanisms vary, ranging
from a limited number of arguments
(possibly with type information inferred
entirely from the message type), to flexible
and extensible systems with an almost
unlimited set of possible arguments
(including the ability to change type from
message call to message call).
                 Mechanisms of Message
                 Passing: Example, Tom
                 Clancy’s Ghost Recon
// compose and send a message to the server
newMsg = RSGameMessage::Create( kMsgID_GunshotMissed );

newMsg->AddPayload( id );                        // game object id
newMsg->AddPayload( index );                     // inventory item that fired
newMsg->AddPayload( direction );                 // direction for tracer

IRSGameMessageMgr::SendMaster( kSystemChannel_Sim, newMsg );
newMsg->Release();            // note: we have to release it since we 'Create' it here


Source, Tom Clancy’s Ghost Recon, copyright Red Storm Entertainment/Ubisoft,
used with permission
              Mechanisms of Message
             Passing: Example, Force 21
void
ClientRouter::SendMessage( F21MessageTarget targetType,
                           const MessageArg& target,
                           F21MessageType message,
                           const MessageArg& arg1,
                           const MessageArg& arg2 )
{
          // Now, handle anything for which we do default networking
          if( ShouldSendToNetwork( targetType, target, message, arg1, arg2 ) )
          {
                    TransmitMessage( targetType, target, message, arg1, arg2 );
          }

         // And finally, do any processing which we need to do on this
         // machine.
         if( ShouldSendToRecipient( targetType, target, message ) )
         {
                   ProcessMessage( targetType, target, message, arg1, arg2 );
         }
}

Source, Force 21, copyright Red Storm Entertainment/Ubisoft, used with permission
      Mechanisms of Message
        Passing: Tradeoffs
Rules based systems like Force 21 provide a
simple and effective mechanism, and guarantee
that the same message is always handled in the
same way.

Use based systems like Tom Clancy’s Ghost
Recon allow for more flexibility in how a message
is used, and allow for the reuse of the same
message, at the cost of increased code complexity
and the risk of human error.
      Mechanisms of Message
        Passing: Tradeoffs
Fixed argument systems allow for a simpler
calling convention and for smaller base cases,
subject to the limitations of the fixed number of
arguments.

Variable argument systems increase the
complexity of the calling convention (although
varargs like helpers can help mask this), and
    require more complex message handlers.
    Summary: Message Passing
      Architecture Benefits
• Basic networking as the normal case, rather
  than a special case
• Journaling/Replays as the normal case,
  rather than a special case
• Obvious integration point for unit testing or
  automated test beds
• Reduction of critical-path conflicts and
  inter-module dependencies
  Message Passing Architectures
      Risks: Performance
• There is no such thing as an “inline message”.
  There is an overhead to any message traffic.
• Increased function calls result in greater stack
  depth and complexity
• Optimal network usage will require special cases
  for the most used messages, however, once those
  cases exist, any change to a message needs to be
  duplicated in the optimized version.
  Message Passing Architectures
   Risks: Team Management
• Team understanding and “buy-in”. If the
  team does not understand the message
  passing heuristics, or is not convinced of the
  efficacy, the result will likely be code that is
  only partly message based, resulting in the
  worst of both worlds.
  Message Passing Architectures
   Risks: Team Management
• Lack of documented heuristics on what
  should be sent as a message, how messages
  should be broken up, and, depending on the
  architecture, what options for sending
  messages are valid when. Many of the
  strengths of the system are lost if it is used
  inconsistently, and bugs will result if it is
  used improperly.
 Message Passing Architectures
      Risks: Engineering
• Sending a message should be as lightweight
  as a function call to the programmer. The
  more difficult it is to construct and send a
  message, the more likely it is that an
  engineer will construct helper functions to
  build the message, obscuring the use of
  messages and making the code more
  difficult to follow.
 Message Passing Architectures
      Risks: Engineering
• Over-engineering can be a significant risk.
  If there are too many possible options,
  especially if they are incompletely
  understood by the team, it can cause
  confusion and result in the wrong options
  being used (or more likely, an engineer
  using the options they are familiar with
  regardless of how appropriate the option is
  for the task at hand)
  Message Passing Architectures
       Risks: Engineering
• Because messages act as virtualized
  functions, it is often necessary for an
  engineer to search the code base to find all
  the places that use a message to determine
  the full semantics of the message.
 Message Passing Architectures
      Risks: Engineering
• The order in which different components
  react to messages may need to vary based
  on the message. This presents risks as to
  timing issues on a message by message
  basis, and can remove many of the benefits
  of message passing systems as far as
  removing dependencies on other modules,
  depending on how message priorities are
  handled.
Message Passing Architectures:
      Next Generation
Based on the categorized risks, and in order
to maximize the benefits we get from
moving to a message passing system, we
can set some requirements for what we want
out of the next generation of message
passing systems.
 Message Passing Architectures:
 Next Generation Requirements
• The full semantics of a message, including
  information on message payload, and the order of
  processing, must be in a single location, such that
  any use of the message will always use the same
  logic.
• Heuristics for what should be sent as a message,
  how messages should be broken up, and how the
  rules for messages should be determined must be
      set and documented at the project level.
 Message Passing Architectures:
 Next Generation Requirements
• Searchable documentation should be kept
  synchronized with the source code, to enable any
  engineer to determine how a message should be
  used and why.
• Enforcement of message semantics should be
  performed, as much as possible, by the compiler,
  and otherwise at run time by the code, rather than
  being enforced by manual programming.
 Message Passing Architectures:
 Next Generation Requirements
• Sending a message must be as simple as a
  normal function call.
• The engineer using a message must be able
  to assume that the message definition and
  messaging engine will determine how the
  message should be sent and handled,
  regardless of single player or multiplayer
      game mode.
Message Passing Architectures:
    The Next Generation
One approach to meeting the requirements of a
next generation system is to draw heavily from
Bertrand Meyer’s DesignByContract™, and apply
that model to our messaging core.

Additionally, we know that it is highly unlikely,
even if messages were fully documented before
creation, that the documentation will be manually
kept in sync throughout the development life of a
project. To meet our documentation requirements,
we will want to draw off the source/view model
                  used in Eiffel.
Message Passing Architectures:
    The Next Generation
What follows is a sketch of what a next generation
message definition could look like. There is
currently no code to take the hypothetical .m file
and turn it into the combination of source code,
documentation, and database needed, nor is there a
messaging engine to use that information.

The example, however, is real. The sequence of a
claiming a gunshot, displaying a gunshot, and
reporting the effects, is drawn from the way in
which Red Storm has traditionally built tactical
                  shooters.
       Message Passing Architectures:
          RequestFireGunshot.m
// A human (either AI or player) has requested to fire their gun
Message: RequestFireGunshot
Descriptors: Combat, Simulation, Action, Request, Input, AI
Modes: Action::Action
Order: Simulation > DontCare
Payload:
               UInt32       mShooterID   is ActorID
               UInt8        mWeaponID    is WeaponIndex
IsValid:
               Reception:
                                { return false; }
IsNotValid:
               Processing:
                                { return( !SystemRulesManager::IsOwnedByLocalSystem( mShooterID ) ); }
Perform:
               Transmission:
                                { return false; }
       Message Passing Architectures:
             ClaimGunshot.m
// Claim a gunshot, and associated results.
Message: ClaimGunshot
Descriptors: Combat, Simulation, Action, Claim
Modes: Action::Action
Order: Simulation > DontCare
Payload:
                UInt32       mShooterID     is ActorID
                UInt8        mWeaponID      is WeaponIndex
                ImpactType mImpactType      is [kImpactNone,kImpactHuman,kImpactObject,kImpactWorld]
                UInt32       mImpactID      is ActorID if ((mImpactType == kImpactHuman) || (mImpactType == kImpactObject))
                                            otherwise is ObjectManager::kInvalidActorID
                Vector3      mImpactPoint   is WorldCoordinate if (mImpactType == kImpactWorld)
                                            is LocalCoordinate if ((mImpactType == kImpactHuman) || (mImpactType == kImpactObject))
                                            otherwise is NullCoordinate
                Vector3      mImpactNormal is NullVector if (mImpactType == kImpactNone)
                                            otherwise is NormalizedVector
IsValid:
                Reception:
                                 { return( SystemRulesManager::IsOwnedBySystem( SenderID(), mShooterID ); }
IsNotValid:
                Reception:
                                 { return( !SystemRulesManager::IsMaster() ); }
Perform:
                Transmission:
                                 { return( SystemRulesManager::IsOwnedByLocalSystem( mShooterID ) ); }
                Processing:
                                 { return( SystemRulesManager::IsMaster() ); }
                                 { return( SystemRulesManager::IsOwnedByLocalSystem( mShooterID ) ); }
DoNotPerform:
                Transmission:
                                 { return( SystemRulesManager::IsMaster() ); }
     Message Passing Architectures:
          ReportGunshot.m
// Report that a gunshot has been fired by the specified player with the specified weapon.
Message: ReportGunshot
Descriptors: Combat, Simulation, Action, Report
Modes: Action::Action
Order: Simulation > DontCare > Sound > UI
Payload:
                UInt32       mShooterID   is ActorID
                UInt32       mWeaponID    is WeaponIndex
Valid:
                Reception:
                                 { return( SenderID() == SystemRulesManager::kMasterSystemID ); }
IsNotValid:
                Reception:
                                 { return( SystemRulesManager::IsOwnedByLocalSystem( mShooterID ) ); }
                                 { return( SystemRulesManager::IsMaster() ); }
Perform:
                Transmission:
                                 { return( SystemRulesManager::IsMaster(); }
                Processing:
                                 { return( SenderID() == SystemRulesManager::kMasterSystemID ); }
                                 { return( SystemRulesManager::IsOwnedByLocalSystem( mShooterID ) ); }
DoNotPerform:
                Transmission:
                                 { return( SystemRulesManager::IsOwnedBySystem( RecipientID(), mShooterID ); }
           Message Passing Architectures:
            ReportGunshotHitWorld.m
// Report a bullet impact with the world geometry.
Message: ReportGunshotHitWorld
Descriptors: Combat, Simulation, Action, Report
Modes: Action::Action
Order: DontCare > Sound > UI
Payload:
               WeaponClass mWeaponClass
               Vector3       mImpactPoint is WorldCoordinate
               Vector3       mImpactNormal is NormalizedVector
IsValid:
               Reception:
                                 { return( SenderID() == SystemRulesManager::kMasterSystemID ); }
Perform:
               Transmission:
                                 { return( SystemRulesManager::IsMaster(); }
               Processing:
                                 { return( SenderID() == SystemRulesManager::kMasterSystemID ); }
        Message Passing Architectures:
               Example Uses
void
Simulation::FireWeapon( const SimHuman& shooter )
{
...
    [Combat calculations]
...
    switch( impactType )
    {
             case kImpactNone:
                Send( ClaimGunshotMsg( shooter.GetID(), shooter.GetActiveWeaponIndex(), impactType ) );
                break;

            case kImpactHuman:
            case kImpactObject:
               Send( ClaimGunshotMsg( shooter.GetID(), shooter.GetActiveWeaponIndex(), impactType,
                                      impactObject, impactPoint, impactNormal ) );
               break;

            case kImpactWorld:
               Send( ClaimGunshotMsg( shooter.GetID(), shooter.GetActiveWeaponIndex(), impactType,
                                      impactPoint, impactNormal ) );
               break;

            default
               ASSERT( false && "Invalid impact type returned by CalculateGunshotImpact()" );
    }
}
              Message Passing Architectures:
                     Example Uses
HANDLER( Simulation, ClaimGunshotMsg )
{
   // If the shooter isn’t in a position to fire this weapon, nothing will happen anyway
   if( CanShoot( msg.mShooterID, msg.mWeaponID ) )
   {
      // Ok, they could fire, so let’s report that
      Send( ReportGunshotMsg( msg.mShooterID, msg.mWeaponID ) );

        // And then let’s see if we believe the results, and report those as well
            if( ValidateGunshot( msg.mShooterID,
                                 msg.mWeaponID,
                                 msg.mImpactType,
                                 msg.mImpactPoint,
                                 msg.mImpactNormal ) )
        {
                    ...
                               [Results Calculations
                    ...
                 case kImpactWorld:
                        Send( ReportGunshotHitWorldMsg( GetWeaponClass( msg.mShooterID, msg.mShooterWeaponID ),
                                                         msg.mImpactPoint,
                                                         msg.mImpactNormal );
                        break;
                    ...
                                [More Results Processing
                    ...
        }
    }
}
Questions?

						
Related docs
Other docs by HC12080702313
Slide 1
Views: 0  |  Downloads: 0
REPUBLIC OF SOUTH AFRICA CASE NO
Views: 1  |  Downloads: 0
Mesa County Community Health Needs Assessment
Views: 2  |  Downloads: 0
Case No
Views: 1  |  Downloads: 0
Birth & Beyond Community Response - DOC
Views: 10  |  Downloads: 0
habisreutinger Interpore2012
Views: 0  |  Downloads: 0
Case No
Views: 0  |  Downloads: 0