📜 ⬆️ ⬇️

Dune 2: The Building of a Dynasty

Developer: Westwood Studios
Publisher: Virgin Games
Genre: Strategy (Real-time) / Top-down
System requirements:
moder browsers


Instead of introducing


Dune 2 is a great strategy, one of the first in the genre. It makes no sense to grovel and say how great this game is for a whole generation of children of the 90s. And since I unexpectedly find it my pleasure to mess around with the code and port it in JavaScript, then of course my goal after TTD ( article ) inevitably became Dune 2. By luck, I didn’t think of starting with it, because I'm afraid I wouldn’t handled it. As it turned out, although Dune 2 is simpler in functionality than TTD, it was more difficult to port it, but more on that later.

Code base


The choice of the “correct” code base is the main factor in the successful porting of a project using emscripten . For example, the use of SDL , lack of multithreading is a good marker of the fact that porting will be successful. I looked through all the projects related to Dune 2, and stopped at OpenDune . The chip that hooked me was a complete copy of all the behavior of the original game, including all its bugs. It seems that the code of this project was originally obtained semi-automatically from the original. In the code here and there are variables called local_03FF , a lot of global variables, the code is very difficult to read. The most serious disadvantage of the source code base in multithreading, it caused a lot of problems when porting. But the result is really good, in the browser the game is very similar to the original, with the exception of the new pack of bugs.
')
So, the dry facts:

Language: C
Number of source files: 143
Number of lines of code: 59151
Binary size: 423.2 Kb
Size equivalent javascript: ~ 1000 Kb
Porting time: ~ 2 months

Later in this article will be described the difficulties that I encountered when porting. Surely it is not interesting to everyone, if so, then lower this subsection to "known problems".

Multithreading vs asynchrony


OpenDune has a rather interesting interrupt based multithreading model. To ensure multithreading, the game code is spinning in endless cycles at the moment of idle, it looks like this:

while (true) { uint16 key; key = GUI_Widget_HandleEvents(w); if (key = 13) { break; } sleepIdle(); } 


When the application starts, the interval timer is initialized with the setitimer function. This timer triggers an interrupt at regular intervals. It pauses the main thread of execution and allows the execution of arbitrary code. For JavaScript, the implementation of a similar timer is trivial, however, another porting path was chosen in order not to artificially divide the project into JavaScript and C implementations. It was decided to completely abandon the use of the setitimer function; instead, the sleepIdle () call was replaced with a timer event function, i.e. instead of idle, this function determines which scheduled events have come up and launches them for execution.

A more serious problem is internal while loops; any occurrence of such a loop in JavaScript will inevitably cause the open tab of the browser (or the browser as a whole) to hang. This is due to the fact that most of the cycles expect user input (pressing the mouse button, keyboard), but the browser cannot handle events from input devices, they are placed in the execution chain after the current JavaScript block being executed. A possible way to solve this problem is to manually edit the code and transfer the problem code to asynchronous mode.

Small primerichik. Here is a draft code that causes problems:

 void someProblemFunction() { { //open 1 } while (true) { // open 2 while (true) { // code 2 } // close 2 } { //close 2 } } 


After agonizing speculative manipulations, asynchronous code:

 void asyncSomeProblemFunction() { Async_InvokeInLoop( asyncSomeProblemFunctionOpen1, asyncSomeProblemFunctionCondition1, asyncSomeProblemFunctionLoop1, asyncSomeProblemFunctionClose1); } void asyncSomeProblemFunctionOpen1() { // code from open 1 } void asyncSomeProblemFunctionCondition1() { // code from loop 1 condition } void asyncSomeProblemFunctionLoop1() { Async_InvokeInLoop( asyncSomeProblemFunctionOpen2, asyncSomeProblemFunctionCondition2, asyncSomeProblemFunctionLoop2, asyncSomeProblemFunctionClose2); } void asyncSomeProblemFunctionClose1() { // code from close 1 } 


Hell of a job. The core of the entire system is the Async_InvokeInLoop function.

 void Async_InvokeInLoop( void (*open)(), void (*condition)(bool* ref), void (*loop)(), void (*close)()); 


Async_InvokeInLoop - allows you to replace any while (true) loop with an asynchronous equivalent. The function guarantees to call open before the start of the cycle, and close after the end of the cycle. References to the condition and loop functions are equal participants in asynchronous iteration, which is clear from the name. Iteration is implemented through the Async_Loop function:

 void Async_Loop() { ScheduledAsync *top = STACK_TOP; switch (top->state) { case ScheduledAsync_OPEN: { top->open(); top->state = ScheduledAsync_CONDITION; return; } case ScheduledAsync_CONDITION: { top->condition(&top->conditionValue); top->state = ScheduledAsync_LOOP; return; } case ScheduledAsync_LOOP: { if (top->conditionValue) { top->loop(); top->state = ScheduledAsync_CONDITION; } else { top->state = ScheduledAsync_CLOSE; } return; } case ScheduledAsync_CLOSE: { popStack(); top->close(); free(top); return; } default: abort(); } } 


The game loop (or the timer in JavaScript) periodically jerks this function forcing everything in the game to spin. If the original function should return a result, then the problems are doubled - you have to save the result globally, and then retrieve it in other functions. Everything works by agreement. As a result, I have a hellish framework for asynchronizing a project, here is its interface:

 /* * async.h * * Created on: 19.10.2012 * Author: caiiiycuk */ #ifndef ASYNC_H_ #define ASYNC_H_ #include "types.h" extern void async_noop(); extern void async_false(bool *condition); extern void async_true(bool *condition); extern void Async_InvokeInLoop(void (*open)(), void (*condition)(bool* ref), void (*loop)(), void (*close)()); extern bool Async_IsPending(); extern void Async_Loop(); extern void Async_InvokeAfterAsync(void (*callback)()); extern void Async_InvokeAfterAsyncOrNow(void (*callback)()); extern void Async_Storage_uint16(uint16* storage); extern void Async_StorageSet_uint16(uint16 value); #endif /* ASYNC_H_ */ 


The synchronous nature of the game mutated into asynchronous, which pleased several funny bugs:


Known Issues


Due to the fact that the tester staff consists of me and my fictional friends, it is only known that:

Everything else works, or should work.
Tracker: https://github.com/caiiiycuk/play-dune

Are we playing?


http://play-dune.com/

71

UPD1. Hotkeys: habrahabr.ru/post/159501/#comment_5516325

UPD2. The server is asking for, I give direct links with statics:
House Play Atreides
House Game Ordos
House Play Harkonnen

UPD3. The server has rested against the restriction tcpsndbuf - the total size of buffers, which can be used to send data via TCP connections. The restriction that the provider puts to me, I cannot afford a more productive server, sorry if you could not play. We are waiting for the load to return to normal.

UPD4. Yet the problem was in my crooked hands, now the server has to cope with the load.

UPD5. The “Invoice” button does not work in the spaceport, be careful, if you have not ordered a single unit in the spaceport and clicked “Send Order”, the “Invoice” button will automatically be pressed and everything will hang.

UPD6. New project site.

Source: https://habr.com/ru/post/159501/


All Articles