• 沒有找到結果。

Chapter 4 Implementation Details

4.8. NPC Support

NPC is an abbreviation of Non Player Character. In most MMOG platform, NPCs reside in a special standalone component. It is a good idea to separate the game logic and AI logic, but it increases the communication overhead. In most cases, NPCs reside in only a region. It is more efficient to communicate between the game world if the NPC and virtual world in the same server. Thus, we tend to implement the NPC

feature on the game server.

The NPC feature is designed as a region scope component. It makes use ofthe timer component, which is a server scope component, to wake up NPCs in a fixed rate.

The NPC logic is divided into two parts: AI logic and game logic. The NPC component is response to collaborate between these two parts. The discussion of design detail is postponed to Chapter 5.2.5.

Chapter 5 Design of Programming Interface

In this chapter, we will shift the emphasis away from implementation of DOIT platform to use of it. In order to simplify the development, there are three layers of library introduced in this chapter. The first is the NetEngine library, which is the underlying network engine of DOIT platform. We also can make use of it at the client-side to communicate with DOIT platform. The second is the DOIT API. It provides the classes and interfaces to implement the game logic. The last one is the DOIT plugin framework. If we hope to extend the DOIT platform, we can customize a plugin and plug it onto the server. In the meantime, we will introduce how to develop a game application to the DOIT platform.

5.1. NetEngine Library

NetEngine is a message-oriented lightware service. Namely, all data are sent as a

concept of message. The Figure 5-1 presents the whole picture of NetEngine library.

The NetEngine class is the core façade class. We also start to introduce this library from this class.

NetEngine can be used as client or server. To be a client, we call the connect() method with the specified address to open a connection. This method will return a Channel object. To be a server, we call the listen() method with the specified address. The NetEngine will listen to a specified port. It is necessary to register a listener implementing the interface ChannelListener. When a channel connects in, the NetEngine will call back the channelConnected() of

ChannelListener. In the same way, when a channel disconnects, the system will call back the channelDisconnected() of ChannelListener

To send a message, we make use of send() method of Channel. This method should pass in an object implementing Message class. For each type of message, developers should define a class extending the Message class. It must override the encode() and decode() abstract methods. The system will call back these two methods in order to encode/decode the message data to/from byte buffers. In addition, we recommend that these message classes adhere the JavaBeans [17] convention, that is, if a class has a property named id, it should provides the setId() and getId() methods in this class. It will make it easier to get use of the message objects.

Figure 5-1: Class diagram of NetEngine library

To receive a message, we should first register a message handler and a message factory for a given type of message. The factory should implement the MessageFactory class and overrides the createMessage() method. The handler should implement the MessageHandler class and overrides the onMessage() method. When a message comes, the NetEngine will first analyze the type of message from its header. Then it creates the message from the corresponding message factory and call the decode() method such that it can read the message from binary stream. Subsequently, it dispatches the message to the corresponding message handler.

Channel is abstraction of connection. We can send a message by send() method. In addition, the channel can be seen as a session to the remote peer. We can store some attributes associating to this session. NetEngine supports two channel establishment methods: active connecting by connect method and passively listening by listen. And when a connection is established, we can get the connection handle Channel by ChannelListener.

The following is some example to use NetEngine.

Server-side Example:

import java.net.InetSocketAddress;

import mmog.net.*;

import mmog.net.tcp.TCPNetEngine;

//...

NetEngine engine = new TCPNetEngine();

InetSocketAddress sockaddr =

Client-side Example:

import java.net.InetSocketAddress;

import mmog.net.*;

import mmog.net.tcp.TCPNetEngine;

//...

NetEngine engine = new TCPNetEngine();

InetSocketAddress sockaddr =

new InetSocketAddress("localhost", 13579);

engine.register(MyMessage.TYPE, new MyMessageFactory(), new MyMessageHandler());

Channel channel = engine.connect(sockaddr);

MyMessage msg = new MyMessage();

channel.send(msg);

//...

5.2. DOIT API

DOIT API provides all the classes and interfaces to develop a game on the DOIT

platform. In this API, developers can totally develop the game in view of game without the complexity of networking, distributed system, and multi-thread issues.

The DOIT API is somewhat similar to NetEngine because it also have the message-oriented property. But the DOIT API is specially designed for game development. It will provide some features aimed at game. The Figure 5-2 is the class diagram of the DOIT API.

