11/20/2010

The Erlang Game Loop

After my last blog post I took the time to signup for Linode and setup my application there.
After running a few tests it was apparent my initial game loop was flawed.
Even with the beefy hardware the erlang beam process was hitting 300% after around 50 areas.

My initial approach to the game loop was a bit naive. 
When loading each area I would spawn an erlang process with a game loop that would grab all the players in that area and process any new move flags, pathfinding, checking collisions, updating character positions, etc.

I figured that even though this was CPU intensive it shouldn't be too much of a problem.  I was wrong.

However, if you pay attention you'll notice I can easily improve this by having a global game loop, and processing all the players regardless of their area.

So, the old was was something like this in pseudo-code:

foreach (areas)
     load_layout_into_memory()
     bootstrap_ets_tables() 
     start_game_loop(area_id)
end

It took me no longer than 30 minutes to update the code.  Now it looks like this:

foreach (areas)
    load_layout_into_memory()
    bootstrap_ets_tables()
end
start_global_game_loop()

I then ran some tests on my Linode, first I had to set my ERL_MAX_ETS_TABLES to 7000.
I was able to load over 3000 areas with the beam process hovering around 10%.  Great!


It now seems like SolomonRPG should handle well for a good while.  There are a few things that need to be smoothed out, but for the most part everything seems to work.
With all these areas the only issue is now running out of memory but Linode has some good plans for adding more memory.  And although I haven't done much testing, thanks to Erlang, I think I can just add more nodes and have them process sets of players.
For example, have node1 process players 1 - 1000, node2 1001 - 2000, node 3....
Or perhaps separate them by area.  Either way should be easy to implement, damn Erlang rocks.

11/19/2010

On The Road to Release - Part: 1

After having ported the map editor to HTML and Javascript, I also took the time to setup a domain and implement the player's website.

The final url for the game is: solrpg.com

There is still a lot of work to do, though, before I release the client in the Android Market and enable signups.
Here's a breakdown of what's needed or being worked on.

For reference, here is the hardware I'm developing the SolomonRPG server on:

1 Core CPU - AMD Duron(tm) 1.2GHz
1GB -  DDR RAM
80GB - PATA Hard Drive
Cent OS 5 - x86

Hosting - Linode
At the moment, the domain is hosted on my only spare computer at home.
Obviously, I can't release the game because my upload speed is pathetic.

Linode has a hosting plan that starts at $20 a month.  I've been hearing good things about them, and I'm excited to try them out.
Here's what the basic hosting plan includes:

4 Core CPU - Intel(R) Xeon(R) CPU L5420 @ 2.50GHz
512MB - RAM
16GB - Hard Drive
Cent OS 5 x86_64

The Solomon server holds a lot of small bits in memory ram, such as player credential, player location, status, tileset layouts, etc.
Simply storing a player and area definitions in memory can be a scaling issue
However, from preliminary tests reveal the real wall seems to be the game loop for each area.

Each area has a process that handles all the game logic for that area.  Each game loop runs at a variable 60FPS.
This is fairly CPU intensive, and my development machine starts giving out at barely 20 areas.

At the moment, I'm not sure if I can optimize this.  Perhaps by having a game loop for each Erlang node and try to squeeze the game logic there, but I haven't given it much thought.
I want to do some tests on the Linode host itself to see if its worth my time to optimize.

Content Creation
The fun part is creating content for the game.  Creating tilesets using Gimp and constantly testing them out and choosing which tiles to use.
I have choose carefully because tilesets have to be as small as possible.  The bigger the tileset, the longer it takes to load in a mobile phone.

Mapping out areas is also quite fun, I have to first map out the world on grid paper with a pencil.
Then, mapping out each area using the new editor. 

Story making is another challenging aspect.  For that, I'm turning to my favorite book in the world, The Bible.  In the coming months, I hope to continue carefully reading the Bible and implementing some of the cool stories into the game.  I want people to know that I'm Christian and that God was the only one who helped me make this game, I never would have been able to get this far by myself.

Polish, Polish, Polish
Continue to refine the SolomonRPG codebase and try to fix as many bugs as possible.
Make the client not drain the phone battery.
Lower the SolomonRPG CPU requirements.
Implement scripting in Erlang using erlang_js (MAYBE)
Make teleporting from area to area as smooth as possible.

