{"id":22,"date":"2012-04-03T00:52:45","date_gmt":"2012-04-02T22:52:45","guid":{"rendered":"https:\/\/blogs.scummvm.org\/jakimushka\/?p=22"},"modified":"2022-04-18T06:49:05","modified_gmt":"2022-04-18T04:49:05","slug":"sky-refactoring","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/jakimushka\/2012\/04\/03\/sky-refactoring\/","title":{"rendered":"SKY refactoring"},"content":{"rendered":"<div><\/div>\n<p>In my first post I&#8217;ve wrote about refactoring of SKY engine. Here I want to describe it in more details what did I mean.<\/p>\n<p>Let&#8217;s look to the main engine&#8217;s loop. It&#8217;s huge, so I&#8217;ll process it part by part<\/p>\n<p>First thing we&#8217;re seeing &#8211; nested pause loop. Are we really need it? Or may remove without any regrets? Obviously it&#8217;s paused state of engine. When engine in this state, it&#8217;s only make delay and check keyboard for &#8216;P&#8217; button pressed. (Also there is one moment. In this state user can&#8217;t close game cause shouldQuit() procedure doesn&#8217;t processed. I don&#8217;t know actually is it bug or feature, but seems like bug)<\/p>\n<pre>    while (!shouldQuit()) {\r\n            _debugger-&gt;onFrame();\r\n            if (shouldPerformAutoSave(_lastSaveTime)) {\r\n                    if (_skyControl-&gt;loadSaveAllowed()) {\r\n                            _lastSaveTime = _system-&gt;getMillis();\r\n                            _skyControl-&gt;doAutoSave();\r\n                    } else\r\n                            _lastSaveTime += 30 * 1000; \/\/ try again in 30 secs\r\n            }\r\n            _skySound-&gt;checkFxQueue();\r\n            _skyMouse-&gt;mouseEngine();\r\n            handleKey();\r\n            if (_systemVars.paused) {\r\n                    do {\r\n                            _system-&gt;updateScreen();\r\n                            delay(50);\r\n                            handleKey();\r\n                    } while (_systemVars.paused);\r\n                    delayCount = _system-&gt;getMillis();\r\n            }\r\n<\/pre>\n<p>After I removed nested loop, code begin to show that the pause is a state of loop. Also removing of totally useless loop helped us to make code more readable<\/p>\n<pre>    while (!shouldQuit()) {\r\n            if (_systemVars.paused) {\r\n                    _system-&gt;updateScreen();\r\n                    delay(50);\r\n                    handleKey();\r\n                    delayCount = _system-&gt;getMillis();\r\n            }\r\n            else {\r\n                    _debugger-&gt;onFrame();\r\n                    if (shouldPerformAutoSave(_lastSaveTime)) {\r\n                            if (_skyControl-&gt;loadSaveAllowed()) {\r\n                                    _lastSaveTime = _system-&gt;getMillis();\r\n                                    _skyControl-&gt;doAutoSave();\r\n                            } else\r\n                                    _lastSaveTime += 30 * 1000; \/\/ try again in 30 secs\r\n                    }\r\n<\/pre>\n<p>Now let&#8217;s pay our attention to the bottom of go() function. There is calculation of delay<\/p>\n<pre>    if (_fastMode &amp; 2)\r\n            delay(0);\r\n    else if (_fastMode &amp; 1)\r\n            delay(10);\r\n    else {\r\n            delayCount += _systemVars.gameSpeed;\r\n            int needDelay = delayCount - (int)_system-&gt;getMillis();\r\n            if ((needDelay &lt; 0) || (needDelay &gt; _systemVars.gameSpeed)) {\r\n                    needDelay = 0;\r\n                    delayCount = _system-&gt;getMillis();\r\n            }\r\n            delay(needDelay);\r\n    }\r\n<\/pre>\n<p>I want to move it to separate function because it make reading of main loop much harder than it can be.<\/p>\n<pre>    int SkyEngine::calculateDelay( uint32 &amp;delayCount ) {\r\n            if (_fastMode &amp; 2) {\r\n                    return 0;\r\n            }\r\n            else if (_fastMode &amp; 1) {\r\n                    return 10;\r\n            }\r\n            else {\r\n                    delayCount += _systemVars.gameSpeed;\r\n                    int needDelay = delayCount - (int)_system-&gt;getMillis();\r\n                    if ((needDelay &lt; 0) || (needDelay &gt; _systemVars.gameSpeed)) {\r\n                            needDelay = 0;\r\n                            delayCount = _system-&gt;getMillis();\r\n                    }\r\n                    return needDelay;\r\n            }\r\n    }\r\n<\/pre>\n<p>In main loop I&#8217;ve left only delay (calculateDelay(delayCount)) string. I don&#8217;t actually know, why delayCount uses in pause state, so I did&#8217;t touch it.<br \/>\nNext code I want to extract from main loop is<\/p>\n<pre>    if (shouldPerformAutoSave(_lastSaveTime)) {\r\n            if (_skyControl-&gt;loadSaveAllowed()) {\r\n                    _lastSaveTime = _system-&gt;getMillis();\r\n                    _skyControl-&gt;doAutoSave();\r\n            } else\r\n                    _lastSaveTime += 30 * 1000; \/\/ try again in 30 secs\r\n    }\r\n<\/pre>\n<p>It&#8217;s absolutely independent and we may extract it into separate method<\/p>\n<pre>    void SkyEngine::performAutosave() {\r\n            if (shouldPerformAutoSave(_lastSaveTime)) {\r\n                    if (_skyControl-&gt;loadSaveAllowed()) {\r\n                            _lastSaveTime = _system-&gt;getMillis();\r\n                            _skyControl-&gt;doAutoSave();\r\n                    } else\r\n                            _lastSaveTime += 30 * 1000; \/\/ try again in 30 secs\r\n            }\r\n    }\r\n<\/pre>\n<p>At the same time i&#8217;ve noticed that acutosaving doesn&#8217;t perform in pause state (bug?)<br \/>\nFor the same reasons, I&#8217;ve extracted this code<\/p>\n<pre>    if (_debugger-&gt;showGrid()) {\r\n            uint8 *grid = _skyLogic-&gt;_skyGrid-&gt;giveGrid(Logic::_scriptVariables[SCREEN]);\r\n            if (grid) {\r\n                    _skyScreen-&gt;showGrid(grid);\r\n                    _skyScreen-&gt;forceRefresh();\r\n            }\r\n    }\r\n<\/pre>\n<p>to separate method<\/p>\n<pre>    void SkyEngine::showGrid() {\r\n            if (_debugger-&gt;showGrid()) {\r\n                    uint8 *grid = _skyLogic-&gt;_skyGrid-&gt;giveGrid(Logic::_scriptVariables[SCREEN]);\r\n                    if (grid) {\r\n                            _skyScreen-&gt;showGrid(grid);\r\n                            _skyScreen-&gt;forceRefresh();\r\n                    }\r\n            }\r\n    }\r\n<\/pre>\n<p>in result of all these actions I got clean and readable main loop.<\/p>\n<pre>    while (!shouldQuit()) {\r\n            if (_systemVars.paused) {\r\n                    _system-&gt;updateScreen();\r\n                    delay(50);\r\n                    handleKey();\r\n                    delayCount = _system-&gt;getMillis();\r\n            }\r\n            else {\r\n                    _debugger-&gt;onFrame();\r\n                    performAutosave();\r\n                    _skySound-&gt;checkFxQueue();\r\n                    _skyMouse-&gt;mouseEngine();\r\n                    handleKey();\r\n                    _skyLogic-&gt;engine();\r\n                    _skyScreen-&gt;processSequence();\r\n                    _skyScreen-&gt;recreate();\r\n                    _skyScreen-&gt;spriteEngine();\r\n                    showGrid();\r\n                    _skyScreen-&gt;flip();                            \r\n                    delay(calculateDelay(delayCount));                     \r\n            }\r\n    }\r\n<\/pre>\n<p>But it&#8217;s not the end. I still can make few improvements here. Each state call delay and handleKey functions. I think, I can move them out of condition. But to do this, I need to change calculateDelay function and add those strings to it:<\/p>\n<pre>    if (_systemVars.paused) {\r\n            delayCount = _system-&gt;getMillis();\r\n            return 50;\r\n    }\r\n<\/pre>\n<p>Paused state calls _system-&gt;updateScreen() method. After few ticks function delay calls the same method. I not sure, but it seems like we can remove this call from paused state. Aad we get situation where engine doesnt perform any actions in paused state. I&#8217;ve changed condition and main loop got following look:<\/p>\n<pre>    while (!shouldQuit()) {\r\n            if (!_systemVars.paused) {\r\n                    _debugger-&gt;onFrame();\r\n                    _skySound-&gt;checkFxQueue();\r\n                    _skyMouse-&gt;mouseEngine();\r\n                    _skyLogic-&gt;engine();\r\n                    _skyScreen-&gt;processSequence();\r\n                    _skyScreen-&gt;recreate();\r\n                    _skyScreen-&gt;spriteEngine();\r\n                    showGrid();\r\n                    _skyScreen-&gt;flip();                                                   \r\n            }\r\n            performAutosave();\r\n            delay(calculateDelay(delayCount));\r\n            handleKey();\r\n    }\r\n<\/pre>\n<p>In my opinion, it much more readable and clear than it was. We can move forward and make it FSM-like and remove delay function and one more loop, but I don&#8217;t think we realy need to do this.<\/p>\n<p>Now, let&#8217;s look to the delay() function.<\/p>\n<pre>    void SkyEngine::delay(int32 amount) {\r\n            Common::Event event;\r\n            uint32 start = _system-&gt;getMillis();\r\n            _keyPressed.reset();\r\n            if (amount &lt; 0) amount = 0; do { while (_eventMan-&gt;pollEvent(event)) {\r\n                            switch (event.type) {\r\n                            case Common::EVENT_KEYDOWN:\r\n                                    _keyPressed = event.kbd;\r\n                                    break;\r\n                            case Common::EVENT_MOUSEMOVE:\r\n                                    if (!(_systemVars.systemFlags &amp; SF_MOUSE_LOCKED))\r\n                                            _skyMouse-&gt;mouseMoved(event.mouse.x, event.mouse.y);\r\n                                    break;\r\n                            case Common::EVENT_LBUTTONDOWN:\r\n                                    if (!(_systemVars.systemFlags &amp; SF_MOUSE_LOCKED))\r\n                                            _skyMouse-&gt;mouseMoved(event.mouse.x, event.mouse.y);\r\n                                    _skyMouse-&gt;buttonPressed(2);\r\n                                    break;\r\n                            case Common::EVENT_RBUTTONDOWN:\r\n                                    if (!(_systemVars.systemFlags &amp; SF_MOUSE_LOCKED))\r\n                                            _skyMouse-&gt;mouseMoved(event.mouse.x, event.mouse.y);\r\n                                    _skyMouse-&gt;buttonPressed(1);\r\n                                    break;\r\n                            default:\r\n                                    break;\r\n                            }\r\n                    }\r\n                    _system-&gt;updateScreen();\r\n                    if (amount &gt; 0)\r\n                            _system-&gt;delayMillis((amount &gt; 10) ? 10 : amount);\r\n            } while (_system-&gt;getMillis() &lt; start + amount);\r\n    }\r\n<\/pre>\n<p>There are 2 loops in delay() function. They make logicaly unrelated to each other actions. Outer loop makes delay, inner loop processes events. I think we need extract events processing to separate function (processEvents). This will make code more redable and willn&#8217;t change logic. Delay funtion became more understandable, cause programmer don&#8217;t need to filter event processing by his mind, he&#8217;s just seeing &#8220;processEvents&#8221; word and understands what&#8217;s going on. So, after extraction there are 2 functions:<\/p>\n<pre>    void SkyEngine::delay(int32 amount) {\r\n          uint32 start = _system-&gt;getMillis();\r\n          _keyPressed.reset();\r\n          if (amount &lt; 0) amount = 0; do { processEvents(); _system-&gt;updateScreen();\r\n                if (amount &gt; 0)\r\n                      _system-&gt;delayMillis((amount &gt; 10) ? 10 : amount);\r\n          } while (_system-&gt;getMillis() &lt; start + amount); } void SkyEngine::processEvents() { Common::Event event; while (_eventMan-&gt;pollEvent(event)) {\r\n                switch (event.type) {\r\n                case Common::EVENT_KEYDOWN:\r\n                      _keyPressed = event.kbd;\r\n                break;\r\n                case Common::EVENT_MOUSEMOVE:\r\n                      if (!(_systemVars.systemFlags &amp; SF_MOUSE_LOCKED))\r\n                            _skyMouse-&gt;mouseMoved(event.mouse.x, event.mouse.y);\r\n                      break;\r\n                case Common::EVENT_LBUTTONDOWN:\r\n                      if (!(_systemVars.systemFlags &amp; SF_MOUSE_LOCKED))\r\n                            _skyMouse-&gt;mouseMoved(event.mouse.x, event.mouse.y);\r\n                      _skyMouse-&gt;buttonPressed(2);\r\n                      break;\r\n                case Common::EVENT_RBUTTONDOWN:\r\n                      if (!(_systemVars.systemFlags &amp; SF_MOUSE_LOCKED))\r\n                            _skyMouse-&gt;mouseMoved(event.mouse.x, event.mouse.y);\r\n                      _skyMouse-&gt;buttonPressed(1);\r\n                      break;\r\n                default:\r\n                      break;\r\n                }\r\n          }\r\n    }\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>In my first post I&#8217;ve wrote about refactoring of SKY engine. Here I want to describe it in more details what did I mean. Let&#8217;s look to the main engine&#8217;s loop. It&#8217;s huge, so I&#8217;ll process it part by part First thing we&#8217;re seeing &#8211; nested pause loop. Are we really need it? Or may [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-22","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/posts\/22","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/comments?post=22"}],"version-history":[{"count":1,"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/posts\/22\/revisions"}],"predecessor-version":[{"id":24,"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/posts\/22\/revisions\/24"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/media?parent=22"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/categories?post=22"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/jakimushka\/wp-json\/wp\/v2\/tags?post=22"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}