Figure 5-2: Class diagram of the DOIT API

5.2.1. Game Protocol and Game Logic

Our platform inherits from NetEngine. So we define the game protocol in the same manner as using NetEngine library, that is, we should define a message class extending the Message class for each type of message. Similarly, we also need to provide a message factory for each message class. To write a game logic, we should provide a message handler extending GameMessageHandler for each type of message. This design is inspired from Java Servlet API [18]. The developers should override the onMessage() method to handle the message. When a message comes, the system will call back the onMessage() method. It will pass in a

GameMessageInfo object. From this object, we can get the message, message type, and game object channel. In particular, the game object channel represents the source of the message to be handled.

The game object channel which is an instance of GameObjChannel class is the similar concept of Channel in NetEngine library. The difference is that the game object channel represents the channel to communicate a game object. In the current version of DOIT API, we provide two kind of GameObjChannel. They are AvatarChannel and NPCChannel respectively. As the name implied, the AvatarChannel is a channel to an avatar. We can send a message to a client indirectly through this class. As for the NPCChannel, it is a channel to a NPC, we can send a message to the NPC bot thought this class.

A game object channel can associate with a game object which extends the GameObject class. The GameObject class is the base class of all game objects.

That is, developers can customize a game object by extending the GameObject class. The GameObjChannel delegate some methods call to his associating game object. They include getX(), getY(), and status() these three methods.

Certainly, the getX() and getY() are the accessor methods to get the location information. As for the status() method, it return a Message instance according to its current state. They will be useful for GameSpace and GameSpaceUtil class.

Therefore, we can conclude the basic flow to design a game.

1. Defines the game protocol.

2. Define the message classes and corresponding message factory classes.

3. Define the game objects.

4. Implements the message handlers.

5.2.2. GameContext and GameSpace

In GameMessageHandler, we can receive a message for a given type, process message, and send updates back to the game object channel. But it is only possible to send updates to original game objects. We need more functionality to deal with game objects. In this subsection, we introduce the two classes, GameContext and GameSpace.

Figure 5-3: GameContext

GameContext represents the context of the game. The concept is similar to ServletContext in the Servet API. But the game context only associates with the current region. We can store and retrieve attributes to and form the context. Besides, we can also obtain some information of the game. The GameContext instance can be obtained from the init() method of MessageHandler, AvatarChannelListner, and AvatarMigrationListener. The Figure 5-3 is the class information of GameContext.

Figure 5-4: GameSpace

GameSpace can be seen as a data structure of the virtual worlds. We can add, remove, move, and find game objects from the GameSpace. The element type of

GameSpace is GameObjChannel. Before adding a GameObjChannel to a GameSpace, we should first associate it with a game object. It is because GameSpace set and retrieve the location information from setX()/getX() and setY()/getY() methods of GameObjChannel. These methods delegate the implementation to the same methods of GameObject associated by the GameObjChannel. Therefore, the GameSpace prohibit a GameObjChannel without GameObject from added to it. The size of GameSpace is specified in the game descriptor (see chapter 5.3). The GameSpace instance can be obtained from the GameContext.

GameSpaceUtil is a class let us use GameSpace more conveniently. We usually need to send a message to surroundings of a game object. We can call the sendMsgToSurroundings() method. It will get the GameObjChannels from the given range and send the message respectively. Likewise, if we need to receive updates from surroundings, we can call the recvUpdtFromSurroundings()

method. It will get the GameObjChannels from the given range, get the updates from status()method of GameObjChannels, and send them to the given channel.

5.2.3. AvatarChannelListener

We can have greater control over the lifecycle of avatar by means of

AvatarChannelListener. A listener implementing AvatarChannelListener can receive the events when an avatar connect or

disconnect. Even though the client disconnected abnormally, the disconnect event also raise correctly.

5.2.4. Avatar Migration

In DOIT API, we use migrate() method of AvatarChannel to migrate a avatar to another. In this method, we should specify the region to migrate, the location, and the migration data. The migration data is type of byte array. We can put arbitrary data in it. Besides, the developers may implement the interface AvatarMigrationListener to get the event about some avatars’ migration.

5.2.5. NPC