Funds and Revenue
If I am unable to lower the CPU requirements, it'll be very expensive to release the website for the game.  Ideally, I'd like players to create as many areas as they'd like for free.
I will have advertisement in both the client and the website, but I'm not sure if it'll be enough.
I don't have a budget for the game at all, and I'm trying to save up enough to sign up for the Android Market and the first month of hosting.  Anyway, I'm thinking of adding a donation button to the blog.  We'll see though.

10/20/2010

New Puppy!!

For the longest I've been wanting a dog.  Finally, this weekend I went down to the animal shelter and picked up a Staffordshire mix.  Here are some pictures, it's sooo awesome!

10/13/2010

Player Map Making in the Browser

After spending some time creating maps and thinking about story telling, quest designing, and map making I've decided to move the map making process to the browser.

For one, it'll help speed up the flow of the development of content for the game.
I've decided to use jQuery for this.

Since the beginning I've thought about the ability of players to be able to create the story and world around them.  The infrastructure was set up with the thought in the back of my head.

So, now that I've decided to port the map maker to a web interface I've also decided to add the ability for players to make the world.

Here's a break down of this new feature-set:


== Player Web Interface + Web Map Maker ==
    Each player has a login to the web interface.
    One can create maps using existing tilesets.
    In each map one can create events/npcs.
    Npcs are javascript driven.
    
    World is empty except for current areas
    Player creates account either in web or in client (pre-allocates X scripting switches)
    Player can then create areas.  
        Set Name, Select a tileset.  Width and height are 20x20.
        Area Settings:
            Name
            Troops (if empty, non-battle area)
            Encounter Rate
        The area is automatically added to the system.  
        Player can add event and script them as necessary
        Regular players cannot modify stats.
    Only an admin can link the outside world to you.
    You can ask to be teleport into your created areas.  When done you can ask to be returned to your previous position.
    Needed Features:
        Upload character sets. (OPTIONAL)
        Create item
    For fast scripting:
        A list of switches (filter by 'created_by_you', 'system_switches')
        A list of items (filter by 'created_by you', 'system_generic_items', 'created_by_others'
    Areas must be able to be deleted!!!
    
    Scripting (Pseudo-code):
            /*********************************************************************************************************************/  
//Merch Area Trader - 10x Small Wolf Claw Quest
//Go to the forest, collect 10 wolf claws and come back and i'll give you a red pin
                       
 if (checkSwitch(25)) //quest_finished_yn
 {
     //player already finished this quest
     message("Hey, how u liking that pin?");
     return;
 }

 if (checkSwitch(24)) //10x wolf claw quest
 {    
     //player has accepted this quest, check the status...
     var nWolfClaws = getItemCount(1005); //number of walf claws collected
     if (nWolfClaws >= 10) //small wolf claw
     {
         //the player has collected the necessary amount of claws...
         setSwitch(25, true); //quest_finished_yn
         addItem(1006, 1); //add 1 red pin item, created by me
         message("Well done!  You've earned this pin!");
         return;
     }
 }
 else
 {
     //player has NOT accepted this quest...
     message("Man I need me some wolf claws, go fetch me 10 claws and I'll give you this shiny ribbon!");
     var response = choice("Do you accept this quest");
     if (response == true)
     {
          //player has accepted the quest....
          setSwitch(24, 10); //10x_wolf_quest
          message("Great!  Good luck!");
          return;
      }
      else
      {
          //player didn't accept the quest...
          message("Fine then...");
          return;
      }
  }
            /*********************************************************************************************************************/  

I want to make map making as easy as possible.  Event/NPC making will be way more difficult.  It'll be scripting based.  The cool thing is you should be able to create areas, modify, and test them using the master server with the android client.  Once approved these areas can become part of the larger world and story.

The scripting will be very RM2K/RMXP inspired and powered by Javascript, so hopefully it'll be easy to use.  I'm guessing the most challenging aspects will be the new territory such as scripting in a multiplayer context.

With this detour there is going to be a significant delay in the release of the game, however there should be no reason this can't be achieved in a reasonable amount of time.

I'd be interested to hear any thoughts on this!

Update:
Here's some snapshots of the work in progress:

10/11/2010

FunShot, Making Maps

For the next few months or so I'll mainly be working on adding some content to the game.
Mainly making tilesets, prototyping the maps in RPG Maker XP, creating the maps in my own custom editor.
After that I have to work on adding quests and adding more features to the scripting system for this.
Getting close to a release.  Stay Tuned.



10/08/2010

Technical Diagram 1A

I was bored so I thought I'd map out the game's network architecture.  
Each component in itself is complex.  
This is the most basic diagram with the server running in a single machine.  
However, it should be fairly easy to decouple each component into separate servers and scale as necessary.  


Anyway, now I'm working on creating tilesets to continue mapping the game.
The SolomonRPG game engine is at version 0.3.  
It's solid and stable enough where I can finally concentrate on adding and creating the content!





10/05/2010

Fun Shot, 3 A.M.

Just working on the battle system...

8/03/2010

Setting Up a Queue Consumer with RabbitMQ's Erlang Client

Ok, so in this post I will outline how to set up a consumer using the rabbitmq erlang client.

We'll start with a basic chat server in which clients can post and listen for messages.
Continuing from my last post: http://developingthedream.blogspot.com/2009/10/rabbitmq-erlang-client-yay.html
You now have a basic project skeleton to start building your server.  We'll call the project chat_server.

I followed this blog post http://mutlix.blogspot.com/2007/10/amqp-in-10-mins-part3-flexible-routing.html for a visual representation of the AMQP specs,
although it's outdated the basics are the same.

So, the project would look like this:

chat_server/
chat_server/db
chat_server/deps
chat_server/ebin
chat_server/include
chat_server/src


Now inside the src/ directory, we'll create these files:
  
src/master.erl
src/chat_server.erl
src/test_client.erl



I made a mistake in my last blog post regarding how to properly symlink the rabbitmq dependencies.
Unlink any previously created symlinks:

unlink /usr/lib/erlang/lib/rabbitmq_common
unlink /usr/lib/erlang/lib/rabbitmq_erlang_client


Now create some symlinks in the deps folder:

cd deps/
ln -s rabbitmq-server/ rabbit_common
ln -s rabbitmq-erlang-client/ rabbitmq_erlang_client


Here's the content of the Makefile we'll be using.

.SUFFIXES: .erl .beam

.erl.beam:
    erlc -W $<

ARGS = -pa ebin \
       -pa deps/rabbit_common/ebin \
       -pa deps/rabbitmq_erlang_client/ebin \
       -boot start_sasl -s rabbit

ERL = erl ${ARGS}
ERLC = erlc -I . -I deps +debug_info -o ebin

MODS = src/master.erl src/chat_server.erl src/test_client.erl

all:
    ${ERLC} ${MODS}

run:
    ${ERL}

clean:
    rm -rf *.beam ebin/*.beam src/*.beam erl_crash.dump 

 
We'll start with the master.erl file which basically sets up the exchange, queues, etc.
Mainly bootstraps the server.
Here is the content of master.erl:

-module(master).
-include_lib("deps/rabbitmq_erlang_client/include/amqp_client.hrl").

-export([start/0]).

start() ->
    RID = amqp_connection:start_direct(),
    Channel = amqp_connection:open_channel(RID),

    X = <<"global_chat_exchange">>,
    Q = <<"global_message_queue">>,
    Key = <<"global_message_queue_publish_key">>,       %%our routing key, all clients have this

    amqp_channel:call(Channel, #'exchange.declare'{exchange = X, type = <<"topic">>, nowait = true}),
    amqp_channel:call(Channel, #'queue.declare'{queue = Q}),
    amqp_channel:call(Channel, #'queue.bind'{queue = Q, exchange = X, routing_key = Key}),
  
    io:fwrite("bound queue: ~p to exchange: ~p using key: ~p~n", [Q, X, Key]),
  
    amqp_channel:close(Channel),
    amqp_connection:close(RID).

 
Here's the breakdown of the start() function.
I'm not doing any sort of error checking, take a look at deps/rabbitmq_erlang_client/test/test_util.erl
for more complete code.

First, we connect to rabbitmq.

RID = amqp_connection:start_direct(),

We simply call start_direct in the amqp_connection module and receive the connection id.  (this is just the id of an erlang process)
Next, we open a channel which is pretty straightforward.

Channel = amqp_connection:open_channel(Pid),

Afterward, we bind the queue to the exchange using a routing key.

So here's our names:

X = <<"global_chat_exchange">>,
Q = <<"global_message_queue">>,
Key = <<"mysecret">>,       %%our routing key, all clients have this

amqp_channel:call(Channel, #'exchange.declare'{exchange = X, type = <<"topic">>, nowait = true}),
amqp_channel:call(Channel, #'queue.declare'{queue = Q}),
amqp_channel:call(Channel, #'queue.bind'{queue = Q, exchange = X, routing_key = RoutingKey}),

 
Here we tell the erlang rabbitmq client to declare the exchange.
You can look at the file: deps/rabbit_common/include/rabbit_framing.hrl for all the record definitions.
You can specify things such as the exchange type and other things.

Now, first we'll start by compiling this module manually to go over the erlang shell.
Run 'make run' to start the rabbitmq server.  You'll be greeted with the shell.
Compile the master module by typing:

c('src/master').

You can now run the code like so:

master:setup().

Now we're ready to start consuming messages.  So, the content of the first draft of chat_server.erl is:

-module(chat_server).
-include_lib("rabbitmq_erlang_client/include/amqp_client.hrl").
-compile(export_all).

start() ->
    RID = amqp_connection:start_direct(),
    Channel = amqp_connection:open_channel(RID),
   
    Queue = <<"global_message_queue">>,
  
    spawn( fun() -> consume_loop(RID, Channel, Queue) end ),
    self().
  
consume_loop(RID, Channel, Q) ->
    amqp_channel:subscribe(Channel, #'basic.consume'{queue = Q}, self()),
  
    receive
         #'basic.consume_ok'{} ->
            io:fwrite("subscribed to queue: ~p listening for messages...~n", [Q])
    end,
    receive
        {#'basic.deliver'{delivery_tag=Tag}, Content} ->
            amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag}),
            handle_message(RID, Channel, Content),
            consume_loop(RID, Channel, Q);
        Unknown ->
            io:fwrite("unknown message: ~p tearing down~n", [Unknown]),
            teardown(RID, Channel)
    end.

handle_message(RID, Channel, Content) ->
    io:fwrite("got message: ~p from pid: ~p on channel: ~p ~n", [RID, Channel, Content]),
    todo.

teardown(RID, Channel) ->
    amqp_channel:close(Channel),
    amqp_connection:close(RID).

 
Here we open a channel and spawn a process to consume messages from the 'global_message_queue' queue.
You've seen the start of the code in master.erl.

Queue = <<"global_message_queue">>,   %%Here we declare the queue name to be passed around
spawn( fun() -> consume_loop(RID, Channel, Queue) end ),    %%spawn a process and return immediately
self().  %%return our process id (unused)

   
Here we spawn a process that loops forever consuming messages from the 'global_message_queue'

consume_loop(RID, Channel, Q) ->
    amqp_channel:subscribe(Channel, #'basic.consume'{queue = Q}, self()),
    receive
         #'basic.consume_ok'{} ->
            io:fwrite("subscribed to queue: ~p listening for messages...~n", [Q])
    end,

  
Here, we start the loop by telling the server that we're ready to consume on the queue 'Q' by calling amqp_channel:subscribe.
We immediately recieve a confirmation message and continue.

    receive
        {#'basic.deliver'{delivery_tag=Tag}, Content} ->


We wait for an erlang type message which contains a valid amqp message.
We save the payload to the variable 'Content' and the delivery tag to 'Tag'.

            amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag}),

We immediately acknowledge that we've received the message.
You may wish to move this after you've processed the message.

         handle_message(RID, Channel, Content),
      consume_loop(RID, Channel, Q);
 
          
We send the content to the handle_message() function and then loop again to wait for messages again.     

        Unknown ->
            io:fwrite("unknown message: ~p tearing down~n", [Unknown]),
            teardown(RID, Channel)
    end.


If we get an unknown message for any reason we exit and tear down the channel and connection.
The handle_message() function simply prints the content of the message to the screen.

Now you can compile the module like so:

c('src/chat_server').

Start the consumer process by typing:

chat_server:start().

Now we can start publishing messages, this is the content of test_client.erl:

-module(test_client).
-include_lib("deps/rabbitmq_erlang_client/include/amqp_client.hrl").
-export([say/1]).
say(Msg) ->
    RID = amqp_connection:start_direct(),
    Channel = amqp_connection:open_channel(RID),
    X = <<"global_chat_exchange">>,
    Key = <<"global_message_queue_publish_key">>,
    
    Packet = list_to_binary(Msg),
    
    Publish = #'basic.publish'{exchange = X, routing_key = Key, mandatory=true, immediate=false},
    amqp_channel:call(Channel, Publish, #amqp_msg{payload = Packet}),
    
    amqp_channel:close(Channel),
    amqp_connection:close(RID).


First we turn the message into a binary for delivery:
    Packet = list_to_binary(Msg),

Here we simply turn the message to a binary.

    Publish = #'basic.publish'{exchange = X, routing_key = Key, mandatory=true, immediate=false},
    amqp_channel:cast(Channel, Publish, #amqp_msg{payload = Packet}),
 
  
First, we declare a variable 'Publish' which sets up the publish options.
Then we call 'cast' instead of 'call' simply because we WANT to wait for the message to be published.
The reason we want to wait is because we immediately close the channel.

Now you can compile the module by running:

c('src/test_client').

And test it by running:

test_client:say("hello world!").

You should now see the output from your consumers which have read the message.

This concludes this introduction to using the rabbitmq-erlang client.
You can find the full test project available at: http://github.com/therevoltingx/chat_server

Any comments, questions, etc are welcomed!

7/28/2010

Fun Shot! #2

Hi all, here's a snapshot of notes on the battle system UI flow.

Enjoi!

7/22/2010

As The Grind Continues

After releasing a super alpha version of the game so far, I've just been grinding along.
Nothing entirely interesting has happened in the last month, here are some of the things improved and/or added:

  • Stabilized name display and tile animation
  • Added team functionality
  • Added personal messaging functionality
  • Battle against monsters [Draft]
The most tedious part was starting the battle system, i turned to rpg maker 2000 again for inspiration.
Fusing together its old school battle system with my own vision of the game (and with the limitations of man power and working with Android.)
Battling is turn based, like an animated card game.
Each player or monster takes a turn. (Attack, Defend, Special Attack, etc)
Everyone takes turns, according to different stats, damage is dealt and players are chosen for attack.  (Level, Speed, Defense, etc)
An animation is played for each attack and players can attack or do things like defend or use items.

At this point many things are finished, but the battle system is still in a rough stage.
I've gone to pains to keep a clean code base though, so I can tweak the settings as I see fit.
So far, for the battle system I've written:

  • Erlang: 
  •   A single module/process handles all the battle systems such as creating the message queues.
  •   Periodically stores to global database and keeps track of handling turns
  •   Stores and executes monster battling action patterns 
  •   Waits and notifies players when a turn is taken
  •    (566 lines of code.)
  • Java/Android:
  •     A dialog activity is launched when it's notified a battle has started
  •     Bootstraps battle UI, downloads resources accordingly
  •     Listens for events and displays battle animations
  •     Have the player select a monster and take attack, etc. (TODO)
  •     The main class is 481 lines of code so far, but a lot of code is in its own class, and I'm lazy.
  • Perl/HTML/SQL:
  •     Add data schemas for new battle system components (attacks, teams, monsters, battles, etc)
  •     Add to the existing admin functionality the necessary components
  •     This includes a web UI for managing: monsters, parties, attacks, backdrops, etc.
  •     The Client->Server Battle module is only 63 lines so far.
  •     The Admin->Monster Management module is 263 lines, (fairly complete.)
 There is still plenty of work to be done, no one said it would be easy.  I think the battle system is one of the most challenging aspects of making any game.  I hate math :p