{"id":23,"date":"2013-07-11T01:27:10","date_gmt":"2013-07-11T01:27:10","guid":{"rendered":"https:\/\/blogs.scummvm.org\/richiesams\/?p=23"},"modified":"2022-05-22T19:58:44","modified_gmt":"2022-05-22T19:58:44","slug":"the-engine-skeleton-gains-some-tendons-part-1","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/richiesams\/2013\/07\/11\/the-engine-skeleton-gains-some-tendons-part-1\/","title":{"rendered":"The Engine Skeleton Gains Some Tendons &#8211; Part 1"},"content":{"rendered":"<p>Being a little tired of the script system, I started last week by adding image handling, video handling and a text debug console to the engine. With that done, I tried piecing together how the script system worked as a whole. After a long talk with Fuzzie, we figured out the majority of the system worked and I&#8217;ve spent the beginning of this week putting it into code.<\/p>\n<p>I&#8217;ll start with the script system since it&#8217;s fresh in my mind. Rather than try to explain what I learned, I&#8217;ll just explain my current understanding of the system and it&#8217;s behavior.<\/p>\n<p>The system is governed by five main containers:<\/p>\n<pre class=\"brush:cpp\">Common::HashMap&lt;uint32, byte&gt; _globalState;\r\n\r\nCommon::List&lt;ActionNode *&gt; _activeNodes;\r\n\r\nCommon::HashMap&lt;uint32, Common::Array&lt;Puzzle *&gt;&gt; _referenceTable;\r\n\r\nCommon::Stack&lt;Puzzle *&gt; _puzzlesToCheck;\r\n\r\nCommon::List&lt;Puzzle&gt; _activePuzzles;\r\n\r\nCommon::List&lt;Control&gt; _activeControls;\r\n<\/pre>\n<p>_globalState\u00a0holds the state of the entire game. Each key is a hash that can represent anything from a timer to whether a certain puzzle has been solved. The value depends on the what the key is, however, the vast majority are boolean states (0 or 1).<\/p>\n<p>_activeNodes\u00a0holds&#8230; wait for it&#8230; the active ActionNodes. Imagine that! Nodes are anything that needs to be processed over time. For example, a timer, an animation, etc. I&#8217;ll explain further later in the post.<\/p>\n<p>_referenceTable\u00a0stores references to the Puzzles that certain globalState keys have. This can be thought of as a reverse of the\u00a0<a href=\"https:\/\/gist.github.com\/RichieSams\/5959662\" target=\"_blank\" rel=\"noopener\">Puzzle struct<\/a>. A Puzzle stores a list of globalState keys to be checked.\u00a0_referenceTable\u00a0stores which Puzzles reference certain globalState keys.\u00a0Why would we want to do this? It means that any puzzles loaded into the _reference table only have to be checked once, instead of every frame. When a value in _globalState is changed, it adds the referenced Puzzle to\u00a0_puzzlesToCheck<\/p>\n<p>_puzzlesToCheck\u00a0holds the Puzzles whose Criteria we want to check against\u00a0_globalState. This stack is exhausted every frame. It is filled either by\u00a0_referenceTable\u00a0or when we enter a new room.<\/p>\n<p>_activePuzzles\u00a0is where the room&#8217;s Puzzles are stored. The Puzzle pointers in\u00a0_referenceTable\u00a0and\u00a0_puzzlesToCheck\u00a0point to here.<\/p>\n<p>I realize that the descriptions are still a bit vague, so I figured I would go through an example of sorts and how the containers behave.<\/p>\n<p>Every time we change rooms:<\/p>\n<ol>\n<li>Clear _referenceTable, _puzzlesToCheck, and _activePuzzles<\/li>\n<li>Open and parse the corresponding .scr file into Puzzle structs and store them in _activePuzzles. (See last three blog posts)<\/li>\n<li>Iterate through all the Puzzles and their Criteria and create references from a globalState key to the Puzzle. (See createReferenceTable below)<\/li>\n<li>Add all Puzzles to _puzzlesToCheck<\/li>\n<\/ol>\n<pre class=\"brush:cpp\">void ScriptManager::createReferenceTable() {\r\n    \/\/ Iterate through each Puzzle\r\n    for (Common::List&lt;Puzzle&gt;::iterator activePuzzleIter = _activePuzzles.begin(); activePuzzleIter != _activePuzzles.end(); activePuzzleIter++) {\r\n        Puzzle *puzzlePtr = &amp;(*activePuzzleIter);\r\n\r\n        \/\/ Iterate through each Criteria and add a reference from the criteria key to the Puzzle\r\n        for (Common::List&lt;Criteria&gt;::iterator criteriaIter = activePuzzleIter-&gt;criteriaList.begin(); criteriaIter != (*activePuzzleIter).criteriaList.end(); criteriaIter++) {\r\n            _referenceTable[criteriaIter-&gt;key].push_back(puzzlePtr);\r\n\r\n            \/\/ If the argument is a key, add a reference to it as well\r\n            if (criteriaIter-&gt;argument)\r\n                _referenceTable[criteriaIter-&gt;argument].push_back(puzzlePtr);\r\n        }\r\n    }\r\n\r\n    \/\/ Remove duplicate entries\r\n    for (Common::HashMap&lt;uint32, Common::Array&lt;Puzzle *&gt;&gt;::iterator referenceTableIter; referenceTableIter != _referenceTable.end(); referenceTableIter++) {\r\n        removeDuplicateEntries(&amp;(referenceTableIter-&gt;_value));\r\n    }\r\n}\r\n<\/pre>\n<p>Every frame:<\/p>\n<ol>\n<li>Iterate through each ActionNode in _activeNodes and call process() on them<\/li>\n<li>If process() returns true, remove and delete the ActionNode<\/li>\n<\/ol>\n<pre class=\"brush:cpp\">void ScriptManager::updateNodes(uint32 deltaTimeMillis) {\r\n    \/\/ If process() returns true, it means the node can be deleted\r\n    for (Common::List&lt;ActionNode *&gt;::iterator iter = _activeNodes.begin(); iter != _activeNodes.end();) {\r\n        if ((*iter)-&gt;process(_engine, deltaTimeMillis)) {\r\n            \/\/ Remove the node from _activeNodes, then delete it\r\n            ActionNode *node = *iter;\r\n            iter = _activeNodes.erase(iter);\r\n            delete node;\r\n        } else {\r\n            iter++;\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<pre class=\"brush:cpp\">bool NodeTimer::process(ZEngine *engine, uint32 deltaTimeInMillis) {\r\n    _timeLeft -= deltaTimeInMillis;\r\n\r\n    if (_timeLeft &lt;= 0) {\r\n        engine-&gt;getScriptManager()-&gt;setStateValue(_key, 0);\r\n        return true;\r\n    }\r\n\r\n    return false;\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<ol>\n<li>While _puzzlesToCheck is not empty, pop a Puzzle off the stack and check its Criteria against\u00a0_globalState<\/li>\n<li>If any of the Criteria pass, call execute() on the corresponding ResultAction.\n<ul>\n<li>Some ResultAction&#8217;s might create ActionNode&#8217;s and add them to\u00a0_activeNodes. IE ActionTimer<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<pre class=\"brush:cpp\">void ScriptManager::checkPuzzleCriteria() {\r\n    while (!_puzzlesToCheck.empty()) {\r\n        Puzzle *puzzle = _puzzlesToCheck.pop();\r\n        \/\/ Check each Criteria\r\n        for (Common::List&lt;Criteria&gt;::iterator iter = puzzle-&gt;criteriaList.begin(); iter != puzzle-&gt;criteriaList.end(); iter++) {\r\n            bool criteriaMet = false;\r\n\r\n            \/\/ Get the value to compare against\r\n            byte argumentValue;\r\n            if ((*iter).argument)\r\n                argumentValue = getStateValue(iter-&gt;argument);\r\n            else\r\n                argumentValue = iter-&gt;argument;\r\n\r\n            \/\/ Do the comparison\r\n            switch ((*iter).criteriaOperator) {\r\n            case EQUAL_TO:\r\n                criteriaMet = getStateValue(iter-&gt;key) == argumentValue;\r\n                break;\r\n            case NOT_EQUAL_TO:\r\n                criteriaMet = getStateValue(iter-&gt;key) != argumentValue;\r\n                break;\r\n            case GREATER_THAN:\r\n                criteriaMet = getStateValue(iter-&gt;key) &gt; argumentValue;\r\n                break;\r\n            case LESS_THAN:\r\n                criteriaMet = getStateValue(iter-&gt;key) &lt; argumentValue;\r\n                break;\r\n            }\r\n\r\n            \/\/ TODO: Add logic for the different Flags (aka, ONCE_PER_INST)\r\n            if (criteriaMet) {\r\n                for (Common::List&lt;ResultAction *&gt;::iterator resultIter = puzzle-&gt;resultActions.begin(); resultIter != puzzle-&gt;resultActions.end(); resultIter++) {\r\n                    (*resultIter)-&gt;execute(_engine);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<pre class=\"brush:cpp\">bool ActionTimer::execute(ZEngine *zEngine) {\r\n    zEngine-&gt;getScriptManager()-&gt;addActionNode(new NodeTimer(_key, _time));\r\n    return true;\r\n}\r\n<\/pre>\n<p>So that&#8217;s the script system. I&#8217;ve tried to explain it in the best way possible, but if you guys have any questions or suggestions for my implementation, as always, feel free to comment.<\/p>\n<p>Details on the image handling, video handling and the text debug console will be in Part 2, which should be up some time tomorrow. As always, thanks for reading. \ud83d\ude42<\/p>\n<p>-RichieSams<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Being a little tired of the script system, I started last week by adding image handling, video handling and a text debug console to the engine. With that done, I tried piecing together how the script system worked as a whole. After a long talk with Fuzzie, we figured out the majority of the system [&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-23","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/23","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/comments?post=23"}],"version-history":[{"count":1,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/23\/revisions"}],"predecessor-version":[{"id":24,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/23\/revisions\/24"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/media?parent=23"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/categories?post=23"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/tags?post=23"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}