NPC (Non Player Character) is also supported in DOIT Platform. In our design, we separate the game logic and AI logic into two parts, NPCBot and GameMessageHandler respectively. They communicate with each other by means of sending messages. The game server is responsible for relaying messages between them (see Figure 5-5). The advantage is that we can process game logic in the same way as processing game logic of avatars. Moreover, it prevents codes of different

purpose from mixing together. To write NPC AI, we should write a class implementing NPCBots. There are three method should be overridden, onInit(), onUpdate(), onTimer(). onInit() is called when a NPC is created.

onUpdate() is called when a message comes. The message is sent from his corresponding NPCChannel. onTimer() is called periodically by GameServer. The timer interval is defined in the description file.

NPCBot GameMessage

5.2.6. Mapping the features to the API

In Chapter 4, we explained the features of the DOIT platform. It is interesting to map these to the API. Table 5-1 lists the features mapping to the API. We can found that the underlying complexity is totally hidden under the easy API.

Features Related API Notes

Customized Protocol

Extends Message, Extends

MessageFactory

Use encode() and decode() callback methods to turn underlying protocol to object form, and vice versa

Customized Game Logic Extends

MessageHandler

Overrides onMessage() method to handle message.

Channel Event

NPC Support extends NPCBot, Use NPCChannel

Overrides onInit()/ onTimeout()/

onUpdate()

Virtual World Uses GameSpace Provides add()/remove()/ find()/move() methods

Game Context Uses

GameContext

Provides put/get attributes

Table 5-1: API for our MMOG Middleware

5.3. Game Deployment

All the game logic run on the DOIT platform, so we must deploy them onto the DOIT platform. All game logics are wrapped as a game application. It should put at the “game” directory in the DOIT platform. In this directory, we put the classes in the

“classes” subdirectory, and we put all the libraries in the “libs” subdirectory. Besides, there are two files necessary in the game application: mmog.xml and vwlogic.proerties. In mmog.xml, it describes which components are in the game

platform and which regions are in the virtual world and the initial assignment of regions. The Figure 5-6 is an example. For the sake of simplicity, we don’t describe it in detail.

<?xml version="1.0"?>

<!DOCTYPE mmog SYSTEM "mmog.dtd">

<mmog>

<components>

<server name="server1" host="localhost" port="8765"/>

<server name="server2" host="localhost" port="8764"/>

<gateway name="gateway1" host="localhost" port="5678"/>

<gateway name="gateway2" host="localhost" port="5679"/>

<coordinator host="localhost" port="8778"/>

</components>

<region name="region4" x1="100" y1="100" x2="200" y2="200"/>

</map>

</maps>

<assignments>

<assignment region-ref="regionlogin" server-ref="server1" />

<assignment region-ref="region1" server-ref="server1" />

<assignment region-ref="region2" server-ref="server1" />

<assignment region-ref="region3" server-ref="server2" />

<assignment region-ref="region4" server-ref="server2" />

</assignments>

</mmog>

Figure 5-6: mmog.xml

Concerning the vwlogic.properties, it lists the classes of game logics, including

GameMessageHandler, MessageFactory, AvatarChannelListener, and AvatarMigrationListener. Especially, the GameMessageHandler and MessageFactory must associate with a message type. The Figure 5-7 is a simple example of vwlogic.properties

Optionly, we may provide the npc.properties if we want to provide NPCs in our game. In this file, we must specify the NPCBot classes of NPCs, the number of NPCs for a given type of NPC in a given region. The Figure 5-8 is a simple example of npc.properties

5.4. Plugins Framework

In DOIT platform, we can plug service components to extend the functionality.

As mention in Chapter 4.6,plugins are divided server scope plugins and region scope plugins. To write a server scope plugin, we should write a class extending mmog.server.ServerScopePlugin. When the server starts up, the server will call back the init() method of ServerScopePlugin. The system will pass in the ServerContext and plugin initial parameters to this method. In ServerContext, we can get the information of server and look up other server components in this server.

