280 likes | 425 Views
Converting Asynchronous Code to Synchronous Code with Embedded Lua. MoCo Programming Language Junkies. Josh Handley February 6, 2014. http://www.meetup.com/mcpjunkies /. Motivation. How do we support custom test protocols for a medical diagnostic device? Simple scripting language
E N D
Converting Asynchronous Code to Synchronous Code with Embedded Lua MoCo Programming Language Junkies Josh Handley February 6, 2014 http://www.meetup.com/mcpjunkies/
Motivation • How do we support custom test protocols for a medical diagnostic device? • Simple scripting language • Add new protocols without new firmware • Hide complexity
What we Had voidTestingPage::instructionPageCancelled() { switch (screen_) { caseElectrodeSetup: test_->cancelTest(); // Mark this specific test as cancelled. A retry will make a new test. gotoConfirmCancelScreen(); break; caseAlignPupil: caseTesting: qCritical("instructionPageCancelled when aligning or testing pupil?"); // Can't happen. break; caseErrorOccurred: gotoConfirmCancelScreen(); break; caseConfirmingCancel: caseConfirmingSkip: emitrestartTest(); // User said no to cancel or skip. Tell the controller to restart the test. break; } }
What we Wanted connectElectrodes() alignPupil() setTestConfiguration(testParams) results = runTest() showResultsScreen(results)
What Language to Use • Custom language Too much work • Python Too big • Javascript No coroutines • Scheme Too many parentheses • Lua Just right
Overview of Lua • Small • 20,000 lines of C code • < 250K compiled • no dependencies • Easy to embed in C • Has coroutines • Significant commercial use • Game engines (Angry Birds, WoW) • Adobe Lightroom • Ginga(Brazillian Digital TV Spec)
Basic Types -- Number print(12) -- 12 print(1.2) -- 1.2 print((1.2 + 0.8)/2) -- 1 -- Bool print(trueorfalseandtrue) -- true -- String print("hello") -- hello print("hello "..tostring(1)) -- hello 1 print(string.format("hello %d", 1)) -- hello 1
DynamicTyping v = 12 print(type(v)) –- number v = "hello" print(type(v)) -- string
Functions - Recursion functionfactorial(n) if n == 0 then return 1 else return n * factorial(n - 1) end end print(factorial(10)) -- 3628800
Functions – Multiple Return Values functionaddVector(x1, y1, x2, y2) return x1 + y1, x2 +y2 end x, y = addVector(1, 6, 2, 4) print(x,y) -- 7 6
Functions – First Class function curry(f, x) returnfunction (y, ...) returnf(x,y,...) end end plusOne= curry(function(x, y) return x + y end, 1) print(plusOne(2)) -- 3
Global by Default -- global n = 12 s = "hello" functionsqr(x) return x * x end -- local to file localn_local = 12 locals_local = "hello2" localfunctionsqr_local(x) return x * x end -- local to block function foo() local message = "dude"-- not visible outside function print(message) print(s_local) -- locals in this file are in foo closure ("up values") end
Loops -- repeat i = 1 repeat print(i) i = i + 1 untili == 5 -- while i= 1 whilei < 5 do print(i) i = i + 1 end -- for fori = 1, 4 do print(i) end
Tables – THE Data Structure -- Arrays localarr = {1, 2, 3, 4, "foo"} print(arr[1]) -- 1 print(arr[5]) -- foo arr[8] = 12 print(arr[8]) – 12 print(arr[7]) -- nil
Tables – THE Data Structure -- Dictionaries localdict = { firstName = "John", lastName = "Doe", dateOfBirth = {year = 1999, month = 12, day = 15} } print(dict.firstName) -- John print(dict.dateOfBirth.year) -- 1999 print(dict["firstName"]) -- John
Tables - Iteration -- Arrays fori, v inipairs(arr) do print(i.." = "..tostring(v)) end -- Dictionaries for k, v in pairs(dict) do print(k.." = "..tostring(v)) end
Errors -- error function error("This will abort the program and show this message") -- assert function assert(foo == 6, "Error: foo is not 6") -- nil, message pattern local file, errorMessage = io.open("myfile.txt", "r") if file == nilthen error("Error opening file: "..errorMessage) end -- nil, message with assert local file2 = assert(io.open("loops.lua", "r"))
pcall localfunctionparseConfigFile(filename) … end local status, resultOrErrorMessage = pcall(parseConfigFile, "config.txt") if status then print(string.format("Sucessfully parsed config file.")) for k, v in pairs(resultOrErrorMessage) do print(k, v) end else print("Failed to parse config file. "..resultOrErrorMessage) end
Coroutines - Generators localevenNumbers = coroutine.create(function() locali = 2 whiletruedo coroutine.yield(i) i = i + 2 end end ) print(evenNumbers) -- thread: 002FF108 print(coroutine.resume(evenNumbers)) -- true 2 print(coroutine.resume(evenNumbers)) -- true 4
Coroutines – Producer/Consumer local producer = coroutine.create(function() fori = 1, 4 do print("Enter something:") coroutine.yield(io.read("*line")) end end ) local consumer = coroutine.create(function(prod) whiletruedo local status, value = coroutine.resume(prod) print("Consume: "..value) end end ) coroutine.resume(consumer, producer)
CallingLuafrom C #include<lua.h> #include<lauxlib.h> #include<lualib.h> int main(intargc, char *argv[]) { lua_State* L = luaL_newstate(); luaL_openlibs(L); constchar *luaCode= "print('Hello World')"; luaL_dostring(L, luaCode); lua_close(L); return 0; }
Exposing C functions to Lua intmyCFunction(lua_State* L) { constchar *arg = luaL_checkstring(L, -1); lua_pushstring(L, "Hello from C"); return 1; } int main(intargc, char *argv[]) { lua_State* L = luaL_newstate(); luaL_openlibs(L); lua_pushcfunction(L, myCFunction); lua_setglobal(L, "myCFunction"); constchar* luaCode = "local answerFromC = myCFunction('Hello From Lua')" "print('C says: '..answerFromC)"; luaL_dostring(L, luaCode); lua_close(L); return 0; }
Back to the Original Problem • Create Lua bindings for our existing C++ objects/functions • Load Lua protocol script from disk • Create coroutine for the script • C++ resumes the coroutine • Lua scripts yield back to C++ to wait for events
Coroutines Straight Line Code • C++ Event Loop • onStartTest() • Load protocol script • Create Luacoroutine • Resume coroutine • Back to event loop • onEventForLua() • Resume coroutine • If coroutine done end test • Back to event loop Lua showConnectElectrodeScreen() localevt repeat evt = waitForEvent() -- yields until evt.id == Event_Next
Protocols Look Synchronous functionrunTests(testParameterList) localpreliminaryResults = {} teststeps.doConnectElectrodes() teststeps.doAlignPupil() ui.showTestingScreen() -- Run the tests fori = 1, #testParameterListdo localparams = testParameterList[i] localtestStimulus = stimconfig.buildStimulusConfig(params.flash, params.background) table.insert(preliminaryResults, teststeps.doTest(testStimulus)) ui.showTestingProgress(i, #testParameterList) end returnpreliminaryResults end
Wrap Everything in a pcall whiletruedo localok,results_or_err = pcall(runTests, testParams) if ok then saveTestResults(results_or_err) break end local err = results_or_err -- If the user cancelled or skipped a test, confirm before taking action if err == RffID.Event_UI_Cancelledthen ifui.doConfirmCancelScreen() ~= RffID.Event_UI_Nextthen err = RffID.Event_UI_Restart end elseif err == RffID.Event_UI_Skippedthen ifui.doConfirmSkipScreen() ~= RffID.Event_UI_Nextthen err = RffID.Event_UI_Restart end end if err == RffID.Event_UI_Skippedthen break elseif err == RffID.Event_UI_Restartthen -- Back to top of loop to restart else -- Cancel or some other error, throw it again and let C++ (or another Luapcall) handle it error(err, 0) end end
Lovin’ Lua • 4,000+ lines of Lua code • Beyond protocol scripts • On-board diagnostics • Interactive Lua shell for testing on device • Windows Lua shell controls device via RPC • Report generation • Viewing saved test results • Manufacturing/calibration
Resources • Programming in Lua • http://www.lua.org/pil/ • Lua reference manual • http://www.lua.org/manual/5.2/ • luausers.org wiki • http://lua-users.org/wiki/ • Source code from this talk • http://github.com/jhandley/mcp-junkies-lua