[In most games there is code that needs to be run periodically, and mine is no different. Web Servers don't normally have the luxury of running code without a page request, so a web based game needs to use a few tricks to get time to pass as expected.]
As discussed in my previous posts I had decided to make my game a browser based game and I am using ASP.Net to implement the code that needs to run on the web server. This has presented some interesting challenges that a game running entirely on a PC or console would have. Namely responding to the passage of time. On most platforms it is possible to use the devices internal clock to react to the passage of time. On a web server this option generally isn't possible because a web server is optimized for responding to requests. If it is no responding to a request it shouldn't be doing anything. While it is possible with ASP.Net to do some tricks like starting another thread to run after an amount of time elapses, its generally a bad idea to do so because it is very likely that the thread will be stopped after the original request was completed. There is no way to ensure that the later thread will ever actually run. This technical limitation requires someone who is designing for a web based game to make some careful considerations.
One of the base features of my game is resource production and game play wise its pretty simple. As long as the required conditions are met, a resource accumulates a calculated rate. One resource in the game is ore which is mined. As long as the mine is staffed it produces ore. There are reasons why a mine might be understaffed such as the player has instructed the workers to work elsewhere, or there aren't enough funds in the treasury to pay the workers, or the workers may starve to death.
Now it is entirely possible to know how much of a given resource is available without having code running all the time. I call this approach the Rate/Time method. This works by assigning a starting value to the resource, usually zero, making a note of the time the value was last know, and determining a Rate of change. Lets say for example a player joins the game at 9:00 and the mine is staffed and producing 10 tons of ore an hour. The game stores the values for 9:00, zero tones of ore and 10 tons per hour. Assuming nothing that affects production changes we can determine the available amount of ore at any point in the future with some simple math. By 11:30 the amount of ore available would be 25 tones. The nice thing about this approach is that you don't need to rely on code running at regular intervals to know how much of a resource is available. However this has its limits. If there is any kind of complexity at all to the the game this quickly runs into a few obstacles. What if for example, the player is running a budget deficit. At some point there will be no money to pay the mine workers. This means that You need a Rate/Time system on the treasury to know when the treasury will be exhausted, and then the code needs to react to that and re-calculate the amount of ore and change its rate of change to zero. And here is the problem. You might know when the treasury will run out but you can't be sure code will be running when it does. It turns out that the Rate/Time method is not a complete solution on its own. It does minimize execution of code since you only need to re-calculate when something changes, but it doesn't solve the underlying problem of reacting to a condition that happens at a later time.
The nature of my design dictates that the web server must be able to react to the completion of an event. Knowing when the code should run is pretty simple. Based on the design of the game, its a matter of adding the time required to the current time. The completion time can be stored in a database.
The simple solution here is to store these completion times in the database and every time there is a request made to the server check the database for completed items and react to that completion appropriately. If the game server is busy enough this shouldn't be a problem but during development, or early testing when there are few players requests might not be made frequently enough on the server to ensure that completed events are being processed in a timely fashion. Luckily there is a relatively simple fix for this. It is pretty simple to create a small service program that periodically makes a request to the server. In my case I made a small windows service that every few seconds makes a call to the server. The server then uses this call as a regular timer and allows code to run pretty much all the time even when no players are actively playing the game.
There are a few things to watch out for. For example, you may need to secure the trigger for timer code. Normally it shouldn't be possible for a player to make a request to the server that would cause a problem. In my case, calls that trigger the timer code to run can only make timers that are already in the database and are expired run, so even if a rogue player figured out how to repeatedly trigger the timer code, it would have no effect on game play, or at least not any more than increasing the load on the server which could be done with any request, and not just the timer request. So in theory it should be impossible for players triggering the timer code to do any harm, but just to be sure I require an authenticated request from a user with the required permission to initiate the timer code.
The result is that I can now set a timer to expire at any time in the future, and store that timer in a table. Because I have a service program making regular calls I can be sure that when it comes time to react to an expired timer code will run to do it within just a few seconds of when the timer was set.
Now getting back to the design of the resource production system, we find that even if we use a Rate/Time system to minimize code execution, we still need a timer system to react to things like supply shortages. Since using a Rate/Time system added a great deal of complexity to the code which in turn would likely result in lots of bugs, I decided to scrap that plan. Instead I have implemented an interval resource production system.
Interval resource production requires timer code to run at regular intervals. Since my game server now has that ability as described above, Its just a matter of setting a timer to run for any items that perform resource production. In a simplified example, when a mine starts producing ore, I set a timer for 10 minutes in the future. When the timer expires production code for the mine is run and assuming the mine is staffed and the workers paid, ore is produced. As a final act for handling the expired timer, a new timer is set for a further 10 minutes in the future and the process repeats itself.
Now I admit that Interval resource production has its own problems. One is that it represents a continuous load on the server even if no player is actively playing. However processing power is getting cheaper all the time so I don't see this as a huge problem. Additionally its possible to limit the number of players in an instance of the game, so that it should never get to the point where the server should be overloaded by this task. Depending on design it is possible to build a system that encourages these regular timers to be staggered such that only a portion of them need to be run at any given time. While I could have all production code for the entire game run every 10 minutes that would result in a spike on processor load on the server every 10 minutes. Its better to spread them in a way where each minute only 10% of the production code has to be run. If needed the interval could be increased so that the code runs less often. There are a lot of ways this technique can be tweaked.
As a side bonus, I've been able to re-use this timer system for other activities. Building construction for example. A player starts construction and a timer is set for when the construction is due to complete. Ordering units to a location in the game is another example, where a timer can be set for when a unit is due to arrive at its destination. For someone like me who has limited free time to write code, building one robust subsystem that can be reused to implement multiple features is a huge plus because of the time savings.
I did run into a problem that happens when a client reacts to when it expects the server to finish a timer, but I'll save that for my next post.