{"id":57,"date":"2013-08-07T17:14:19","date_gmt":"2013-08-07T17:14:19","guid":{"rendered":"https:\/\/blogs.scummvm.org\/t0by\/?p=57"},"modified":"2022-05-24T17:17:11","modified_gmt":"2022-05-24T17:17:11","slug":"the-debug-console","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/t0by\/2013\/08\/07\/the-debug-console\/","title":{"rendered":"The debug console"},"content":{"rendered":"<p>So, we were talking about the debugger.<\/p>\n<p>We\u2019ve seen how hooks are inserted into the execution unit, let\u2019s see how you interact with the debugger.<br \/>\nWe extend Common::Console, which already provides some very minimal functionality.<br \/>\nIt mainly enables us to easily bind commands to functions, tokenizing arguments on our behalf and giving us C-style argc\/argv.<br \/>\nIt works like this:<\/p>\n<pre>Console::Console(WintermuteEngine *vm) : GUI::Debugger() {\r\n    DCmd_Register(\"print_foo\", WRAP_METHOD(Console, Cmd_PrintFoo));\r\n    DCmd_Register(\"do_bar\", WRAP_METHOD(Console, Cmd_DoBar));\r\n    \/\/\r\n}\r\n \r\n\/\/ ...\r\nbool Console::Cmd_AddBreakpoint(int argc, const char **argv) {\r\n    DebugPrintf(\"Foo!\\n\");\r\n    return true;\r\n}\r\n\/\/...<\/pre>\n<p>Then, we proceed to parse user input on our own.<\/p>\n<p>Our methods must return true or false \u2013 true for keeping the console open, false for closing it.<\/p>\n<p>Ideally, for simple tasks, we could work on the engine directly from there, e.g.:<\/p>\n<pre>DCmd_Register( ....  Cmd_SkipInstruction));\r\n \r\nCmd_SkipInstruction (...) {\r\n    \/\/ usage: skip threadnumber\r\n    _script[atoi(argv[1]))-&gt;_iP++;\r\n    return true;\r\n}<\/pre>\n<p>This could get messy fast, though.<br \/>\nWhat we have going on here, though, is a MVA sort of pattern, which is more or less like its more famous brother MVC, except that the +** is bidirectional \u2013 the adapter can fiddle with the model and vice versa \u2013 the reason is decoupling and ease of mantainance: <a href=\"http:\/\/en.wikipedia.org\/wiki\/Model%E2%80%93view%E2%80%93adapter\" rel=\"nofollow\">http:\/\/en.wikipedia.org\/wiki\/Model%E2%80%93view%E2%80%93adapter<\/a><\/p>\n<p><strong>MVC:<\/strong><\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/mvc.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-59\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/mvc.png\" alt=\"\" width=\"408\" height=\"210\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/mvc.png 408w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/mvc-300x154.png 300w\" sizes=\"auto, (max-width: 408px) 100vw, 408px\" \/><\/a><strong>MVA:<\/strong><\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/mva.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-60\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/mva.png\" alt=\"\" width=\"529\" height=\"160\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/mva.png 529w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/mva-300x91.png 300w\" sizes=\"auto, (max-width: 529px) 100vw, 529px\" \/><\/a>(Image: <a href=\"https:\/\/www.palantir.com\" rel=\"nofollow\">https:\/\/www.palantir.com<\/a>)<\/p>\n<p>We have a middle layer called the DebuggerAdapter that works on the script engine for us, while we need to knownothing about the script engine itself in the console proper.<\/p>\n<p>The header looks kinda like this:<\/p>\n<pre>namespace Wintermute {\r\nclass DebuggerAdapter {\r\npublic:\r\n    DebuggerAdapter(WintermuteEngine *vm);\r\n    \/\/ Called by Script (=~Model)\r\n    bool triggerBreakpoint(ScScript *script);\r\n    bool triggerStep(ScScript *script);\r\n    bool triggerWatch(ScScript *script, const char *symbol);\r\n    \/\/ Called by Console (~=View)\r\n    int addWatch(const char *filename, const char *symbol);\r\n    int addBreakpoint(const char *filename, int line);\r\n    \/\/ ...\r\n    int stepOver();\r\n    int stepInto();\r\n    int stepContinue();\r\n    \/\/ ...\r\n    SourceFile *_lastSource;\r\nprivate:\r\n    bool compiledExists(Common::String filename);\r\n    void reset();\r\n    WintermuteEngine *_engine;\r\n    int32 _lastDepth;\r\n    ScScript *_lastScript;\r\n    int32 _lastLine;\r\n};\r\n}<\/pre>\n<p>An example of how it works:<\/p>\n<pre>\/\/ in debugger.cpp:\r\n \r\nbool Console::Cmd_AddBreakpoint(int argc, const char **argv) {\r\n    \/**\r\n     * Add a breakpoint\r\n     *\/\r\n    if (argc == 3) {\r\n        int error = ADAPTER-&gt;addBreakpoint(argv[1], atoi(argv[2]));\r\n        if (!error) {\r\n            DebugPrintf(\"%s: OK\\n\", argv[0]);\r\n        } else if (error == NO_SUCH_SCRIPT) {\r\n            Common::String msg = Common::String::format(\"no such script: %s, breakpoint NOT created\\n\", argv[1]);\r\n            debugWarning(argv[0], ERROR, msg);\r\n        } else if (error == NO_SUCH_SOURCE) {\r\n            Common::String msg = Common::String::format(\"no such source file: %s\\n\", argv[1]);\r\n            debugWarning(argv[0], WARNING, msg);\r\n        } else if (error == NO_SUCH_LINE) {\r\n            Common::String msg = Common::String::format(\"source %s has no line %d\\n\", argv[1], atoi(argv[2]));\r\n            debugWarning(argv[0], WARNING, msg);\r\n        } else if (error == IS_BLANK) {\r\n            Common::String msg = Common::String::format(\"%s:%d looks like a comment\/blank line.\\n\", argv[1], atoi(argv[2]));\r\n            debugWarning(argv[0], WARNING, msg);\r\n        } else {\r\n            Common::String msg = Common::String::format(\"Error code %d\", error);\r\n            debugWarning(argv[0], WARNING, msg);\r\n        }\r\n    } else {\r\n        DebugPrintf(\"Usage: %s   to break at line  of file \\n\", argv[0]);\r\n    }\r\n    return true;\r\n}\r\n\r\n\/\/ in debugger_adapter.cpp\r\n \r\nint DebuggerAdapter::addBreakpoint(const char *filename, int line) {\r\n    \/\/ TODO: Check if file exists, check if line exists\r\n    assert(SCENGINE);\r\n    if (!compiledExists(filename)) {\r\n        return NO_SUCH_SCRIPT;\r\n    }\r\n    int isLegal = isBreakpointLegal(filename, line);\r\n    if (isLegal == OK) {\r\n        SCENGINE-&gt;addBreakpoint(filename, line);\r\n        return OK;\r\n    } else if (isLegal == IS_BLANK) {\r\n        \/\/ We don't have the SOURCE. A warning will do.\r\n        SCENGINE-&gt;addBreakpoint(filename, line);\r\n        return IS_BLANK;\r\n    } else if (isLegal == NO_SUCH_SOURCE) {\r\n        \/\/ We don't have the SOURCE. A warning will do.\r\n        SCENGINE-&gt;addBreakpoint(filename, line);\r\n        return NO_SUCH_SOURCE;\r\n    } else if (isLegal == NO_SUCH_LINE) {\r\n        \/\/ No line in the source A warning will do.\r\n        SCENGINE-&gt;addBreakpoint(filename, line);\r\n        return NO_SUCH_LINE;\r\n    } else {\r\n        \/\/ Something weird? Don't do anything.\r\n        return isLegal;\r\n    }\r\n}\r\n<\/pre>\n<p>This is a somewhat trivial case since SCENGINE gives us addBreakpoint and we simply wrap it, but DebuggerAdapter does get more complex.<\/p>\n<p>This way, when and if we want a prettier user interface, we don\u2019t need to worry (not too much, at least) about breaking functionality, and if we decide to restructure the script engine and \/ or the way the adapter interacts with it, we don\u2019t need to worry too much about rendering the interface unusable.<\/p>\n<p>This could actually turn out useful if somebody decides to stitch together a graphical debugger \u2013 just code the actual GUI and add water.<\/p>\n<p>One cool thing our interface does is displaying source files \u2013 we\u2019ll talk about it in the next installment.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>So, we were talking about the debugger. We\u2019ve seen how hooks are inserted into the execution unit, let\u2019s see how you interact with the debugger. We extend Common::Console, which already provides some very minimal functionality. It mainly enables us to easily bind commands to functions, tokenizing arguments on our behalf and giving us C-style argc\/argv. [&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-57","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/57","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/comments?post=57"}],"version-history":[{"count":2,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/57\/revisions"}],"predecessor-version":[{"id":61,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/57\/revisions\/61"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/media?parent=57"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/categories?post=57"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/tags?post=57"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}