This is an overview of the do's and don'ts when architecting systems that require client to server communication, but would create a negative user experience if only synchronous data transfer was relied upon.
There are a number of factors that can potentially contribute to player actions being "delayed" when architecting a complicated system like combat in a massive online environment.
synchronous vs asynchronous architectures object construction and persistence bandwidth shaping and replication
Synchronous vs Asynchronous Architectures
While some actions are technically asynchronous due to the nature of remote calls, the functionality may behave as if it expects synchronous response (time). In other words, as a user executes an action like "activate" (i.e. letting up on the a hotkey) the expectation is that the character will immediately respond. In single player games, this is trivial to implement even if technically asynchronous because the communication is all within the same process or set of threads. The latency between key release, and when the modern desktop CPU will get to processing the release is easily within a period of time that is imperceptible to the player. Networked games (i.e. gaming over a LAN) can also get away with assuming a latency that is below the perception threshold of a player.
Massive Multiplayer Games (e.g. WOW, EQ etc) suffer different physical constraints where even if one were to ignore all of the hardware involved in the process of connecting a player to a remote server, the pure physics of speed of light become a consideration. A consequence of these constraints is that it may be necessary for the local client to initial local processing such as animation under the assumption that the server will agree that the action is allowed in order to provide the player with the illusion of responsiveness that is divorced from the reality of communication across the internet. In the case that the server disallows an action, the client can handle the rejection in a variety of ways either halting an action in process or allowing the visualization to continue even if the "result" (e.g. do 10 pts of damage) never actually occurs.
Social Games in particular leverage this illusion by allowing the client to perform almost ALL processing, only in instances when the server rejects an action is the client forced to resync to the persisted reality of the server.
Depending on the game design, it may be acceptable for the latency of internet communication to be perceptible or not. This will dictate some necessary architectural choices.
First Thing to Think About:
Separate the action's "visualization" from server communication. Assuming the client is performing normally it will (the vast majority of the time) behave identically to how the server would have allowed.
Object Construction and Persistence
There are a few things that should almost always be addressed:
Dynamic Composition (GLOMming) Write Strategies
When a class is added to a persistent object via dynamic composition (glomming), it initiates an immediate hit on the database to apply the structural change. Immediate (synchronous) operations against the database are among the most expensive operations possible in HeroEngine (or any persistent client to server environment), as they are subject to both network as well as physical (IO) limitations. The most common pitfall engineers encounter is the attempt to dynamically composite an object (GLOM) when the object logically, and based on its persistence, is already possessed the class.
Potential Ways to Avoid This:
Add an if check prior to GLOM() operations. Construct objects through a factory that are already of the classes conceptually always required for basic functionality.
Appropriate choices for write strategies can also significantly affect overall performance, frequently many fields related to ability activation are mistakenly marked as LAZY when they conceptually should have been NEXTWRITE or even NEVER.
Potential Ways to Avoid This:
Choose persistence/write strategies that are appropriate for the use case.
Bandwidth Shaping and Replication
This is the second most common pitfall.
By default, HeroEngine is configured to shape bandwidth based on what is available to a 56k connection. This base value can be modified either through external functions or by default through configuration in master control.
// adjust individual bandwidth shaping parameters
external function SetPlayerBandwidthSettings(playerAccount as ID, bandwidthLimit as Integer, maxBurst as Integer)
// The callbackNode must implement method _PlayerBandwidthSettings(player as ID, bandwidthLimit as Integer, maxBurst as Integer)
external function GetPlayerBandwidthSettings(playerAccount as ID, callbackNode as NodeRef)
Bandwidth shaping by its very nature delays certain types of traffic based on a prioritization scheme.
Frequently there is significant bandwidth consumption in remote calls related to systems like combat that are sometimes (which can explain issues being intermittent) affecting the transmission of other (conceptually, perhaps higher priority) traffic. Additionally, remote calls are significantly less efficient than replication, consuming a relatively large amount of bandwidth compared to the bandwidth consumption of a similarly communicated message (via replication).
Note: Analysis can be performed using replication tracing (e.g. reptrace CLI command Client Console Commands).
There are a number of ways to optimize remote calls, including creating more specific targeted (i.e. transmitting less data) remote calls or optimized replication traffic utilizing its features including prioritization of traffic.