As for designing a region scope plugin, we should provide a class extending mmog.server.RegionScopePlugin. Similarly, when a region is initiated, the server will call back the init() method of RegionScopePlugin. The difference is that there is no RegionContext parameter passed in the init() method of RegionScopePlugin. It will be postponed to bind() method. Region scope plugins live with a region. When a region is bound to a server, all the region scope plugins in this region will have the bind() called. Relatively, when a region is unbound from a server, all the region scope plugins in this region will have the unbound called. It must be noted that init() is only called once throughout the lifecycle of game platform, and however, bind()/unbind() is called once whenever a region is migrated in/out. Moreover, region scope plugins should implement java.io.Serializable. Because region scope plugins migrate accompany with regions, the plugin should be the serialization form. Note to add the transient keyword to the field which is not able to serialize. We can restore this field in the bind() method.

A plugin should be pack as a jar file and put in the “plugins” directory of DOIT platform. For each plugin, we should provide a plugin definition file. In this file, we can define the type classname of plugin, plugin type, plugin initial parameters. The Figure 5-9 is an example of plugin definition file.

# MyPlugin.properties

# put plugins config here

mmog.plugin.server.test = hello.MyPlugin mmog.plugin.server.test.foo = bar

mmog.plugin.server.test.foo2 = bar2

Figure 5-9: MyPlugin.properties

Chapter 6 Experiment and Evaluation

We start to evaluate our platform in this chapter. To evaluate the performance of our platform, we experimented in three rounds. In the first round, we hope to realize the performance of the NetEngine. We implement a simple echo server by using NetEngine. In the second round, in the same way, we implement a simple echo server.

The difference is that the echo server is implemented with client-gateway-server architecture. We provide a simple EchoMessageHandler and deploy it on our platform.

The purpose is that we hope to know the performance baseline of our game platform.

In the last round, we simulate the real online game. We implement the most significant game logic, login and move. Besides, the virtual world is divided into four regions. So an avatar may migrate from one region to another. Through this experiment, we hope to observer the performance of the real game.

6.1. Round 1: NetEngine Performance

In the first round, we tried to find out the limit of the NetEngine.

6.1.1. Hardware Configuration

For the hardware configuration, we use one machine as the server and 10 machines as clients. The Table 6-1 describes the detail configuration.

Usage Number Configuration

Server 1 P4 2.4GHz CPU with 1MB ram

Client 10 P4 1.6~2.4GHz CPU with 512MB ram

Table 6-1: Hardware configuration of round 1

6.1.2. Software Configuration

Server Client

Client Client

Figure 6-1: Communication architecture of round 1

In order to perform stress test, we designed a virtual client generator program to simulate multiple players in a single machine. We run this program in 10 machines simultaneously, and we increased the number of clients by 50 for each run each machine. Namely, the number of client is increased by 500 for each run. We totally test 8 runs, and the client number scale from 500 to 4000. Each client sent one message per second and lasted for 10 minutes. Clients and the server run on the same LAN and they are connected with 100 Mbit Ethernet (see Figure 6-1).

The server performed simple echo action. Such that, when the server was received a message, it simply sent back this message to client. The echo message contains a 4-bytes serial number field, which is used to identify which message is sent back. For each test, we run for 10 minutes and use the data in the medium 8 minutes as the effective data. We analyzed the log data and evaluated the average response time and standard deviation for each test.

6.1.3. Experiment Result

Client number Average Response

Time Standard Deviation

500 0.3 3

Table 6-2: Experiment result of round 1

Round1

0 1000 2000 3000 4000

Client Number

response time (ms) Avg.

SD

Figure 6-2: Experiment result of round1

6.1.4. Evaluation

Through this experiment result, we discovered that it got excellent performance under 3000 clients concurrently. The average response time is less than 10 ms and the

standard deviation is less than 75ms. This performance is very ideal for a game environment. When the client number reaches to 4000, the average response time is also less than 100 ms, but the deviation is more than 500 ms. It begins to have unstable behavior.

6.2. Round 2: Perfomance baseline of DOIT Platforrm

In our platform, we adopt the client-gateway-server architecture. It is desirable to realize the performance baseline under this architecture. In this round, we also use the simple echo program to experiment with the performance baseline of client-gateway-server architecture.

6.2.1. Hardware Configuration

In this round, we prepared one more machine to run as a gateway. This machine has the same configuration as the server.

Usage Number Configuration

Server 1 P4 2.4GHz CPU with 1MB ram

Gateway 1 P4 2.4GHz CPU with 1MB ram

Table 6-3: Hardware configuration. of round 2

相關文件