learn webassembly: build web applications with native … · production coordinator: nilesh mohite...
TRANSCRIPT
LearnWebAssembly
Copyright©2018PacktPublishingAllrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishingoritsdealersanddistributors,willbeheldliableforanydamagescausedorallegedtohavebeencauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
CommissioningEditor:KunalChaudhariAcquisitionEditor:TrushaShriyanContentDevelopmentEditor:AishwaryaGawankarTechnicalEditor:SurabhiKulkarniCopyEditor:SafisEditingProjectCoordinator:SheejalShahProofreader:SafisEditingIndexer:PriyankaDhadkeGraphics:AlishonMendonsaProductionCoordinator:NileshMohite
Firstpublished:September2018
Productionreference:1240918
PublishedbyPacktPublishingLtd.LiveryPlace35LiveryStreetBirminghamB32PB,UK.
ISBN978-1-78899-737-9
www.packtpub.com
Tomybeautifulandinfinitelypatientwife,Elisabeth.Icouldn'thavedonethiswithoutyourloveandsupport.Toallthemembersofmywolfpack,howl.
mapt.io
Maptisanonlinedigitallibrarythatgivesyoufullaccesstoover5,000booksandvideos,aswellasindustryleadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.Formoreinformation,pleasevisitourwebsite.
Whysubscribe?SpendlesstimelearningandmoretimecodingwithpracticaleBooksandVideosfromover4,000industryprofessionals
ImproveyourlearningwithSkillPlansbuiltespeciallyforyou
GetafreeeBookorvideoeverymonth
Maptisfullysearchable
Copyandpaste,print,andbookmarkcontent
PacktPub.comDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewsletters,andreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
AbouttheauthorMikeRourkehasbeenwritingcodeforoveradecade.HegothisstartcreatingMicrosoftAccessapplicationsusingVBAanddecidedhewantedtoworkwithJavaScriptfull-timeafterbuildingaMozillaFirefoxextension.HehasaB.S.inmechanicalengineeringtechnologyandworkedprimarilyinproductdesign/manufacturingengineeringrolesbeforestartingacareerasasoftwareengineerin2017.Currently,heworksforaChicago-basedconsultingcompanyandisfocusedprimarilyonfrontendJavaScriptdevelopment.When'shenotwritingcode,he'soutinthewoodscampingwithhiswolfbrothers.
Iwouldliketothankmywife,Elisabeth,forherloveandsupport.IwouldalsoliketothankmycolleaguesatPanderaLabsfortheirenthusiasm,support,andvaluablesuggestions.
Aboutthereviewers
DanRutaisafreshgraduate,abouttostartanMScincomputervision.HegotstartedwithWebAssemblybyimplementingasmallweb-baseddeeplearninglibrary,andmessingaroundwithWebAssemblyandGPGPU.
OtherpublicationshehasworkedonincludeoccasionaltechnicalblogsonMedium,andateamresearchpapercombiningAI,AR,andWebGLshaderstoassistthevisuallyimpaired,whichhepresentedataconference.
HisprojectscanbefollowedonGitHubandMedium(DanRuta),oronhiswebsiteandtweets(Dan_Ruta).
MaximShaydoakaMoreasMaxGraeyisanindependentdeveloper,consultant,systemarchitectfromUkraine,hehasworkedwithatLaSoftasaCTOandisabigfanofopensourcecommunity.
HecontinuestobeanenduringcontributorforopensourceprojectsdedicatedtoWebAssembly,suchasAssemblyScriptlanguagethathasbeengainingalotofattentionlately.HehappenstobeveryinterestedindevelopmentofWebGL,WebVRtechnologies,andFlowBasedProgrammingaswell.
ThisprojectcouldnothavebeencompletedwithoutbeingreviewedbyAlonZakai(kripken)knownforhisworkonemscriptenandbinaryen.SpecialthankstoDanielWirtz(dcodeIO)whoisthemaincontributorofAssemplyScriptandanincrediblyproductivemate.Lastbutnottheleast,Iwouldliketothankmyparents—Mr.andMrsShaydo,withoutthemnoneofthiswouldindeedbepossible.
PacktissearchingforauthorslikeyouIfyou'reinterestedinbecominganauthorforPackt,pleasevisitauthors.packtpub.comandapplytoday.Wehaveworkedwiththousandsofdevelopersandtechprofessionals,justlikeyou,tohelpthemsharetheirinsightwiththeglobaltechcommunity.Youcanmakeageneralapplication,applyforaspecifichottopicthatwearerecruitinganauthorfor,orsubmityourownidea.
TableofContentsTitlePage
CopyrightandCredits
LearnWebAssembly
Dedication
PacktPub.com
Whysubscribe?
PacktPub.com
Contributors
Abouttheauthor
Aboutthereviewers
Packtissearchingforauthorslikeyou
Preface
Whothisbookisfor
Whatthisbookcovers
Togetthemostoutofthisbook
Downloadtheexamplecodefiles
Downloadthecolorimages
Conventionsused
Getintouch
Reviews
1. WhatisWebAssembly?
TheroadtoWebAssembly
TheevolutionofJavaScript
GoogleandNativeClient
Mozillaandasm.js
WebAssemblyisborn
WhatexactlyisWebAssemblyandwherecanIuseit?
Officialdefinition
Binaryinstructionformat
Portabletargetforcompilation
Thecorespecification
Languageconcepts
Semanticphases
TheJavaScriptandWebAPIs
SowillitreplaceJavaScript?
WherecanIuseit?
Whatlanguagesaresupported?
CandC++
Rust
Otherlanguages
Whatarethelimitations?
Nogarbage collection
NodirectDOMaccess
Nosupportinolderbrowsers
HowdoesitrelatetoEmscripten?
Emscripten'srole
TheEMSDKandBinaryen
Summary
Questions
Furtherreading
2. ElementsofWebAssembly-Wat,Wasm,andtheJavaScriptAPI
Commonstructureandabstractsyntax
Wat
DefinitionsandS-expressions
Values,types,andinstructions
Roleinthedevelopmentprocess
Binaryformatandthemodulefile(Wasm)
Definitionandmoduleoverview
Modulesections
TheJavaScriptandWebAPIs
WebAssemblystoreandobjectcaches
LoadingamoduleandtheWebAssemblynamespacemethods
WebAssemblyobjects
WebAssembly.Module
WebAssembly.Instance
WebAssembly.Memory
WebAssembly.Table
WebAssemblyerrors(CompileError,LinkError,RuntimeError)
ConnectingthedotswithWasmFiddle
WhatisWasmFiddle?
CcodetoWat
WasmtoJavaScript
Summary
Questions
Furtherreading
3. SettingUpaDevelopmentEnvironment
Installingthedevelopmenttooling
Operatingsystemsandhardware
macOS 
Ubuntu
Windows
Packagemanagers
HomebrewformacOS
AptforUbuntu
ChocolateyforWindows
Git
InstallingGitonmacOS
InstallingGitonUbuntu
InstallingGitonWindows
Node.js
nvm
InstallingnvmonmacOS
InstallnvmonUbuntu
InstallingnvmonWindows
InstallingNode.jsusingnvm
GNUmakeandrimraf
GNUMakeonmacOSandUbuntu
InstallingGNUMakeonmacOS
InstallingGNUMakeonUbuntu
InstallingGNUmakeonWindows
Installingrimraf
VSCode
InstallingVisualStudioCodeonmacOS
InstallingVisualStudioCodeonUbuntu
InstallingVSCodeonWindows
ConfiguringVSCode
Managingsettingsandcustomization
Extensionsoverview
ConfigurationforC/C++andWebAssembly
InstallingC/C++forVSCode
ConfiguringC/C++forVSCode
WebAssemblyToolkitforVSCode
Otherusefulextensions
Autorenametag
Bracketpaircolorizer
MaterialIconthemeandAtomOneLighttheme
Settingupfortheweb
Cloningthebookexamplesrepository
Installingalocalserver
Validatingyourbrowser
ValidatingGoogleChrome
ValidatingMozillaFirefox
Validatingotherbrowsers
Othertools
iTerm2formacOS
TerminatorforUbuntu
cmderforWindows
ZshandOh-My-Zsh
Summary
Questions
Furtherreading
4. InstallingtheRequiredDependencies
Thedevelopmentworkflow
Stepsintheworkflow
IntegratingToolingintotheworkflow
EmscriptenandtheEMSDK
Emscriptenoverview
WheredoestheEMSDKfitin?
Installingtheprerequisites
Commonprerequisites
InstallingtheprerequisitesonmacOS
InstallingtheprerequisitesonUbuntu
InstallingtheprerequisitesonWindows
InstallingandconfiguringtheEMSDK
Installationprocessacrossallplatforms
InstallationonmacOSandUbuntu
InstallationandconfigurationonWindows
ConfigurationinVSCode
Testingthecompiler
TheCcode
CompilingtheCcode
Summary
Questions
Furtherreading
5. CreatingandLoadingaWebAssemblyModule
CompilingCwithEmscriptengluecode
WritingtheexampleCcode
CompilingtheexampleCcode
OutputtingHTMLwithgluecode
OutputtinggluecodewithnoHTML
LoadingtheEmscriptenmodule
Pre-generatedloadingcode
Writingcustomloadingcode
CompilingCwithoutthegluecode
CcodeforWebAssembly
CompilingwithaBuildTaskinVSCode
FetchingandinstantiatingaWasmfile
CommonJavaScriptloadingcode
TheHTMLpage
Servingitallup
Summary
Questions
Furtherreading
6. InteractingwithJavaScriptandDebugging
TheEmscriptenmoduleversustheWebAssemblyobject
WhatistheEmscriptenmodule?
Defaultmethodsinthegluecode
DifferenceswiththeWebAssemblyobject
CallingcompiledC/C++functionsfromJavaScript
CallingfunctionsfromaModule 
Module.ccall()
Module.cwrap()
C++andnamemangling
CallingfunctionsfromaWebAssemblyinstance
CallingJavaScriptfunctionsfromC/C++
InteractingwithJavaScriptusinggluecode
Executingstringswithemscripten_run_script()
ExecutinginlineJavaScriptwithEM_ASM()
ReusinginlineJavaScriptwithEM_JS()
Examplesofusinggluecode
TheCcode
TheHTMLcode
Compilingandservingtheresult
InteractingwithJavaScriptwithoutgluecode
PassingJavaScripttoC/C++usingtheimportobject
CallingimportedfunctionsinC/C++
Anexamplewithoutgluecode
TheC++code
TheHTMLcode
Compilingandservingtheresult
AdvancedEmscriptenfeatures
Embind
FileSystemAPI
FetchAPI
Debugginginthebrowser
High-leveloverview
Usingsourcemaps
Summary
Questions
Furtherreading
7. CreatinganApplicationfromScratch
CooktheBooks– makingWebAssemblyaccountable
Overviewandfunctionality
JavaScriptlibrariesused
Vue
UIkit
Lodash
Data-drivendocuments
Otherlibraries
Candthebuildprocess
Settinguptheproject
ConfiguringforNode.js
Addingfilesandfolders
Configuringthebuildstep
SettingupamockAPI
DownloadingtheCstdlibWasm
Thefinalresult
BuildingtheCportion
Overview
Anoteregardingworkflow
Cfilecontents
Declarations
Linkedlistoperations
transactionsoperations
transactionscalculations
Categorycalculations
CompilingtoWasm
BuildingtheJavaScriptportion
Overview
Anoteaboutbrowsercompatibility
CreatingaWasminstanceininitializeWasm.js
InteractingwithWasminWasmTransactions.js
UtilizingtheAPIinapi.js
Managingglobalstateinstore.js
Theimportandstoredeclarations
Transactionsoperations
TransactionModalmanagement
Balancescalculation
Storeinitialization
Loadingtheapplicationinmain.js
Addingthewebassets
CreatingtheVuecomponents
ThestructureofaVuecomponent
TheAppcomponent
TheBalancesBar
TheTransactionsTab
TheChartsTab
Runningtheapplication
Validatingthe/srcfolder
Startitup!
Testingitout
Changinginitialbalances
Creatinganewtransaction
Deletinganexistingtransaction
Editinganexistingtransaction
TestingtheChartstab
Wrapup
Summary
Questions
Furtherreading
8. PortingaGamewithEmscripten
Overviewofthegame
WhatisTetris?
Thesourceofthesource
Anoteaboutporting
Gettingthecode
Buildingthenativeproject
Thegameinaction
Thecodebaseindepth
Breakingthecodeintoobjects
Theconstantsfile
Thepiececlass
Theconstructoranddraw()function
Themove(),rotate(),andisBlock()functions
ThegetColumn()andgetRow()functions
TheBoardclass
Theconstructoranddraw()function
TheisCollision()function
Theunite()function
ThedisplayScore()function
TheGameclass
Theconstructoranddestructor
Theloop()function
Themainfile
PortingtoEmscripten
Preparingforporting
What'schanging?
Addingthewebassets
Portingtheexistingcode
Updatingtheconstantsfile
Buildingandrunningthegame
BuildingwithVSCodetasks
BuildingwithaMakefile
Runningthegame
Summary
Questions
Furtherreading
9. IntegratingwithNode.js
WhyNode.js?
Seamlessintegration
Complementarytechnologies
Developmentwithnpm
Server-sideWebAssemblywithExpress
Overviewoftheproject
Expressconfiguration
InstantiatingaWasmmodulewithNode.js
Creatingamockdatabase
InteractingwiththeWebAssemblymodule
WrappinginteractioninTransaction.js
Transactionoperationsinassign-routes.js
Buildingandrunningtheapplication
Buildingtheapplication
Startingandtestingouttheapplication
Client-sideWebAssemblywithWebpack
Overviewoftheproject
WhatisWebpack?
InstallingandconfiguringWebpack
Dependenciesoverview
Configuringloadersandpluginsinwebpack.config.js
TheCcode
Definitionsanddeclarations
Thestart()function
TheupdateRectLocation()function
TheJavaScriptcode
Theimportstatements
Componentstate
Wasminitialization
Componentmounting
Componentrendering
Buildingandrunningtheapplication
Testingthebuild
Runningthestartscript
TestingWebAssemblymoduleswithJest
Thecodebeingtested
Testingconfiguration
Testsfilereview
Runningthetests
Summary
Questions
Furtherreading
10. AdvancedToolsandUpcomingFeatures
WABTandBinaryen
WABT–theWebAssemblybinarytoolkit
Binaryen
CompilingwithLLVM
Theinstallationprocess
Theexamplecode
TheC++file
TheHTMLfile
Compilingandrunningtheexample
Onlinetooling
WasmFiddle
WebAssemblyExplorer
WebAssemblyStudio
ParallelWasmwithWebWorkers
WebWorkersandWebAssembly
Creatingaworker
TheWebAssemblyworkflow
LimitationsinGoogleChrome
Overviewofthecode
TheCcode
TheJavaScriptcode
Definingthreadexecutioninworker.js
InteractingwithWasminWasmWorker.js
Loadingtheapplicationinindex.js
Thewebassets
Buildingandrunningtheapplication
CompilingtheCfiles
Interactingwiththeapplication
DebuggingWebWorkers
Upcomingfeatures
Thestandardizationprocess
Threads
Hostbindings
Garbagecollection
Referencetypes
Summary
Questions
Furtherreading
OtherBooksYouMayEnjoy
Leaveareview-letotherreadersknowwhatyouthink
PrefaceThisbookintroducesreaderstoWebAssembly,anewandexcitingtechnologycapableofexecutinglanguagesotherthanJavaScriptinthebrowser.ThebookdescribeshowtobuildaC/JavaScriptapplicationfromscratchthatusesWebAssemblyandtheprocessforportinganexistingC++codebasetoruninthebrowserwiththehelpofEmscripten.
WebAssemblyrepresentsanimportantshiftforthewebplatform.AsacompilationtargetforlanguagessuchasC,C++,andRust,itprovidestheabilitytobuildanewclassofapplication.WebAssemblyissupportedbyallofthemajorbrowservendorsandrepresentsacollaborativeeffort.
Inthisbook,we'lldescribetheelementsthatmakeupWebAssembly,aswellasitsorigins.We'llwalkthroughtheprocessofinstallingtherequiredtools,settingupadevelopmentenvironment,andinteractingwithWebAssembly.We'llworkthroughsimpleexamplesandprogressthroughincreasinglyadvancedusecases.Bytheendofthisbook,you'llbewell-equippedtouseWebAssemblyinyourC,C++,orJavaScriptproject.
WhothisbookisforIfyouareaC/C++programmerwhowishestobuildapplicationsfortheweb,orawebdeveloperwhowishestoimprovetheperformanceoftheirJavaScriptapplications,thenthisbookisforyou.ThebookisintendedfordevelopersfamiliarwithJavaScriptwhowouldn'tmindlearningsomeCandC++(andviceversa).ThisbookaccommodatesforC/C++programmersandJavaScriptprogrammersalikebyprovidingtwoexampleapplications.
WhatthisbookcoversChapter1,WhatisWebAssembly?,describestheoriginsofWebAssemblyandprovidesahigh-leveloverviewofthetechnology.ItcovershowWebAssemblycanbeused,whichprogramminglanguagesaresupported,anditscurrentlimitations.
Chapter2,ElementsofWebAssembly–Wat,Wasm,andtheJavaScriptAPI,outlinestheelementsthatcompriseWebAssembly.Itprovidesadetailedexplanationofthetextandbinaryformats,aswellasthecorrespondingJavaScriptandWebAPIs.
Chapter3,SettingUpaDevelopmentEnvironment,walksthroughthetoolingusedtodevelopwithWebAssembly.Itprovidestheinstallationinstructionsforeachplatformandprovidesrecommendationsforimprovingthedevelopmentexperience.
Chapter4,InstallingtheRequiredDependencies,providesinstructionsforinstallingthetoolchainrequirementsforeachplatform.Bytheendofthischapter,you'llbeabletocompileCandC++toWebAssemblymodules.
Chapter5,CreatingandLoadingaWebAssemblyModule,explainshowtogenerateaWebAssemblymoduleusingEmscriptenandhowflagsarepassedtothecompileraffecttheresultingoutput.ItdescribesthetechniquesforloadingaWebAssemblymoduleinthebrowser.
Chapter6,InteractingwithJavaScriptandDebugging,elaboratesonthedifferencesbetweenEmscripten'sModuleobjectandthebrowser'sglobalWebAssemblyobject.ThischapterdescribesthefeaturesEmscriptenprovidesaswellasinstructionsforgeneratingsourcemaps.
Chapter7,CreatinganApplicationfromScratch,walksthroughthecreationofaJavaScriptaccountingapplicationthatinteractswithaWebAssemblymodule.WewillwriteCcodetocalculatevaluesfromaccountingtransactionsandpassthedatabetweenJavaScriptandthecompiledWebAssemblymodule.
Chapter8,PortingaGamewithEmscripten,takesastep-by-stepapproachtoportinganexistingC++gametoWebAssemblyusingEmscripten.AfterreviewingtheexistingC++codebase,changesaremadetotheappropriatefilestoenablethegametoruninthebrowser.
Chapter9,IntegratingwithNode.js,demonstrateshowNode.jsandnpmcanbeusedwithWebAssemblyontheserverandclientside.ThechaptercoverstheuseofWebAssemblyinanExpressapplication,integratingWebAssemblywithwebpack,andtestingaWebAssemblymoduleusingJest.
Chapter10,AdvancedToolsandUpcomingFeatures,coversadvancedtools,usecases,andnewWebAssemblyfeaturescurrentlyintheprocessofstandardization.ThischapterdescribesWABT,Binaryen,andthetoolingavailableonline.Inthischapter,you'lllearnhowtocompileWebAssemblymodulesusingLLVMandhowWebAssemblymodulescanbeusedwithWebWorkers.Thechapterwrapsupwithadescriptionofthestandardizationprocessandareviewofsomeoftheexcitingfeaturesintheprocessofbeingaddedtothespecification.
TogetthemostoutofthisbookYoushouldhavesomeprogrammingexperienceandunderstandconceptssuchasvariables,andfunctions.Ifyou'veneverseenalineofJavaScriptorC/C++code,youmaywanttodosomepreliminaryresearchbeforeworkingthroughtheexamplesinthisbook.I'vechosentouseJavaScriptES6/7featuressuchasdestructuringandarrowfunctions,soifyouhaven'tworkedwithJavaScriptinthelast3-4years,thesyntaxmaylookslightlydifferent.
DownloadtheexamplecodefilesYoucandownloadtheexamplecodefilesforthisbookfromyouraccountatwww.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisitwww.packtpub.com/supportandregistertohavethefilesemaileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. Loginorregisteratwww.packtpub.com.2. SelecttheSUPPORTtab.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchboxandfollowtheonscreen
instructions.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Learn-WebAssembly.Incasethere'sanupdatetothecode,itwillbeupdatedontheexistingGitHubrepository.
Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!
DownloadthecolorimagesWealsoprovideaPDFfilethathascolorimagesofthescreenshots/diagramsusedinthisbook.Youcandownloadithere:https://www.packtpub.com/sites/default/files/downloads/9781788997379_ColorImages.pdf.
ConventionsusedThereareanumberoftextconventionsusedthroughoutthisbook.
CodeInText:Indicatescodewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandles.Hereisanexample:"instantiate()istheprimaryAPIforcompilingandinstantiatingWebAssemblycode."
Ablockofcodeissetasfollows:
intaddTwo(intnum){
returnnum+2;
}
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:
intcalculate(intfirstVal,intsecondVal){
returnfirstVal-secondVal;
}
Anycommand-lineinputoroutputiswrittenasfollows:
npminstall-gwebassembly
Bold:Indicatesanewterm,animportantword,orwordsthatyouseeonscreen.Forexample,wordsinmenusordialogboxesappearinthetextlikethis.Hereisanexample:"YoucandothisbypressingtheStartmenubutton,andright-clickingontheCommandPromptapplicationandselectingRunasadministrator."
Warningsorimportantnotesappearlikethis.
Tipsandtricksappearlikethis.
GetintouchFeedbackfromourreadersisalwayswelcome.
Generalfeedback:Emailfeedback@packtpub.comandmentionthebooktitleinthesubjectofyourmessage.Ifyouhavequestionsaboutanyaspectofthisbook,[email protected].
Errata:Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyouhavefoundamistakeinthisbook,wewouldbegratefulifyouwouldreportthistous.Pleasevisitwww.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetails.
Piracy:IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,wewouldbegratefulifyouwouldprovideuswiththelocationaddressorwebsitename.Pleasecontactusatcopyright@packtpub.comwithalinktothematerial.
Ifyouareinterestedinbecominganauthor:Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,pleasevisitauthors.packtpub.com.
ReviewsPleaseleaveareview.Onceyouhavereadandusedthisbook,whynotleaveareviewonthesitethatyoupurchaseditfrom?Potentialreaderscanthenseeanduseyourunbiasedopiniontomakepurchasedecisions,weatPacktcanunderstandwhatyouthinkaboutourproducts,andourauthorscanseeyourfeedbackontheirbook.Thankyou!
FormoreinformationaboutPackt,pleasevisitpacktpub.com.
WhatisWebAssembly?WebAssembly(Wasm)representsanimportantsteppingstoneforthewebplatform.Enablingadevelopertoruncompiledcodeonthewebwithoutapluginorbrowserlock-inpresentsmanynewopportunities.SomeconfusionexistsaboutwhatWebAssemblyis,asdoessomeskepticismaboutitsstayingpower.
Inthischapter,wewilldiscusshowWebAssemblycametobe,whatWebAssemblyiswithregardtotheofficialdefinition,andthetechnologiesitencompasses.Thepotentialusecases,supportedlanguages,andlimitationswillbecovered,aswellaswheretofindadditionalinformation.
Ourgoalforthischapteristounderstandthefollowing:
ThetechnologiesthatledthewayforWebAssemblyWhatWebAssemblyisandsomeofitspotentialusecasesWhichprogramminglanguagescanbeusedwithWebAssemblyThecurrentlimitationsofWebAssemblyHowWebAssemblyrelatestoEmscriptenandasm.js
TheroadtoWebAssemblyWebdevelopmenthashadaninterestinghistory,tosaytheleast.Several(failed)attemptshavebeenmadetoexpandtheplatformtosupportdifferentlanguages.Clunkysolutionssuchaspluginsfailedtostandthetestoftime,andlimitingausertoasinglebrowserisarecipefordisaster.
WebAssemblywasdevelopedasanelegantsolutiontoaproblemthathasexistedsincebrowserswerefirstabletoexecutecode:Ifyouwanttodevelopfortheweb,youhavetouseJavaScript.Fortunately,usingJavaScriptdoesn'thavethesamenegativeconnotationsithadbackintheearly2000s,butitcontinuestohavecertainlimitationsasaprogramminglanguage.Inthissection,we'regoingtodiscussthetechnologiesthatledtoWebAssemblytogetabettergraspofwhythisnewtechnologyisneeded.
TheevolutionofJavaScriptJavaScriptwascreatedbyBrendanEichinjust10daysbackin1995.Originallyseenasatoylanguagebyprogrammers,itwasusedprimarilytomakebuttonsflashorbannersappearonawebpage.ThelastdecadehasseenJavaScriptevolvefromatoytoaplatformwithprofoundcapabilitiesandamassivefollowing.
In2008heavycompetitioninthebrowsermarketresultedintheadditionofjust-in-time(JIT)compilers,whichincreasedtheexecutionspeedofJavaScriptbyafactorof10.Node.jsdebutedin2009andrepresentedaparadigmshiftinwebdevelopment.RyanDahlcombinedGoogle'sV8JavaScriptengine,aneventloop,andalow-levelI/OAPItobuildaplatformthatallowedfortheuseofJavaScriptacrosstheserverandclientside.Node.jsledtonpm,apackagemanagerthatallowedforthedevelopmentoflibrariestobeusedwithintheNode.jsecosystem.Asofthetimeofwriting,thereareover600,000packagesavailablewithhundredsbeingaddedeveryday:
Packagecountgrowthonnpmsince2012,takenfromModulecounts
It'snotjusttheNode.jsecosystemthatisgrowing;JavaScriptitselfisbeingactivelydeveloped.TheECMATechnicalCommittee39(TC39),whichdictatesthestandardsforJavaScriptandoverseestheadditionofnewlanguagefeatures,releasesyearlyupdatestoJavaScriptwithacommunity-drivenproposalprocess.Betweenitswealthoflibrariesandtooling,constantimprovementstothelanguage,andpossessingoneofthelargestcommunitiesofprogrammers,JavaScripthasbecomeaforcetobereckonedwith.
Butthelanguagedoeshavesomeshortcomings:
Upuntilrecently,JavaScriptonlyincluded64-bitfloatingpointnumbers.Thiscancauseissueswithverylargeorverysmallnumbers.BigInt,anewnumericprimitivethatcanalleviatesomeoftheseissues,isinthetheprocessofbeingaddedtotheECMAScriptspecification,butitmaytakesometimeuntilit'sfullysupportedinbrowsers.JavaScriptisweaklytyped,whichaddstoitsflexibility,butcancauseconfusionandbugs.Itessentiallygivesyouenoughropetohangyourself.JavaScriptisn'tasperformantascompiledlanguagesdespitethebesteffortsofthebrowservendors.Ifadeveloperwantstocreateawebapplication,theyneedtolearnJavaScript—whethertheylikeitornot.
ToavoidhavingtowritemorethanafewlinesofJavaScript,somedevelopersbuilttranspilerstoconvertotherlanguagestoJavaScript.Transpilers(orsource-to-sourcecompilers)aretypesofcompilersthatconvertsourcecodeinoneprogramminglanguagetoequivalentsourcecodeinanotherprogramminglanguage.TypeScript,whichisapopulartoolforfrontendJavaScriptdevelopment,transpilesTypeScripttovalidJavaScripttargetedforbrowsersorNode.js.Pickanyprogramminglanguageandthere'sagoodchancethatsomeonecreatedaJavaScripttranspilerforit.Forexample,ifyouprefertowritePython,youhaveabout15differenttoolsthatyoucanusetogenerateJavaScript.Intheend,though,it'sstillJavaScript,soyou'restillsubjecttotheidiosyncrasiesofthelanguage.
Asthewebevolvedintoavalidplatformforbuildinganddistributingapplications,moreandmorecomplexandresource-intensiveapplicationswerecreated.Inordertomeetthedemandsoftheseapplications,browservendorsbeganworkingonnewtechnologiestointegrateintotheirsoftwarewithout
disruptingthenormalcourseofwebdevelopment.GoogleandMozilla,creatorsofChromeandFirefox,respectively,tooktwodifferentpathstoachievethisgoal,culminatinginthecreationofWebAssembly.
GoogleandNativeClientGoogledevelopedNativeClient(NaCl)withtheintenttosafelyrunnativecodewithinawebbrowser.Theexecutablecodewouldruninasandboxandofferedtheperformanceadvantagesofnativecodeexecution.
Inthecontextofsoftwaredevelopment,asandboxisanenvironmentthatpreventsexecutablecodefrominteractingwithotherpartsofyoursystem.Itisintendedtopreventthespreadofmaliciouscodeandplacerestrictionsonwhatsoftwarecando.
NaClwastiedtoaspecificarchitecture,whilePortableNativeClient(PNaCl)wasanarchitecture-independentversionofNaCldevelopedtorunonanyplatform.Thetechnologyconsistedoftwoelements:
ToolchainswhichcouldtransformC/C++codetoNaClmodulesRuntimecomponentswhichwerecomponentsembeddedinthebrowserthatallowedexecutionofNaClmodules:
TheNativeClienttoolchainsandtheiroutputs
NaCl'sarchitecture-specificexecutable(nexe)waslimitedtoapplicationsandextensionsthatwereinstalledfromGoogle'sChromeWebStore,butPNaCl
executables(pexe)canbefreelydistributedonthewebandembeddedinwebapplications.PortabilitywasmadepossiblewithPepper,anopensourceAPIforcreatingNaClmodules,anditscorrespondingpluginAPI(PPAPI).PepperenabledcommunicationbetweenNaClmodulesandthehostingbrowser,andallowedforaccesstosystem-levelfunctionsinasafeandportableway.Applicationscouldbeeasilydistributedbyincludingamanifestfileandacompiledmodule(pexe)withthecorrespondingHTML,CSS,andJavaScript:
Pepper'sroleinaNativeClientapplication
NaClofferedpromisingopportunitiestoovercometheperformancelimitationsoftheweb,butithadsomedrawbacks.AlthoughChromehadbuilt-insupportforPNaClexecutablesandPepper,othermajorbrowserdidnot.Detractorsofthetechnologytookissuewiththeblack-boxnatureoftheapplicationsaswellasthepotentialsecurityrisksandcomplexity.
MozillafocuseditseffortsonimprovingtheperformanceofJavaScriptwithasm.js.Theywouldn'taddsupportforPeppertoFirefoxduetotheincompletenessofitsAPIspecificationandlimiteddocumentation.Intheend,NaClwasdeprecatedinMay,2017,infavorofWebAssembly.
Mozillaandasm.jsMozilladebutedasm.jsin2013andprovidedawayfordeveloperstotranslatetheirCandC++sourcecodetoJavaScript.Theofficialspecificationforasm.jsdefinesitasastrictsubsetofJavaScriptthatcanbeusedasalow-level,efficienttargetlanguageforcompilers.It'sstillvalidJavaScript,butthelanguagefeaturesarelimitedtothosethatareamenabletoahead-of-time(AOT)optimization.AOTisatechniquethatthebrowser'sJavaScriptengineusestoexecutecodemoreefficientlybycompilingitdowntonativemachinecode.asm.jsachievestheseperformancegainsbyhaving100%typeconsistencyandmanualmemorymanagement.
UsingatoolsuchasEmscripten,C/C++codecanbetranspileddowntoasm.jsandeasilydistributedusingthesamemeansasnormalJavaScript.Accessingthefunctionsinanasm.jsmodulerequireslinking,whichinvolvescallingitsfunctiontoobtainanobjectwiththemodule'sexports.
asm.jsisincrediblyflexible,however,certaininteractionswiththemodulecancausealossofperformance.Forexample,ifanasm.jsmoduleisgivenaccesstoacustomJavaScriptfunctionthatfailsdynamicorstaticvalidation,thecodecan'ttakeadvantageofAOTandfallsbacktotheinterpreter:
Theasm.jsAOTcompilationworkflow
asm.jsisn'tjustasteppingstone.ItformsthebasisforWebAssembly'sMinimumViableProduct(MVP).TheofficialWebAssemblysiteexplicitlymentionsasm.jsinthesectionentitledWebAssemblyHigh-LevelGoals.
SowhycreateWebAssemblywhenyoucoulduseasm.js?Asidefromthepotentialperformanceloss,anasm.jsmoduleisatextfilethatmustbetransferredoverthenetworkbeforeanycompilationcantakeplace.AWebAssemblymoduleisinabinaryformat,whichmakesitmuchmoreefficienttotransferduetoitssmallersize.
WebAssemblymodulesuseapromise-basedapproachtoinstantiation,whichtakesadvantageofmodernJavaScriptandeliminatestheneedforanyisthisloadedyet?code.
WebAssemblyisbornTheWorldWideWebConsortium(W3C),aninternationalcommunitybuilttodevelopwebstandards,formedtheWebAssemblyWorkingGroupinApril,2015,tostandardizeWebAssemblyandoverseethespecificationandproposalprocess.Sincethen,theCoreSpecificationandcorrespondingJavaScriptAPIandWebAPIhavebeenreleased.TheinitialimplementationofWebAssemblysupportinbrowserswasbasedonthefeaturesetofasm.js.WebAssembly'sbinaryformatandcorresponding.wasmfilecombinedfacetsofasm.jsoutputwithPNaCl'sconceptofadistributedexecutable.
SohowwillWebAssemblysucceedwhereNaClfailed?AccordingtoDr.AxelRauschmayer,therearethreereasonsdetailedathttp://2ality.com/2015/06/web-assembly.html#what-is-different-this-time:
"First,thisisacollaborativeeffort,nosinglecompanygoesitalone.Atthemoment,thefollowingprojectsareinvolved:Firefox,Chromium,EdgeandWebKit.
Second,theinteroperabilitywiththewebplatformandJavaScriptisexcellent.UsingWebAssemblycodefromJavaScriptwillbeassimpleasimportingamodule.
Third,thisisnotaboutreplacingJavaScriptengines,itismoreaboutaddinganewfeaturetothem.ThatgreatlyreducestheamountofworktoimplementWebAssemblyandshouldhelpwithgettingthesupportofthewebdevelopmentcommunity."
-Dr.AxelRauschmayer
WhatexactlyisWebAssemblyandwherecanIuseit?WebAssemblyhasasuccinctanddescriptivedefinitionontheofficialsite,butit'sonlyapieceofthepuzzle.ThereareseveralothercomponentsthatfallundertheumbrellaofWebAssembly.Understandingtheroleeachcomponentplayswillgiveyouabetterunderstandingofthetechnologyasawhole.Inthissection,wewillprovideadetailedbreakdownofWebAssembly'sdefinitionanddescribepotentialusecases.
OfficialdefinitionTheofficialWebAssemblywebsite(https://webassembly.org)offersthisdefinition:Wasmisabinaryinstructionformatforastack-basedvirtualmachine.Wasmisdesignedasaportabletargetforcompilationofhigh-levellanguageslikeC/C++/Rust,enablingdeploymentonthewebforclientandserverapplications.
Let'sbreakthatdefinitiondownintopartstoaddsomeclarification.
BinaryinstructionformatWebAssemblyactuallyencompassesseveralelements—abinaryformatandtextformat,whicharedocumentedintheCoreSpecification,thecorrespondingAPIs(JavaScriptandweb),andacompilationtarget.Thebinaryandtextformatbothmaptoacommonstructureintheformofanabstractsyntax.Tobetterunderstandabstractsyntax,itcanbeexplainedinthecontextofanabstractsyntaxtree(AST).AnASTisatreerepresentationofthestructureofsourcecodeforaprogramminglanguage.ToolssuchasESLintuseJavaScript'sASTtofindlintingerrors.ThefollowingexamplecontainsafunctionandthecorrespondingASTforJavaScript(takenfromhttps://astexplorer.net).
AsimpleJavaScriptfunctionfollows:
functiondoStuff(thingToDo){
console.log(thingToDo);
}
ThecorrespondingASTisasfollows:
{
"type":"Program",
"start":0,
"end":57,
"body":[
{
"type":"FunctionDeclaration",
"start":9,
"end":16,
"id":{
"type":"Identifier",
"start":17,
"end":26,
"name":"doStuff"
},
"generator":false,
"expression":false,
"params":[
{
"type":"Identifier",
"start":28,
"end":57,
"name":"thingToDo"
}
],
"body":{
"type":"BlockStatement",
"start":32,
"end":55,
"body":[
{
"type":"ExpressionStatement",
"start":32,
"end":55,
"expression":{
"type":"CallExpression",
"start":32,
"end":54,
"callee":{
"type":"MemberExpression",
"start":32,
"end":43,
"object":{
"type":"Identifier",
"start":32,
"end":39,
"name":"console"
},
"property":{
"type":"Identifier",
"start":40,
"end":43,
"name":"log"
},
"computed":false
},
"arguments":[
{
"type":"Identifier",
"start":44,
"end":53,
"name":"thingToDo"
}
]
}
}
]
}
}
],
"sourceType":"module"
}
AnASTmaybeverbose,butitdoesanexcellentjobatdescribingthecomponentsofaprogram.RepresentingsourcecodeinanASTmakesverificationandcompilationsimpleandefficient.WebAssemblycodeintextformatisserializedintoanASTandcompiledtothebinaryformat(asa.wasmfile),whichisfetched,loaded,andutilizedbyawebpage.Whenthemoduleisloaded,thebrowser'sJavaScriptengineutilizesadecodingstacktodecodethe.wasmfileintoanAST,performtypechecking,andinterpretittoexecutefunctions.WebAssemblystartedasabinaryinstructionformatforanAST.DuetotheperformanceimplicationsofverifyingWasmexpressionsthatreturnvoid,thebinaryinstructionformatwasupdatedtotargetastackmachine.
Astackmachineconsistsoftwoelements:astackandinstructions.Astackisadatastructurewithtwooperations:pushandpop.Itemsarepushedontothestackandsubsequentlypoppedfromthestackinlastin,firstout(LIFO)order.Astackalsoincludesapointer,whichpointstotheitematthetopofthestack.Instructionsrepresentactionstoperformontheitemsinthestack.Forexample,anADDinstructionmightpopthetoptwoitemsfromthestack(thevalues100and10),andpushasingleitemwiththesumbackontothestack(thevalue110):
Asimplestackmachine
WebAssembly'sstackmachineoperatesinthesameway.Aprogramcounter(pointer)maintainstheexecutionpositionwithinthecodeandavirtualcontrolstackkeepstrackofblocksandifconstructsastheyareentered(pushed)andexited(popped).TheinstructionsareexecutedwithnoreferencetoanAST.Thus,thebinaryinstructionformatportionofthedefinitionreferstoabinaryrepresentationofinstructionsthatareinaformatreadablebythedecodingstackinthebrowser.
PortabletargetforcompilationWebAssemblywasdesignedfromthebeginningwithportabilityinmind.PortabilityinthiscontextmeansthatWebAssembly'sbinaryformatcanbeexecutedefficientlyonavarietyofoperatingsystemsandinstructionsetarchitectures,onandofftheweb.ThespecificationforWebAssemblydefinesportabilityinthecontextofanexecutionenvironment.WebAssemblywasdesignedtorunefficientlyinenvironmentsthatmeetcertaincharacteristics,mostofwhicharerelatedtomemory.WebAssembly'sportabilitycanalsobeattributedtotheabsenceofaspecificAPIaroundthecoretechnologies.Instead,itdefinesanimportmechanismwherethesetofavailableimportsisdefinedbythehostenvironment.
Inanutshell,thismeansthatWebAssemblyisn'ttiedtoaspecificenvironment,suchasthewebordesktop.TheWebAssemblyWorkingGrouphasdefinedaWebAPI,butthat'sseparatefromtheCoreSpecification.TheWebAPIcaterstoWebAssembly,nottheotherwayaround.
ThecompilationaspectofthedefinitionindicatesthatWebAssemblywillbesimpletocompiledowntoitsbinaryformatfromsourcecodewritteninhigh-levellanguages.TheMVPfocusesontwolanguages,CandC++,butRustcanalsobeusedgivenitssimilaritiestoC++.CompilationwillbeachievedthroughtheuseofaClang/LLVMbackend,althoughwe'llbeusingEmscripteninthisbooktogenerateourWasmmodules.Theplanistoeventuallyaddsupportforotherlanguagesandcompilers(suchasGCC),buttheMVPisfocusedonLLVM.
ThecorespecificationTheofficialdefinitiongivessomehigh-levelinsightintotheoveralltechnology,butforthesakeofcompleteness,it'sworthdiggingalittledeeper.WebAssembly'sCoreSpecificationistheofficialdocumenttoreferenceifyouwanttounderstandWebAssemblyataverygranularlevel.Ifyou'reinterestedinlearningaboutthecharacteristicsoftheruntimestructurewithregardtotheexecutionenvironment,checkoutsection4:Execution.Wewon'tcoverthathere,butunderstandingwheretheCoreSpecificationfitsinwillhelpinestablishingacompletedefinitionofWebAssembly.
LanguageconceptsTheCoreSpecificationstatesWebAssemblyencodesalow-level,assembly-likeprogramminglanguage.Thespecificationdefinesthestructure,execution,andvalidationofthislanguageaswellasthedetailsofthebinaryandtextformats.Thelanguageitselfisstructuredaroundthefollowingconcepts:
Values,orrathervaluetypesthatWebAssemblyprovidesInstructionsthatareexecutedwithinthestackmachineTrapsproducedundererrorconditionsandabortexecutionFunctionsintowhichcodeisorganized,eachofwhichtakesasequenceofvaluesasparametersandreturnsasequenceofvaluesasaresultTables,whicharearraysofvaluesofaparticularelementtype(suchasfunctionreferences)thatareselectablebytheexecutingprogramLinearMemory,whichisanarrayofrawbytesthatcanbeusedtostoreandloadvaluesModules,WebAssemblybinary(.wasmfile)thatcontainsfunction,tables,andlinearmemoriesEmbedder,themechanismbywhichWebAssemblycanbeexecutedinahostenvironment,suchasawebbrowser
Functions,tables,memory,andmoduleshavedirectcorrelationswiththeJavaScriptAPIandareimportanttobeawareof.TheseconceptsdescribetheunderlyingstructureofthelanguageitselfandhowtowriteorencodeWebAssembly.Withregardtousage,understandingthecorrespondingsemanticphasesofWebAssemblyprovidesacompletedefinitionofthetechnology:
SemanticphasesTheCoreSpecificationdescribesthedifferentphasesanencodedmodule(.wasmfile)undergoeswhenitisbeingutilizedinahostenvironment(suchasawebbrowser).Thisaspectofthespecificationrepresentshowtheoutputishandledandexecuted:
Decoding:ThebinaryformatisconvertedintoamoduleValidation:Thedecodedmoduleundergoesvalidationchecks(suchastypechecking)toensurethemoduleiswellformedandsafeExecution,Part1:Instantiation:Amoduleinstance,whichisthedynamicrepresentationofthemodule,isinstantiatedbyinitializingtheGlobals,Memories,andTables,andinvokesthemodule'sstart()functionExecution,Part2:Invocation:Exportedfunctionsarecalledfromthemoduleinstance:
Thefollowingdiagramprovidesavisualrepresentationofthesemanticphases:
Semanticphasesofmoduleuse
TheJavaScriptandWebAPIsTheWebAssemblyWorkingGroupalsoreleasedAPIspecificationsforinteractingwithJavaScriptandtheweb,whichqualifiesthemforinclusionintheWebAssemblytechnologyspace.TheJavaScriptAPIisscopedtotheJavaScriptlanguageitself,withoutbeingspecificallytiedtoanenvironment(forexample,webbrowsersorNode.js).Itdefinesclasses,methods,andobjectsforinteractingwithWebAssemblyandmanagingthecompilationandinstantiationprocesses.TheWebAPIisanextensionoftheJavaScriptAPIthatdefinesfunctionalityspecifictowebbrowsers.TheWebAPIspecificationcurrentlyonlydefinestwomethods,compileStreamingandinstantiateStreaming,whichareconveniencemethodsthatsimplifytheuseofWasmmodulesinthebrowser.ThesewillbecoveredingreaterdetailinChapter2,ElementsofWebAssembly-Wat,Wasm,andtheJavaScriptAPI.
SowillitreplaceJavaScript?WebAssembly'sultimategoalisnottoreplaceJavaScript,butrathertocomplementit.JavaScript'srichecosystemandflexibilitystillmakesittheideallanguagefortheweb.WebAssembly'sJavaScriptAPImakesinteroperabilitybetweenthetwotechnologiesrelativelysimple.SowillyoubeabletobuildawebapplicationusingjustWebAssembly?OneoftheexplicitgoalsofWebAssemblyisportability,andreplicatingallofJavaScript'sfunctionalitycouldinhibitthatgoal.However,theofficialsiteincludesagoaltoexecuteandintegratewellwiththeexistingwebplatform,soonlytimewilltell.ItmaynotbepracticaltowritetheentirecodebaseinalanguagethatcompilesdowntoWebAssembly,butmovingsomeoftheapplicationlogictoWasmmodulescouldbebeneficialintermsofperformanceandloadtimes.
WherecanIuseit?WebAssembly'sofficialsitehasanextensivelistofpotentialusecases.I'mnotgoingtocoverthemallhere,butthereareseveralthatrepresentsignificantenhancementstothecapabilitiesofthewebplatform:
Image/videoeditingGamesMusicapplications(streaming,caching)ImagerecognitionLivevideoaugmentationVRandaugmentedreality
AlthoughsomeoftheseusecasesaretechnicallyfeasiblewithJavaScript,HTML,andCSS,usingWebAssemblycanoffersignificantperformancegains.Servingupabinaryfile(insteadofasingleJavaScriptfile)cangreatlyreducethebundlesize,andinstantiatingtheWasmmoduleonpageloadspeedsupcodeexecution.
WebAssemblyisn'tjustlimitedtothebrowser.Outsidethebrowser,youcoulduseittobuildhybridnativeappsonmobiledevicesorperformserver-sidecomputationsofuntrustedcode.UsingWasmmodulesforphoneappscouldbeincrediblybeneficialintermsofpowerusageandperformance.
WebAssemblyalsooffersflexibilitywithregardtohowitcanbeused.YoucanwriteyourentirecodebaseinWebAssembly,althoughthismaynotbepracticalinitscurrentformorinthecontextofawebapplication.GivenWebAssembly'srobustJavaScriptAPI,youcouldwritetheUIinJavaScript/HTMLanduseWasmmodulesforfunctionalitythatdoesn'tdirectlyaccesstheDOM.Onceadditionallanguagesaresupported,objectscanbeeasilypassedbetweentheWasmmoduleandJavaScriptcode,whichwillgreatlysimplifyintegrationandincreasedeveloperadoption.
Whatlanguagesaresupported?WebAssembly'shigh-levelgoalsfortheirMVPwastoprovideroughlythesamefunctionalityasasm.js.Thetwotechnologiesareverycloselyrelated.C,C++,andRustareverypopularlanguagesthatsupportmanualmemoryallocation,whichmadethemidealcandidatesfortheinitialimplementation.Inthissection,we'regoingtoprovideabriefoverviewofeachprogramminglanguage.
CandC++CandC++arelow-levelprogramminglanguagesthathavebeenaroundforover30years.Cisproceduralanddoesn'tinherentlysupportobject-orientedprogrammingconceptssuchasclassesandinheritance,butit'sfast,portable,andwidelyused.
C++wasbuilttofillthegapsinCbyaddingfeaturessuchasoperatoroverloadingandimprovedtypechecking.Bothlanguagesconsistentlyrankinthetop10mostpopularprogramminglanguages,whichmakethemideallysuitedfortheMVP:
TIOBEVeryLongTermHistoryofthetop10programminglanguagesCandC++supportisalsobakedintoEmscripten,soinadditiontosimplifyingthecompilationprocess,itallowsyoutotakeadvantageofWebAssembly'sfullcapabilities.ItisalsopossibletocompileC/C++codedowntoa.wasmfileusingLLVM.LLVMisacollectionofmodularandreusablecompilerandtoolchain
technologies.Inanutshell,it'saframeworkthatsimplifiestheconfigurationofacompilationprocessfromsourcecodetomachinecode.Ifyoumadeyourownprogramminglanguageandwouldliketobuildacompiler,LLVMhastoolstosimplifytheprocess.I'll
coverhowtocompileC/C++into.wasmfilesusingLLVMinChapter10,AdvancedToolsandUpcomingFeatures.
ThefollowingsnippetdemonstrateshowtoprintHelloWorld!totheconsoleusingC++:#include<iostream>
intmain(){std::cout<<"Hello,World!\n";return0;}
OtherlanguagesVarioustoolingexiststoenabletheuseofWebAssemblywithsomeoftheotherpopularprogramminglanguages,althoughtheyaremostlyexperimental:
C#viaBlazorHaxeviaWebIDLJavaviaTeaVMorBytecoderKotlinviaTeaVMTypeScriptviaAssemblyScript
ItisalsotechnicallypossibletotranspilealanguagetoCandconsequentlycompilethattoaWasmmodule,butthesuccessofcompilationiscontingentontheoutputofthetranspiler.Morethanlikely,you'dhavetomakesignificantchangestothecodetogetittowork.
Whatarethelimitations?Admittedly,WebAssemblyisnotwithoutitslimitations.Newfeaturesarebeingactivelydevelopedandthetechnologyisconstantlyevolving,buttheMVPfunctionalityrepresentsonlyaportionofWebAssembly'scapabilities.Inthissection,we'llcoversomeoftheselimitationsandhowtheyimpactthedevelopmentprocess.
NogarbagecollectionWebAssemblysupportsaflatlinearmemory,whichisn'talimitationperse,butrequiressomeunderstandingofhowtoexplicitlyallocatememorytoexecutecode.CandC++werelogicalchoicesfortheMVPbecausememorymanagementisbuiltintothelanguage.Thereasonwhysomeofthemorepopularhigh-levellanguagessuchasJavaweren'tincludedinitiallyisduetosomethingcalledgarbagecollection(GC).
GCisaformofautomatedmemorymanagementwhereinmemoryoccupiedbyobjectsthatarenolongerinusebytheprogramisreclaimedautomatically.GCisanalogoustoanautomatictransmissiononacar.Ithasbeenheavilyoptimizedbyskilledengineerstooperateasefficientlyaspossible,butlimitstheamountofcontrolthedriverhas.Manuallyallocatingmemoryislikedrivingacarwithamanualtransmission.Itaffordsgreatercontroloverspeedandtorque,butmisuseorlackofexperiencecanleaveyoustrandedwithaseverelydamagedcar.PartofCandC++'sexcellentperformanceandspeedcanbeattributedtothemanualallocationofmemory.
GClanguagesallowyoutoprogramwithouthavingtoworryaboutmemoryavailabilityorallocation.JavaScriptisanexampleofaGClanguage.Thebrowserengineemployssomethingcalledamark-and-sweepalgorithmtocollectunreachableobjectsandfreeupthecorrespondingmemory.SupportforGClanguagesiscurrentlybeingworkedoninWebAssembly,butit'shardtosayexactlywhenitwillbecompleted.
NodirectDOMaccessWebAssemblyisunabletoaccesstheDOM,soanyDOMmanipulationneedstobedoneindirectlythroughJavaScriptorusingatoolsuchasEmscripten.ThereareplanstoaddtheabilitytoreferenceDOMandotherWebAPIobjectsdirectly,butthat'sstillintheproposalphase.DOMmanipulationwilllikelygohandinhandwithGClanguages,sinceitwillallowtheseamlesspassingofobjectsbetweenWebAssemblyandJavaScriptcode.
NosupportinolderbrowsersOlderbrowsersdon'thavetheglobalWebAssemblyobjectavailabletoinstantiateandloadWasmmodules.Thereareexperimentalpolyfillsthatutilizeasm.jsiftheobjectisn'tfound,buttheWebAssemblyWorkingGroupcurrentlyhasnoplanstocreateone.Sinceasm.jsandWebAssemblyarecloselyrelated,simplyservingupanasm.jsfileiftheWebAssemblyobjectisunavailablewillstillofferperformancegainswhileaccommodatingforbackwardcompatibility.YoucanseewhichbrowserscurrentlysupportWebAssemblyathttps://caniuse.com/#feat=wasm.
HowdoesitrelatetoEmscripten?Emscriptenisthesource-to-sourcecompilerthatcangenerateasm.jsfromCandC++sourcecode.We'lluseitasabuildtooltogeneratetheWasmmodules.Inthissection,we'llquicklyreviewhowEmscriptenrelatestoWebAssembly.
Emscripten'sroleEmscriptenisanLLVM-to-JavaScriptcompiler,whichmeansittakesLLVMbitcodeoutputofacompilersuchasClang(forCandC++),andconvertsthattoJavaScript.Itisn'tonespecifictechnology,butratheracombinationoftechnologiesthatworktogethertobuild,compile,andrunasm.js.TogenerateWasmmodules,we'llusetheEmscriptenSDK(EMSDK)Manager:
WasmmodulegenerationwiththeEMSDK
TheEMSDKandBinaryenInChapter4,InstallingtheRequiredDependencies,we'llinstalltheEMSDKanduseittomanagethedependenciesrequiredtocompileCandC++toWasmmodules.EmscriptenusesBinaryen'sasm2wasmtooltocompiletheasm.jsoutputbyEmscriptentoa.wasmfile.BinaryenisacompilerandtoolchaininfrastructurelibrarythatincludestoolstocompilevariousformatstoWebAssemblymodulesandviceversa.UnderstandingtheinnerworkingsofBinaryenisn'trequiredtouseWebAssembly,butitisimportanttobeawareoftheunderlyingtechnologiesandhowtheyworktogether.BypassingcertainflagsintothecompilecommandforEmscripten(emcc),wecanpipetheresultantasm.jscodetoBinaryentooutputour.wasmfile.
SummaryInthischapter,wediscussedthehistoryofWebAssemblywithregardtothetechnologiesthatledtoitscreation.AdetailedoverviewofthedefinitionofWebAssemblywasprovidedtoallowforagreaterunderstandingoftheunderlyingtechnologiesinvolved.
TheCoreSpecification,JavaScriptAPI,andWebAPIwerepresentedasimportantelementsofWebAssemblyanddemonstratehowthetechnologywillevolve.Wealsoreviewedpotentialsusecases,currentlysupportedlanguages,andtoolsthatenabletheuseofnon-supportedlanguages.
ThelimitationsofWebAssemblyaretheabsenceofGC,theinabilitytocommunicatedirectlywiththeDOM,andthelackofsupportforolderbrowsers.Thesewerediscussedtoconveythenewnessofthetechnologyandshedlightonsomeofitsshortcomings.Finally,wediscussedEmscripten'sroleinthedevelopmentprocessandwhereitfitsintotheWebAssemblydevelopmentworkflow.
InChapter2,ElementsofWebAssembly-Wat,Wasm,andtheJavaScriptAPI,we'llbedivingdeeperintotheelementsthatmakeupWebAssembly:theWebAssemblytextformat(Wat),binaryformat(Wasm),JavaScript,andWebAPIs.
Questions1. WhichtwotechnologiesinfluencedthecreationofWebAssembly?2. WhatisastackmachineandhowdoesitrelatetoWebAssembly?3. InwhatwaysdoesWebAssemblycomplementJavaScript?4. WhichthreeprogramminglanguagescanbecompiledtoWasmmodules?5. WhatroledoesLLVMplaywithregardtoWebAssembly?6. WhatarethreepotentialusecasesforWebAssembly?7. HowareDOMaccessandGCrelated?8. WhattooldoesEmscriptenusetogenerateWasmmodules?
FurtherreadingOfficialWebAssemblysite:https://webassembly.orgNativeClienttechnicaloverview:https://developer.chrome.com/native-client/overview
TheLLVMCompilerInfrastructureProject:https://llvm.orgAboutEmscripten:http://kripken.github.io/emscripten-site/docs/introducing_emscripten/about_emscripten.html
asm.jsspecification:http://asmjs.org/spec/latest
ElementsofWebAssembly-Wat,Wasm,andtheJavaScriptAPIChapter1,WhatisWebAssembly?,describedthehistoryofWebAssemblyandprovidedahigh-leveloverviewofthetechnologyaswellasthepotentialusecasesandlimitations.WebAssemblywasdescribedasbeingcomposedofmultipleelements,notjustthebinaryinstructionformatspecifiedintheofficialdefinition.
Inthischapter,wewilldigintotheelementsthatcorrespondtotheofficialspecificationscreatedbytheWebAssemblyWorkingGroup.WewillexaminetheWatandthebinaryformatingreaterdetailtogainabetterunderstandingofhowtheyrelatetomodules.WewillreviewtheJavaScriptAPIandWebAPItoensureyou'reabletoutilizetheWebAssemblyeffectivelyinthebrowser.
Ourgoalforthischapteristounderstandthefollowing:
HowthetextandbinaryformatsarerelatedWhatWatisandwhereitfitsintothedevelopmentprocessThebinaryformatandmodule(Wasm)fileThecomponentsoftheJavaScriptandWebAPIandhowtheyrelatetotheWasmmoduleHowtoutilizeWasmFiddletoevaluatethephasesofWebAssembly(C/C++>Wat>Wasm)
CommonstructureandabstractsyntaxInChapter1,WhatisWebAssembly?,wetalkedabouthowthebinaryandtextformatsofWebAssemblybothmaptoacommonstructureintheformofanabstractsyntax.Beforegettingintothenutsandboltsoftheseformats,it'sworthmentioninghowthesearerelatedwithintheCoreSpecification.Thefollowingdiagramisavisualrepresentationofthetableofcontents(withsomesectionsexcludedforclarity):
CoreSpecificationtableofcontents
Asyoucansee,theTextFormatandBinaryFormatsectionscontainsubsectionsforValues,Types,Instructions,andModulesthatcorrelatewiththeStructuresection.Consequently,muchofwhatwecoverinthenextsectionforthetextformathavedirectcorollarieswiththebinaryformat.Withthatinmind,let'sdiveintothetextformat.
WatTheTextFormatsectionoftheCoreSpecificationprovidestechnicaldescriptionsforcommonlanguageconceptssuchasvalues,types,andinstructions.Theseareimportantconceptstoknowandunderstandifyou'replanningonbuildingtoolingforWebAssembly,butnotnecessaryifyoujustplanonusingitinyourapplications.Thatbeingsaid,thetextformatisanimportantpartofWebAssembly,sothereareconceptsyoushouldbeawareof.Inthissection,wewilldigintosomeofthedetailsofthetextformatandhighlightimportantpointsfromtheCoreSpecification.
DefinitionsandS-expressionsTounderstandWat,let'sstartwiththefirstsentenceofthedescriptiontakendirectlyfromtheWebAssemblyCoreSpecification:
"ThetextualformatforWebAssemblymodulesisarenderingoftheirabstractsyntaxintoS-expressions."
Sowhataresymbolicexpressions(S-expressions)?S-expressionsarenotationsfornestedlist(tree-structured)data.Essentially,theyprovideasimpleandelegantwaytorepresentlist-baseddataintextualform.Tounderstandhowtextualrepresentationsofnestedlistsmaptoatreestructure,let'sextrapolatethetreestructurefromanHTMLpage.ThefollowingexamplecontainsasimpleHTMLpageandthecorrespondingtreestructurediagram.
AsimpleHTMLpage:
<html>
<head>
<linkrel="icon"href="favicon.ico">
<title>PageTitle</title>
</head>
<body>
<div>
<h1>Header</h1>
<p>Thisisaparagraph.</p>
</div>
<div>Somecontent</div>
<nav>
<ul>
<li>Item1</li>
<li>Item2</li>
<li>Item3</li>
</ul>
</nav>
</body>
</html>
Thecorrespondingtreestructureis:
AtreestructurediagramforanHTMLpage
Evenifyou'veneverseenatreestructurebefore,it'sstillcleartoseehowtheHTMLmapstothetreeintermsofstructureandhierarchy.MappingHTMLelementsisrelativelysimplebecauseit'samarkuplanguagewithwell-definedtagsandnoactuallogic.
Watrepresentsmodulesthatcanhavemultiplefunctionswithvaryingparameters.Todemonstratetherelationshipbetweensourcecode,Wat,andthecorrespondingtreestructure,let'sstartwithasimpleCfunctionthatadds2tothenumberthatispassedinasaparameter:
HereisaCfunctionthatadds2tothenumargumentpassedinandreturnstheresult:
intaddTwo(intnum){
returnnum+2;
}
ConvertingtheaddTwofunctiontovalidWatproducesthisresult:
(module
(table0anyfunc)
(memory$01)
(export"memory"(memory$0))
(export"addTwo"(func$addTwo))
(func$addTwo(;0;)(param$0i32)(resulti32)
(i32.add
(get_local$0)
(i32.const2)
)
)
)
InChapter1,WhatisWebAssembly?,wetalkedaboutlanguageconceptsassociatedwiththeCoreSpecification(Functions,LinearMemory,Tables,andsoon).Withinthatspecification,theStructuresectiondefineseachoftheseconceptsinthecontextofanabstractsyntax.TheTextFormatsectionofthespecificationcorrespondswiththeseconceptsaswell,andyoucanseethemdefinedbytheirkeywordsintheprecedingsnippet(func,memory,table).
TreeStructure:
AtreestructurediagramforWat
Theentiretreewouldbetoolargetofitonapage,sothisdiagramislimitedto
thefirstfivelinesoftheWatsourcetext.Eachfilled-indotrepresentsalistnode(orthecontentsofasetofparentheses).Asyoucansee,codewrittenins-expressionscanbeclearlyandconciselyexpressedinatreestructure,whichiswhys-expressionswerechosenforWebAssembly'stextformat.
Values,types,andinstructionsAlthoughdetailedcoverageoftheTextFormatsectionoftheCoreSpecificationisoutofthescopeofthistext,it'sworthdemonstratinghowsomeofthelanguageconceptsmaptothecorrespondingWat.ThefollowingdiagramdemonstratesthesemappingsinasampleWatsnippet.TheCcodethatthiswascompiledfromrepresentsafunctionthattakesawordasaparameterandreturnsthesquarerootofthecharactercount:
Watexamplewithlanguageconceptdetails
IfyouintendonwritingoreditingWat,notethatitsupportsblockandlinecomments.Theinstructionsaresplitupintoblocksandconsistofsettingandgettingmemoryassociatedwithvariableswithvalidtypes.Youareabletocontroltheflowoflogicusingifstatementsandloopsaresupportedusingtheloopkeyword.
RoleinthedevelopmentprocessThetextformatallowsfortherepresentationofabinaryWasmmoduleintextualform.Thishassomeprofoundimplicationswithregardtotheeaseofdevelopmentanddebugging.HavingatextualrepresentationofaWebAssemblymoduleallowsdeveloperstoviewthesourceofaloadedmoduleinabrowser,whicheliminatestheblack-boxissuesthatinhibitedtheadoptionofNaCl.Italsoallowsfortoolingtobebuiltaroundtroubleshootingmodules.Theofficialwebsitedescribestheusecasesthatdrovethedesignofthetextformat:•ViewSourceonaWebAssemblymodule,thusfittingintotheWeb(whereeverysourcecanbeviewed)inanaturalway.
•Presentationinbrowserdevelopmenttoolswhensourcemapsaren'tpresent(whichisnecessarilythecasewiththeMinimumViableProduct(MVP)).
•WritingWebAssemblycodedirectlyforreasonsincludingpedagogical,experimental,debugging,optimization,andtestingofthespecitself.
Thelastiteminthelistreflectsthatthetextformatisn'tintendedtobewrittenbyhandinthecourseofnormaldevelopment,butrathergeneratedfromatoollikeEmscripten.Youprobablywon'tseeormanipulateany.watfileswhenyou'regeneratingmodules,butyoumaybeviewingtheminadebuggingcontext.
Notonlyisthetextformatvaluablewithregardstodebugging,buthavingthisintermediateformatreducestheamountofrelianceonasingletoolforcompilation.Severaldifferenttoolscurrentlyexisttoconsumeandemitthiss-expressionsyntax,someofwhichareusedbyEmscriptentocompileyourcodedowntoa.wasmfile.
Binaryformatandthemodulefile(Wasm)TheBinaryFormatsectionoftheCoreSpecificationprovidesthesamelevelofdetailwithregardtolanguageconceptsastheTextformatsection.Inthissection,wewillbrieflycoversomehigh-leveldetailsaboutthebinaryformatanddiscussthevarioussectionsthatmakeupaWasmmodule.
DefinitionandmoduleoverviewThebinaryformatisdefinedasadenselinearencodingoftheabstractsyntax.Withoutgettingtootechnical,thatessentiallymeansit'sanefficientformofbinarythatallowsforfastdecoding,smallfilesize,andreducedmemoryusage.Thefilerepresentationofthebinaryformatisa.wasmfile,whichwillbethecompilationoutputfromEmscriptenthatwe'lluseforourexamples.
TheValues,Types,andInstructionssubsectionsoftheCoreSpecificationforthebinaryformatcorrelatedirectlytotheTextFormatsection.Eachoftheseconceptsiscoveredinthecontextofencoding.Forexample,accordingtothespecification,theIntegertypesareencodedusingtheLEB128variable-lengthintegerencoding,ineitherunsignedorsignedvariant.TheseareimportantdetailstoknowifyouwishtodeveloptoolingforWebAssembly,butnotnecessaryifyoujustplanonusingitonyourwebsite.
TheStructure,BinaryFormat,andTextFormat(wat)sectionsoftheCoreSpecificationhaveaModulesubsection.Wedidn'tcoveraspectsofthemoduleintheprevioussectionbecauseit'smoreprudenttodescribetheminthecontextofabinary.TheofficialWebAssemblysiteoffersthefollowingdescriptionforamodule:
"Thedistributable,loadable,andexecutableunitofcodeinWebAssemblyiscalledamodule.Atruntime,amodulecanbeinstantiatedwithasetofimportvaluestoproduceaninstance,whichisanimmutabletuplereferencingallthestateaccessibletotherunningmodule."
WewilldiscusshowtointeractwiththemoduleusingtheJavaScriptandWebAPIslaterinthischapter,solet'sestablishsomecontexttounderstandhowthemoduleelementsmaptotheAPImethods.
ModulesectionsAmoduleismadeupofseveralsections,someofwhichyou'llbeinteractingwiththroughtheJavaScriptAPI:
Imports(import)areelementsthatcanbeaccessedwithinthemoduleandcanbeoneofthefollowing:
Function,whichcanbecalledinsidethemoduleusingthecalloperatorGlobal,whichcanbeaccessedinsidethemoduleviatheglobaloperatorsLinearMemory,whichcanbeaccessedinsidethemoduleviathememoryoperatorsTable,whichcanbeaccessedinsidethemoduleusingcall_indirect
Exports(export)areelementsthatcanbeaccessedbytheconsumingAPI(thatis,calledbyaJavaScriptfunction)Modulestartfunction(start)iscalledafterthemoduleinstanceisinitializedGlobal(global)containstheinternaldefinitionofglobalvariablesLinearmemory(memory)containstheinternaldefinitionoflinearmemorywithaninitialmemorysizeandoptionalmaximumsizeData(data)containsanarrayofdatasegmentswhichspecifytheinitialcontentsoffixedrangesofagivenmemoryTable(table)isalinearmemorywhoseelementsareopaquevaluesofaparticulartableelementtype:
IntheMVP,itsprimarypurposeistoimplementindirectfunctioncallsinC/C++
Elements(elements)isasectionthatallowsamoduletoinitializetheelementsofanyimportorinternallydefinedtablewithanyotherdefinitioninthemoduleFunctionandcode:
ThefunctionsectiondeclaresthesignaturesofeachinternalfunctiondefinedinthemoduleThecodesectioncontainsthefunctionbodyofeachfunctiondeclaredbythefunctionsection
Someofthekeywords(import,export,andsoon)shouldlookfamiliar;they'representinthecontentsoftheWatfileintheprevioussection.WebAssembly'scomponentsfollowalogicalmappingthatdirectlycorrespondtotheAPIs(forexample,youpassamemoryandtableinstanceintoJavaScript'sWebAssembly.instantiate()function).YourprimaryinteractionwithamoduleinbinaryformatwillbethroughtheseAPIs.
TheJavaScriptandWebAPIsInadditiontotheWebAssemblyCoreSpecification,therearetwoAPIspecificationsforinteractingwithWebAssemblymodules:theWebAssemblyJavaScriptInterface(JavaScriptAPI)andtheWebAssemblyWebAPI.Intheprevioussections,wecoveredpertinentaspectsoftheCoreSpecificationtobecomefamiliarwiththeunderlyingtechnology.IfyouneverreadtheCoreSpecification(orifyouskippedthefirstfewsectionsofthischapter),itwouldn'tinhibittheuseofWebAssemblyinyourapplication.ThatisnotthecasefortheAPIs,astheydescribethemethodsandinterfacerequiredtoinstantiateandinteractwithyourcompiledWasmmodule.Inthissection,wewillreviewtheWebandJavaScriptAPIsanddescribehowtoloadandcommunicatewithaWasmmoduleusingJavaScript.
WebAssemblystoreandobjectcachesBeforediggingintointeractions,let'sdiscusstherelationshipbetweenJavaScriptandWebAssemblyinthecontextofexecution.TheCoreSpecificationcontainsthefollowingdescriptionintheExecutionsection:"WebAssemblycodeisexecutedwheninstantiatingamoduleorinvokinganexportedfunctionontheresultingmoduleinstance.
Executionbehaviorisdefinedintermsofanabstractmachinethatmodelstheprogramstate.Itincludesastack,whichrecordsoperandvaluesandcontrolconstructs,andanabstractstorecontainingglobalstate."
Underthehood,JavaScriptusessomethingcalledagentstomanageexecution.Thestorebeingreferredtointhedefinitioniscontainedwithinanagent.ThefollowingdiagramrepresentsaJavaScriptagent:
JavaScriptagentelements
Thestorerepresentsthestateoftheabstractmachine.WebAssemblyoperationstakeastoreandreturnanupdatedstore.EachagentisassociatedwithcachesthatmapJavaScriptobjectstoWebAssemblyaddresses.Sowhyisthisimportant?ItrepresentstheunderlyingmethodofinteractionbetweenWebAssemblymodulesandJavaScript.TheJavaScriptobjectscorrespondtothe
LoadingamoduleandtheWebAssemblynamespacemethodsTheJavaScriptAPIcoversthevariousobjectsavailableontheglobalWebAssemblyobjectinthebrowser.Beforewediscussthose,we'llstartwiththemethodsavailableontheWebAssemblyobject,withabriefoverviewoftheirintendedpurposes:
instantiate()istheprimaryAPIforcompilingandinstantiatingWebAssemblycodeinstantiateStreaming()performsthesamefunctionalityasinstantiate(),butitusesstreamingtocompileandinstantiatethemodule,whicheliminatesanintermediatestepcompile()onlycompilesaWebAssemblymodule,butdoesn'tinstantiateitcompileStreaming()alsoonlycompilesaWebAssemblymodule,butitusesstreamingsimilartoinstantiateStreaming()validate()checkstheWebAssemblybinarycodetoensurethebytesarevalidandreturnstrueifvalidorfalseifnotvalid
TheinstantiateStreaming()andcompileStreaming()methodsarecurrentlyonlypresentintheWebAPI.Infact,thesetwomethodscomprisetheentirespecification.ThemethodsavailableontheWebAssemblyobjectarefocusedprimarilyoncompilingandinstantiatingmodules.Withthatinmind,let'sdiscusshowtofetchandinstantiateaWasmmodule.
Whenyouperformafetchcalltogetamodule,itreturnsaPromisethatresolveswiththerawbytesofthatmodule,whichneedtobeloadedintoanArrayBufferandinstantiated.Goingforward,wewillrefertothisprocessasloadingamodule.
Thefollowingdiagramdemonstratesthisprocess:
FetchingandloadingaWebAssemblymodule
ThisprocessisactuallyquitesimpleusingPromises.Thefollowingcodedemonstrateshowamoduleisloaded.TheimportObjargumentpassesanydataorfunctionstotheWasmmodule.Youcandisregarditfornow,aswe'llbe
discussingitingreaterdetailinChapter5,CreatingandLoadingaWebAssemblyModule:
fetch('example.wasm')
.then(response=>response.arrayBuffer())
.then(buffer=>WebAssembly.instantiate(buffer,importObj))
.then(({module,instance})=>{
//Dosomethingwithmoduleorinstance
});
Theprecedingexampledictatesthemethodforloadingthemoduleusingtheinstantiate()method.TheinstantiateStreaming()methodisalittledifferentandsimplifiestheprocessevenmorebyfetching,compiling,andinstantiatingamoduleinasinglestep.Thefollowingcodeachievesthesamegoal(loadingamodule)usingthismethod:
WebAssembly.instantiateStreaming(fetch('example.wasm'),importObj)
.then(({module,instance})=>{
//Dosomethingwithmoduleorinstance
});
TheinstantiationmethodsreturnaPromisethatresolveswithanobjectcontainingacompiledWebAssembly.Module(module)andWebAssembly.Instance(instance),bothofwhichwillbecoveredlaterinthissection.Inmostcases,youwilluseoneofthesemethodstoloadaWasmmoduleonyoursite.TheinstancecontainsalloftheexportedWebAssemblyfunctionsthatyoucancallfromyourJavaScriptcode.
Thecompile()andcompileStreaming()methodsreturnaPromisethatonlyresolveswithacompiledWebAssembly.Module.Thisisusefulifyouwanttocompileamoduleandinstantiateitatalatertime.MozillaDeveloperNetwork(MDN),thewebdocssitemanagedbyMozilla,providesanexampleinwhichthecompiledmoduleispassedtoaWebWorker.
Asfarasthevalidate()methodisconcerned,itsonlypurposeistotestwhetherthetypedarrayorArrayBufferpassedinasaparameterisvalid.ThiswouldbecalledaftertherawbytesoftheresponseareloadedintoanArrayBuffer.Thismethodwasn'tincludedinthecodeexamplesbecauseattemptingtoinstantiateorcompileaninvalidWasmmodulewillthroweitheraTypeErrororoneoftheErrorobjectspresentontheWebAssemblyobject.WewillcovertheseErrorobjectslaterinthissection.
WebAssemblyobjectsInadditiontothemethodscoveredintheLoadingamoduleandtheWebAssemblynamespacemethodssection,theglobalWebAssemblyobjecthaschildobjectsthatareusedtointeractwithandtroubleshootWebAssembly.TheseobjectscorrelatedirectlytotheconceptswediscussedinthesectionsontheWebAssemblybinaryandtextformats.ThefollowinglistcontainstheseobjectsaswellastheirdefinitionstakenfromMDN:
TheWebAssembly.ModuleobjectcontainsstatelessWebAssemblycodethathasalreadybeencompiledbythebrowserandcanbeefficientlysharedwithworkers,cachedinIndexedDB,andinstantiatedmultipletimesTheWebAssembly.Instanceobjectisastateful,executableinstanceofaWebAssembly.ModulewhichcontainsalloftheexportedWebAssemblyfunctionsthatallowcallingintoWebAssemblycodefromJavaScriptWebAssembly.Memory,whencalledwiththeconstructor,createsanewMemoryobjectwhichisaresizableArrayBufferthatholdstherawbytesofmemoryaccessedbyaWebAssemblyInstanceWebAssembly.Table,whencalledwiththeconstructor,createsanewTableobjectofthegivensizeandelementtypethatrepresentsaWebAssemblyTable(whichstoresfunctionreferences)WebAssembly.CompileError,whencalledwiththeconstructor,createsanerrorwhichindicatesthatanissueoccurredduringWebAssemblydecoding,orvalidationWebAssembly.LinkError,whencalledwiththeconstructor,createsanerrorwhichindicatesthatanissueoccurredduringmoduleinstantiationWebAssembly.RuntimeError,whencalledwiththeconstructor,createsanerrorwhichindicatesthatWebAssemblyspecifiedatrap(forexample,stackoverflowoccurred)
Let'sdigintoeachoneindividually,startingwiththeWebAssembly.Moduleobject.
WebAssembly.ModuleTheWebAssembly.ModuleobjectistheintermediatestepbetweentheArrayBufferandtheinstantiatedmodule.Thecompile()andinstantiate()methods(andtheirstreamingcounterparts)returnaPromisethatresolveswithamodule(moduleinlowercaserepresentsthecompiledModule).AmodulecanalsobecreatedsynchronouslybypassingatypedarrayorArrayBufferdirectlyintotheconstructor,butthisisdiscouragedforlargemodules.
TheModuleobjectalsohasthreestaticmethods:exports(),imports(),andcustomSections().Allthreetakeamoduleasaparameter,butcustomSections()takesastringrepresentingthesectionnameasitssecondparameter.CustomsectionsaredescribedintheBinaryFormatsectionoftheCoreSpecificationandareintendedtobeusedfordebugginginformationorthird-partyextensions.Inmostcases,youwon'tneedtodefinethese.Theexports()functionisusefulifyou'reusingaWasmmodulethatyoudidn'tcreate,althoughyou'llonlybeabletoseethenameandkind(forexample,function)ofeachexport.
Forsimpleusecases,youwon'tbedealingdirectlywiththeModuleobjectorcompiledmodule.MostoftheinteractionwilltakeplacewithanInstance.
WebAssembly.InstanceTheWebAssembly.InstanceobjectistheinstantiatedWebAssemblymodule,whichmeansyoucancallexportedWebAssemblyfunctionsfromit.Callinginstantiate()orinstantiateStreaming()returnsaPromisethatresolveswithanobjectcontaininganinstance.YoucallWebAssemblyfunctionsbyreferencingthenameofthefunctionontheinstance'sexportproperty.Forexample,ifamodulecontainedanexportedfunctionnamedsayHello(),you'dcallthefunctionusinginstance.exports.sayHello().
WebAssembly.MemoryTheWebAssembly.MemoryobjectholdsthememoryaccessedbyaWebAssemblyInstance.ThismemorycanbeaccessedandchangedfrombothJavaScriptandWebAssembly.TocreateanewinstanceofMemory,youneedtopassanobjectwithaninitialand(optional)maximumvaluetotheWebAssembly.Memory()constructor.ThesevaluesareinunitsofWebAssemblypages,whereonepageis64KB.Youincreasethesizeofthememoryinstancebycallingthegrow()functionwithasingleparameterthatrepresentsthenumberofWebAssemblypagestogrowby.Youcanalsoaccessthecurrentbuffercontainedinthememoryinstancethroughitsbufferproperty.
MDNdescribestwowaystogettoaWebAssembly.Memoryobject.ThefirstwayistoconstructitfromJavaScript(varmemory=newWebAssembly.Memory(...)),whilethesecondwayistohaveitexportedbyaWebAssemblymodule.TheimportanttakeawayisthatmemorycanbepassedeasilybetweenJavaScriptandWebAssembly.
WebAssembly.TableTheWebAssembly.Tableobjectisanarray-likestructurethatisusedtostorefunctionreferences.JustaswithWebAssembly.Memory,aTablecanbeaccessedandchangedfrombothJavaScriptandWebAssembly.Asofthetimeofwriting,tablescanonlystorefunctionreferences,butit'slikelythat,asthetechnologyevolves,additionalentitieswillbeabletobestoredintablesaswell.
TocreateanewTableinstance,youneedtopassanobjectwithanelement,initial,and(optional)maximumvalue.Theelementmemberisastringthatrepresentsthetypeofvaluestoredinthetable;currentlytheonlyvalidvalueis"anyfunc"(forfunctions).TheinitialandmaximumvaluesrepresentthenumberofelementsintheWebAssemblyTable.
YoucanaccessthenumberofelementsintheTableinstanceusingthelengthproperty.Theinstancealsoincludesmethodstomanipulateandqueryelementsinthetable.Theget()methodallowsyoutoaccesstheelementatthegivenindex,whichispassedinasaparameter.Theset()methodallowsyoutosetanelementattheindexspecifiedasthefirstparametertothevaluespecifiedasthesecondparameter(pertheprecedingnote,onlyfunctionsaresupported).Finally,grow()allowsyoutoincreasethesizeoftheTableinstance(numberofelements)bythenumberpassedinasaparameter.
WebAssemblyerrors(CompileError,LinkError,RuntimeError)TheJavaScriptAPIprovidesconstructorstocreateinstancesoftheErrorobjectsspecifictoWebAssembly,butwewon'tspendtoomuchtimecoveringtheseobjects.Theobjectdefinitionlistatthebeginningofthissectiondescribesthenatureofeacherror,whichmayberaisedifthespecifiedconditionismet.Allthreeerrorscanbeconstructedwithamessage,filename,andlinenumberparameter(allofwhichareoptional),andhasthesamepropertiesandmethodsasthestandardJavaScriptErrorobject.
ConnectingthedotswithWasmFiddleWespentthischapterreviewingthevariouselementsofWebAssemblyandthecorrespondingJavaScriptandWebAPIs,butunderstandinghowthepiecesfittogethercanstillbeconfusing.Asweprogressthroughtheexamplesinthisbookandyou'reabletoseehowC/C++,WebAssembly,andJavaScriptinteract,theseconceptswillbecomeclearer.
Thatbeingsaid,ademonstrationofthisinteractionmayhelpinclearingupsomeoftheconfusion.Inthissection,we'regoingtouseanonlinetoolcalledWasmFiddletodemonstratetherelationshipbetweentheseelementssoyoucanseeWebAssemblyinactionandgetahigh-leveloverviewofthedevelopmentworkflow.
WhatisWasmFiddle?WasmFiddle,locatedathttps://wasdk.github.io/WasmFiddle/,isanonlinecodeeditingtoolthatallowsyoutowritesomeCorC++codeandconvertittoWat,compileittoWasm,orinteractwithitdirectlyusingJavaScript.TheC/C++andJavaScripteditorsareminimalandaren'tintendedtobeusedasyourprimarydevelopmentenvironment,butitoffersavaluableserviceintheWasmcompiler.InChapter3,SettingUpADevelopmentEnvironment,you'lldiscoverthatgoingfromsquareonetogeneratingWasmfilesrequiresalittlebitofwork—beingabletopasteyourCcodeintothebrowserandhittingacoupleofbuttonsmakesthingsmuchmoreconvenient.Thefollowingdiagramgivesaquickoverviewoftheinterface:
ComponentsoftheWasmFiddleuserinterfaceAsyoucansee,theinterfaceisrelativelysimple.Let'stryoutsomecode!
CcodetoWatTheupper-leftpaneinthefollowingscreenshotcontainsasimpleCfunctionthatadds2tothenumberspecifiedasaparameter.Thelower-leftpanecontainsthecorrespondingWat:
CfunctionandthecorrespondingWat
Ifthislooksfamiliar,it'sbecausethissamecodewasusedfortheexplanationofWat'ss-expressionsinthebeginningofthischapter.Diggingalittledeeper,youcanseehowtheCcodecorrespondstotheWatoutput.TheaddTwo()functionisexportedfromthemoduleasastringonline5.Line5alsocontains(func$addTwo),whichreferencesthe$addTwofunctiononline6.Line6specifiesthatasingleparameteroftypei32(aninteger)canbepassedinandtheresultreturnedisalsoani32.PressingtheBuildbuttonintheupper-rightcorner(orabovetheC/C++editor)willcompiletheCcodeintoaWasmfile.TheWasmwillbeavailablefordownloadorinteractionwithJavaScriptoncethebuildiscompleted.
WasmtoJavaScriptTheupper-rightpaneinthefollowingscreenshotcontainssomeJavaScriptcodetocompiletheWasmthatwasgeneratedinthepreviousstep.ThewasmCodewasgeneratedwhenthebuildfinished,soitshouldbeavailableautomatically.Ratherthanusetheinstantiate()method,WasmFiddlecreatesacompiledWebAssembly.ModuleinstanceandpassesthatintotheconstructorofanewWebAssembly.Instance.ThewasmImportsobjectiscurrentlyempty,althoughwecouldpassinaWebAssembly.MemoryandWebAssembly.Tableinstanceifdesired:
JavaScriptcodecallingtheCfunctionfromthecompiledWasmmodule
ThefinallineofJavaScriptprintstheresultofaddTwo()totheoutputinthelower-rightpanewhenpassedthenumber2.Thelog()methodisacustomfunctionthatensurestheoutputisprintedtothelower-rightpane(thenumber4).NotehowtheJavaScriptcodeinteractswithwasmInstance.TheaddTwo()functioniscalledfromtheinstance'sexportsobject.Althoughthiswasacontrivedexample,itdemonstratesthestepsCorC++codegoesthroughbeforeitcanbeusedbyJavaScriptasaWasmmodule.
SummaryInthischapter,wediscussedtheelementsofWebAssemblyandtheirrelationship.ThestructureoftheCoreSpecificationwasusedtodescribethemappingofthetextandbinaryformatstoacommonabstractsyntax.Wehighlightedaspectsofthetextformat(Wat)thatcanbeusefulinthecontextofdebugginganddevelopment,aswellaswhys-expressionsareanexcellentfitforthetextualrepresentationoftheabstractsyntax.Wealsorevieweddetailspertainingtothebinaryformatandthevariouselementsthatmakeupamodule.ThemethodsandobjectswithintheJavaScriptandWebAPIsweredefinedwithdescriptionsoftheirroleswithregardtoWebAssemblyinteraction.Finally,asimpleexampleoftherelationshipbetweensourcecode,Wat,andJavaScriptwaspresentedusingtheWasmFiddletool.
InChapter3,SettingUpaDevelopmentEnvironment,we'llinstallthedevelopmenttoolingwe'llusetoworkeffectivelywithWebAssembly.
Questions1. Whatkindofdataares-expressionsgoodatrepresenting?2. Whatarethefourlanguageconceptsthataresharedbetweenthebinaryand
textformats?3. Whatisoneoftheusecasesforthetextformat?4. WhatistheonlyelementtypethatcanbestoredinaWebAssemblyTable?5. WhatdoestheJavaScriptengineusetomanageexecution?6. Whichmethodrequireslesscodetoinstantiateamodule,instantiate()or
instantiateStreaming()?7. WhaterrorobjectsareavailableontheWebAssemblyJavaScriptobjectand
whateventcauseseachone?
FurtherreadingWebAssemblyonMDN:https://developer.mozilla.org/en-US/docs/WebAssemblyWasmFiddle:https://wasdk.github.io/WasmFiddleS-expressionsonWikipedia:https://en.wikipedia.org/wiki/S-expressionExamplesofTrees:http://interactivepython.org/runestone/static/pythonds/Trees/ExamplesofTrees.html
SettingUpaDevelopmentEnvironment
Nowthatyou'refamiliarwiththeelementsofWebAssembly,it'stimetosetupasuitabledevelopmentenvironment.DevelopingwithWebAssemblyistantamounttodevelopinginCorC++.Thedifferenceliesinthebuildprocessandtheoutput.Inthischapter,wewillcoverthedevelopmenttooling,andhowtoinstallandconfigureitonyoursystem.
Ourgoalforthischapteristounderstandthefollowing:
Howtoinstalltherequireddevelopmenttooling(Git,Node.js,andVisualStudioCode)HowtoconfigureVisualStudioCodeforusewithC/C++andWebAssemblyusingextensionsHowtosetupalocalHTTPservertoserveuptheHTML,JavaScript,and.wasmfilesCheckingyourbrowserforWebAssemblysupportWhathelpfultoolsareavailabletosimplifyandimprovethedevelopmentprocess
InstallingthedevelopmenttoolingYou'llneedtoinstallsomeapplicationsandtoolingtostartdevelopingWebAssembly.WewilluseVisualStudioCode,atexteditor,towriteourC/C++,JavaScript,HTML,andWat.We'llalsouseNode.jsforservingupthefilesandGittomanageourcode.Wewillusepackagemanagerstoinstallthesetools,whichmakestheinstallationprocessmuchsimplerthandownloadingandinstallingthemmanually.Inthissection,wewillcovertheoperatingsystems,aswellasthepackagemanagersforeachplatform.We'llalsorevieweachoftheapplications,withabriefoverviewoftheirroleinthedevelopmentprocess.
OperatingsystemsandhardwareToensurethattheinstallationandconfigurationprocessgoessmoothly,it'simportanttobeawareoftheoperatingsystemsIwillusefortheexamplesinthisbook.Ifyouencounteranissue,itmaybeduetoanincompatibilitybetweentheplatformyou'reusingandtheoneI'musing.Inmostcases,youshouldn'thaveanissue.ForthesakeofeliminatingtheOSversionasapotentialproblemcauser,I'veprovideddetailsfortheoperatingsystemsI'musinginthefollowinglist:
PackagemanagersPackagemanagersaretoolsthatsimplifytheinstallationprocessforsoftware.Theyallowustoupgrade,configure,uninstall,andsearchforavailablesoftwarefromthecommandlinewithouthavingtogotoawebsitetodownloadandruntheinstaller.Theyalsosimplifytheinstallationprocessforsoftwarethatmayhavemultipledependenciesorrequiremanualconfigurationbeforeuse.Inthissection,I'llcoverthepackagemanagerforeachplatform.
HomebrewformacOSHomebrewisanexcellentpackagemanagerformacOSthatallowsustoinstallmostofthetoolswewilluseoutofthebox.HomebrewisassimpleaspastingthefollowingcommandinTerminalandrunningit:
/usr/bin/ruby-e"$(curl-fsSLhttps://raw.githubusercontent.com/Homebrew/install/master/install)"
You'llseemessagesinTerminalthatwillwalkyouthroughtheinstallationprocess.Oncethat'scomplete,you'llneedtoinstallanextensionforHomebrewcalledHomebrew-CaskthatallowsyoutoinstallmacOSapplicationswithouthavingtodownloadtheinstaller,mountit,anddragtheapplicationintotheApplicationsfolder.Youcaninstallthisbyrunningthefollowingcommand:
brewtapcaskroom/cask
That'sit!You'renowabletoinstallapplicationsbyrunningeitherofthesecommands:
#Forcommandlinetools:
brewinstall<ToolName>
#Fordesktopapplications:
brewcaskinstall<ApplicationName>
AptforUbuntuAptisthepackagemanagerprovidedwithUbuntu;there'snoneedtoinstallit.Itallowsyoutoinstallbothcommand-linetoolsandapplicationsoutofthebox.Ifanapplicationisn'tavailablefromApt'srepository,youcanaddarepositoryusingthefollowingcommand:
add-apt-repository
ChocolateyforWindowsChocolateyisapackagemanagerforWindows.It'ssimilartoAptinthatitletsyouinstallbothcommand-linetoolsandapplications.ToinstallChocolatey,youneedtorunthecommandprompt(cmd.exe)asanadministrator.YoucandothisbypressingtheStartmenubutton,typingcmd,andright-clickingontheCommandPromptapplicationandselectingRunasadministrator:
RunningtheCommandPromptasanadministratorThenjustrunthefollowingcommand:
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"-NoProfile-InputFormatNone-ExecutionPolicyBypass-Command"iex((New-ObjectSystem.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"&&SET"PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
TheeasiestwaytogetthecommandtextisthroughChocolatey'sinstallationpageathttps://chocolatey.org/install.There'sabuttontocopythetexttoyourclipboardundertheInstallwithcmd.exesection.YoucouldalsoinstalltheapplicationusingPowerShellifyoufollowthestepsontheInstallationpage.
GitGitisaversioncontrolsystem(VCS)thatallowsyoutotrackchangestofilesandmanageworkbetweenmultipledeveloperscontributingtothesamecodebase.GitistheVCSpoweringGitHubandGitLab,andisalsoavailableonBitbucket(theyalsoofferMercurial,whichisanotherVCS).GitwillallowustoclonerepositoriesfromGitHub,andisaprerequisitefortheEMSDK,whichwe'llcoverinthenextchapter.Inthissection,wewillcovertheinstallationprocessforGit.
InstallingGitonmacOSGitisprobablyalreadyavailableifyou'reusingmacOS.macOScomesbundledwithAppleGit,whichwillprobablybeafewversionsbehindthemostrecentversion.Forthepurposesofthisbook,theversionyoualreadyhaveinstalledshouldbesufficient.Ifyouwishtoupgrade,youcaninstallthemostrecentversionofGitusingHomebrewbyrunningthefollowingcommandsinTerminal:
#InstallGittotheHomebrewinstallationfolder(/usr/local/bin/git):
brewinstallgit
#EnsurethedefaultGitispointingtotheHomebrewinstallation:
sudomv/usr/bin/git/usr/bin/git-apple
Ifyourunthiscommand,youshouldsee/usr/local/bin/git:
whichgit
Youcanchecktoensurethattheinstallationwassuccessfulbyrunningthiscommand:
git--version
InstallingGitonUbuntuYoucanuseapttoinstallGit;justrunthefollowingcommandinTerminal:
sudoaptinstallgit
Youcanchecktoensurethattheinstallationwassuccessfulbyrunningthiscommand:
git--version
InstallingGitonWindowsYoucaninstallGitusingChocolatey.OpenupCommandPromptorPowerShellandrunthiscommand:
chocoinstallgit
Youcanchecktoensurethattheinstallationwassuccessfulbyrunningthiscommand:
git--version
Youcanbypasstheconfirmationmessagesbyaddinga-ytotheendoftheinstallcommand(forexample,chocoinstallgit-y).Youcanalsoopttoalwaysskiptheconfirmationbyenteringthechocofeatureenable-nallowGlobalConfirmationcommand.
Node.jsTheofficialwebsiteforNode.jsdescribesitasanasynchronousevent-drivenJavaScriptruntime.Nodeisdesignedtobuildscalablenetworkapplications.Wewilluseitinthisbooktoserveupourfilesandworkwiththeminabrowser.Node.jscomespackagedwithnpm,apackagemanagerforJavaScript,whichwillallowustoinstallpackagesgloballyandaccessthemthroughthecommandline.Inthissection,we'llcovertheinstallationprocessforeachplatformusingtheNodeVersionManager(nvm).
nvmWewillusethelong-termstable(LTS)releaseofNode.js(Version8)toensurethatwe'reusingthemoststableversionoftheplatform.WewillusenvmtomanageNode.jsversions.Thiswillpreventconflictsifyoualreadyhaveahigher(orlower)versionofNode.jsinstalledonyourcomputer.nvmallowsyoutohavemultipleversionsofNode.jsinstalledthatyoucanquicklyswitchtoandisolateinthecontextofasingleterminalwindow.
InstallingnvmonmacOSRunthefollowingcommandinTerminal:
brewinstallnvm
Followthepost-installationstepsHomebrewspecifiestoensurethatyoucanstartusingit(youmayhavetorestartyourTerminalsession).IfyouclearedyourTerminalcontentsbeforeperformingthesteps,youcanrunthiscommandtoseetheinstallationstepsagain:
brewinfonvm
Youcanchecktoensurethattheinstallationwassuccessfulbyrunningthiscommand:
nvm--version
InstallnvmonUbuntu
Ubuntucomesbundledwithwget,whichcanretrievefilesusingHTTP/SandFTP/Sprotocols.TheGitHubpagefornvm(https://github.com/creationix/nvm)containsthefollowingcommandtoinstallitusingwget:wget-qO-https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh|bash
Onceinstalled,restartTerminaltocompletetheinstallation.Youcanchecktoensurethattheinstallationwassuccessfulbyrunningthefollowingcommand:nvm--version
InstallingnvmonWindows
nvmdoesn'tcurrentlysupportWindows,soyou'reactuallyinstallingadifferentapplicationnamednvm-windows.TheGitHubpagefornvm-windowscanbefoundathttps://github.com/coreybutler/nvm-windows.Someofthecommandsareslightlydifferent,buttheinstallationcommandwerunwillbethesame.Toinstallnvm-windows,openupCommandPromptorPowerShellandrunthiscommand:chocoinstallnvm
Youcanchecktoensurethattheinstallationwassuccessfulbyrunningthefollowingcommand:nvm--version
InstallingNode.jsusingnvmAfterinstallingnvm,youneedtoinstalltheversionofNode.jswewilluseinthisbook:version8.11.1.Toinstallit,runthiscommand:nvminstall8.11.1
Ifyoudidn'thaveNode.jsornvmpreviouslyinstalled,itwillautomaticallysetthistoyourdefaultNode.jsinstallation,sotheoutputofthiscommandshouldbev8.11.1:node--version
IfyouhaveexistingNode.jsversionsinstalled,youcaneitherusev8.11.1asadefault,orensurethatyourunthiscommandtousev8.11.1whenworkingthroughtheexamplesinthisbook:nvmuse8.11.1
Youcancreateafilenamed.nvmrcinthefolderwithyourcodeandpopulateitwiththecontentsv8.11.1.Youcanrunnvmusewithinthisdirectoryanditwillsettheversionto8.11.1withouthavingtospecifyit.
GNUmakeandrimrafInthelearn-webassemblyrepository,thecodeexamplesuseGNUMakeandVSCode'sTasksfeature(whichwe'llcoverinChapter5,CreatingandLoadingaWebAssemblyModule)toperformthebuildtasksdefinedthroughoutthebook.GNUMakeisanexcellentcross-platformtoolforautomatingbuildprocesses.YoucanreadmoreaboutGNUMakeathttps://www.gnu.org/software/make.Let'sreviewtheinstallationstepsforeachplatform.
<strong>make-v</strong>
Ifyouseeversioninformation,you'rereadytogo.SkipaheadtotheInstallingrimrafsection.Otherwise,followtheGNUMakeinstallationinstructionsforyourplatform.
InstallingGNUMakeonmacOSToinstallGNUMakeonmacOS,runthefollowingcommandfromTerminal:
brewinstallmake
Youcanchecktoensurethattheinstallationwassuccessfulbyrunningthiscommand:
make-v
Ifyouseeversioninformation,skiptotheInstallingrimrafsection.
InstallingGNUMakeonUbuntuToinstallGNUMakeonUbuntu,runthefollowingcommandfromTerminal:
sudoapt-getinstallmake
Youcanchecktoensurethattheinstallationwassuccessfulbyrunningthiscommand:
make-v
Ifyouseeversioninformation,skiptotheInstallingrimrafsection.
InstallingGNUmakeonWindowsYoucaninstallGNUmakeonWindowsusingChocolatey.OpenupCommandPromptorPowerShellandrunthefollowingcommand:chocoinstallmake
YoumayneedtorestarttheCLItousethemakecommand.Oncerestarted,runthefollowingcommandtovalidatetheinstallation:make-v
Ifyouseeversioninformation,continuetothenextsection.Ifyouencounterissues,youmayneedtodownloadandinstallthesetuppackageathttp://gnuwin32.sourceforge.net/packages/make.htm.
InstallingrimrafSomeofthebuildstepsdefinedintheMakefilesorVSCodeTasksdeletefilesordirectories.Thecommandsrequiredtodeleteafileorfolderdifferbasedonyourplatformandshell.Toaddressthisissuewe'llusetherimrafnpmpackage(https://www.npmjs.com/package/rimraf).Installingthepackagegloballyprovidesarimrafcommandthatperformsthecorrectdeletionoperationfortheoperatingsystemandshell.
Toinstallrimraf,ensurethatNode.jsisinstalledandrunthefollowingcommandfromaCLI:
npminstall-grimraf
Toensurethattheinstallationwassuccessful,runthefollowingcommand:
rimraf--help
Youshouldseeusageinstructionsandalistofcommandlineflags.Let'smoveontotheVSCodeinstallation.
VSCodeVSCodeisacross-platformtexteditorwithmultiple-languagesupportandarichextensionsecosystem.IntegrateddebuggingandGitsupportarebuiltin,andnewfeaturesarebeingaddedallthetime.We'reabletouseitfortheentireWebAssemblydevelopmentprocessthroughoutthecourseofthisbook.Inthissection,wewillcovertheinstallationstepsforeachplatform:
ScreenshotfromVisualStudioCode'swebsite
InstallingVisualStudioCodeonmacOSUseHomebrew-CasktoinstallVSCode.RunthefollowingcommandinTerminaltoinstall:
brewcaskinstallvisual-studio-code
Onceit'scomplete,youshouldbeabletolaunchitfromtheApplicationsfolderortheLaunchpad.
InstallingVisualStudioCodeonUbuntuTheprocessforinstallingVSCodeonUbuntuhasafewextrasteps,butisstillrelativelysimple.First,downloadthe.debfilefromVSCode'sdownloadpage(https://code.visualstudio.com/Download).Oncethedownloadcompletes,runthefollowingcommandstocompletetheinstallation:
#ChangedirectoriestotheDownloadsfolder
cd~/Downloads
#Replace<file>withthenameofthedownloadedfile
sudodpkg-i<file>.deb
#Completeinstallation
sudoapt-getinstall-f
Ifyougetamissingdependencyerror,youcanfixitbyrunningthefollowingcommandbeforesudodpkg:
sudoapt-getinstalllibgconf-2-4
sudoapt--fix-brokeninstall
YoushouldnowbeabletoopenVSCodefromtheLauncher.
InstallingVSCodeonWindowsYoucaninstallVSCodeusingChocolatey.RunthiscommandfromCommandPromptorPowerShell:
chocoinstallvisualstudiocode
Onceinstalled,youcanaccessitfromtheStartmenu.
YoucanopenVSCodewiththecurrentworkingdirectoryastheprojectbyrunningcode.intheCLI.
ConfiguringVSCodeOutofthebox,VSCodeisapowerfultexteditorwithalotofgreatfunctionality.Inadditiontobeinghighlyconfigurableandcustomizable,itpossessesanincrediblyrichextensionsecosystem.We'llneedtoinstallsomeoftheseextensionssowewon'tneedtousedifferenteditorsfordifferentprogramminglanguages.Inthissection,wewillcoverhowtoconfigureVSCodeandwhichextensionstoinstalltosimplifytheWebAssemblydevelopmentprocess.
ManagingsettingsandcustomizationCustomizingandconfiguringVSCodeissimpleandintuitive.YoucanmanagecustomsettingssuchaseditorfontandtabsizesbyselectingCode|Preferences|SettingsonmacOSorFile|Preferences|SettingsonWindows.UserandworkspacesettingsaremanagedseparatelyinJSONfilesandautocompletionisprovidedincaseyoucan'tremembertheexactnameofasetting.YoucanalsochangethethemesorkeyboardshortcutsbyselectingtheappropriateoptioninthePreferencesmenu.Thesettingsfileisalsowhereyoucansetcustomsettingsforanyextensionsyouinstall.Somesettingsareaddedbydefaultwhenyouinstallanextension,sochangingthemisassimpleasupdatingandsavingthisfile.
ExtensionsoverviewWe'llneedtoinstallsomeextensionsaspartoftheconfigurationprocess.TherearemultiplewaystofindandinstallextensionsinVSCode.IprefertoclickontheExtensionsbutton(fourthbuttonfromthetopintheActivitybarontheleft-handsideoftheeditor),enterwhatI'mlookingforintheSearchbox,andpressthegreenInstallbuttonfortheextensionI'dliketoinstall.YoucouldalsovisittheVSCodeMarketplaceathttps://marketplace.visualstudio.com/vscode,searchforandselectanextensionyou'dliketoinstall,andpressthegreenInstallbuttonontheextension'spage.Youcanmanageextensionsthroughthecommandlineaswell.Formoreinformation,visithttps://code.visualstudio.com/docs/editor/extension-gallery:
InstallingextensionsinVSCode
ConfigurationforC/C++andWebAssemblyVSCodedoesn'tsupportCandC++outofthebox,butthereisanexcellentextensionthatallowsyoutoworkwiththeselanguages.Italsodoesn'tsupportsyntaxhighlightingfortheWebAssemblytextformat,butthereisanextensionthataddsthatfunctionalityaswell.Inthissection,wewillcovertheinstallationandconfigurationoftheC/C++forVSCodeandWebAssemblyToolkitforVSCodeextensions.
InstallingC/C++forVSCodeTheC/C++extensionforVSCodeincludesseveralfeaturesforwritinganddebuggingCandC++code,suchasautocompletion,symbolsearching,class/methodnavigation,line-by-linecodestepping,andmanyothers.Toinstalltheextension,searchforC/C++intheExtensionsandinstalltheextensiontitledC/C++(it'screatedbyMicrosoft)ornavigatetotheextension'sofficialpageathttps://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptoolsandpressthegreenInstallbutton.
Onceinstalled,youcanviewconfigurationdetailsfortheextensionbyselectingtheextensionfromtheExtensionslistinVSCodeandselectingtheContributionstab.Thistabcontainsthevarioussettings,commands,anddebuggerdetails:
ContributionstabfortheC/C++extension
ConfiguringC/C++forVSCodeMicrosofthasanofficialpagefortheextension,whichyoucanviewathttps://code.visualstudio.com/docs/languages/cpp.Thispagedescribes,amongotherthings,howtoconfigurethroughtheuseofJSONfiles.Let'sstartbycreatinganewconfigurationfiletomanageourC/C++environment.YoucangenerateanewconfigurationfilebypressingtheF1key,typingC/C,andselectingC/Cpp:EditConfigurations…:
CommandPalettewithC/C++extensionoptions
Thiswillgenerateanewc_cpp_properties.jsonina.vscodefolderwithinyourcurrentproject.ThefilewillcontainconfigurationoptionsforyourC/C++compilerbasedonyourplatform,theCandC++standardstouse,andtheincludepathsforheaderfiles.Youcanclosethisfileonceit'sgenerated.WewillrevisititwhenweconfiguretheEMSDK.
WebAssemblyToolkitforVSCodeThereareafewdifferentWebAssemblyextensionsforVSCodecurrentlyavailable.I'musingtheWebAssemblyToolkitforVSCodeextensionbecauseitallowsyoutoright-clickona.wasmfileandselectShowWebAssembly,whichdisplaystheWatrepresentationofthefile.YoucaninstallthisextensionthroughtheExtensionspanel(searchforWebAssembly),orfromtheofficialextensionpageintheVSCodeMarketplace(https://marketplace.visualstudio.com/items?itemName=dtsvet.vscode-wasm):
ViewingtheWatfora.wasmfileusingtheWebAssemblyToolkitfortheVSCodeextension
Onceinstalled,you'rereadytogo!Nowthatyou'vegotalloftherequired
OtherusefulextensionsVSCodehassomegreatextensionstoimproveefficiencyandcustomizetheinterface.Inthissection,IwillcoversomeoftheextensionsIhaveinstalledthatsimplifycommontasksaswellastheuserinterface/iconthemes.Youdon'tneedtoinstallanyoftheseextensionsfortheexamplesinthisbook,butyoumayfindsomeofthemuseful.
AutorenametagThisextensionisincrediblyhelpfulwhenworkingwithHTML.Itautomaticallychangesthenameoftheclosingtagifyouchangethetagtype.Forexample,ifyouhavea<div>elementandyouwanttomakeita<span>,changingthetextoftheopeningelementtospanwillupdatetheclosingelementtext(</div>to</span>):
AutorenamingtagrenamingHTMLtag
BracketpaircolorizerThisextensioncolorizesthebrackets,braces,andparenthesesinyourcodesoyoucanquicklyidentifytheopeningandclosingbrackets.WebAssembly'stextformatusesparenthesesextensively,sobeingabletodeterminewhichelementsareenclosedinwhichlistmakesdebuggingandevaluationmuchsimpler:
BracketpaircolorizercolormatchingparenthesesinaWatfile
MaterialIconthemeandAtomOneLightthemeThereareover1,000iconandinterfacethemesavailableontheVSCodeMarketplace.I'mincludingtheMaterialIconthemeandAtomOneLightthemeinthissectionbecausethey'rebeingusedinthescreenshotsinthisbook.TheMaterialIconthemeisincrediblypopular,withover2milliondownloads,whiletheAtomOneLightthemehasover70,000downloads:
IconsintheMaterialIconstheme
SettingupforthewebInteractingwithanddebuggingWasmmoduleswillbedoneinthebrowser,whichmeanswe'llneedawaytoserveupafoldercontainingourexamplefiles.AswediscussedinChapter2,ElementsofWebAssembly-Wat,Wasm,andtheJavaScriptAPI,WebAssemblyisintegratedintothebrowser'sJavaScriptengine,butyou'llneedtomakesureyou'reusingabrowserthatsupportsit.Inthissection,wewillprovideinstructionsforcloningthebookexamplesrepository.Wewillalsoreviewhowtoquicklysetupalocalwebserverfortestingandevaluatingbrowseroptionstoensurethatyou'reabletodeveloplocally.
CloningthebookexamplesrepositoryYoumaywanttoclonetheGitHubrepositorynowwithalloftheexamplescontainedinthisbook.You'lldefinitelyneedtohavethecodeavailableforChapter7,CreatinganApplicationfromScratch,becausetheapplication'scodebaseistoolargetofitintoasinglechapter.Selectafolderonyourharddriveandrunthefollowingcommandtoclonetherepository:gitclonehttps://github.com/mikerourke/learn-webassembly
Oncethecloneprocessiscomplete,you'llfindthattheexamplesareorganizedbychapter.Ifthereareseveralexamplesinachapter,they'rebrokendownbysubfolderswithinthechapterfolder.
Ifyou'reusingWindows,donotclonetherepositoryintothe\Windowsfolderoranyotherfolderwithlimitedpermissions.Otherwise,youwillrunintoissueswhenattemptingtocompiletheexamples.
InstallingalocalserverWewilluseannpmpackage,serve,forservingupthefiles.Toinstall,simplyrunthiscommand:npminstall-gserve
Onceinstallationiscompleted,youcanserveupthefilesinanyfolder.Toensurethatit'sworking,let'stryservingupalocalfolder.Thecodeforthissectionislocatedinthe/chapter-03-dev-envfolderofthelearn-webassemblyrepository.Followtheseinstructionstovalidateyourserverinstallation:
1. First,let'screateafolderthatwillcontainthecodesampleswe'llbeworkingthroughfortheremainderofthebook(theexamplesusethenamebook-examples).
2. LaunchVSCodeandselectFile|Open...fromthemenubarformacOS/Linux,andFile|OpenFolder...forWindows.
3. Next,selectthefolder,book-examples,andpresstheOpen(orSelectFolder)button.
4. OnceVSCodefinishesloading,right-clickwithintheVSCodefileexplorerandselectNewFolderfromthemenuandnamethefolderchapter-03-dev-env.
5. Selectthechapter-03-dev-envfolderandpresstheNewFilebutton(orCmd/Ctrl+N)tocreateanewfile.Namethefileindex.htmlandpopulateitwiththefollowingcontents:
<!doctypehtml>
<htmllang="en-us">
<title>TestServer</title>
</head>
<body>
<h1>Test</h1>
<div>
Thisissometextonthemainpage.Click<ahref="stuff.html">here</a>
tocheckoutthestuffpage.
</div>
</body>
</html>
6. Createanotherfileinthechapter-03-dev-envfoldernamedstuff.htmlandpopulateitwiththefollowingcontents:
<!doctypehtml>
<htmllang="en-us">
<head>
<title>TestServer</title>
</head>
<body>
<h1>Stuff</h1>
<div>
Thisissometextonthestuffpage.Click<ahref="index.html">here</a>
togobacktotheindexpage.
</div>
</body>
</html>
7. WewilluseVSCode'sintegratedterminaltoserveupthefiles.YoucanaccessthisbyselectingView|IntegratedTerminal,orusingthekeyboardshortcutCtrl+`(the`isthebacktickkeyundertheEsckey).Onceloaded,runthiscommandtoserveuptheworkingfolder:
serve-l8080chapter-03-dev-env
Youshouldseethefollowing:
ResultsofrunningtheservecommandinterminalThe-l8080flagtellsservetoservethefolderonport8080.Thefirstlink(http://127.0.0.1:8080)isonlyaccessibleonyourcomputer.Anylinksbelowthatcanbeusedtoaccessthepagefromanother
computeronyourlocalnetwork.Ifyounavigatetothefirstlink(http://127.0.0.1:8080/index.html)inyourbrowser,youshouldsee
this:TestpageservedupinGoogleChromeClickingontheherelinkshouldbringyoutotheStuffpage(theaddressbarwillshow
127.0.0.1:8080/stuff.html.Ifeverythingisworkingcorrectly,it'stimetovalidateyourbrowser.
ValidatingyourbrowserToensurethatyou'reabletotestouttheexamplesinabrowser,youneedtomakesurethatthere'saglobalWebAssemblyobjectavailable.Topreventanyissuesrelatedtobrowsercompatibility,IrecommendthatyouhaveeitherGoogleChromeorMozillaFirefoxinstalledfordevelopment.Ifyouhadeitherofthesebrowsersinstalledbeforehand,there'saverygoodchancethatyourbrowserisalreadyvalid.Forthesakeofbeingthorough,wewillstillcoverthevalidationprocess.Inthissection,IwillreviewthestepsyoucantaketoensurethatyourbrowsersupportsWebAssembly.
ValidatingGoogleChromeTheprocessforvalidatingChromeprettystraightforward.Selectthebuttonthatlookslikethreeverticaldots(nexttotheaddressbar)andselectMoreTools|DeveloperToolsorusethekeyboardshortcutCmd/Ctrl+Shift+I:
AccessingDeveloperToolsinGoogleChromeOncetheDeveloperToolswindowappears,selecttheConsoletab,typeWebAssembly,andpressEnter.Ifyouseethis,yourbrowserisvalid:
ResultsofWebAssemblyvalidationinGoogleChrome'sDeveloperToolsconsole
ValidatingMozillaFirefoxTheprocessforvalidatingFirefoxisalmostidenticaltothatforGoogleChrome.SelectTools|WebDeveloper|ToggleToolsfromthemenubarorusethekeyboardshortcutCmd/Ctrl+Shift+I:
AccessingDeveloperToolsinMozillaFirefoxSelecttheConsoletab,clickinsidethecommandinputbox,typeWebAssembly,andpressEnter.You'llseethisifyourversionofFirefoxisvalid:
ResultsofWebAssemblyvalidationinMozillaFirefox'sDeveloperToolsconsole
ValidatingotherbrowsersThevalidationprocessforotherbrowsersisessentiallythesame;theonlyaspectofvalidationthatdiffersacrossbrowsersishowtoaccessthedevelopertools.IfaWebAssemblyobjectisavailablethroughtheconsoleofthebrowseryou'reusing,youcanusethatbrowserforWebAssemblydevelopment.
OthertoolsInadditiontotheapplicationsandtoolswecoveredintheprevioussections,therearesomegreattoolsthatarefreetouseandrichinfunctionalitythatcangreatlyimproveyourdevelopmentprocess.Iwon'thavetimetocoverthemall,butI'dliketohighlighttheonesIuseregularly.Inthissection,Iwillbrieflyreviewsomeofthepopulartoolingandapplicationsthatareavailableforeachplatform.
iTerm2formacOSThedefaultmacOSinstallationincludesTerminalapplication,Terminal,thatissufficientforuseinthisbook.Ifyouwantamorefull-featuredTerminal,iTerm2isanexcellentoption.Itoffersfeaturessuchassplittingwindows,extensivecustomization,multipleprofiles,andaToolbeltfeaturethatcandisplaynotes,runningjobs,commandhistory,andsoon.Youcandownloadtheimagefilefromtheofficialwebsite(https://www.iterm2.com/)andinstallitmanually,orinstalliTermwithHomebrew-Caskusingthiscommand:
brewcaskinstalliterm2
HereisiTerm2runningwiththeToolbeltopenandmultipleeditorwindows:
ITerminstancewithmultiplepanesandToolbelt
TerminatorforUbuntuTerminatoristheiTermandcmderofUbuntu,Terminalemulatorthatallowsformultipletabsandpaneswithinasinglewindow.Terminatoralsoprovidesfeaturessuchasdraganddrop,findfunctionality,andawidearrayofpluginsandthemes.YoucaninstallTerminatorthroughapt.Toensurethatyou'reusingthemostrecentversion,runthefollowingcommandsinTerminal:
sudoadd-apt-repositoryppa:gnome-terminator
sudoapt-getupdate
sudoapt-getinstallterminator
Referthescreenshot:
Terminatorscreenshottakenfromhttp://technicalworldforyou.blogspot.comB09984_03_17
cmderforWindowscmderisaconsoleemulatorforWindowsthataddsalotoffunctionalityandfeaturestothestandardCommandPromptorPowerShell.Itoffersfeaturessuchasmultipletabsandcustomizability.Itallowsyoutoopenupinstancesofdifferentshellswithinthesameprogram.Youcandownloadandinstallitfromtheofficialwebsite(cmder.net)orinstallitwithChocolateyusingthiscommand:chocoinstallcmder
Thisishowitlooks:
cmderscreenshotfromtheofficialwebsite
ZshandOh-My-ZshZshisaninteractiveshellthatimprovesuponBash.Oh-My-ZshisaconfigurationmanagerforZshthathasawidearrayofusefulplugins.Youcanseethewholelistontheirwebsite(https://github.com/robbyrussell/oh-my-zsh).Forexample,ifyouwantpowerfulautocompleteandsyntaxhighlightingfunctionalityinyourCLI,therearepluginssuchaszsh-autosuggestionandzsh-syntax-highlighting.YoucaninstallandconfigureZshandOh-My-ZshonmacOS,Linux,andWindows.TheOh-My-Zshpagehasinstallationinstructionsaswellasalistofthemesandplugins.
SummaryInthischapter,wecoveredtheinstallationandconfigurationprocessforthedevelopmenttoolingwewillusetostartworkingwithWebAssembly.WediscussedhowtoinstallGit,Node.js,andVSCodequicklyandeasilyusingapackagemanagerforyouroperatingsystems(forexample,HomebrewformacOS).ThestepstoconfigureVSCodewerepresentedaswellastherequiredandoptionalextensionsyoucanaddtoenhancethedevelopmentexperience.WediscussedhowtoinstallalocalwebserverfortestingandhowtovalidateyourbrowsertoensurethatWebAssemblyissupported.Finally,webrieflyreviewedsomeadditionaltoolsyoucaninstallforyourplatformtoaidindevelopment.
InChapter4,InstallingtheRequiredDependencies,we'llinstalltherequireddependenciesandtestoutthetoolchain.
Questions1. Whatisthenameofthepackagemanageryoushoulduseforyour
operatingsystem?2. DoesBitBucketsupportGit?3. Whyareweusingversion8ofNode.jsinsteadofthemostrecentversion?4. HowdoyouchangethecolorthemeinVisualStudioCode?5. HowdoyouaccesstheCommandPaletteinVisualStudioCode?6. HowdoyoucheckifyourbrowsersupportsWebAssembly?7. WhichofthetoolsintheOthertoolssectionissupportedonallthree
operatingsystems?
FurtherreadingHomebrew:https://brew.shaptdocumentation:https://help.ubuntu.com/lts/serverguide/apt.html.enChocolatey:https://chocolatey.orgGit:https://git-scm.comNode.js:https://nodejs.org/enGNUMake:https://www.gnu.org/software/makeVSCode:https://code.visualstudio.com
InstallingtheRequiredDependenciesNowthatyouhaveyourdevelopmentenvironmentsetupandyou'rereadytostartwritingC,C++,andJavaScript,it'stimetoaddthefinalpieceofthepuzzle.Inordertogenerate.wasmfilesfromourC/C++code,weneedtoinstallandconfiguretheEmscriptenSDK(EMSDK).
Inthischapter,we'lldiscussthedevelopmentworkflowandtalkabouthowtheEMSDKfitsintothedevelopmentprocess.DetailedinstructionswillbeprovidedonhowtoinstallandconfiguretheEMSDKoneachplatform,aswellasanyprerequisites.Oncetheinstallationandconfigurationprocessiscomplete,you'lltestitoutbywritingandcompilingsomeCcode.
Ourgoalforthischapteristounderstandthefollowing:
TheoveralldevelopmentworkflowwhenworkingwithWebAssemblyHowtheEMSDKrelatestoEmscriptenandWebAssemblyandwhyit'sneededHowtoinstalltheprerequisitesfortheEMSDKHowtoinstallandconfiguretheEMSDKHowtotesttheEMSDKtoensureit'sworkingcorrectly
ThedevelopmentworkflowThedevelopmentworkflowforWebAssemblyiscomparabletomostotherlanguagesthatrequirecompilationandabuildprocess.Beforegettingintothetoolingsetup,wewillcoverthedevelopmentcycle.Inthissection,wewillestablishsomecontextforthetoolingwewillinstallandconfigureintherestofthischapter.
StepsintheworkflowForthisbook,wewillwriteCandC++codeandcompileitdowntoaWasmmodule,buttheworkflowwillbeapplicabletoanyprogramminglanguagethatcompilesdowntoa.wasmfile.Thefollowingdiagramgivesanoverviewofthe
process:
Stepsinthedevelopmentworkflow
Thisprocesswillbeusedthroughoutthebookforourexamples,soyou'llgetanideaofhowtheprojectstructurecorrespondstotheworkflow.We'llusesomeofthetoolingavailabletoexpediteandsimplifytheprocess,butthestepswillstillbethesame.
IntegratingToolingintotheworkflowTherearemanyeditorsandtoolsavailabletosimplifythedevelopmentprocess.Fortunately,C/C++andJavaScripthavebeenaroundforquitesometime,soyoucantakeadvantageoftheoptionsthatsuityoubest.ThelistoftoolsforWebAssemblyisconsiderablyshorter,giventheshorterdurationofwhichthetechnologyhasexisted,buttheyareoutthere.
Theprimarytoolwe'lluse,VSCode,offerssomeexcellentandusefulfeaturesforsimplifyingthebuildanddevelopmentprocess.Inadditiontousingitforwritingourcode,we'llutilizeVSCode'sbuilt-inTasksfeaturetobuildthe.wasmfilefromC/C++.Bycreatinga.vscode/tasks.jsonfileintheprojectrootfolder,we'reabletospecifyalloftheparametersassociatedwiththebuildstepandrunitquicklyusingakeyboardshortcut.Inadditiontoperformingabuild,wecanstartandstoparunningNode.jsprocess(thatis,thelocalserverintheworkflowdiagram).We'llcoverhowtoaddandconfigurethesefeaturesinthenextchapter.
EmscriptenandtheEMSDKWe'lluseEmscriptentocompileourC/C++codedownto.wasmfiles.Uptothispoint,Emscriptenhasonlybrieflybeenmentionedinageneralcontext.Sincewe'llusethistoolandthecorrespondingEmscriptenSDK(EMSDK)inthebuildprocess,it'simportanttounderstandwhateachtechnologyisandthepartitplaysinthedevelopmentworkflow.Inthissection,we'lldescribeEmscripten'spurposeanddiscussitsrelationshiptotheEMSDK.
EmscriptenoverviewSowhatisEmscripten?Wikipediaprovidesthefollowingdefinition:
"Emscriptenisasource-to-sourcecompilerthatrunsasabackendtotheLLVMcompilerandproducesasubsetofJavaScriptknownasasm.js.ItcanalsoproduceWebAssembly."
Wediscussedsource-to-sourcecompilers(ortranspilers)inthefirstchapterandusedTypeScriptasanexample.Transpilersconvertsourcecodeinoneprogramminglanguagetoequivalentsourcecodeinanotherprogramminglanguage.ToelaborateonEmscriptenrunningasabackendtotheLLVMcompiler,weneedtoprovidesomeadditionaldetailsaboutLLVM.
TheofficialwebsiteforLLVM(https://llvm.org)definestheLLVMasacollectionofmodularandreusablecompilerandtoolchaintechnologies.Thereareseveralsub-projectsthatmakeupLLVM,butwe'llbefocusingonthetwothatEmscriptenutilizes:ClangandtheLLVMCorelibraries.Tounderstandhowthesepiecesfittogether,let'sreviewthedesignofathree-stagecompiler:
Designofageneralthree-stagecompiler
Theprocessisrelativelystraightforward:threeseparatestagesorendshandlethecompilationprocess.Thisdesignallowsfordifferentfrontendsandbackendsforvariousprogramminglanguagesandtargetarchitecturesandcompletelydecouplesthemachinecodefromthesourcecodebyusinganintermediaterepresentation.Nowlet'sassociateeachcompilationstagewithacomponentofthetoolchainwe'llusetogenerateWebAssembly:
Three-stagecompilationusingtheLLVM,Clang,andEmscripten
ClangisusedtocompileC/C++downtoLLVM'sIntermediateRepresentation(IR),whichEmscriptencompilestoaWasmmodule(binaryformat).ThetwodiagramsalsodemonstratetherelationshipbetweenWasmandmachinecode.YoucanthinkofWebAssemblyasaCPUinthebrowser,withWasmbeingthemachinecodeonwhichitruns.
WheredoestheEMSDKfitin?EmscriptenreferstothetoolchainusedtocompileCandC++downtoasm.jsorWebAssembly.TheEMSDKisusedtomanagethetoolsinthetoolchainandthecorrespondingconfiguration.Thiseliminatestheneedforcomplexenvironmentsetupandpreventsissueswithincompatibleversionsoftooling.ByinstallingtheEMSDK,wehaveallofthetoolingweneed(withtheexceptionoftheprerequisites)tousetheEmscriptencompiler.ThefollowingdiagramisavisualrepresentationoftheEmscriptentoolchain(withtheEMSDKshownindarkgray):
EmscriptenToolchain(modifiedslightlyfromemscripten.org)
NowthatyouhaveabetterunderstandingofEmscriptenandtheEMSDK,let'smoveontotheinstallationprocessfortheprerequisites.
InstallingtheprerequisitesBeforeinstallingandconfiguringtheEMSDK,we'llneedtoinstallsomeprerequisites.YouinstalledtwooftheprerequisitesinChapter3,SettingUpaDevelopmentEnvironment:Node.jsandGit.Eachplatformhasslightlydifferentinstallationprocessesandtoolingrequirements.Inthissection,wecovertheinstallationprocessfortheprerequisitetoolingforeachplatform.
CommonprerequisitesIt'spossiblethatyoualreadyhavealloftheprerequisitesinstalled.Herearethethreethatyou'llneedregardlessoftheplatform:
GitNode.jsPython2.7
NotethePythonversion;thisisimportantbecauseinstallingthewrongversioncouldcausetheinstallationprocesstofail.IfyoufollowedalonginChapter2,ElementsofWebAssembly-Wat,Wasm,andtheJavaScriptAPI,andinstalledNode.jsandGit,allthat'sleftistoinstallPython2.7andanyadditionalprerequisitesspecifiedforyourplatform.ThePythoninstallationprocessforeachplatformwillbespecifiedinthefollowingsubsections.
Pythonisahigh-levelprogramminglanguageusedforgeneral-purposeprogramming.Ifyou'dliketolearnmore,checkouttheofficialwebsiteathttps://www.python.org/.
InstallingtheprerequisitesonmacOSTherearethreeadditionaltoolsyou'llneedtoinstallpriortoinstallingtheEMSDK:
XcodeXcodeCommandLineToolsCMake
YoucaninstallXcodefromthemacOSAppStore.IfyoualreadyhadXcodeinstalled,youcancheckiftheCommandLineToolsareinstalledbygoingtoXcode|Preferences|LocationsandcheckingiftheCommandLineToolsoptionhasavalue.TheCommandLineToolsshouldhavealreadybeeninstalledifyouinstalledtheHomebrewpackagemanager:
CheckingthecurrentversionoftheXcodeCommandLineTools
Ifyoudon'tseethat,openupTerminalandrunthiscommand:
xcode-select--install
Oncecomplete,youcaninstallCMakebyrunningthiscommand:
brewinstallcmake
PriortoinstallingPython,runthiscommand:
python--version
IfyouseePython2.7.xx(wherexxisthepatchversionandcanbeanynumber),you'rereadytoinstalltheEMSDK.IfyougetanerrorsayingthePythoncommandwasn'tfoundoryouseePython3.x.xx,Irecommendyouinstallpyenv,aPythonVersionmanager.Toinstallpyenv,runthiscommand:
brewinstallpyenv
You'llneedtotakesomeadditionalconfigurationstepstofinalizetheinstallation.FollowtheinstallationinstructionsforHomebrewathttps://github.com/pyenv/pyenv#homebrew-on-mac-os-x.Afterinstallingandconfiguringpyenv,runthiscommandtoinstallPython2.7:
pyenvinstall2.7.15
Aftertheinstallationiscomplete,runthiscommand:
pyenvglobal2.7.15
Toensureyou'reusingthecorrectversionofPython,runthiscommand:
python--version
YoushouldseePython2.7.xx,wherexxisthepatchversion(Iwasseeing2.7.10,whichwillworkfine).
InstallingtheprerequisitesonUbuntuUbuntushouldalreadyhavePython2.7installed.Youcanconfirmthisbyrunningthiscommand:
python--version
IfyouseePython2.7.xx(wherexxisthepatchversionandcanbeanynumber),you'rereadytoinstalltheEMSDK.Ifyougetanerrorsayingthepythoncommandwasn'tfoundoryouseePython3.x.xx,Irecommendyouinstallpyenv,aPythonversionmanager.Beforeinstallingpyenv,checkifyouhavecurlinstalled.Youcandothisbyrunningthefollowingcommand:
curl--version
Ifyouseeaversionnumberandotherinformation,curlisinstalled.Ifnot,youcaninstallcurlbyrunningthefollowingcommand:
sudoapt-getinstallcurl
Oncethecurlinstallationiscomplete,runthiscommandtoinstallpyenv:
curl-Lhttps://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer|bash
Afterinstallingandconfiguringpyenv,runthiscommandtoinstallPython2.7:
pyenvinstall2.7.15
Ifyouencounterbuildissues,navigatetotheCommonbuildproblemspageathttps://github.com/pyenv/pyenv/wiki/common-build-problems.Aftertheinstallationiscomplete,runthiscommand:
pyenvglobal2.7.15
Toensureyou'reusingthecorrectversionofPython,runthiscommand:
python--version
InstallingtheprerequisitesonWindowsTheonlyadditionalprerequisiteforWindowsisPython2.7.Beforeattemptingtheinstallation,runthiscommand:python--version
IfyouseePython2.7.xx(wherexxisthepatchversionandcanbeanynumber),you'rereadytoinstalltheEMSDK.IfyougetanerrorsayingthePythoncommandwasn'tfound,oryouseePython3.x.xxandPython2.7isn'tinstalledonyoursystem,runthiscommandtoinstallPython2.7:chocoinstallpython2-y
IfyousawPython3.x.xxpriortoinstallingPython2.7,youshouldbeabletochangethecurrentPythonversionbyupdatingyourpath.BeforeattemptingtheEMSDKinstallation,runthiscommandtosetPythonto2.7:SETPATH=C:\Python27\python.exe
InstallingandconfiguringtheEMSDKIfyouhavealloftheprerequisitesinstalled,you'rereadytoinstalltheEMSDK.TheprocessforgettingtheEMSDKupandrunningisrelativelystraightforward.Inthissection,wecovertheinstallationprocessfortheEMSDKanddemonstratehowtoupdateyourVSCodeC/C++configurationtoaccommodateforEmscripten.
InstallationprocessacrossallplatformsFirst,selectafoldertoinstalltheEMSDK.Icreatedafolderat~/Tooling(orC:\Users\Mike\ToolingonWindows).Inaterminal,cdintothefolderyoujustcreatedandrunthiscommand:
gitclonehttps://github.com/juj/emsdk.git
Oncethecloneprocessiscomplete,followtheinstructionstocompletetheinstallationfromthesectionbelowthatcorrespondstoyourplatform.
InstallationonmacOSandUbuntuOncethecloneprocessiscomplete,runeachofthecommandsfromthefollowingcodesnippet.Ifyouseeamessagerecommendingthatyourungitpullinsteadof./emsdkupdate,usethegitpullcommandpriortorunningthe./emsdkinstalllatestcommand:#ChangedirectoryintotheEMSDKinstallationfoldercdemsdk
#Fetchthelatestregistryofavailabletools./emsdkupdate
#DownloadandinstallthelatestSDKtools./emsdkinstalllatest
#MakethelatestSDKactiveforthecurrentuser(writes~/.emscriptenfile)./emsdkactivatelatest
#ActivatePATHandotherenvironmentvariablesinthecurrentTerminalsource./emsdk_env.sh
Thesource./emsdk_env.shcommandwillactivatetheenvironmentvariablesinthecurrentTerminal,whichmeanseverytimeyoucreateanewTerminalinstance,you'dhavetore-runit.Topreventhavingtotakethisstep,youcanaddthefollowinglinetoyourBashorZshconfigurationfile(thatis,~/.bash_profileor~/.zshrc):source~/Tooling/emsdk/emsdk_env.sh>/dev/null
IfyouinstalledtheEMSDKinadifferentlocation,makesurethatyouupdatethepathtoreflectthis.AddingthislinetoyourconfigurationfilewillrunthatenvironmentupdatecommandautomaticallysoyoucanstartusingtheEMSDKimmediately.ToensureyoucanusetheEmscriptencompiler,runthiscommand:emcc--version
Ifyouseeamessagewithversioninformation,thesetupwassuccessful.Ifyouseeanerrormessagestatingthatthecommandwasnotfound,double-checkyourconfiguration.Youmayhavespecifiedaninvalidpathfortheemsdk_env.shin
InstallationandconfigurationonWindowsBeforecompletingtheinstallation,IrecommendyouusePowerShellgoingforward.TheexamplesinthisbookwillbeusingPowerShellinsidecmder.Oncethecloneprocessiscomplete,runeachofthecommandsgiveninthefollowingcodesnippet.Ifyouseeamessagerecommendingthatyourungitpullinsteadof./emsdkupdate,usethegitpullcommandpriortorunningthe./emsdkinstalllatestcommand:
#ChangedirectoryintotheEMSDKinstallationfolder
cdemsdk
#Fetchthelatestregistryofavailabletools
.\emsdkupdate
#DownloadandinstallthelatestSDKtools
.\emsdkinstalllatest
#MakethelatestSDKactiveforthecurrentuser(writes~/.emscriptenfile)
.\emsdkactivate--globallatest
The--globalflaginthe.\emsdkactivatecommandallowsyoutorunemccwithouthavingtorunascripttosettheenvironmentvariableseachsession.ToensureyoucanusetheEmscriptencompiler,restartyourCLIandrunthiscommand:
emcc--version
Ifyouseeamessagewithversioninformation,thesetupwassuccessful.
ConfigurationinVSCodeIfyouhaven'talreadydoneso,createafolderthatwillcontainthecodesampleswe'llbeworkingthrough(theexamplesusethenamebook-examples).OpenthisfolderinVSCode,presstheF1key,andselectC/Cpp:EditConfigurations…tocreatea.vscode/c_cpp_properties.jsonfileintherootofyourproject.Itshouldopenthefileautomatically.Addthefollowinglinetothebrowse.patharray:"${env:EMSCRIPTEN}/system/include".Thiswillpreventerrorsbeingthrownifyouincludetheemscripten.hheader.Youmayneedtomanuallycreatethebrowseobjectwithapathentryifitdidn'tgenerateoneautomatically.ThefollowingsnippetrepresentstheupdatedconfigurationfileonUbuntu:
{
"name":"Linux",
"includePath":[
"/usr/include",
"/usr/local/include",
"${workspaceFolder}",
"${env:EMSCRIPTEN}/system/include"
],
"defines":[],
"intelliSenseMode":"clang-x64",
"browse":{
"path":[
"/usr/include",
"/usr/local/include",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders":true,
"databaseFilename":""
}
}
TestingthecompilerAfterinstallingandconfiguringtheEMSDK,you'llneedtotestittoensureyou'reabletogenerateWasmmodulesfromC/C++code.Theeasiestwaytotestitistocompilesomecodeusingtheemcccommandandtryrunningitinabrowser.Inthissection,we'llvalidatetheEMSDKinstallationbywritingandcompilingsomesimpleCcodeandevaluatingtheWatassociatedwiththe.wasmoutput.
TheCcode
We'llusesomeverysimpleCcodetotestourcompilerinstallation.Wewon'tneedtoimportanyheadersorexternallibraries.Wewon'tuseC++forthistestbecauseweneedtoperformanextrastepwithC++topreventnamemangling,whichwe'lldescribeingreaterdetailinChapter6,InteractingwithJavaScriptandDebugging.Thecodeforthissectionislocatedinthe/chapter-04-installing-depsfolderofthelearn-webassemblyrepository.FollowtheinstructionslistedheretotestouttheEMSDK.
Createasubfoldernamed/chapter-04-installing-depsinyour/book-examplesfolder.Next,createanewfileinthisfoldernamedmain.candpopulateitwiththefollowingcontents:
intaddTwoNumbers(intleftValue,intrightValue){
returnleftValue+rightValue;
}
CompilingtheCcodeInordertocompileaC/C++filewithEmscripten,we'llusetheemcccommand.Weneedtopasssomeargumentstothecompilertoensurewegetavalidoutputthatwecanutilizeinthebrowser.TogenerateaWasmfilefromaC/C++file,thecommandfollowsthisformat:
emcc<file.c>-Os-sWASM=1-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0-o<file.wasm>
Here'sabreakdownofeachoftheargumentsfortheemcccommand:
Argument Description
<file.c>
PathoftheCorC++inputfilethatwillbecompileddowntoaWasmmodule;we'llreplacethiswiththeactualfilepathwhenwerunthecommand.
-Os
Compileroptimizationlevel.ThisoptimizationflagallowsformoduleinstantiationwithoutrequiringEmscripten'sgluecode.
-sWASM=1TellsthecompilertocompilecodetoWebAssembly.
-sSIDE_MODULE=1EnsuresonlyaWebAssemblymoduleisoutput(nogluecode).
-s
BINARYEN_ASYNC_COMPILATION=0
Fromofficialdocs:
Whethertocompilethewasmasynchronously,whichismoreefficientanddoesnotblockthemainthread.ThisiscurrentlyrequiredforallbutthesmallestmodulestoruninV8.
-o<file.wasm>
Pathofoutputfile.wasmfile.We'llreplacethiswiththedesiredoutputpathwhenwerunthecommand.
TotestifEmscriptenisworkingcorrectly,opentheintegratedterminalinVSCodeandrunthefollowingcommands:
#Ensureyou'reinthe/chapter-04-installing-depsfolder:
cdchapter-04-installing-deps
#Compilethemain.cfiletomain.wasm:
emccmain.c-Os-sWASM=1-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0-omain.wasm
Itmaytakeaminutetocompilethefilethefirsttime,butsubsequentbuildswillbemuchfaster.Ifthecompilationwassuccessful,youshouldseeamain.wasmfileinthe/chapter-04-installing-depsfolder.Ifyouencounteranerror,Emscripten'serrormessageshouldbedescriptiveenoughtohelpyoucorrecttheissue.
Ifeverythingcompletedsuccessfully,youcanviewtheWatassociatedwiththemain.wasmfilebyright-clickingmain.wasminVSCode'sfileexplorerandselectingShowWebAssemblyfromthecontextmenu.Theoutputshouldlooklikethis:
(module
(type$t0(func(parami32)))
(type$t1(func(parami32i32)(resulti32)))
(type$t2(func))
(type$t3(func(resultf64)))
(import"env""table"(table$env.table2anyfunc))
(import"env""memoryBase"(global$env.memoryBasei32))
(import"env""tableBase"(global$env.tableBasei32))
(import"env""abort"(func$env.abort(type$t0)))
(func$_addTwoNumbers(type$t1)(param$p0i32)(param$p1i32)(resulti32)
get_local$p1
get_local$p0
i32.add)
(func$runPostSets(type$t2)
nop)
(func$__post_instantiate(type$t2)
get_global$env.memoryBase
set_global$g2
get_global$g2
i32.const5242880
i32.add
set_global$g3)
(func$f4(type$t3)(resultf64)
i32.const0
call$env.abort
f64.const0x0p+0(;=0;))
(global$g2(muti32)(i32.const0))
(global$g3(muti32)(i32.const0))
(global$fp$_addTwoNumbersi32(i32.const1))
(export"__post_instantiate"(func$__post_instantiate))
(export"_addTwoNumbers"(func$_addTwoNumbers))
(export"runPostSets"(func$runPostSets))
(export"fp$_addTwoNumbers"(global4))
(elem(get_global$env.tableBase)$f4$_addTwoNumbers))
Ifthecompilerransuccessfully,you'rereadytomoveontothenextstepandwriteJavaScriptcodetointeractwiththemodule,whichwe'llcoverinthenextchapter.
SummaryInthischapter,wecoveredtheoveralldevelopmentworkflowwhenworkingwithWebAssembly.Inordertogenerateour.wasmfiles,we'reusingEmscripten,whichrequirestheinstallationoftheEMSDK.Priortoreviewinganyinstallationdetails,wediscussedthetechnologiesunderthehoodanddescribedhowtheyrelatetoeachotherandtoWebAssembly.WecoveredeachofthestepsrequiredtogetEMDSKworkinglocallyonyourcomputer.TheinstallationprocessfortheEMSDKoneachplatformwaspresented,aswellastheinstallationandconfigurationinstructionsfortheEMSDK.AfterinstallingtheEMSDK,wetestedthecompiler(noto).Thatwastheemcccommandweranintheprevioussection.UsingtheemcccommandonasimpleCcodefiletoensureEmscriptenwasworkingcorrectly.Inthenextchapter,we'llwalkthroughtheprocessofcreatingandloadingyourfirstmodule!
Questions1. Whatarethefivestepsinthedevelopmentworkflow?2. WhichstageorenddoesEmscriptenrepresentinthecompilationprocess?3. WhatdoesIRstandfor(LLVM'soutput)?4. WhatroledoestheEMSDKplaywithregardtoEmscripten?5. WhichEMSDKprerequisitesarerequiredonallthreeplatforms(macOS,
Windows,andLinux)?6. Whydoyouneedtoruntheemsdk_envscriptbeforeyoucanusethe
Emscriptencompiler?7. Whydoyouneedtoaddthe"${env:EMSCRIPTEN}/system/include"pathtothe
C/Cppconfigurationfile?8. WhatisthecommandusedtocompileC/C++downtoWasmmodules?9. Whatdoesthe-Oscompilerflagrepresent?
FurtherreadingEmscripten:http://emscripten.orgTheLLVMCompilerInfrastructureProject:https://llvm.orgC++programmingwithVisualStudioCode:https://code.visualstudio.com/docs/languages/cpp
CreatingandLoadingaWebAssemblyModuleTheflagswepassedtotheemcccommandinChapter4,InstallingtheRequiredDependencies,producedasingle.wasmfilethatcouldbeloadedandinstantiatedinthebrowserusingthenativeWebAssemblyobject.TheCcodewasaverysimpleexampleintendedtotestthecompilerwithouthavingtoaccommodateforincludedlibrariesorWebAssembly'slimitations.WecanovercomesomeofthelimitationsofWebAssemblyinourC/C++codewithminimalperformancelossbyutilizingsomeofEmscripten'scapabilities.
Inthischapter,we'llcoverthecompilationandloadingstepsthatcorrespondwiththeuseofEmscripten'sgluecode.We'llalsodescribetheprocessforcompiling/outputtingstrictly.wasmfilesandloadingthemusingthebrowser'sWebAssemblyobject.
Ourgoalforthischapteristounderstandthefollowing:
ThecompilationprocessforCcodethatutilizesEmscripten'sJavaScript"glue"codeHowtoloadanEmscriptenmoduleinthebrowserThecompilationprocessforCcodethatoutputsonly.wasmfiles(no"glue"code)HowtoconfigurebuildtasksinVSCodeHowtocompileandloadaWasmmoduleinthebrowserusingtheglobalWebAssemblyobject
CompilingCwithEmscriptengluecodeInChapter4,InstallingtheRequiredDependencies,youwroteandcompiledasimplethree-lineprogramtoensureyourEmscripteninstallationwasvalid.Wepassedseveralflagstotheemcccommandthatwererequiredtoonlyoutputasingle.wasmfile.Bypassingotherflagstotheemcccommand,wecanoutputJavaScriptgluecodealongsidethe.wasmfileaswellasanHTMLfiletohandletheloadingprocess.Inthissection,we'regoingtowriteamorecomplexCprogramandcompileitwiththeoutputoptionsthatEmscriptenoffers.
WritingtheexampleCcodeWedidn'tincludeanyheaderfilesorpassinanyfunctionsintheexamplewecoveredinChapter4,InstallingtheRequiredDependencies.Sincetheintentionofthecodewassolelytotestifthecompilerinstallationwasvalid,therewasn'tmuchneed.EmscriptenoffersalotofextrafunctionalitythatenablesustointeractwithourCandC++codewithJavaScriptandviceversa.SomeofthesecapabilitiesareEmscripten-specificanddon'tcorrespondtotheCoreSpecificationoritsAPIs.Inourfirstexample,we'lltakeadvantageofoneofEmscripten'sportedlibrariesandafunctionprovidedbyEmscripten'sAPI.
ThefollowingprogramusesaSimpleDirectMediaLayer(SDL2)tomovearectanglediagonallyacrossacanvasinaninfiniteloop.Itwastakenfromhttps://github.com/timhutton/sdl-canvas-wasm,butIconverteditfromC++toCandmodifiedthecodeslightly.Thecodeforthissectionislocatedinthe/chapter-05-create-load-modulefolderofthelearn-webassemblyrepository.FollowthefollowinginstructionstocompileCwithEmscripten.
Createafolderinyour/book-examplesfoldernamed/chapter-05-create-load-module.Createanewfileinthisfoldernamedwith-glue.candpopulateitwiththefollowingcontents:
/*
*ConvertedtoCcodetakenfrom:
*https://github.com/timhutton/sdl-canvas-wasm
*Someofthevariablenamesandcommentswerealso
*slightlyupdated.
*/
#include<SDL2/SDL.h>
#include<emscripten.h>
#include<stdlib.h>
//Thisenablesustohaveasinglepointofreference
//forthecurrentiterationandrenderer,ratherthan
//havetorefertothemseparately.
typedefstructContext{
SDL_Renderer*renderer;
intiteration;
}Context;
/*
*Loopingfunctionthatdrawsabluesquareonared
*backgroundandmovesitacrossthe<canvas>.
*/
voidmainloop(void*arg){
Context*ctx=(Context*)arg;
SDL_Renderer*renderer=ctx->renderer;
intiteration=ctx->iteration;
//Thissetsthebackgroundcolortored:
SDL_SetRenderDrawColor(renderer,255,0,0,255);
SDL_RenderClear(renderer);
//Thiscreatesthemovingbluesquare,therect.x
//andrect.yvaluesupdatewitheachiterationtomove
//1pxatatime,sothesquarewillmovedownand
//totherightinfinitely:
SDL_Rectrect;
rect.x=iteration;
rect.y=iteration;
rect.w=50;
rect.h=50;
SDL_SetRenderDrawColor(renderer,0,0,255,255);
SDL_RenderFillRect(renderer,&rect);
SDL_RenderPresent(renderer);
//Thisresetsthecounterto0assoonastheiteration
//hitsthemaximumcanvasdimension(otherwiseyou'd
//neverseethebluesquareafterittravelledacross
//thecanvasonce).
if(iteration==255){
ctx->iteration=0;
}else{
ctx->iteration++;
}
}
intmain(){
SDL_Init(SDL_INIT_VIDEO);
SDL_Window*window;
SDL_Renderer*renderer;
//Thefirsttwo255valuesrepresentthesizeofthe<canvas>
//elementinpixels.
SDL_CreateWindowAndRenderer(255,255,0,&window,&renderer);
Contextctx;
ctx.renderer=renderer;
ctx.iteration=0;
//Callthefunctionrepeatedly:
intinfinite_loop=1;
//Callthefunctionasfastasthebrowserwantstorender
//(typically60fps):
intfps=-1;
//Thisisafunctionfromemscripten.h,itsetsaCfunction
//asthemaineventloopforthecallingthread:
emscripten_set_main_loop_arg(mainloop,&ctx,fps,infinite_loop);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
returnEXIT_SUCCESS;
}
Theemscripten_set_main_loop_arg()towardtheendofthemain()functionisavailablebecauseweincludedemscripten.hatthetopofthefile.ThevariablesandfunctionsprefixedwithSDL_areavailablebecauseofthe#include<SDL2/SDL.h>atthetopofthefile.Ifyou'reseeingasquigglyrederrorlineunderthe<SDL2/SDL.h>statement,youcandisregardit.It'sduetoSDL'sincludepathnotbeingpresentinyourc_cpp_properties.jsonfile.
CompilingtheexampleCcodeNowthatwehaveourCcodewritten,we'llneedtocompileit.Oneoftherequiredflagsyoumustpasstotheemcccommandis-o<target>,where<target>isthepathtothedesiredoutputfile.Theextensionofthatfilewilldomorethanjustoutputthatfile;itimpactssomeofthedecisionsthecompilermakes.Thefollowingtable,takenfromEmscripten'semccdocumentationathttp://kripken.github.io/emscripten-site/docs/tools_reference/emcc.html#emcc-o-target,definesthegeneratedoutputtypesbasedonthefileextensionspecified:
Extension Output
<name>.js JavaScriptgluecode(and.wasmifthesWASM=1flagisspecified).
<name>.htmlHTMLandseparateJavaScriptfile(<name>.js).HavingtheseparateJavaScriptfileimprovespageloadtime.
<name>.bc LLVMbitcode(default).
<name>.o LLVMbitcode(sameas.bc).
<name>.wasmWasmfileonly(withflagsspecifiedfromChapter4,InstallingtheRequiredDependencies).
Youcandisregardthe.bcand.ofileextensions—wewon'tneedtooutputLLVMbitcode.The.wasmextensionisn'tontheemccToolsReferencepage,butitisavalidoptionifyoupassthecorrectcompilerflags.TheseoutputoptionsfactorintotheC/C++codewewrite.
OutputtingHTMLwithgluecodeIfyouspecifyanHTMLfileextension(forexample,-owith-glue.html)fortheoutput,you'llendupwithawith-glue.html,with-glue.js,andwith-glue.wasmfile(assumingyoualsospecified-sWASM=1).Ifyouhaveamain()functioninthesourceC/C++file,it'llexecutethatfunctionassoonastheHTMLloads.Let'scompileourexampleCcodetoseethisinaction.TocompileitwiththeHTMLfileandJavaScriptgluecode,cdintothe/chapter-05-create-load-modulefolderandrunthefollowingcommand:
emccwith-glue.c-O3-sWASM=1-sUSE_SDL=2-owith-glue.html
Thefirsttimeyourunthiscommand,EmscriptenisgoingtodownloadandbuildtheSDL2library.Itcouldtakeseveralminutestocompletethis,butyou'llonlyneedtowaitonce.Emscriptencachesthelibrarysosubsequentbuildswillbemuchfaster.Oncethebuildiscomplete,you'llseethreenewfilesinthefolder:HTML,JavaScript,andWasmfiles.Runthefollowingcommandtoservethefilelocally:
serve-l8080
Ifyouopenyourbrowseruptohttp://127.0.0.1:8080/with-glue.html,youshouldseethefollowing:
Emscriptenloadingcoderunninginthebrowser
Thebluerectangleshouldbemovingdiagonallyfromtheupper-leftcorneroftheredrectangletothelower-right.Sinceyouspecifiedamain()functionintheCfile,Emscriptenknowsitshouldexecuteitrightaway.Ifyouopenupthewith-glue.htmlfileinVScodeandscrolltothebottomofthefile,youwillseetheloadingcode.Youwon'tseeanyreferencestotheWebAssemblyobject;that'sbeinghandledintheJavaScriptgluecodefile.
OutputtinggluecodewithnoHTMLTheloadingcodethatEmscriptengeneratesintheHTMLfilecontainserrorhandlingandotherhelpfulfunctionstoensurethemoduleisloadingbeforeexecutingthemain()function.Ifyouspecify.jsfortheextensionoftheoutputfile,you'llhavetocreateanHTMLfileandwritetheloadingcodeyourself.Inthenextsection,we'regoingtodigintotheloadingcodeinmoredetail.
LoadingtheEmscriptenmoduleLoadingandinteractingwithamodulethatutilizesEmscripten'sgluecodeisconsiderablydifferentfromWebAssembly'sJavaScriptAPI.ThisisbecauseEmscriptenprovidesadditionalfunctionalityforinteractingwiththeJavaScriptcode.Inthissection,we'regoingtodiscusstheloadingcodethatEmscriptenprovideswhenoutputtinganHTMLfileandreviewtheprocessforloadinganEmscriptenmoduleinthebrowser.
Pre-generatedloadingcodeIfyouspecify-o<target>.htmlwhenrunningtheemcccommand,EmscriptengeneratesanHTMLfileandautomaticallyaddscodetoloadthemoduletotheendofthefile.Here'swhattheloadingcodeintheHTMLfilelookslikewiththecontentsofeachModulefunctionexcluded:
varstatusElement=document.getElementById('status');
varprogressElement=document.getElementById('progress');
varspinnerElement=document.getElementById('spinner');
varModule={
preRun:[],
postRun:[],
print:(function(){...})(),
printErr:function(text){...},
canvas:(function(){...})(),
setStatus:function(text){...},
totalDependencies:0,
monitorRunDependencies:function(left){...}
};
Module.setStatus('Downloading...');
window.onerror=function(event){
Module.setStatus('Exceptionthrown,seeJavaScriptconsole');
spinnerElement.style.display='none';
Module.setStatus=function(text){
if(text)Module.printErr('[post-exceptionstatus]'+text);
};
};
ThefunctionswithintheModuleobjectarepresenttodetectandaddresserrors,monitortheloadingstatusoftheModule,andoptionallyexecutesomefunctionsbeforeoraftertherun()methodfromthecorrespondinggluecodefileexecutes.Thecanvasfunction,showninthefollowingsnippet,returnsthe<canvas>elementfromtheDOMthatwasspecifiedintheHTMLfilebeforetheloadingcode:
canvas:(function(){
varcanvas=document.getElementById('canvas');
canvas.addEventListener(
'webglcontextlost',
function(e){
alert('WebGLcontextlost.Youwillneedtoreloadthepage.');
e.preventDefault();
},
false
);
returncanvas;
})(),
ThiscodeisconvenientfordetectingerrorsandensuringtheModuleisloaded,butforourpurposes,wewon'tneedtobeasverbose.
WritingcustomloadingcodeEmscripten'sgeneratedloadingcodeprovideshelpfulerrorhandling.Ifyou'reusingEmscripten'soutputinproduction,Iwouldrecommendthatyouincludeittoensureyou'rehandlingerrorscorrectly.However,wedon'tactuallyneedallthecodetoutilizeourModule.Let'swritesomemuchsimplercodeandtestitout.First,let'scompileourCfiledowntogluecodewithnoHTMLoutput.Todothat,runthefollowingcommand:emccwith-glue.c-O3-sWASM=1-sUSE_SDL=2-sMODULARIZE=1-ocustom-loading.js
The-sMODULARIZE=1compilerflagallowsustouseaPromise-likeAPItoloadourModule.Oncethecompilationiscomplete,createafileinthe/chapter-05-create-load-modulefoldernamedcustom-loading.htmlandpopulateitwiththefollowingcontents:<!doctypehtml><htmllang="en-us"><head><title>CustomLoadingCode</title></head><body><h1>UsingCustomLoadingCode</h1><canvasid="canvas"></canvas><scripttype="application/javascript"src="custom-loading.js"></script><scripttype="application/javascript">Module({canvas:(()=>document.getElementById('canvas'))(),}).then(()=>{console.log('Loaded!');});</script></body></html>
TheloadingcodeisnowusingES6'sarrowfunctionsyntaxforthecanvasloadingfunction,whichreducesthelinesofcoderequired.Startyourlocal
serverbyrunningtheservecommandwithinthe/chapter-05-create-load-modulefolder:serve-l8080
Whenyounavigatetohttp://127.0.0.1:8080/custom-loading.htmlinyourbrowser,youshouldseethis:
Customloadingcoderunninginthebrowser
Ofcourse,thefunctionwe'rerunningisn'tverycomplex,butitdemonstratesthebare-bonesrequirementsforloadingEmscripten'sModule.WewillexaminetheModuleobjectinmuchgreaterdetailinChapter6,InteractingwithJavaScriptandDebugging,butfornowjustbeawarethattheloadingprocessisdifferentfromWebAssembly,whichwe'llcoverinthenextsection.
CompilingCwithoutthegluecodeIfwewanttouseWebAssemblyaccordingtotheofficialspecification,withouttheextrafeaturesthatEmscriptenprovides,weneedtopasssomeflagstotheemcccommandandensurewe'rewritingcodethatcanbeusedbyWebAssemblywithrelativeease.IntheWritingtheexampleCcodesection,wewroteaprogramthatrenderedabluerectanglethatmoveddiagonallyacrossaredcanvas.ItutilizedoneofEmscripten'sportedlibraries,SDL2.Inthissection,we'regoingtowriteandcompilesomeCcodethatdoesn'trelyonEmscripten'shelpermethodsandportedlibraries.
CcodeforWebAssemblyBeforewegettotheCcodewe'lluseforourWebAssemblymodule,let'stryanexperiment.OpentheCLIinthe/chapter-05-create-load-modulefolder,andtryrunningthiscommand:
emccwith-glue.c-Os-sWASM=1-sUSE_SDL=2-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0-otry-with-glue.wasm
Youshouldseeatry-with-glue.wasmfileappearinVSCode'sfileexplorerpanelafterthecompilationiscomplete.Right-clickonthefileandselectShowWebAssembly.ThebeginningofthecorrespondingWatrepresentationshouldresemblethefollowingcode:
(module
(type$t0(func(parami32)))
(type$t1(func(parami32i32i32i32i32)(resulti32)))
(type$t2(func(parami32)(resulti32)))
(type$t3(func))
(type$t4(func(parami32i32)(resulti32)))
(type$t5(func(parami32i32i32i32)))
(type$t6(func(resulti32)))
(type$t7(func(resultf64)))
(import"env""memory"(memory$env.memory256))
(import"env""table"(table$env.table4anyfunc))
(import"env""memoryBase"(global$env.memoryBasei32))
(import"env""tableBase"(global$env.tableBasei32))
(import"env""abort"(func$env.abort(type$t0)))
(import"env""_SDL_CreateWindowAndRenderer"(func$env._SDL_CreateWindowAndRenderer(type$t1)))
(import"env""_SDL_DestroyRenderer"(func$env._SDL_DestroyRenderer(type$t0)))
(import"env""_SDL_DestroyWindow"(func$env._SDL_DestroyWindow(type$t0)))
(import"env""_SDL_Init"(func$env._SDL_Init(type$t2)))
(import"env""_SDL_Quit"(func$env._SDL_Quit(type$t3)))
(import"env""_SDL_RenderClear"(func$env._SDL_RenderClear(type$t2)))
(import"env""_SDL_RenderFillRect"(func$env._SDL_RenderFillRect(type$t4)))
(import"env""_SDL_RenderPresent"(func$env._SDL_RenderPresent(type$t0)))
(import"env""_SDL_SetRenderDrawColor"(func$env._SDL_SetRenderDrawColor(type$t1)))
(import"env""_emscripten_set_main_loop_arg"(func$env._emscripten_set_main_loop_arg(type$t5)))
...
Ifyouwantedtoloadthisinabrowserandexecuteit,you'dhavetopassinanimportObjobjecttoWebAssembly'sinstantiate()orcompile()functionwithanenvobjectcontainingeachofthoseimport"env"functions.Emscriptenhandlesallofthisforusbehindthesceneswiththegluecode,whichmakesitanincrediblyvaluabletool.However,wecanreplacetheSDL2functionalitybyusingthe
DOMwhilestilltrackingtherectangle'slocationinC.
WewillwritetheCcodedifferentlytoensureweonlyhavetopassafewfunctionsintotheimportObj.envobjecttoexecutethecode.Createafilenamedwithout-glue.cinthe/chapter-05-create-load-modulefolderandpopulateitwiththefollowingcontents:
/*
*Thisfileinteractswiththecanvasthroughimportedfunctions.
*Itmovesabluerectanglediagonallyacrossthecanvas
*(mimicstheSDLexample).
*/
#include<stdbool.h>
#defineBOUNDS255
#defineRECT_SIDE50
#defineBOUNCE_POINT(BOUNDS-RECT_SIDE)
//ThesefunctionsarepassedinthroughtheimportObj.envobject
//andupdatetherectangleonthe<canvas>:
externintjsClearRect();
externintjsFillRect(intx,inty,intwidth,intheight);
boolisRunning=true;
typedefstructRect{
intx;
inty;
chardirection;
}Rect;
structRectrect;
/*
*Updatestherectanglelocationby1pxinthexandyina
*directionbasedonitscurrentposition.
*/
voidupdateRectLocation(){
//Sincewewanttherectangleto"bump"intotheedgeofthe
//canvas,weneedtodeterminewhentherightedgeofthe
//rectangleencounterstheboundsofthecanvas,whichiswhy
//we'reusingthecanvaswidth-rectanglewidth:
if(rect.x==BOUNCE_POINT)rect.direction='L';
//Assoonastherectangle"bumps"intotheleftsideofthe
//canvas,itshouldchangedirectionagain.
if(rect.x==0)rect.direction='R';
//Ifthedirectionhaschangedbasedonthexandy
//coordinates,ensurethexandypointsupdate
//accordingly:
intincrementer=1;
if(rect.direction=='L')incrementer=-1;
rect.x=rect.x+incrementer;
rect.y=rect.y+incrementer;
}
/*
*Cleartheexistingrectangleelementfromthecanvasanddrawa
*newoneintheupdatedlocation.
*/
voidmoveRect(){
jsClearRect();
updateRectLocation();
jsFillRect(rect.x,rect.y,RECT_SIDE,RECT_SIDE);
}
boolgetIsRunning(){
returnisRunning;
}
voidsetIsRunning(boolnewIsRunning){
isRunning=newIsRunning;
}
voidinit(){
rect.x=0;
rect.y=0;
rect.direction='R';
setIsRunning(true);
}
WewillcallthefunctionsfromtheCcodetodeterminethexandycoordinates.ThesetIsRunning()functioncanbeusedtopausetherectangle'smovement.NowthatourCcodeisready,let'scompileit.IntheVSCodeterminal,cdintothe/chapter-05-create-load-modulefolder,andrunthefollowingcommand:
emccwithout-glue.c-Os-sWASM=1-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0-owithout-glue.wasm
Oncethecompilationiscomplete,youcanright-clickontheresultantwithout-glue.wasmfileandselectShowWebAssemblytoseetheWatrepresentation.Youshouldseethefollowingatthetopofthefilefortheimport"env"items:
(module
(type$t0(func(parami32)))
(type$t1(func(resulti32)))
(type$t2(func(parami32i32i32i32)(resulti32)))
(type$t3(func))
(type$t4(func(resultf64)))
(import"env""memory"(memory$env.memory256))
(import"env""table"(table$env.table8anyfunc))
(import"env""memoryBase"(global$env.memoryBasei32))
(import"env""tableBase"(global$env.tableBasei32))
(import"env""abort"(func$env.abort(type$t0)))
(import"env""_jsClearRect"(func$env._jsClearRect(type$t1)))
(import"env""_jsFillRect"(func$env._jsFillRect(type$t2)))
...
Weneedtopassinthe_jsClearRectand_jsFillRectfunctionswithintheimportObjobject.We'llcoverhowtodothatinthesectionontheHTMLfilewith
CompilingwithaBuildTaskinVSCodeTheemcccommandisalittleverbose,andhavingtomanuallyrunthisonthecommandlinefordifferentfilescangetcumbersome.Toexpeditethecompilationprocess,wecanuseVSCode'sTasksfeaturetocreateabuildtaskforthefileswe'lluse.Tocreateabuildtask,selectTasks|ConfigureDefaultBuildTask…,selecttheCreatetasks.jsonfromtemplateoption,andselectOtherstogenerateasimpletasks.jsonfileinthe.vscodefolder.Updatethecontentsofthefiletocontainthefollowing:{//Seehttps://go.microsoft.com/fwlink/?LinkId=733558//forthedocumentationaboutthetasks.jsonformat"version":"2.0.0","tasks":[{"label":"Build","type":"shell","command":"emcc","args":["${file}","-Os","-s","WASM=1","-s","SIDE_MODULE=1","-s","BINARYEN_ASYNC_COMPILATION=0","-o","${fileDirname}/${fileBasenameNoExtension}.wasm"],"group":{"kind":"build","isDefault":true},"presentation":{"panel":"new"}}
]}
Thelabelvalueissimplyanametorefertowhenrunningatask.Thetypeandcommandvaluesindicatethatitshouldruntheemcccommandinashell(terminal).Theargsvalueisanarrayofargumentstobepassedtotheemcccommand(basedonspaceseparation).The"${file}"argumenttellsVSCodetocompilethecurrentlyopenfile.The"${fileDirname}/${fileBasenameNoExtension}.wasm"argumentindicatesthatthe.wasmoutputwillhavethesamenameasthecurrentlyopenfile(witha.wasmextension),anditshouldbeplacedintheactivefolderofthecurrentlyopenfile.Ifyoudon'tspecify${fileDirname},theoutputfilewillbeplacedintherootfolder(ratherthan/chapter-05-create-load-moduleinthiscase).
Thegroupobjectindicatesthatthistaskisthedefaultbuildstep,soifyouusethekeyboardshortcutCmd/Ctrl+Shift+B,thisisthetaskthatwillberun.Thepresentation.panelvalueof"new"tellsVSCodetoopenupanewCLIinstancewhenthebuildstepruns.Thisisapersonalpreferenceandcanbeomitted.
Youcansaveandclosethetasks.jsonfileonceit'sfullypopulated.Totestitout,firstdeletethewithout-glue.wasmfilethatyougeneratedwiththeemcccommandintheprevioussection.Next,ensureyouhavewithout-glue.copenwiththecursorinthefileandrunthebuildtaskbyeitherselectingTasks|RunBuildTask…orusingthekeyboardshortcutCmd/Ctrl+Shift+B.Anewpanelintheintegratedterminalwillperformthecompilationandawithout-glue.wasmfileshouldappearafterasecondortwo.
FetchingandinstantiatingaWasmfileNowthatwehaveaWasmfile,we'llneedsomeJavaScriptcodetocompileandexecuteit.There'safewstepswe'llhavetofollowtoensurethecodecanbesuccessfullyutilizedinthebrowser.Inthissection,wewillwritesomecommonJavaScriptloadingcodethatwecanreuseforotherexamples,createanHTMLfilethatdemonstratestheuseoftheWasmmodule,andtesttheresultsinthebrowser.
CommonJavaScriptloadingcodeWewillfetchandinstantiatea.wasmfileinseveraloftheexamples,soitmakessensetomovetheJavaScriptloadingcodetoacommonfile.Theactualfetchandinstantiationcodeisonlyafewlines,buthavingtorepeatedlyredefinetheimportObjobjectthatEmscriptenexpectsisawasteoftime.We'llmakethiscodeavailableinacommonlyaccessiblefiletoexpeditethecode-writingprocess.Createanewfoldernamed/commoninthe/book-examplesfolderandaddafilenamedload-wasm.jswiththefollowingcontents:/***ReturnsavalidimportObj.envobjectwithdefaultvaluestopass*intotheWebAssembly.InstanceconstructorforEmscripten's*Wasmmodule.*/constgetDefaultEnv=()=>({memoryBase:0,tableBase:0,memory:newWebAssembly.Memory({initial:256}),table:newWebAssembly.Table({initial:2,element:'anyfunc'}),abort:console.log});
/***ReturnsaWebAssembly.Instanceinstancecompiledfromthespecified*.wasmfile.*/functionloadWasm(fileName,importObj={env:{}}){//OverrideanydefaultenvvalueswiththepassedinimportObj.env//values:constallEnv=Object.assign({},getDefaultEnv(),importObj.env);
//EnsuretheimportObjobjectincludesthevalidenvvalue:constallImports=Object.assign({},importObj,{env:allEnv});
//Returntheresultofinstantiatingthemodule(instanceandmodule):returnfetch(fileName)
.then(response=>{if(response.ok)returnresponse.arrayBuffer();thrownewError(`UnabletofetchWebAssemblyfile${fileName}`);}).then(bytes=>WebAssembly.instantiate(bytes,allImports));}
ThegetDefaultEnv()functionprovidestherequiredimportObj.envcontentsforEmscripten'sWasmmodule.Wewanttheabilitytopassinanyadditionalimports,whichiswhytheObject.assign()statementisused.WiththeadditionofanyotherimportstheWasmmoduleexpects,Emscripten'sWasmoutputwillalwaysrequirethesefiveimportstatementsforthe"env"object:
(import"env""memory"(memory$env.memory256))
(import"env""table"(table$env.table8anyfunc))
(import"env""memoryBase"(global$env.memoryBasei32))
(import"env""tableBase"(global$env.tableBasei32))
(import"env""abort"(func$env.abort(type$t0)))
Weneedtopassthoseintotheinstantiate()functiontoensuretheWasmmoduleloadssuccessfully,otherwisethebrowserwillthrowanerror.Nowthatwehaveourloadingcodeready,let'smoveontotheHTMLandrectangle-renderingcode.
<!doctypehtml><br/><htmllang="en-us"><br/><head><br/><title>NoGlueCode</title><br/><scripttype="application/javascript"src="../common/load-wasm.js"></script><br/></head><br/><body><br/><h1>NoGlueCode</h1><br/><canvasid="myCanvas"width="255"height="255"></canvas><br/><divstyle="margin-top:16px;"><br/><buttonid="actionButton"style="width:100px;height:24px;"><br/>Pause<br/></button><br/></div><br/><scripttype="application/javascript"><br/>constcanvas=document.querySelector('#myCanvas');<br/>constctx=canvas.getContext('2d');<br/><br/>constenv={<br/>table:newWebAssembly.Table({initial:8,element:'anyfunc'}),<br/>_jsFillRect:function(x,y,w,h){<br/>ctx.fillStyle='#0000ff';<br/>ctx.fillRect(x,y,w,h);<br/>},<br/>_jsClearRect:function(){<br/>ctx.fillStyle='#ff0000';<br/>ctx.fillRect(0,0,255,255);<br/>},<br/>};<br/><br/>loadWasm('without-glue.wasm',{env}).then(({instance})=>{<br/>constm=instance.exports;<br/>m._init();<br/><br/>//Movetherectangleby1pxinthexandyevery20milliseconds:<br/>constloopRectMotion=()=>{<br/>setTimeout(()=>{<br/>m._moveRect();<br/>if(m._getIsRunning())loopRectMotion();<br/>},20)<br/>};<br/><br/>//Enableyoutopauseandresumetherectanglemovement:<br/>document.querySelector('#actionButton')<br/>.addEventListener('click',event=>{<br/>constnewIsRunning=!m._getIsRunning();<br/>m._setIsRunning(newIsRunning);<br/>event.target.innerHTML=newIsRunning?'Pause':'Start';<br/>if(newIsRunning)loopRectMotion();<br/>});<br/><br/>loopRectMotion();<br/>});<br/></script><br/></body><br/></html>
ThiscodewillreplicatetheSDLexamplewecreatedintheprevioussectionswithsomeaddedfunctionality.Whentherectanglebumpsintothelower-righthandcorner,itchangesdirection.You'realsoabletopauseandresumetherectangle'smovementusingabuttonunderthe<canvas>element.Youcanseehowwepassedthe_jsFillRectand_jsClearRectfunctionsintotheimportObj.envobjectsotheycanbereferencedbytheWasmmodule.
ServingitallupLet'stestourcodeoutinthebrowser.FromtheVSCodeterminal,makesureyou'reinthe/book-examplesfolderandrunthecommandtostartupalocalserver:
serve-l8080
It'simportantthatyou'reinthe/book-examplesfolder.Ifyoutryservingupthecodeinthe/chapter-05-create-load-modulefolderonly,youwon'tbeabletousetheloadWasm()function.Ifyouopenupyourbrowsertohttp://127.0.0.1:8080/chapter-05-create-load-module/without-glue.html,youshouldseethis:
Withoutgluecodeexamplerunninginthebrowser
TrypressingthePausebutton;thecaptionshouldchangetoStartandtherectangleshouldstopmoving.Clickingitagainshouldcausetherectangletostartmovingagain.
SummaryInthischapter,wecoveredthecompilationandloadingprocessesformodulesthatutilizetheEmscriptengluecodealongsidetheWasmmodules.ByutilizingsomeofEmscripten'sbuilt-infeatures,suchasportedlibrariesandhelpermethods,wewereabletodemonstratetheadvantagesEmscriptenoffers.Wediscussedsomeofthecompilerflagsthatyoucanpasstotheemcccommandandhowthatwillaffectyouroutput.ByutilizingVSCode'sTasksfeature,wewereabletosetupabuildcommandtoexpeditethebuildprocessgoingforward.WealsoreviewedtheprocessforcompilingandloadingaWasmmodulewithoutthegluecode.WewrotesomereusableJavaScriptcodetoloadthemoduleaswellascodetointeractwithourcompiledWasmmodule.
InChapter6,InteractingwithJavaScriptandDebugging,we'regoingtocoverinteractingwithJavaScriptanddebuggingtechniquesinthebrowser.
Questions1. WhatdoesSDLstandfor?2. InadditiontoJavaScript,HTML,andWasm,whatotheroutputtypecan
yougeneratewiththe-oflagfortheemcccommand?3. WhatadvantagesdoesusingEmscripten'spre-generatedloadingcodeoffer?4. WhatmustyounameyourfunctionintheC/C++filetoensureit
automaticallyexecutesthecompiledoutputinthebrowser?5. Whycan'tweusejusttheWasmfileoutputwithoutthe"glue"codewhen
usingportedlibraries?6. WhatisthekeyboardshortcutinVSCodeforrunningyourdefaultbuild
task?7. WhydoweneedthegetDefaultEnv()methodintheWasmloadingcode?8. WhichfiveitemsarerequiredfortheimportObj.envobjectpassedintothe
WasminstantiationcodeforaWasmmodulecreatedwithEmscripten?
FurtherreadingAboutSDL:https://www.libsdl.org/index.phpEmscriptenCompilerFrontend(emcc):http://kripken.github.io/emscripten-site/docs/tools_reference/emcc.html
IntegratewithExternalToolsviaTasks:https://code.visualstudio.com/docs/editor/tasks
LoadingandrunningWebAssemblycode:https://developer.mozilla.org/en-US/docs/WebAssembly/Loading_and_running
InteractingwithJavaScriptandDebuggingThere'sagreatdealofexcitingfeaturesandproposalsintheworksforWebAssembly.However,atthetimeofwritingthisbook,thefeaturesetisratherlimited.Asitstands,youcanbenefitgreatlyfromusingsomeofthefeaturesEmscriptenprovides.TheprocessforinteractingwithC/C++fromJavaScript(andviceversa)willdifferdependingonwhetheryoudecidetouseEmscripten.
Inthischapter,wewillcoverhowtoutilizeJavaScriptfunctionswithC/C++codeaswellashowtointeractwiththecompiledoutputofyourC/C++codefromJavaScript.We'llalsodescribehowEmscripten'sgluecodeaffectsthewaysaWasminstanceisutilizedandhowtodebugcompiledcodeinthebrowser.
Ourgoalforthischapteristounderstandthefollowing:
ThedifferencesbetweenEmscripten'sModuleandthebrowser'sWebAssemblyobjectHowtocallcompiledC/C++functionsfromyourJavaScriptcodeHowtocallJavaScriptfunctionsfromyourC/C++codeSpecialconsiderationstobeawareofwhenworkingwithC++Techniquesfordebuggingcompiledoutputinthebrowser
TheEmscriptenmoduleversustheWebAssemblyobjectInthepreviouschapter,webrieflycoveredEmscripten'sModuleobjectandhowtoloaditinthebrowser.TheModuleobjectprovidesseveralconvenientmethodsanddifferssignificantlyfromthebrowser'sWebAssemblyobject.Inthissection,we'regoingtoreviewEmscripten'sModuleobjectingreaterdetail.We'llalsodiscussthedifferencebetweenEmscripten'sModuleandtheobjectsdescribedinWebAssembly'sJavaScriptAPI.
WhatistheEmscriptenmodule?Emscripten'sofficialsiteprovidesthefollowingdefinitionfortheModuleobject:
"ModuleisaglobalJavaScriptobjectwithattributesthatEmscripten-generatedcodecallsatvariouspointsinitsexecution."
NotonlyistheloadingproceduredifferentfromWebAssembly'scompileandinstantiatefunctions,buttheModuleprovidessomehelpfulfunctionalityoutoftheboxthatwouldotherwiserequireacustomimplementationinWebAssembly.TheModuleisavailableinaglobalscope(window.Module)afterfetchingandloadingEmscripten'sJavaScriptgluecode.
DefaultmethodsinthegluecodeEmscripten'sModuleobjectprovidessomedefaultmethodsandpropertiestoaidindebuggingandensuringthesuccessfulexecutionofyourcompiledcode.YoucanutilizethepreRunandpostRunpropertiestoexecuteJavaScriptcodebeforeoraftertheModule'srun()functioniscalled,orpipetheoutputoftheprint()andprintErr()functionstoanHTMLelementonthepage.We'llutilizesomeofthesemethodslaterinthisbook.Youcanreadmoreaboutthemathttps://kripken.github.io/emscripten-site/docs/api_reference/module.html.
DifferenceswiththeWebAssemblyobjectWecoveredthebrowser'sWebAssemblyobjectandthecorrespondingloadingproceduresinChapter5,CreatingandLoadingaWebAssemblyModule.WebAssembly'sJavaScriptandWebAPIsdefinetheobjectsandmethodsavailableinthebrowser'swindow.WebAssemblyobject.Emscripten'sModulecanbeseenasacombinationofWebAssembly'sModuleandInstanceobjects,whicharepresentintheresultobjectthatWebAssembly'sinstantiationfunctionreturns.Bypassingthe-sMODULARIZE=1flagtotheemcccommand,we'reabletoreplicateWebAssembly'sinstantiationmethod(toadegree).WewillexaminethedifferencesbetweenEmscripten'sModuleandthebrowser'sWebAssemblyobjectingreaterdetailasweevaluatethemethodsofintegratingJavaScriptandC/C++intheupcomingsections.
CallingcompiledC/C++functionsfromJavaScriptCallingfunctionsfromaWasminstanceisarelativelystraightforwardprocesswithorwithoutEmscripten'sgluecode.UtilizingEmscripten'sAPIaffordsawiderrangeoffunctionalityandintegrationattheexpenseofincludingthegluecodealongsidethe.wasmfile.Inthissection,wewillreviewthemeansofinteractingwiththecompiledWasminstancethroughJavaScriptandtheaddedtoolingEmscriptenprovides.
CallingfunctionsfromaModuleEmscriptenprovidestwofunctionsforcallingcompiledC/C++functionsfromJavaScript:ccall()andcwrap().BothofthesefunctionsarepresentintheModuleobject.Decidingwhichonetouseiscontingentonwhetherthefunctionwillbecalledmorethanonce.ThecontentinthefollowingsectionswastakenfromEmscripten'sAPIreferencedocumentationforpreamble.js,whichcanbeviewedathttp://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html.
Youdon'tneedtoprefixfunctioncallswith_whenusingccall()orcwrap()–justusethenamespecifiedintheC/C++file.
Module.ccall()Module.ccall()callsacompiledCfunctionfromJavaScriptandreturnstheresultofthatfunction.ThefunctionsignatureforModule.ccall()isasfollows:ccall(ident,returnType,argTypes,args,opts)
YoumustspecifyatypenameforthereturnTypeandargTypesparameters.Thepossibletypesare"number","string","array",and"boolean",whichcorrespondtotheappropriateJavaScripttypes.Youcannotspecify"array"forthereturnTypeparameterbecausethereisnowaytoknowthelengthofthearray.Ifthefunctiondoesn'treturnanything,youcanspecifynullforthereturnType(notetheabsenceofquotationmarks).
TheoptsparameterisanoptionaloptionsobjectthatcancontainaBooleanpropertynamedasync.Specifyingavalueoftrueforthispropertyimpliesthatthecallwillperformanasyncoperation.Wewon'tusethisparameterforanyofourexamples,butifyouwishtolearnmoreaboutit,thedocumentationisavailableathttp://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#calling-compiled-c-functions-from-javascript.
Let'slookatanexampleofccall().Thefollowingcode,takenfromtheEmscriptensite,demonstrateshowtocallafunctionnamedc_add()fromthecompiledoutputofaCfile://CallCfromJavaScriptvarresult=Module.ccall('c_add',//nameofCfunction'number',//returntype['number','number'],//argumenttypes[10,20]//arguments);
//resultis30
Module.cwrap()Module.cwrap()issimilartoccall()inthatitcallsacompiledCfunction.However,ratherthanreturningavalue,itreturnsaJavaScriptfunctionthatcanbereusedasmanytimesasneeded.ThefunctionsignatureforModule.cwrap()isasfollows:
cwrap(ident,returnType,argTypes)
Justaswithccall(),youcanspecifystringvaluesthatrepresenttypesforthereturnTypeandargTypesparameters.Youcannotusethe"array"typeinargTypesbecausethereisnowaytoknowthelengthofthearraywhenthefunctioniscalled.Forafunctionthatdoesn'treturnavalue,usenull(withnoquotationmarks)forthereturnTypeparameter.
Thefollowingcode,takenfromtheEmscriptensite,demonstratestheuseofcwrap()tocreateareusablefunction:
//CallCfromJavaScript
varc_javascript_add=Module.cwrap(
'c_add',//nameofCfunction
'number',//returntype
['number','number']//argumenttypes
);
//Callc_javascript_addnormally
console.log(c_javascript_add(10,20));//30
console.log(c_javascript_add(20,30));//50
intaddNumbers(intnum1,intnum2){<br/>returnnum1+num2;<br/>}<br/><br/>intaddNumbers(intnum1,intnum2,intnum3){<br/>returnnum1+num2+num3;<br/>}<br/><br/>intaddNumbers(intnum1,intnum2,intnum3,intnum4){<br/>returnnum1+num2+num3+num4;<br/>}<br/><br/>//Thefunctionwillreturnavaluebasedonhowmany<br/>//argumentsyoupassit:<br/>intgetSumOfTwoNumbers=addNumbers(1,2);<br/>//returns3<br/><br/>intgetSumOfThreeNumbers=addNumbers(1,2,3);<br/>//returns6<br/><br/>intgetSumOfFourNumbers=addNumbers(1,2,3,4);<br/>//returns10
extern"C"{<br/>intaddTwoNumbers(intnum1,intnum2){<br/>returnnum1+num2;<br/>}<br/><br/>intaddThreeNumbers(intnum1,intnum2,intnum3){<br/>returnnum1+num2+num3;<br/>}<br/><br/>intaddFourNumbers(intnum1,intnum2,intnum3,intnum4){<br/>returnnum1+num2+num3+num4;<br/>}<br/>}
CallingfunctionsfromaWebAssemblyinstanceWedemonstratedhowtocallafunctioninaWasminstancefromJavaScriptinthepreviouschapter,butthatwasassumingyouinstantiatedamoduleinthebrowserwithnogluecode.EmscriptenprovidestheabilitytocallfunctionsfromtheWasminstanceaswell.Afteramoduleisinstantiated,youcallfunctionsbyinvokingthemfromtheinstance.exportsobject,whichisaccessiblefromtheresultoftheresolvedPromise.MDN'sdocumentationprovidesthefollowingfunctionsignatureforWebAssembly.instantiateStreaming:Promise<ResultObject>WebAssembly.instantiateStreaming(source,importObject);
YoumayneedtousetheWebAssembly.instantiate()method,dependingonyourbrowser.ChromecurrentlysupportsWebAssembly.instantiateStreaming(),butifyouencounteranerrorwhenattemptingtoloadyourmodule,usetheWebAssembly.instantiate()methodinstead.
TheResultObjectcontainstheinstanceobjectthatweneedtoreferencetocallexportedfunctionsfromthemodule.Here'ssomecodethatcallsafunctionnamed_addTwoNumbersfromthecompiledWasminstance:
//AssumetheimportObjisalreadydefined.
WebAssembly.instantiateStreaming(
fetch('simple.wasm'),
importObj
)
.then(result=>{
constaddedNumbers=result.instance.exports._addTwoNumbers(1,2);
//resultis3
});
Emscriptenprovidesawaytoperformfunctioncallsinmuchthesameway,albeitinaslightlydifferentimplementation.IfyouusethePromise-likeAPI,youcanaccessthefunctionfromanasmobjectthatthepromiseoftheModule()resolveswith.Thefollowingexampledemonstrateshowtoutilizethisfunctionality:
//UsingEmscripten'sModule
Module()
.then(result=>{
//"asm"isessentially"instance"
constexports=result.asm;
constaddedNumbers=exports._addTwoNumbers(1,2);
//resultis3
});
ReplicatingtheWebAssembly'sWebAPIsyntaxwithEmscriptensimplifiesanyfuturerefactoring.YoucaneasilyreplaceModule()withWebAssembly'sinstantiateStreaming()methodandresult.asmwithresult.instanceinthefutureifyoudecidetouseWebAssembly'sWebAPI.
CallingJavaScriptfunctionsfromC/C++AccessingJavaScript'sfunctionalityfromC/C++codeallowsforaddedflexibilitywhenworkingwithWebAssembly.ThemethodologiesandmeansofutilizingJavaScriptdifferconsiderablybetweenEmscripten'sgluecodeandWasm-onlyimplementations.Inthissection,wewillcoverthevariouswaysyoucanintegrateJavaScriptintoyourC/C++codewithandwithoutEmscripten.
InteractingwithJavaScriptusinggluecodeEmscriptenprovidesseveraltechniquesforintegratingJavaScriptwithyourC/C++code.Thetechniquesavailabledifferinimplementationandcomplexity,andsomeonlyapplytospecificexecutionenvironments(forexample,thebrowser).Decidingwhichonetouseiscontingentonyourspecificusecase.We'llfocusontheemscripten_run_script()functionandinliningJavaScriptwithEM_*wrappers.ThecontentinthefollowingsectionswastakenfromtheInteractingwithCodesectionofEmscripten'ssite,whichcanbeviewedathttps://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.ht
ml#interacting-with-code.
Executingstringswithemscripten_run_script()TheEmscriptensitedescribestheemscripten_run_script()functionasthemostdirect,butslightlyslowerapproachforcallingJavaScriptforC/C++.It'satechniquethatiswellsuitedforasinglelineofJavaScriptcodeandcanbeusefulfordebugging.Thedocumentationstatesthatiteffectivelyrunsthecodeusingeval(),whichisaJavaScriptfunctionthatexecutesastringascode.ThefollowingcodetakenfromtheEmscriptensitedemonstratestheuseofemscripten_run_script()tocallthebrowser'salert()functionwiththetext'hi':emscripten_run_script("alert('hi')");
Formorecomplexusecaseswhereperformanceisafactor,usinginlineJavaScriptprovidesabettersolution.
#include<emscripten.h><br/><br/>intmain(){<br/>EM_ASM(<br/>console.log('ThisissomeJScode.');<br/>);<br/>return0;<br/>}
EM_ASM({<br/>console.log('Ireceived:'+[$0,$1]);<br/>},100,35.5);
ReusinginlineJavaScriptwithEM_JS()IfyouneedareusablefunctionwithinyourC/C++file,youcanwrapJavaScriptcodewithinanEM_JS()blockandexecuteitlikeanormalC/C++function.ThedefinitionforEM_JS()isdescribedinthefollowingcodesnippet:
EM_JS(return_type,function_name,arguments,code)
Thereturn_typeparameterrepresentstheCtypethatcorrespondswiththeJavaScriptcode'soutput(forexample,intorfloat).IfnothingisreturnedfromtheJavaScriptcode,specifyvoidforthereturn_type.Thenextparameter,function_name,representsthenametousewhencallingtheJavaScriptcodefromotherlocationsintheC/C++file.TheargumentsparameterisusedtodefineargumentsthatcanbepassedintotheJavaScriptcodefromtheCcallingfunction.ThecodeparameteristheJavaScriptcodethat'swrappedincurlybraces.Thefollowingcodesnippet,takenfromtheEmscriptensite,demonstratestheuseofEM_JS()inaCfile:
#include<emscripten.h>
EM_JS(void,take_args,(intx,floaty),{
console.log(`Ireceived${x}and${y}`);
});
intmain(){
take_args(100,35.5);
return0;
}
ExamplesofusinggluecodeLet'swritesomecodethatutilizesallofthesefeatures.Inthissection,wewillmodifythecodeweusedintheCompilingCwithoutthegluecodeandFetchingandinstantiatingaWasmfilesectionsofChapter5,CreatingandLoadingaWebAssemblyModule.Thiswasthecodethatdisplayedamovingbluerectangleonaredcanvasandcouldbepausedandrestartedwiththeclickofabutton.Thecodeforthissectionislocatedinthe/chapter-06-interact-with-jsfolderinthelearn-webassemblyrepository.Let'sstartbyupdatingtheCcode.
TheCcodeCreateanewfolderinyour/book-examplesfoldernamed/chapter-06-interact-with-js.Createanewfileinthe/chapter-06-interact-with-jsfoldernamedjs-with-glue.candpopulateitwiththefollowingcontents:
/*
*Thisfileinteractswiththecanvasthroughimportedfunctions.
*Itmovesabluerectanglediagonallyacrossthecanvas
*(mimicstheSDLexample).
*/
#include<emscripten.h>
#include<stdbool.h>
#defineBOUNDS255
#defineRECT_SIDE50
#defineBOUNCE_POINT(BOUNDS-RECT_SIDE)
boolisRunning=true;
typedefstructRect{
intx;
inty;
chardirection;
}Rect;
structRectrect;
/*
*Updatestherectanglelocationby1pxinthexandyina
*directionbasedonitscurrentposition.
*/
voidupdateRectLocation(){
//Sincewewanttherectangleto"bump"intotheedgeofthe
//canvas,weneedtodeterminewhentherightedgeofthe
//rectangleencounterstheboundsofthecanvas,whichiswhy
//we'reusingthecanvaswidth-rectanglewidth:
if(rect.x==BOUNCE_POINT)rect.direction='L';
//Assoonastherectangle"bumps"intotheleftsideofthe
//canvas,itshouldchangedirectionagain.
if(rect.x==0)rect.direction='R';
//Ifthedirectionhaschangedbasedonthexandy
//coordinates,ensurethexandypointsupdate
//accordingly:
intincrementer=1;
if(rect.direction=='L')incrementer=-1;
rect.x=rect.x+incrementer;
rect.y=rect.y+incrementer;
}
EM_JS(void,js_clear_rect,(),{
//Cleartherectangletoensurethere'snocolorwhereit
//wasbefore:
varcanvas=document.querySelector('#myCanvas');
varctx=canvas.getContext('2d');
ctx.fillStyle='#ff0000';
ctx.fillRect(0,0,255,255);
});
EM_JS(void,js_fill_rect,(intx,inty,intwidth,intheight),{
//Filltherectanglewithblueinthespecifiedcoordinates:
varcanvas=document.querySelector('#myCanvas');
varctx=canvas.getContext('2d');
ctx.fillStyle='#0000ff';
ctx.fillRect(x,y,width,height);
});
/*
*Cleartheexistingrectangleelementfromthecanvasanddrawa
*newoneintheupdatedlocation.
*/
EMSCRIPTEN_KEEPALIVE
voidmoveRect(){
//Eventthoughthejs_clear_rectdoesn'thaveany
//parameters,wepass0intopreventacompilerwarning:
js_clear_rect(0);
updateRectLocation();
js_fill_rect(rect.x,rect.y,RECT_SIDE,RECT_SIDE);
}
EMSCRIPTEN_KEEPALIVE
boolgetIsRunning(){
returnisRunning;
}
EMSCRIPTEN_KEEPALIVE
voidsetIsRunning(boolnewIsRunning){
isRunning=newIsRunning;
EM_ASM({
//isRunningiseither0or1,butinJavaScript,0
//is"falsy",sowecansetthestatustextbased
//withoutexplicitlycheckingifthevalueis0or1:
varnewStatus=$0?'Running':'Paused';
document.querySelector('#runStatus').innerHTML=newStatus;
},isRunning);
}
EMSCRIPTEN_KEEPALIVE
voidinit(){
emscripten_run_script("console.log('Initializingrectangle...')");
rect.x=0;
rect.y=0;
rect.direction='R';
setIsRunning(true);
emscripten_run_script("console.log('Rectangleshouldbemoving!')");
}
YoucanseethatweusedallthreeoftheJavaScriptintegrationsthatEmscriptenprovides.Therearetwofunctions,js_clear_rect()andjs_fill_rect(),thataredefinedinEM_JS()blocksthattaketheplaceoftheimportedfunctionsfromtheoriginalexample.TheEM_ASM()blockwithinthesetIsRunning()functionupdatesthe
textofanewstatuselementwe'lladdtotheHTMLcode.Theemscripten_run_script()functionssimplylogoutsomestatusmessages.WeneedtospecifyEMSCRIPTEN_KEEPALIVEabovethefunctionswe'replanningtoutilizeoutsideofthemodule.Ifyoudon'tspecifythis,thecompilerwilltreatthefunctionsasdeadcodeandremovethem.
<!doctypehtml><br/><htmllang="en-us"><br/><head><br/><title>InteractwithJSusingGlueCode</title><br/></head><br/><body><br/><h1>InteractwithJSusingGlueCode</h1><br/><canvasid="myCanvas"width="255"height="255"></canvas><br/><divstyle="margin-top:16px;"><br/><buttonid="actionButton"style="width:100px;height:24px;">Pause</button><br/><spanstyle="width:100px;margin-left:8px;">Status:</span><br/><spanid="runStatus"style="width:100px;"></span><br/></div><br/><scripttype="application/javascript"src="js-with-glue.js"></script><br/><scripttype="application/javascript"><br/>Module()<br/>.then(result=>{<br/>constm=result.asm;<br/>m._init();<br/><br/>//Movetherectangleby1pxinthexandyevery20milliseconds:<br/>constloopRectMotion=()=>{<br/>setTimeout(()=>{<br/>m._moveRect();<br/>if(m._getIsRunning())loopRectMotion();<br/>},20)<br/>};<br/><br/>//Enableyoutopauseandresumetherectanglemovement:<br/>document.querySelector('#actionButton')<br/>.addEventListener('click',event=>{<br/>constnewIsRunning=!m._getIsRunning();<br/>m._setIsRunning(newIsRunning);<br/>event.target.innerHTML=newIsRunning?'Pause':'Start';<br/>if(newIsRunning)loopRectMotion();<br/>});<br/><br/>loopRectMotion();<br/>});<br/></script><br/></body><br/></html>
Weaddedtwo<span>elementstodisplaythestatusoftherectangle'smovement,alongwithacorrespondinglabel.We'reusingEmscripten'sPromise-likeAPItoloadthemoduleandreferencethefunctionsfromthecompiledcode.We'renolongerpassinginthe_jsFillRectand_jsClearRectfunctionstothemodulebecausewe'rehandlingthatwithinthejs-with-glue.cfile.
CompilingandservingtheresultTocompilethecode,ensurethatyou'reinthe/chapter-06-interact-with-jsfolderandrunthefollowingcommand:
emccjs-with-glue.c-O3-sWASM=1-sMODULARIZE=1-ojs-with-glue.js
Oncecomplete,runthefollowingcommandtostartyourlocalserver:
serve-l8080
Openupabrowserandnavigatetohttp://127.0.0.1:8080/js-with-glue.html.Youshouldseesomethinglikethis:
Gluecoderunninginthebrowser
IfyoupressthePausebutton,thecaptiononthebuttonshouldchangetoStart,thetextnexttoStatusshouldchangetoPaused,andtherectangleshouldstopmoving.
InteractingwithJavaScriptwithoutgluecodeUtilizingJavaScriptcodeinC/C++filesfollowsadifferentparadigmthanthetechniquesusedforEmscripten.RatherthanwritingJavaScriptwithintheC/C++files,youpassthefunctionsintoyourWebAssemblyinstantiationcode.Inthissection,wewilldescribethisprocessingreaterdetail.
//Youcandefinethefunctioninsideoftheenvobject:<br/>constenv={<br/>//Makesureyouprefixthefunctionnamewith"_"!<br/>_logValueToConsole:value=>{<br/>console.log(`'Thevalueis${value}'`);<br/>}<br/>};<br/><br/>//Ordefineitoutsideofenvandreferenceitwithinenv:<br/>constlogValueToConsole=value=>{<br/>console.log(`'Thevalueis${value}'`);<br/>};<br/><br/>constenv={<br/>_logValueToConsole:logValueToConsole<br/>};
GiventhemanualmemorymanagementandstricttypingrequirementsofC,C++,andRust,you'relimitedinwhatcanbepassedinandutilizedinaWasmmodule.JavaScriptallowsyoutoeasilyadd,remove,andchangethevaluesofpropertiesonanobjectoverthecourseofcodeexecution.Youcanevenextendthelanguagebyaddingfunctionstotheprototypeofabuilt-inlanguagefeature.C,C++,andRustaremuchmorerestrictive,anditcanbedifficulttotakefulladvantageofWebAssemblyifyou'renotfamiliarwiththeselanguages.
CallingimportedfunctionsinC/C++YouneedtodefinetheJavaScriptfunctionyoupassedintoimportObj.envwithintheC/C++codethatutilizesit.Thefunctionsignaturemustmatchwhatyoupassedin.Thefollowingexampledemonstratesthisingreaterdetail.Here'stheJavaScriptcodethatinteractswiththecompiledCfile(index.html)://index.html<script>contentsconstenv={_logAndMultiplyTwoNums:(num1,num2)=>{constresult=num1*num2;console.log(result);returnresult;},};
loadWasm('main.wasm',{env}).then(({instance})=>{constresult=instance.exports._callMultiply(5.5,10);console.log(result);//55isloggedtotheconsoletwice});
Thisisthecontentsofmain.c,whichiscompiledtomain.wasmandusedwithinindex.html:
//main.c(compiledtomain.wasm)
externfloatlogAndMultiplyTwoNums(floatnum1,floatnum2);
floatcallMultiply(floatnum1,floatnum2){
returnlogAndMultiplyTwoNums(num1,num2);
}
YoucalltheJavaScriptfunctioninyourC/C++thesamewayyou'dcallanormalC/C++function.Althoughyouprefixyourfunctionwitha_whenyoupassitintotheimportObj.env,youdon'tneedtoincludetheprefixwhendefiningitintheC/C++file.
AnexamplewithoutgluecodeTheexamplecodefromtheCompilingCwithoutthegluecodeandFetchingandinstantiatingaWasmfilesectionsofChapter5,CreatingandLoadingaWebAssemblyModule,demonstratedhowtointegrateJavaScriptinourCfilewithoutusingEmscripten'sgluecode.Inthissection,wewillmodifytheexamplecodeslightlyandchangethefiletypetoC++.
/*<br/>*Thisfileinteractswiththecanvasthroughimportedfunctions.<br/>*Itmovesacirclediagonallyacrossthecanvas.<br/>*/<br/>#defineBOUNDS255<br/>#defineCIRCLE_RADIUS50<br/>#defineBOUNCE_POINT(BOUNDS-CIRCLE_RADIUS)<br/><br/>boolisRunning=true;<br/><br/>typedefstructCircle{<br/>intx;<br/>inty;<br/>chardirection;<br/>}Circle;<br/><br/>structCirclecircle;<br/><br/>/*<br/>*Updatesthecirclelocationby1pxinthexandyina<br/>*directionbasedonitscurrentposition.<br/>*/<br/>voidupdateCircleLocation(){<br/>//Sincewewantthecircleto"bump"intotheedgeofthecanvas,<br/>//weneedtodeterminewhentherightedgeofthecircle<br/>//encounterstheboundsofthecanvas,whichiswhywe'reusing<br/>//thecanvaswidth-circlewidth:<br/>if(circle.x==BOUNCE_POINT)circle.direction='L';<br/><br/>//Assoonasthecircle"bumps"intotheleftsideofthe<br/>//canvas,itshouldchangedirectionagain.<br/>if(circle.x==CIRCLE_RADIUS)circle.direction='R';<br/><br/>//Ifthedirectionhaschangedbasedonthexandy<br/>//coordinates,ensurethexandypointsupdateaccordingly:<br/>intincrementer=1;<br/>if(circle.direction=='L')incrementer=-1;<br/>circle.x=circle.x+incrementer;<br/>circle.y=circle.y-incrementer;<br/>}<br/><br/>//Weneedtowrapanyimportedorexportedfunctionsinan<br/>//externblock,otherwisethefunctionnameswillbemangled.<br/>extern"C"{<br/>//ThesefunctionsarepassedinthroughtheimportObj.envobject<br/>//andupdatethecircleonthe<canvas>:<br/>externintjsClearCircle();<br/>externintjsFillCircle(intx,inty,intradius);<br/><br/>/*<br/>*Cleartheexistingcircleelementfromthecanvasanddrawa<br/>*newoneintheupdatedlocation.<br/>*/<br/>voidmoveCircle(){<br/>jsClearCircle();<br/>updateCircleLocation();<br/>jsFillCircle(circle.x,circle.y,CIRCLE_RADIUS);<br/>}<br/><br/>boolgetIsRunning(){<br/>returnisRunning;<br/>}<br/><br/>voidsetIsRunning(boolnewIsRunning){<br/>isRunning=newIsRunning;<br/>}<br/><br/>voidinit(){<br/>circle.x=0;<br/>circle.y=255;<br/>circle.direction='R';<br/>setIsRunning(true);<br/>}<br/>}
Thiscodeissimilartothepreviousexample,buttheshapeanddirectionoftheelementonthecanvashaschanged.Now,theelementisacirclethatstartsinthelower-leftcornerofthecanvasandmovesdiagonallytowardtheupper-right.
<!doctypehtml><br/><htmllang="en-us"><br/><head><br/><title>InteractwithJSwithoutGlueCode</title><br/><script<br/>type="application/javascript"<br/>src="../common/load-wasm.js"><br/></script><br/><style><br/>#myCanvas{<br/>border:2pxsolidblack;<br/>}<br/>#actionButtonWrapper{<br/>margin-top:16px;<br/>}<br/>#actionButton{<br/>width:100px;<br/>height:24px;<br/>}<br/></style><br/></head><br/><body><br/><h1>InteractwithJSwithoutGlueCode</h1><br/><canvasid="myCanvas"width="255"height="255"></canvas><br/><divid="actionButtonWrapper"><br/><buttonid="actionButton">Pause</button><br/></div><br/><scripttype="application/javascript"><br/>constcanvas=document.querySelector('#myCanvas');<br/>constctx=canvas.getContext('2d');<br/><br/>constfillCircle=(x,y,radius)=>{<br/>ctx.fillStyle='#fed530';<br/>//Faceoutline:<br/>ctx.beginPath();<br/>ctx.arc(x,y,radius,0,2*Math.PI);<br/>ctx.fill();<br/>ctx.stroke();<br/>ctx.closePath();<br/><br/>//Eyes:<br/>ctx.fillStyle='#000000';<br/>ctx.beginPath();<br/>ctx.arc(x-15,y-15,6,0,2*Math.PI);<br/>ctx.arc(x+15,y-15,6,0,2*Math.PI);<br/>ctx.fill();<br/>ctx.closePath();<br/><br/>//Mouth:<br/>ctx.beginPath();<br/>ctx.moveTo(x-20,y+10);<br/>ctx.quadraticCurveTo(x,y+30,x+20,y+10);<br/>ctx.lineWidth=4;<br/>ctx.stroke();<br/>ctx.closePath();<br/>};<br/><br/>constenv={<br/>table:newWebAssembly.Table({initial:8,element:'anyfunc'}),<br/>_jsFillCircle:fillCircle,<br/>_jsClearCircle:function(){<br/>ctx.fillStyle='#fff';<br/>ctx.fillRect(0,0,255,255);<br/>},<br/>};<br/><br/>loadWasm('js-without-glue.wasm',{env}).then(({instance})=>{<br/>constm=instance.exports;<br/>m._init();<br/><br/>//Movethecircleby1pxinthexandyevery20milliseconds:<br/>constloopCircleMotion=()=>{<br/>setTimeout(()=>{<br/>m._moveCircle();<br/>if(m._getIsRunning())loopCircleMotion();<br/>},20)<br/>};<br/><br/>//Enableyoutopauseandresumethecirclemovement:<br/>document.querySelector('#actionButton')<br/>.addEventListener('click',event=>{<br/>constnewIsRunning=!m._getIsRunning();<br/>m._setIsRunning(newIsRunning);<br/>event.target.innerHTML=newIsRunning?'Pause':'Start';<br/>if(newIsRunning)loopCircleMotion();<br/>});<br/><br/>loopCircleMotion();<br/>});<br/></script><br/></body><br/></html>
Insteadofusingtherect()element,wecanmanuallydrawpathsusingthefunctionsavailableonthecanvaselement's2Dcontext.
CompilingandservingtheresultWe'reonlygeneratingaWasmmodule,sowecanusethebuildtaskwesetupinthepreviouschaptertocompileourcode.SelectTasks|RunBuildTask…orusethekeyboardshortcutCtrl/Cmd+Shift+Btocompilethecode.Ifyou'renotusingVSCode,openaCLIinstanceinthe/chapter-06-interact-with-jsfolderandrunthefollowingcommand:
emccjs-without-glue.cpp-Os-sWASM=1-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0-ojs-without-glue.wasm
Oncecomplete,openaterminalinthe/book-examplesfolder,andrunthefollowingcommandtostartyourlocalserver:
serve-l8080
Openupabrowserandnavigatetohttp://127.0.0.1:8080/chapter-06-interact-with-js/js-without-glue.html.Youshouldseesomethinglikethis:
TheWasmmodulerunninginthebrowserwithoutgluecode
Justaswiththepreviousexamples,ifyoupressthePausebutton,thecaptiononthebuttonshouldchangetoStartandthecircleshouldstopmoving.
AdvancedEmscriptenfeaturesWecoveredtheEmscriptenfeatureswe'llbeusingmostfrequentlyforcommunicatingbetweenJavaScriptandC/C++intheprevioussections,butthosearen'ttheonlycapabilitiesEmscriptenprovides.ThereareadvancedfeaturesandadditionalAPIsthatyouneedtobeawareof,especiallyifyouplanonaddingmorecomplexfunctionalitytoyourapplication.Inthissection,we'llbrieflyreviewsomeoftheseadvancedfeaturesandprovidedetailsaboutwhereyoucanlearnmore.
EmbindEmbindisanadditionalfeaturethatEmscriptenoffersforconnectingJavaScriptandC++.Emscripten'ssiteprovidesthefollowingdescription:
"EmbindisusedtobindC++functionsandclassestoJavaScript,sothatthecompiledcodecanbeusedinanaturalwayby'normal'JavaScript.EmbindalsosupportscallingJavaScriptclassesfromC++."
EmbindisapowerfulfeaturethatallowsfortightintegrationbetweenJavaScriptandC++.YoucanwrapsomeC++codeinanEMSCRIPTEN_BINDINGS()blockandreferenceitthroughtheModuleobjectinyourbrowser.Let'slookatanexamplefromEmscripten'ssite.Thefollowingfile,example.cpp,iscompiledwiththe--bindflagofemcc:
//example.cpp
#include<emscripten/bind.h>
usingnamespaceemscripten;
floatlerp(floata,floatb,floatt){
return(1-t)*a+t*b;
}
EMSCRIPTEN_BINDINGS(my_module){
function("lerp",&lerp);
}
Theresultantmoduleisloadedinexample.htmlandthelerp()functioniscalled:
<!--example.html-->
<!doctypehtml>
<html>
<scriptsrc="example.js"></script>
<script>
//example.jswasgeneratedbyrunningthiscommand:
//emcc--bind-oexample.jsexample.cpp
console.log('lerpresult:'+Module.lerp(1,2,0.5));
</script>
</html>
TheprecedingexamplerepresentsasmallportionofEmbind'scapabilities.YoucanlearnmoreaboutEmbindathttps://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html.
FileSystemAPIEmscriptenprovidessupportforfileoperationsbyusingtheFSlibraryandexposesanAPIforworkingwiththefilesystem.However,it'snotincludedbydefaultwhenyoucompileyourprojectbecauseitcouldincreasethefile'ssizesignificantly.IfyourC/C++codeusesfiles,thelibrarywillbeaddedautomatically.Thefilesystemtypesvarybasedontheexecutionenvironment.Forexample,ifyou'rerunningcodeinsideaworker,theWORKERFSfilesystemcanbeused.Bydefault,MEMFSisused,whichstoresthedatainmemory,andanydatawrittentomemoryislostwhenthepageisreloaded.YoucanreadmoreabouttheFileSystemAPIathttps://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html#filesystem-api.
FetchAPIEmscriptenprovidesaFetchAPIaswell.Thefollowingistakenfromthedocumentation:
"TheEmscriptenFetchAPIallowsnativecodetotransferfilesviaXHR(HTTPGET,PUT,POST)fromremoteservers,andtopersistthedownloadedfileslocallyinbrowser'sIndexedDBstorage,sothattheycanbere-accessedlocallyonsubsequentpagevisits.TheFetchAPIiscallablefrommultiplethreads,andthenetworkrequestscanberuneithersynchronouslyorasynchronouslyasdesired."
TheFetchAPIcanbeusedtointegratewithEmscripten'sotherfeatures.Ifyouneedtofetchdatathatisn'tutilizedbyEmscripten,youshouldusethebrowser'sFetchAPI(https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).YoucanreadmoreabouttheFetchAPIathttps://kripken.github.io/emscripten-site/docs/api_reference/fetch.html.
DebugginginthebrowserEffectivelydebuggingJavaScriptcodeinthebrowserhasnotalwaysbeeneasy.However,developmenttoolinghasmarkedlyimprovedinthebrowserandineditors/IDEswithbuilt-indebuggingcapabilities.Unfortunately,addingWebAssemblytoawebapplicationaddsanadditionallevelofcomplexitytothedebuggingprocess.Inthissection,wewillreviewsometechniquesfordebuggingJavaScriptthatutilizesWasmaswellassomeoftheadditionalcapabilitiesEmscriptenoffers.
High-leveloverviewDebuggingEmscripten'sModuleisrelativelystraightforward.Emscripten'serrormessagesarewellformedanddescriptive,soyou'llusuallydiscoverwhat'scausingtheissuerightaway.Youcanviewthesemessagesinyourbrowser'sdevelopmenttoolsconsole.
Ifyouspecifieda.htmloutputwhenrunningtheemcccommand,somedebuggingcodewillalreadybebuiltin(Module.printandModule.printErr).WithintheHTMLfile,theloadingcodesetsthewindow.onerroreventtocalltheModule.printErrevent,soyoucanseedetailsabouttheerrorthatoccurredwhenloading.
Onecommonerroryoumayencounteriscallingthewrongfunctionname.Ifyou'reusingEmscripten'sPromise-likeAPI,youcanprintouttheavailablefunctionsbyrunningthefollowingcodeinyourbrowser'sconsole:
console.log(Module().asm);
Thefollowingscreenshotshowstheoutputforthejs-with-glue.jsexampleweusedintheCallingJavaScriptfunctionsfromC/C++sectionofthischapter:
LoggingthecontentsofModule().asminthebrowserconsole
Yourfunctions,aswellassomefunctionsthatEmscriptengenerates,willbeprefixedwitha_.Theadvantageofwritingcodethatgetscompiledisthatthecompilerwillcatchmosterrorsupfront.GiventheextensivetoolingavailableforlanguagessuchasCandC++,youshouldbeabletounderstandandaddresstheseerrorsquickly.
Ifyou'renotusinganygluecodeandinstantiatingaWasmfileusingWebAssembly'sJavaScriptandWebAPIs,debuggingcangetalittlemorecomplex.Aspreviouslystated,youhavetheadvantageofcatchingmosterrorsatcompiletimeinyourCorC++code.JustaswithEmscripten,theerrormessagesprintedoutinyourbrowser'sdevelopmenttoolsconsoleprovideastacktraceandarelativelycleardescriptionoftheissue.However,loggingtotheconsolemaybecomecumbersomeanddifficulttomanageifyou'retroubleshootingaparticularlydifficultbug.Fortunately,youcanusesourcemapstoimproveyourdebuggingcapabilities.
UsingsourcemapsEmscriptenhastheabilitytogeneratesourcemapsbypassingsomeadditionalflagstothecompiler.Sourcemapsallowyourbrowsertomapthesourceofafiletothefilebeingutilizedinanapplication.Forexample,youcanuseaJavaScriptbuildtoolsuchWebpacktominifythecodeaspartofyourbuildprocess.However,it'sincrediblydifficulttonavigateandtroubleshoottheminifiedcodeifyou'retryingtofindabug.Bygeneratingasourcemap,youcanviewthecodeinitsoriginalformwithinthebrowser'sdevelopmenttoolsandsetbreakpointsfordebugging.Let'sgenerateasourcemapforour/chapter-06-interact-with-js/js-without-glue.cppfile.Withinthe/book-examplesfolder,runthefollowingcommandinaterminal:
emccchapter-06-interact-with-js/js-without-glue.cpp-O1-g4-sWASM=1-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0-ochapter-06-interact-with-js/js-without-glue.wasm--source-map-basehttp://localhost:8080/chapter-06-interact-with-js/
The-g4argumentenablessourcemaps,whilethe--source-map-baseargumenttellsthebrowserwheretofindthesourcemapfile.Oncecompiled,startyourlocalserverupfromthe/book-examplesfolderbyrunningthefollowingcommand:
serve-l8080
Navigatetohttp://127.0.0.1:8080/chapter-06-interact-with-js/js-without-glue.html,opentheDeveloperTools,andselecttheSourcestab(inChrome)orDebuggertab(inFirefox).Ifyou'reusingChrome,youshouldseethefollowing:
WasmsourcemapsinChromeDeveloperTools
Asyoucansee,thefilenamesaren'tveryhelpful.Eachfileshouldincludethefunctionnameatthetop,althoughsomeofthenamesmayhavebeenmangled.Youcansetbreakpointsifyouencountererrors,andChrome'sdebuggingfunctionalityallowsyoutonavigatethecallstack.Firefoxhandlestheirsourcemapsdifferently.ThefollowingscreenshotshowstheDebuggerviewinFirefox'sDeveloperTools:
WasmsourcemapinFirefoxDeveloperTools
ThesourcemapisasinglefilethatcontainstheWatrepresentationoftheWasmfile.Youcansetbreakpointsanddebugcodehereaswell.AsWebAssemblyevolves,more(andbetter)toolingwillbecomeavailable.Inthemeantime,loggingtotheconsoleandutilizingsourcemapsarethecurrentdebuggingmethodsyoucanuse.
SummaryInthischapter,wefocusedontheintercommunicationofJavaScriptandC/C++,someofthefeaturesEmscriptenoffers,andhowtoeffectivelydebugwebapplicationsthatutilizeWasminthebrowser.WereviewedthevariousmeansofcallingcompiledC/C++functionsfromJavaScript,andhowtointegrateJavaScriptwithyourC/C++code.Emscripten'sAPIswerepresentedasawaytounderstandhowyoucanovercomesomeofWebAssembly'scurrentlimitationsbyincludinggluecodewithyourcompiledWasmfiles.EventhoughthecapabilitiesEmscriptenprovidesarenotpresentintheofficialWebAssemblyCoreSpecification(andmayneverbe),thatshouldn'tdeteryoufromtakingadvantageofthem.Finally,webrieflycoveredhowtodebugWasmfilesinthebrowserinthecontextofanEmscriptenmoduleoraWebAssemblyinstance.
Inthenextchapter,we'llbuildareal-worldWebAssemblyapplicationfromscratch.
Questions1. WhatarethenamesofthetwofunctionsavailableontheModuleobjectthat
youusetointeractwiththecompiledcodefromthebrowser?2. WhatdoyouneedtowrapyourC++codeintoensurethefunctionnames
don'tgetmangled?3. What'sthedifferencebetweenEM_ASM()andEM_JS()?4. Whichismoreperformant,emscripten_run_script()orEM_ASM()/EM_JS()?5. Whatdoyouneedtoincludeinthelineaboveyourfunctionifyouwantto
useitoutsideofyourC/C++code(hint:itstartswithEMSCRIPTEN)?6. Wherecanyoudefineafunctionthatneedstobepassedintothe
importObj.envobjectwheninstantiatingamodule?7. WhatadditionalAPIsdoesEmscriptenprovide?8. Whatisthepurposeofsourcemaps?
FurtherreadingEmscriptenAPIReference:http://kripken.github.io/emscripten-site/docs/api_reference/index.html
AnIntroductiontoSourceMaps:http://blog.teamtreehouse.com/introduction-source-maps
UsingBrowserstoDebugWebAssembly:http://webassemblycode.com/using-browsers-debug-webassembly
CreatinganApplicationfromScratch
Nowit'stimetoapplyyourknowledge!SinceoneofWebAssembly'sprimarydesigngoalsistoexecutewithinandintegratewellwiththeexistingwebplatform,itmakessensetobuildawebapplicationtotestitout.EventhoughWebAssembly'scurrentfeaturesetisratherlimited,wecanutilizethetechnologyatabasiclevel.Inthischapter,wewillbuildasingle-pageapplicationfromscratchthatutilizesWasmmoduleswithinthecontextoftheCoreSpecification.
Bytheendofthischapter,you'llknowhowto:
WritefunctionsthatperformsimplecomputationswithCBuildabasicJavaScriptapplicationwithVueIntegrateWasmintoyourJavaScriptapplicationIdentifythecapabilitiesandlimitationsofWebAssemblyinitscurrentformRunandtestaJavaScriptapplicationusingbrowser-sync
CooktheBooks–makingWebAssemblyaccountableAsmentionedbefore,WebAssembly'scurrentfeaturesetisratherlimited.WecanuseEmscriptentogreatlyextendthecapabilitiesofawebapplication,butthatcarriesthecostofnoncompliancewiththeofficialspecificationandtheadditionofgluecode.WecanstilluseWebAssemblyeffectivelytoday,whichbringsustotheapplicationwe'llbuildinthischapter.Inthissection,wewillreviewthelibrariesandtoolswe'llusetobuildtheapplication,aswellasabriefoverviewofitsfunctionality.
OverviewandfunctionalityInWebAssembly'scurrentform,wecanpassnumbersbetweenaWasmmoduleandJavaScriptcodewithrelativeease.Anaccountingapplicationseemslikealogicalchoiceintermsofreal-worldapplicability.TheonlycontentionIhavewithaccountingsoftwareisthatit'salittleboring(nooffense).We'regoingtospiceitupabitbybuildinginsomeunethicalaccountingpractices.TheapplicationisnamedCooktheBooks,atermassociatedwithaccountingfraud.InvestopediaprovidesthefollowingdefinitionofCooktheBooks:
"CooktheBooksisanidiomdescribingfraudulentactivitiesperformedbycorporationsinordertofalsifytheirfinancialstatements.Typically,cookingthebooksinvolvesaugmentingfinancialdatatoyieldpreviouslynonexistentearnings.Examplesoftechniquesusedtocookthebooksinvolveacceleratingrevenues,delayingexpenses,manipulatingpensionplans,andimplementingsyntheticleases."
TheInvestopediapageathttps://www.investopedia.com/terms/c/cookthebooks.aspoffersdetailedexamplesofwhatconstitutescookingthebooks.We'lltakeasimpleapproachforourapplication.Wewillallowtheusertoenteratransactionwitharawandcookedamount.Therawamountrepresentstheactualamountofmoneythatwaseitherdepositedorwithdrawn,whilethecookedamountiswhateveryoneelsewillsee.Theapplicationwillgeneratepiechartsthatdisplayexpensesandincomebycategoryforeithertheraworcookedtransactions.Theuserwillbeabletoeasilytogglebetweenthetwoviews.Theapplicationconsistsofthefollowingcomponents:
TabsforswitchingbetweentransactionsandchartsTablethatdisplaystransactionsButtonsthatallowausertoadd,edit,orremoveatransactionModaldialogforadding/updatingatransactionPiechartstodisplaytheincome/expensesbycategory
JavaScriptlibrariesusedTheJavaScriptportionoftheapplicationwilluseseverallibrariesservedfromaCDN.Itwillalsouseonelocallyinstalledlibrarytowatchforchangesinthecode.Thefollowingsectionswilldescribeeachlibraryanditspurposeintheapplication.
VueVueisaJavaScriptframeworkthatallowsyoutosplitanapplicationintoindividualcomponentsforeaseofdevelopmentanddebugging.We'reusingittoavoidhavingonemonolithicJavaScriptfilewithallofourapplicationlogicandanothermonolithicHTMLfilewiththeentireUI.Vuewaschosenbecauseitdoesn'trequiretheaddedcomplexityofabuildsystemandallowsustouseHTML,CSS,andJavaScriptwithouthavingtodoanytranspiling.Theofficialwebsiteishttps://vuejs.org.
UIkitUIkitisthefrontendframeworkwewillusetoaddstylingandlayouttoourapplication.Therearedozensofalternatives,likeBootstraporBulma,thatoffercomparablecomponentsandfunctionality.ButIchoseUIkitbecauseofthehelpfulutilityclassesandaddedJavaScriptfunctionality.Youcanviewthedocumentationathttps://getuikit.com.
LodashLodashisanexcellentutilitylibrarythatprovidesmethodsforperformingcommonactionsinJavaScriptthataren'talreadybuiltintothelanguage.Wewilluseittoperformcalculationsandmanipulatethetransactionsdata.Documentationandinstallationinstructionscanbefoundathttps://lodash.com.
Data-drivendocumentsData-drivendocuments(D3)isamulti-facetedlibrarythatallowsyoutotranslatedataintoimpressivevisualizations.D3'sAPIconsistsofseveralmodulesthatrangefromarraymanipulationtochartingandtransitions.WewilluseD3primarilytocreatethepiecharts,butwe'llalsotakeadvantageofsomeoftheutilitymethodsitprovides.Youcanfindmoreinformationathttps://d3js.org.
OtherlibrariesInordertodisplaycurrencyvaluesinthecorrectformatandensuretheuserentersavaliddollaramount,wewillutilizetheaccounting.js(http://openexchangerates.github.io/accounting.js)andvue-numeric(https://kevinongko.github.io/vue-numeric)libraries.Tosimplifydevelopment,we'llsetupabasicnpmprojectandusebrowser-sync(https://www.browsersync.io)toimmediatelyseecodechangesreflectedintherunningapplication.
CandthebuildprocessTheapplicationusesCsincewe'reperformingsimplecalculationswithbasicalgebra.Itwouldn'tmakesensetouseC++inthiscase.ThatwouldintroducetheaddedstepofensuringthefunctionsweneedtocallfromJavaScriptarewrappedinanexternblock.We'llwritethecalculationfunctionsinasingleCfileandcompileitdowntoasingleWasmmodule.WecancontinuetouseVSCode'sTasksfunctionalitytoperformthebuild,buttheargumentswillneedtobeupdatedsincewe'llonlycompileasinglefile.Let'smoveontoprojectconfiguration.
SettinguptheprojectWebAssemblyhasn'tbeenaroundlongenoughtohaveestablishedbestpracticeswithregardtofolderstructure,filenamingconventions,andsoon.IfyouweretosearchforbestpracticesforC/C++orJavaScriptprojects,you'dencounteragreatdealofconflictingadviceandstronglyheldopinions.Withthatinmind,let'sspendthissectionsettingupourprojectwiththerequiredconfigurationfiles.
Thecodeforthisprojectislocatedinthe/chapter-07-cook-the-booksfolderinthelearn-webassemblyrepository.YoumusthavethiscodeavailablewhenwegettotheJavaScriptportionoftheapplication.Iwon'tbeprovidingthesourcecodeforalloftheVuecomponentsinthebook,soyouneedtocopythemfromtherepository.
ConfiguringforNode.jsIntheinterestofkeepingtheapplicationassimpleaspossible,we'llavoidabuild/bundlingtoollikeWebpackorRollup.js.Thisallowsustocutdownonthenumberofrequireddependenciesandensuresthatanyissuesyourunintoaren'tcausedbyabreakingchangeinabuilddependency.
We'llcreateaNode.jsprojectbecauseitallowsustorunscriptsandinstalladependencylocallyfordevelopmentpurposes.We'veusedthe/book-examplesfolderuptothispoint,butwe'llcreateanewprojectfolderoutsideof/book-examplestoconfigureadifferentdefaultbuildtaskinVSCode.Openaterminal,cdintothedesiredfolder,andenterthefollowingcommands:
//Createanewdirectoryandcdintoit:
mkdircook-the-books
cdcook-the-books
//Createapackage.jsonfilewithdefaultvalues
npminit-y
The-ycommandforgoesthepromptsandpopulatesthepackage.jsonfilewithsensibledefaults.Oncecompleted,runthefollowingcommandtoinstallbrowser-sync:
npminstall-Dbrowser-sync@^2.24.4
The-Disoptionalandindicatesthatthelibraryisadevelopmentdependency.Youwouldusethe-Dflagifyouwerebuildinganddistributingtheapplication,soIincludedittoadheretocommonpractice.I'drecommendinstallingthatspecificversiontoensurethestartscriptrunswithoutanyissues.Afterbrowser-syncinstalls,addthefollowingentrytothescriptsentryinthepackage.jsonfile:
...
"scripts":{
...
"start":"browser-syncstart--server\"src\"--files\"src/**\"--single--no-open--port4000"
},
…
Ifyourunnpminitwiththe-yflag,thereshouldbeanexistingscriptnamedtest,whichIomittedforclarity.Ifyoudidn'trunitwiththe-yflag,youmayneedtocreatethescriptsentry.
Youcanpopulatethe"description"and"author"keysifdesired.Thefileshouldend
uplookingsimilartothis:
{
"name":"cook-the-books",
"version":"1.0.0",
"description":"ExampleapplicationforLearnWebAssembly",
"main":"src/index.js",
"scripts":{
"start":"browser-syncstart--server\"src\"--files\"src/**\"--single--no-open--port4000",
"test":"echo\"Error:notestspecified\"&&exit1"
},
"keywords":[],
"author":"MikeRourke",
"license":"MIT",
"devDependencies":{
"browser-sync":"^2.24.4"
}
}
Ifyouomitthe--no-openflagfromthestartscript,thebrowserwillopenautomatically.Theflagwasincludedtopreventissueswithusersrunninginaheadlessenvironment.
AddingfilesandfoldersCreatetwonewfolderswithintherootfolder:/liband/src.TheJavaScript,HTML,CSS,andWasmfileswillbelocatedinthe/srcfolderwhiletheCfilewillbein/lib.Ionlywanttoincludefilesthatareusedbythewebapplicationin/src.We'llneverusetheCfiledirectlyfromtheapplication,onlythecompiledoutput.
Copythe/.vscodefolderfromyour/book-examplesprojectintotherootfolder.Thiswillensureyou'reusingtheexistingC/C++settingsandgiveyouagoodstartingpointforthebuildtask.
Ifyou'reusingmacOSorLinux,you'llhavetousetheterminaltocopythefolder;youcanaccomplishthisbyrunningthecp-rcommand.
ConfiguringthebuildstepWeneedtomodifythedefaultbuildstepinthe/.vscode/tasks.jsonfiletoaccommodateourupdatedworkflow.Theargumentsforthebuildstepweusedinour/book-examplesprojectallowedustocompilewhicheverfilewascurrentlyactiveintheeditor.Italsooutputthe.wasmfileintothesamefolderasthesourceCfile.However,thisconfigurationdoesn'tmakesenseforthisproject.We'llalwayscompilethesameCfilethatisoutputtothecompiled.wasmfileinaspecificfolder.Toaccomplishthis,updatetheargsarrayintheBuildtaskin/.vscode/tasks.jsonwiththefollowingcontents:"args":["${workspaceFolder}/lib/main.c","-Os","-s","WASM=1","-s","SIDE_MODULE=1","-s","BINARYEN_ASYNC_COMPILATION=0","-o","${workspaceFolder}/src/assets/main.wasm"],
Wechangedtheinputandoutputpaths,whicharethefirstandlastelementsintheargsarray.Nowbotharestaticpathsthatalwayscompileandoutputthesamefilesregardlessofwhichfileisopenintheactiveeditor.
SettingupamockAPIWeneedsomemockdataandameansofpersistinganyupdates.IfyoustorethedatalocallyinaJSONfile,anychangesyoumaketothetransactionswillbelostassoonasyourefreshthepage.WecouldsetupalocalserverwithalibrarylikeExpress,mockadatabase,writeroutes,andsoon.Butinsteadwe'regoingtotakeadvantageoftheexcellentdevelopmenttoolingavailableonline.Theonlinetoojsonstore.ioisallowsyoutostoreJSONdataforsmallprojectsandprovidesendpointsoutofthebox.TakethefollowingstepstogetyourmockAPIupandrunning:
1. Navigatetohttps://www.jsonstore.io/andpresstheCopybuttontocopytheendpointtoyourclipboard;thisistheendpointyou'llbemakingHTTPrequeststo.
2. GototheJSFiddleathttps://jsfiddle.net/mikerourke/cta0km6d,pasteyourjsonstore.ioendpointintotheinput,andpressthePopulateDatabutton.
3. Openupanewtabandpasteyourjsonstore.ioendpointintheaddressbarandadd/transactionstotheendoftheURLandpressEnter.IfyouseethecontentsoftheJSONfileinyourbrowser,theAPIsetupwassuccessful.
Keepthatjsonstore.ioendpointhandy—you'llneeditwhenwebuildtheJavaScriptportionoftheapp.
DownloadingtheCstdlibWasmWeneedthemalloc()andfree()functionsfromC'sstandardlibraryforthefunctionalityinourCcode.WebAssemblydoesn'thavethesefunctionsbuiltin,soweneedtoprovideourownimplementation.
Fortunately,someonehasalreadybuiltthatforus;wejustneedtodownloadthemoduleandincludeitintheinstantiationstep.ThemodulecanbedownloadedfromGuyBedford'swasm-stdlib-hackGitHubrepositoryathttps://github.com/guybedford/wasm-stdlib-hack.Youneedthememory.wasmfilefromthe/distfolder.Oncethefileisdownloaded,createafoldernamed/assetsinthe/srcfolderofyourprojectandcopythememory.wasmfilethere.
Youcancopythememory.wasmfilefromthe/chapter-07-cook-the-books/src/assetsfolderofthelearn-webassemblyrepositoryinsteadofdownloadingitfromGitHub.
ThefinalresultAfterperformingthesesteps,yourprojectshouldlooklikethis:
├──/.vscode
│├──tasks.json
│└──c_cpp_properties.json
├──/lib
├──/src
│└──/assets
│└──memory.wasm
├──package.json
└──package-lock.json
BuildingtheCportionTheCportionoftheapplicationwillaggregatetransactionandcategoryamounts.ThecalculationsweperforminCcouldbedonejustaseasilyinJavaScript,butWebAssemblyisidealforcomputation.We'lldivedeeperintomorecomplexusageofC/C++inChapter8,PortingaGamewithEmscripten,butfornowwe'retryingtolimitourscopetowhatcanbedonewithintheconfinesoftheCoreSpecification.Inthissection,we'llwritesomeCcodetodemonstratehowtointegrateWebAssemblywithawebapplicationwithouttheuseofEmscripten.
OverviewWewillwritesomeCfunctionsthatcalculatethegrandtotalsaswellastheendingbalancesforrawandcookedtransactions.Inadditiontocalculatingthegrandtotals,weneedtocalculatethetotalsforeachcategoryfordisplayinthepiecharts.AllofthesecalculationswillbeperformedinasingleCfileandcompileddowntoasingleWasmfilethatwillbeinstantiatedwhentheapplicationloads.Ccanbealittledauntingfortheuninitiated,soourcodewillbesacrificingsomeefficiencyforthesakeofclarity.I'dliketotakeamomenttoapologizetotheC/C++programmersreadingthisbook;you'renotgoingtolikewhatyouC.
Inordertoperformcalculationsdynamically,weneedtoallocateanddeallocatememoryastransactionsareaddedanddeleted.Toaccomplishthis,we'lluseadoublylinkedlist.Adoublylinkedlistisadatastructurethatallowsustoremoveitemsornodesinsidealistandaddandeditnodesasneeded.Nodesareaddedusingmalloc()andremovedusingfree(),bothofwhichareprovidedbythememory.wasmmoduleyoudownloadedintheprevioussection.
AnoteregardingworkflowTheorderofoperationsintermsofdevelopmentdoesn'treflecthowyouwouldnormallybuildanapplicationthatusesWebAssembly.TheworkflowwouldconsistofjumpingbetweenC/C++andJavaScripttoachievethedesiredresults.Inthiscase,thefunctionalitythatwe'reoffloadingfromJavaScriptintoWebAssemblyisalreadyknown,sowe'llwritetheCcodeupfront.
CfilecontentsLet'swalkthrougheachsectionoftheCfile.Createafileinthe/libfoldernamedmain.candpopulateitwiththefollowingcontentsineachsection.It'llbeeasiertocomprehendwhat'shappeningintheCfileifwebreakitintosmallerchunks.Let'sstartwiththeDeclarationssection.
DeclarationsThefirstsectioncontainsdeclarationswewillusetocreateandtraversethedoublylinkedlist,asfollows:
#include<stdlib.h>
structNode{
intid;
intcategoryId;
floatrawAmount;
floatcookedAmount;
structNode*next;
structNode*prev;
};
typedefenum{
RAW=1,
COOKED=2
}AmountType;
structNode*transactionsHead=NULL;
structNode*categoriesHead=NULL;
TheNodestructisusedtorepresentatransactionorcategory.ThetransactionsHeadandcategoriesHeadnodeinstancesrepresentthefirstnodeineachlinkedlistwe'lluse(onefortransactionsandoneforcategories).TheAmountTypetheenumisn'trequired,butwe'lldiscusshowit'susefulwhenwegettothesectionofcodethatutilizesit.
LinkedlistoperationsThesecondsectioncontainsthetwofunctionsusedtoaddanddeletenodesfromthelinkedlist:
voiddeleteNode(structNode**headNode,structNode*delNode){
//Basecase:
if(*headNode==NULL||delNode==NULL)return;
//Ifnodetobedeletedisheadnode:
if(*headNode==delNode)*headNode=delNode->next;
//ChangenextonlyifnodetobedeletedisNOTthelastnode:
if(delNode->next!=NULL)delNode->next->prev=delNode->prev;
//ChangeprevonlyifnodetobedeletedisNOTthefirstnode:
if(delNode->prev!=NULL)delNode->prev->next=delNode->next;
//Finally,freethememoryoccupiedbydelNode:
free(delNode);
}
voidappendNode(structNode**headNode,intid,intcategoryId,
floatrawAmount,floatcookedAmount){
//1.Allocatenode:
structNode*newNode=(structNode*)malloc(sizeof(structNode));
structNode*last=*headNode;//UsedinStep5
//2.Populatewithdata:
newNode->id=id;
newNode->categoryId=categoryId;
newNode->rawAmount=rawAmount;
newNode->cookedAmount=cookedAmount;
//3.Thisnewnodeisgoingtobethelastnode,somakenextNULL:
newNode->next=NULL;
//4.Ifthelinkedlistisempty,thenmakethenewnodeashead:
if(*headNode==NULL){
newNode->prev=NULL;
*headNode=newNode;
return;
}
//5.Otherwise,traversetillthelastnode:
while(last->next!=NULL){
last=last->next;
}
//6.Changethenextoflastnode:
last->next=newNode;
//7.Makelastnodeaspreviousofnewnode:
newNode->prev=last;
}
Thecommentswithinthecodedescribewhat'shappeningateachstep.WhenweneedtoaddaNodetothelist,wehavetoallocatethememorytakenupbythestructNodeusingmalloc()andappendittothelastnodeinthelinkedlist.Ifweneedtodeleteanode,wehavetoremoveitfromthelinkedlistanddeallocatethememorythatthenodewasusingbycallingthefree()function.
transactionsoperationsThethirdsectioncontainsfunctionstoadd,edit,andremovetransactionsfromthetransactionslinkedlist,asfollows:
structNode*findNodeById(intid,structNode*withinNode){
structNode*node=withinNode;
while(node!=NULL){
if(node->id==id)returnnode;
node=node->next;
}
returnNULL;
}
voidaddTransaction(intid,intcategoryId,floatrawAmount,
floatcookedAmount){
appendNode(&transactionsHead,id,categoryId,rawAmount,cookedAmount);
}
voideditTransaction(intid,intcategoryId,floatrawAmount,
floatcookedAmount){
structNode*foundNode=findNodeById(id,transactionsHead);
if(foundNode!=NULL){
foundNode->categoryId=categoryId;
foundNode->rawAmount=rawAmount;
foundNode->cookedAmount=cookedAmount;
}
}
voidremoveTransaction(intid){
structNode*foundNode=findNodeById(id,transactionsHead);
if(foundNode!=NULL)deleteNode(&transactionsHead,foundNode);
}
TheappendNode()anddeleteNode()functionswereviewedintheprevioussectionaren'tintendedtobecalledfromtheJavaScriptcode.Instead,callstoaddTransaction(),editTransaction(),andremoveTransaction()areusedtoupdatethelocallinkedlist.TheaddTransaction()functioncallstheappendNode()functiontoaddthedatapassedinasargumentstoanewnodeinthelocallinkedlist.TheremoveTransaction()callsthedeleteNode()functiontodeletethecorrespondingtransactionnode.ThefindNodeById()functionisusedtodeterminewhichnodeneedstobeupdatedordeletedwithinthelinkedlistbasedonthespecifiedID.
transactionscalculationsThefourthsectioncontainsfunctionstocalculatethegrandtotalsandfinalbalancesforrawandcookedtransactions,asfollows:
voidcalculateGrandTotals(float*totalRaw,float*totalCooked){
structNode*node=transactionsHead;
while(node!=NULL){
*totalRaw+=node->rawAmount;
*totalCooked+=node->cookedAmount;
node=node->next;
}
}
floatgetGrandTotalForType(AmountTypetype){
floattotalRaw=0;
floattotalCooked=0;
calculateGrandTotals(&totalRaw,&totalCooked);
if(type==RAW)returntotalRaw;
if(type==COOKED)returntotalCooked;
return0;
}
floatgetFinalBalanceForType(AmountTypetype,floatinitialBalance){
floattotalForType=getGrandTotalForType(type);
returninitialBalance+totalForType;
}
TheAmountTypeenumwedeclaredinthedeclarationssectionisusedheretoavoidmagicnumbers.Itmakesiteasytorememberthat1representsrawtransactionsand2representscookedtransactions.ThegrandtotalsforbothrawandcookedtransactionsarecalculatedinthecalculateGrandTotals()function,eventhoughwe'reonlyaskingforonetypeingetGrandTotalForType().SincewecanonlyreturnasinglevaluefromaWasmfunction,weenduploopingthroughallofthetransactionstwicewhenwecallgetGrandTotalForType()forbothrawandcookedtransactions.Witharelativelysmallamountoftransactionsandthesimplicityofthecalculation,thisdoesn'tpresentanyissues.ThegetFinalBalanceForType()returnsthegrandtotalplusthespecifiedinitialBalance.You'llseethisinactionwhenweaddtheabilitytochangeinitialbalancesinthewebapplication.
CategorycalculationsThefifthandfinalsectioncontainsfunctionstocalculatetotalsbycategory,whichwe'llutilizeinthepiecharts,asfollows:
voidupsertCategoryNode(intcategoryId,floattransactionRaw,
floattransactionCooked){
structNode*foundNode=findNodeById(categoryId,categoriesHead);
if(foundNode!=NULL){
foundNode->rawAmount+=transactionRaw;
foundNode->cookedAmount+=transactionCooked;
}else{
appendNode(&categoriesHead,categoryId,categoryId,transactionRaw,
transactionCooked);
}
}
voidbuildValuesByCategoryList(){
structNode*node=transactionsHead;
while(node!=NULL){
upsertCategoryNode(node->categoryId,node->rawAmount,
node->cookedAmount);
node=node->next;
}
}
voidrecalculateForCategories(){
categoriesHead=NULL;
buildValuesByCategoryList();
}
floatgetCategoryTotal(AmountTypetype,intcategoryId){
//Ensurethecategorytotalshavebeencalculated:
if(categoriesHead==NULL)buildValuesByCategoryList();
structNode*categoryNode=findNodeById(categoryId,categoriesHead);
if(categoryNode==NULL)return0;
if(type==RAW)returncategoryNode->rawAmount;
if(type==COOKED)returncategoryNode->cookedAmount;
return0;
}
ThebuildValuesByCategoryList()functioniscalledwhenevertherecalculateForCategories()orgetCategoryTotal()functionsarecalled.Thefunctionloopsthroughallofthetransactionsinthetransactionslinkedlistandcreatesanodeinaseparatelinkedlistforeachcorrespondingcategorywiththeaggregatedrawandtotalamounts.TheupsertCategoryNode()functionlooksforanodethatcorrespondstothecategoryIdinthecategorieslinkedlist.Ifitfindsit,therawandcookedtransactionamountsareaddedtotheexistingamountsonthatnode,otherwiseanewnodeiscreatedforsaidcategory.The
recalculateForCategories()functioniscalledtoensurethecategorytotalsareuptodatewithanytransactionschanges.
CompilingtoWasmAfterpopulatingthefile,weneedtocompileitdowntoWasmforuseintheJavaScriptportionoftheapplication.RunthebuildtaskbyselectingTasks|RunBuildTask...fromthemenuorusingthekeyboardshortcutCmd/Ctrl+Shift+B.Ifthebuildwassuccessful,you'llseeafilenamedmain.wasminthe/src/assetsfolder.Ifanerroroccurred,theterminalshouldprovidedetailsonhowtoresolveit.
Ifyou'renotusingVSCode,openaterminalinstanceinthe/cook-the-booksfolderandrunthefollowingcommand:emcclib/main.c-Os-sWASM=1-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0-osrc/assets/main.wasm
That'sitfortheCcode.Let'smoveontotheJavaScriptportion.
BuildingtheJavaScriptportionTheJavaScriptportionoftheapplicationpresentsthetransactionsdatatotheuserandallowsthemtoeasilyadd,edit,andremovetransactions.TheapplicationissplitacrossseveralfilestosimplifythedevelopmentprocessandusesthelibrariesdescribedintheJavaScriptlibrariesusedsectionofthischapter.Inthissection,wewillbuildtheapplicationstepbystep,startingwiththeAPIandglobalstateinteractionlayer.We'llwritefunctionstoinstantiateandinteractwithourWasmmoduleandreviewtheVuecomponentsrequiredtobuildtheuserinterface.
OverviewTheapplicationisbrokendownintocontextstosimplifythedevelopmentprocess.We'llbuildtheapplicationfromthebottomuptoensurewedon'thavetobouncebackandforthbetweenthedifferentcontextswhenwritingcode.We'llstartwiththeWasminteractioncode,thenmoveontotheglobalstoreandAPIinteraction.I'lldescribethepurposeofeachVuecomponent,butthesourcecodewillonlybeprovidedforaselectfew.Ifyou'refollowingalongandwishtoruntheapplicationlocally,you'llneedtocopythe/src/componentsfolderfromthe/chapter-07-cook-the-booksfolderinthelearn-webassemblyrepositoryintothe/srcfolderofyourproject.
AnoteaboutbrowsercompatibilityBeforewestartwritinganycode,youmustensureyourbrowsersupportsthenewerJavaScriptfeatureswe'lluseintheapplication.YourbrowserhastosupportESModules(importandexport),theFetchAPI,andasync/await.YouneedatleastVersion61ofGoogleChromeorVersion60ofFirefox.Youcancheckwhichversionyou'recurrentlyusingbyselectingAboutChromeorAboutFirefoxfromthemenubar.I'mcurrentlyrunningtheapplicationwithChromeVersion67andFirefoxVersion61withoutanyissues.
CreatingaWasminstanceininitializeWasm.jsYoushouldhavetwocompiledWasmfilesinthe/src/assetsfolderofyourproject:main.wasmandmemory.wasm.Sinceweneedtoutilizethemalloc()andfree()functionsexportedfrommemory.wasminthemain.wasmcode,ourloadingcodeisgoingtolookdifferentfromtheearlierexamples.Createafileinthe/src/storefoldernamedinitializeWasm.jsandpopulateitwiththefollowingcontents:
/**
*Returnsanarrayofcompiled(notinstantiated!)Wasmmodules.
*Weneedthemain.wasmfilewecreated,aswellasthememory.wasmfile
*thatallowsustouseCfunctionslikemalloc()andfree().
*/
constfetchAndCompileModules=()=>
Promise.all(
['../assets/main.wasm','../assets/memory.wasm'].map(fileName=>
fetch(fileName)
.then(response=>{
if(response.ok)returnresponse.arrayBuffer();
thrownewError(`UnabletofetchWebAssemblyfile:${fileName}`);
})
.then(bytes=>WebAssembly.compile(bytes))
)
);
/**
*Returnsaninstanceofthecompiled"main.wasm"file.
*/
constinstantiateMain=(compiledMain,memoryInstance,wasmMemory)=>{
constmemoryMethods=memoryInstance.exports;
returnWebAssembly.instantiate(compiledMain,{
env:{
memoryBase:0,
tableBase:0,
memory:wasmMemory,
table:newWebAssembly.Table({initial:16,element:'anyfunc'}),
abort:console.log,
_consoleLog:value=>console.log(value),
_malloc:memoryMethods.malloc,
_free:memoryMethods.free
}
});
};
/**
*Compilesandinstantiatesthe"memory.wasm"and"main.wasm"filesand
*returnsthe`exports`propertyfrommain's`instance`.
*/
exportdefaultasyncfunctioninitializeWasm(){
constwasmMemory=newWebAssembly.Memory({initial:1024});
const[compiledMain,compiledMemory]=awaitfetchAndCompileModules();
constmemoryInstance=awaitWebAssembly.instantiate(compiledMemory,{
env:{
memory:wasmMemory
}
});
constmainInstance=awaitinstantiateMain(
compiledMain,
memoryInstance,
wasmMemory
);
returnmainInstance.exports;
}
Thefile'sdefaultexportfunction,initializeWasm(),performsthefollowingsteps:
1. CreateanewWebAssembly.Memoryinstance(wasmMemory).2. CallthefetchAndCompileModules()functiontogetaWebAssembly.Moduleinstancefor
memory.wasm(compiledMemory)andmain.wasm(compiledMain).3. InstantiatecompiledMemory(memoryInstance)andpassthewasmMemoryintothe
importObj.4. PasscompiledMain,memoryInstance,andwasmMemoryintotheinstantiateMain()
function.5. InstantiatecompiledMainandpasstheexportedmalloc()andfree()functions
frommemoryInstancealongwithwasmMemoryintotheimportObj.6. ReturntheexportspropertyoftheInstancereturnedfrominstantiateMain
(mainInstance).
Asyoucansee,theprocessismorecomplexwhenyouhavedependencieswithinWasmmodules.
YoumayhavenoticedthatthemallocandfreemethodsonthememoryInstanceexportspropertyweren'tprefixedwithanunderscore.Thisisbecausethememory.wasmfilewascompiledusingLLVMwithoutEmscripten,whichdoesn'taddthe_.
importinitializeWasmfrom'./initializeWasm.js';<br/><br/>/**<br/>*ClassusedtowrapthefunctionalityfromtheWasmmodule(rather<br/>*thanaccessitdirectlyfromtheVuecomponentsorstore).<br/>*@class<br/>*/<br/>exportdefaultclassWasmTransactions{<br/>constructor(){<br/>this.instance=null;<br/>this.categories=[];<br/>}<br/><br/>asyncinitialize(){<br/>this.instance=awaitinitializeWasm();<br/>returnthis;<br/>}<br/><br/>getCategoryId(category){<br/>returnthis.categories.indexOf(category);<br/>}<br/><br/>//Ensurestherawandcookedamountshavethepropersign(withdrawals<br/>//arenegativeanddepositsarepositive).<br/>getValidAmounts(transaction){<br/>const{rawAmount,cookedAmount,type}=transaction;<br/>constgetAmount=amount=><br/>type==='Withdrawal'?-Math.abs(amount):amount;<br/>return{<br/>validRaw:getAmount(rawAmount),<br/>validCooked:getAmount(cookedAmount)<br/>};<br/>}<br/><br/>//AddsthespecifiedtransactiontothelinkedlistintheWasmmodule.<br/>addToWasm(transaction){<br/>const{id,category}=transaction;<br/>const{validRaw,validCooked}=this.getValidAmounts(transaction);<br/>constcategoryId=this.getCategoryId(category);<br/>this.instance._addTransaction(id,categoryId,validRaw,validCooked);<br/>}<br/><br/>//UpdatesthetransactionnodeintheWasmmodule:<br/>editInWasm(transaction){<br/>const{id,category}=transaction;<br/>const{validRaw,validCooked}=this.getValidAmounts(transaction);<br/>constcategoryId=this.getCategoryId(category);<br/>this.instance._editTransaction(id,categoryId,validRaw,validCooked);<br/>}<br/><br/>//RemovesthetransactionnodefromthelinkedlistintheWasmmodule:<br/>removeFromWasm(transactionId){<br/>this.instance._removeTransaction(transactionId);<br/>}<br/><br/>//PopulatesthelinkedlistintheWasmmodule.Thecategoriesare<br/>//neededtosetthecategoryIdintheWasmmodule.<br/>populateInWasm(transactions,categories){<br/>this.categories=categories;<br/>transactions.forEach(transaction=>this.addToWasm(transaction));<br/>}<br/><br/>//Returnsthebalanceforrawandcookedtransactionsbasedonthe<br/>//specifiedinitialbalances.<br/>getCurrentBalances(initialRaw,initialCooked){<br/>constcurrentRaw=this.instance._getFinalBalanceForType(<br/>AMOUNT_TYPE.raw,<br/>initialRaw<br/>);<br/>constcurrentCooked=this.instance._getFinalBalanceForType(<br/>AMOUNT_TYPE.cooked,<br/>initialCooked<br/>);<br/>return{currentRaw,currentCooked};<br/>}<br/><br/>//Returnsanobjectthathascategorytotalsforallincome(deposit)<br/>//andexpense(withdrawal)transactions.<br/>getCategoryTotals(){<br/>//This
isdonetoensurethetotalsreflectthemostrecent<br/>//transactions:<br/>this.instance._recalculateForCategories();<br/>constcategoryTotals=this.categories.map((category,idx)=>({<br/>category,<br/>id:idx,<br/>rawTotal:this.instance._getCategoryTotal(AMOUNT_TYPE.raw,idx),<br/>cookedTotal:this.instance._getCategoryTotal(AMOUNT_TYPE.cooked,idx)<br/>}));<br/><br/>consttotalsByGroup={income:[],expenses:[]};<br/>categoryTotals.forEach(categoryTotal=>{<br/>if(categoryTotal.rawTotal<0){<br/>totalsByGroup.expenses.push(categoryTotal);<br/>}else{<br/>totalsByGroup.income.push(categoryTotal);<br/>}<br/>});<br/>returntotalsByGroup;<br/>}<br/>}
Whentheinitialize()functioniscalledonaninstanceoftheclass,thereturnvalueoftheinitializeWasm()functionisassignedtotheinstancepropertyoftheclass.Theclassmethodscallfunctionsfromthis.instanceand,ifapplicable,returnthedesiredresults.NotetheAMOUNT_TYPEobjectreferencedinthegetCurrentBalances()andgetCategoryTotals()functions.ThiscorrespondstotheAmountTypeenuminourCfile.TheAMOUNT_TYPEobjectisdeclaredgloballyinthe/src/main.jsfilewheretheapplicationisloaded.NowthatwehaveourWasminteractioncodewritten,let'smoveontoAPIinteractioncode.
//Pasteyourjsonstore.ioendpointhere(noendingslash):<br/>constAPI_URL='[JSONSTORE.IOENDPOINT]';<br/><br/>/**<br/>*WrapperforperformingAPIcalls.Wedon'twanttocallresponse.json()<br/>*eachtimewemakeafetchcall.<br/>*@param{string}endpointEndpoint(e.g."/transactions"tomakeAPIcallto<br/>*@param{Object}initFetchoptionsobjectcontaininganycustomsettings<br/>*@returns{Promise<*>}<br/>*@seehttps://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch<br/>*/<br/>constperformApiFetch=(endpoint='',init={})=><br/>fetch(`${API_URL}${endpoint}`,{<br/>headers:{<br/>'Content-type':'application/json'<br/>},<br/>...init<br/>}).then(response=>response.json());<br/><br/>exportconstapiFetchTransactions=()=><br/>performApiFetch('/transactions').then(({result})=><br/>/*<br/>*Theresponseobjectlookslikethis:<br/>*{<br/>*"result":{<br/>*"1":{<br/>*"category":"SalesRevenue",<br/>*...<br/>*},<br/>*"2":{<br/>*"category":"Hotels",<br/>*...<br/>*},<br/>*...<br/>*}<br/>*}<br/>*Weneedthe"1"and"2"valuesfordeletingoreditingexisting<br/>*records,sowestorethatinthetransactionrecordas"apiId".<br/>*/<br/>Object.keys(result).map(apiId=>({<br/>...result[apiId],<br/>apiId<br/>}))<br/>);<br/><br/>exportconstapiEditTransaction=transaction=><br/>performApiFetch(`/transactions/${transaction.apiId}`,{<br/>method:'POST',<br/>body:JSON.stringify(transaction)<br/>});<br/><br/>exportconstapiRemoveTransaction=transaction=><br/>performApiFetch(`/transactions/${transaction.apiId}`,{<br/>method:'DELETE'<br/>});<br/><br/>exportconstapiAddTransaction=transaction=><br/>performApiFetch(`/transactions/${transaction.apiId}`,{<br/>method:'POST',<br/>body:JSON.stringify(transaction)<br/>});
You'llneedthejsonstore.ioendpointyoucreatedintheSettinguptheprojectsectioninordertointeractwiththeAPI.Replace[JSONSTORE.IOENDPOINT]withyourjsonstore.ioendpoint.Ensuretheendpointdoesn'tendwithaforwardslashorthewordtransactions.
Managingglobalstateinstore.jsThefilethatmanagesglobalstateintheapplicationhasalotofmovingparts.Consequently,wewillbreakthecodedownintosmallerchunksandwalkthrougheachsectionindividually.Createafileinthe/src/storefoldernamedstore.jsandpopulateitwiththecontentsfromeachofthefollowingsections.
TheimportandstoredeclarationsThefirstsectioncontainsimportstatementsandthewasmandstatepropertiesontheexportedstoreobject,asfollows:
import{
apiFetchTransactions,
apiAddTransaction,
apiEditTransaction,
apiRemoveTransaction
}from'./api.js';
importWasmTransactionsfrom'./WasmTransactions.js';
exportconststore={
wasm:null,
state:{
transactions:[],
activeTransactionId:0,
balances:{
initialRaw:0,
currentRaw:0,
initialCooked:0,
currentCooked:0
}
},
...
AllAPIinteractionislimitedtothestore.jsfile.Sinceweneedtomanipulate,add,andsearchtransactions,alloftheexportedfunctionsfromapi.jsareimported.ThestoreobjectholdstheWasmTransactionsinstanceinthewasmpropertyandinitialstateinthestateproperty.Thevaluesinstatearereferencedinmultiplelocationsthroughouttheapplication.Thestoreobjectwillbeaddedtotheglobalwindowobjectwhentheapplicationloads,soallcomponentshaveaccesstotheglobalstate.
TransactionsoperationsThesecondsectioncontainsfunctionsthatmanagetransactionsintheWasminstance(throughtheWasmTransactionsinstance)andtheAPI,asfollows:
...
getCategories(){
constcategories=this.state.transactions.map(
({category})=>category
);
//Removeduplicatecategoriesandsortthenamesinascendingorder:
return_.uniq(categories).sort();
},
//PopulateglobalstatewiththetransactionsfromtheAPIresponse:
populateTransactions(transactions){
constsortedTransactions=_.sortBy(transactions,[
'transactionDate',
'id'
]);
this.state.transactions=sortedTransactions;
store.wasm.populateInWasm(sortedTransactions,this.getCategories());
this.recalculateBalances();
},
addTransaction(newTransaction){
//WeneedtoassignanewIDtothetransaction,sothisjustadds
//1tothecurrentmaximumtransactionID:
newTransaction.id=_.maxBy(this.state.transactions,'id').id+1;
store.wasm.addToWasm(newTransaction);
apiAddTransaction(newTransaction).then(()=>{
this.state.transactions.push(newTransaction);
this.hideTransactionModal();
});
},
editTransaction(editedTransaction){
store.wasm.editInWasm(editedTransaction);
apiEditTransaction(editedTransaction).then(()=>{
this.state.transactions=this.state.transactions.map(
transaction=>{
if(transaction.id===editedTransaction.id){
returneditedTransaction;
}
returntransaction;
}
);
this.hideTransactionModal();
});
},
removeTransaction(transaction){
consttransactionId=transaction.id;
store.wasm.removeFromWasm(transactionId);
//We'repassingthewholetransactionrecordintotheAPIcall
//forthesakeofconsistency:
apiRemoveTransaction(transaction).then(()=>{
this.state.transactions=this.state.transactions.filter(
({id})=>id!==transactionId
);
this.hideTransactionModal();
});
},
...
ThepopulateTransactions()functionfetchesallofthetransactionsfromtheAPIandloadsthemintotheglobalstateandtheWasminstance.ThecategorynamesareextrapolatedfromthetransactionsarrayinthegetCategories()function.TheresultsarepassedtotheWasmTransactionsinstancewhenstore.wasm.populateInWasm()iscalled.
TheaddTransaction(),editTransaction(),andremoveTransaction()functionsperformtheactionsthatcorrespondwiththeirnames.AllthreefunctionsmanipulatetheWasminstanceandupdatethedataontheAPIthroughafetchcall.Eachofthefunctionscallthis.hideTransactionModal()becausechangestoatransactioncanonlybemadethroughtheTransactionModalcomponent.Oncethechangeissuccessfullymade,themodalshouldclose.Let'slookattheTransactionModalmanagementcodenext.
...<br/>showTransactionModal(transactionId){<br/>this.state.activeTransactionId=transactionId||0;<br/>consttransactModal=document.querySelector('#transactionModal');<br/>UIkit.modal(transactModal).show();<br/>},<br/><br/>hideTransactionModal(){<br/>this.state.activeTransactionId=0;<br/>consttransactModal=document.querySelector('#transactionModal');<br/>UIkit.modal(transactModal).hide();<br/>},<br/><br/>getActiveTransaction(){<br/>const{transactions,activeTransactionId}=this.state;<br/>constfoundTransaction=transactions.find(transaction=><br/>transaction.id===activeTransactionId);<br/>returnfoundTransaction||{id:0};<br/>},<br/>...
TheshowTransactionModal()andhideTransactionModal()functionsshouldbeself-explanatory.Thehide()orshow()methodofUIkit.modal()iscalledontheDOMelementrepresentingtheTransactionModal.ThegetActiveTransaction()functionreturnsthetransactionrecordassociatedwiththeactiveTransactionIdvalueinglobalstate.
...<br/>updateInitialBalance(amount,fieldName){<br/>this.state.balances[fieldName]=amount;<br/>},<br/><br/>//Updatethe"balances"objectinglobalstatebasedonthecurrent<br/>//initialbalances:<br/>recalculateBalances(){<br/>const{initialRaw,initialCooked}=this.state.balances;<br/>const{currentRaw,currentCooked}=this.wasm.getCurrentBalances(<br/>initialRaw,<br/>initialCooked<br/>);<br/>this.state.balances={<br/>initialRaw,<br/>currentRaw,<br/>initialCooked,<br/>currentCooked<br/>};<br/>}<br/>};
TheupdateInitialBalance()functionsetsthepropertyvalueinthebalancesobjectinglobalstatebasedontheamountandfieldNamearguments.TherecalculateBalances()functionupdatesallofthefieldsonthebalancesobjecttoreflectanychangesmadetotheinitialbalancesortransactions.
StoreinitializationThefinalsectionofcodeinthefileinitializesthestore:
/**
*ThisfunctioninstantiatestheWasmmodule,fetchesthetransactions
*fromtheAPIendpoint,andloadsthemintostateandtheWasm
*instance.
*/
exportconstinitializeStore=async()=>{
constwasmTransactions=newWasmTransactions();
store.wasm=awaitwasmTransactions.initialize();
consttransactions=awaitapiFetchTransactions();
store.populateTransactions(transactions);
};
TheinitializeStore()functioninstantiatestheWasmmodule,fetchesalltransactionsfromtheAPI,andpopulatesthecontentsofstate.Thisfunctioniscalledfromtheapplicationloadingcodein/src/main.js,whichwe'llcoverinthenextsection.
Loadingtheapplicationinmain.jsWeneedanentrypointtoloadourapplication.Createafileinthe/srcfoldernamedmain.jsandpopulateitwiththefollowingcontents:
importAppfrom'./components/App.js';
import{store,initializeStore}from'./store/store.js';
//Thisallowsustousethe<vue-numeric>componentglobally:
Vue.use(VueNumeric.default);
//Createagloballyaccessiblestore(withouthavingtopassitdown
//asprops):
window.$store=store;
//SincewecanonlypassnumbersintoaWasmfunction,theseflags
//representtheamounttypewe'retryingtocalculate:
window.AMOUNT_TYPE={
raw:1,
cooked:2
};
//AfterfetchingthetransactionsandinitializingtheWasmmodule,
//rendertheapp.
initializeStore()
.then(()=>{
newVue({render:h=>h(App),el:'#app'});
})
.catch(err=>{
console.error(err);
});
ThisfileisloadedafterthelibrariesarefetchedandloadedfromCDNsin/src/index.html.WeusetheglobalVueobjecttospecifythatwewanttousetheVueNumericcomponent.Weaddthestoreobjectexportedfrom/store/store.jstowindowas$store.Thisisn'tthemostrobustsolution,butwillbesufficientgiventhescopeoftheapplication.Ifyouwerecreatingaproductionapplication,you'dusealibrarylikeVuexorReduxforglobalstatemanagement.We'llforegothisapproachintheinterestofkeepingthingssimple.
WealsoaddedAMOUNT_TYPEtothewindowobject.ThiswasdonetoensuretheentireapplicationcanreferencetheAMOUNT_TYPEvalue,ratherthanspecifyamagicnumber.Aftervaluesareassignedtowindow,theinitializeStore()functioniscalled.IftheinitializeStore()functionfiredsuccessfully,anewVueinstanceiscreatedtorendertheapplication.Let'saddthewebassetsnext,thenmoveontotheVuecomponents.
AddingthewebassetsBeforewestartaddingVuecomponentstotheapplication,let'screatetheHTMLandCSSfilesthathouseourmarkupandstyles.Createafileinthe/srcfoldernamedindex.htmlandpopulateitwiththefollowingcontents:<!doctypehtml><htmllang="en-us"><head><title>CooktheBooks</title><linkrel="stylesheet"type="text/css"href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-rc.6/css/uikit.min.css"/><linkrel="stylesheet"type="text/css"href="styles.css"/><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-rc.6/js/uikit.min.js"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-rc.6/js/uikit-icons.min.js"></script><scriptsrc="https://unpkg.com/[email protected]/dist/accounting.umd.js"></script><scriptsrc="https://unpkg.com/[email protected]/lodash.min.js"></script><scriptsrc="https://unpkg.com/[email protected]/dist/d3.min.js"></script><scriptsrc="https://unpkg.com/[email protected]/dist/vue.min.js"></script><scriptsrc="https://unpkg.com/[email protected]/dist/vue-numeric.min.js"></script><scriptsrc="main.js"type="module"></script></head><body><divid="app"></div></body></html>
We'reonlyusingtheHTMLfiletofetchlibrariesfromCDNs,specifya<div>thatVuecanrenderto,andloadmain.jstostarttheapplication.Notethetype="module"attributeonthefinal<script>element.ThisallowsustouseESmodules
throughoutourapplication.Nowlet'saddtheCSSfile.Createafileinthe/srcfoldernamedstyles.cssandpopulateitwiththefollowingcontents:@importurl("https://fonts.googleapis.com/css?family=Quicksand");
:root{--blue:#2889ed;}
*{font-family:"Quicksand",Helvetica,Arial,sans-serif!important;}
#app{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}
.addTransactionButton{color:white;height:64px;width:64px;background:var(--blue);position:fixed;bottom:24px;right:24px;}
.addTransactionButton:hover{color:white;background-color:var(--blue);opacity:.6;}
.errorText{color:white;font-size:36px;}
.appHeader{height:80px;margin:0;}
.balanceEntry{font-size:2rem;}
.tableAmount{white-space:pre;}
Thisfilehasonlyafewclassesbecausemostofthestylingwillbehandledatthecomponentlevel.Inthenextsection,we'llreviewtheVuecomponentsthatmakeupourapplication.
CreatingtheVuecomponentsWithVue,wecancreateseparatecomponentsthatencapsulatetheirownfunctionality,thencomposethesecomponentstobuildanapplication.Thismakesdebugging,extensibility,andchangemanagementmucheasierthanstoringtheapplicationinasinglemonolithicfile.
Theapplicationusesasingle-component-per-filedevelopmentmethodology.Beforewestartreviewingthecomponentfiles,let'slookatthefinishedproduct.ThefollowingscreenshotisoftheapplicationwiththeTRANSACTIONStabselected:
RunningtheapplicationwithTRANSACTIONStabvisible
Here'sascreenshotoftheapplicationwiththeCHARTStabselected:
importSomeComponentfrom'./SomeComponent.js';<br/><br/>exportdefault{<br/>name:'dummy-component',<br/><br/>//Propspassedfromothercomponents:<br/>props:{<br/>label:String,<br/>},<br/><br/>//OtherVuecomponentstorenderwithinthetemplate:<br/>components:{<br/>SomeComponent<br/>},<br/><br/>//Usedtostorelocaldata/state:<br/>data(){<br/>return{<br/>amount:0<br/>}<br/>},<br/><br/>//Usedtostorecomplexlogicthatoutsideofthe`template`:<br/>computed:{<br/>negativeClass(){<br/>return{<br/>'negative':this.amount<0<br/>};<br/>}<br/>},<br/><br/>//Methodsthatcanbeperformedwithinthecomponent:<br/>methods:{<br/>addOne(){<br/>this.amount+=1;<br/>}<br/>},<br/><br/>//Performactionsifthelocaldatachanges:<br/>watch:{<br/>amount(val,oldVal){<br/>console.log(`New:${val}|Old:${oldVal}`);<br/>}<br/>},<br/><br/>//ContainstheHTMLtorenderthecomponent:<br/>template:`<br/><div><br/><some-component></some-component><br/><labelfor="someAmount">{{label}}</label><br/><input<br/>id="someAmount"<br/>:class="negativeClass"<br/>v-model="amount"<br/>type="number"<br/>/><br/><button@click="addOne">AddOne</button><br/></div><br/>`<br/>};
Thecommentsaboveeachpropertydescribeitspurpose,albeitataveryhighlevel.Let'sseeVueinactionbyreviewingtheAppcomponent.
TheAppcomponentTheAppcomponentisthebasecomponentthatrendersallofthechildcomponentsintheapplication.We'llbrieflyreviewtheAppcomponent'scodetogainabetterunderstandingofVue.Goingforward,we'lldescribetheroleeachremainingcomponentplays,butonlyreviewsectionsofthecorrespondingcode.ThecontentsoftheAppcomponentfile,locatedat/src/components/App.js,areshownasfollows:
importBalancesBarfrom'./BalancesBar/BalancesBar.js';
importChartsTabfrom'./ChartsTab/ChartsTab.js';
importTransactionsTabfrom'./TransactionsTab/TransactionsTab.js';
/**
*Thiscomponentistheentrypointfortheapplication.Itcontainsthe
*header,tabs,andcontent.
*/
exportdefault{
name:'app',
components:{
BalancesBar,
ChartsTab,
TransactionsTab
},
data(){
return{
balances:$store.state.balances,
activeTab:0
};
},
methods:{
//Anytimeatransactionisadded,edited,orremoved,weneedto
//ensurethebalanceisupdated:
onTransactionChange(){
$store.recalculateBalances();
this.balances=$store.state.balances;
},
//Whenthe"Charts"tabisactivated,thisensuresthatthecharts
//getautomaticallyupdated:
onTabClick(event){
this.activeTab=+event.target.dataset.tab;
}
},
template:`
<div>
<divclass="appHeaderuk-background-primaryuk-flexuk-flex-middle">
<h2class="uk-lightuk-margin-remove-bottomuk-margin-left">
CooktheBooks
</h2>
</div>
<divclass="uk-position-relative">
<uluk-tabclass="uk-margin-small-bottomuk-margin-top">
<liclass="uk-margin-small-left">
<ahref="#"data-tab="0"@click="onTabClick">Transactions</a>
</li>
<li>
<ahref="#"data-tab="1"@click="onTabClick">Charts</a>
</li>
</ul>
<balances-bar
:balances="balances"
:onTransactionChange="onTransactionChange">
</balances-bar>
<ulclass="uk-switcher">
<li>
<transactions-tab:onTransactionChange="onTransactionChange">
</transactions-tab>
</li>
<li>
<charts-tab:isActive="this.activeTab===1"></charts-tab>
</li>
</ul>
</div>
</div>
`
};
WeusethecomponentspropertytospecifytheotherVuecomponentswe'llrenderinthetemplatefortheAppcomponent.Thedata()function,whichreturnsthelocalstate,isusedtokeeptrackofbalancesandwhichtabisactive(TRANSACTIONSorCHARTS).Themethodspropertycontainstwofunctions:onTransactionChange()andonTabClick().TheonTransactionChange()functioncalls$store.recalculateBalances()andupdatesbalancesinlocalstateifachangeismadetoatransactionrecord.TheonTabClick()functionchangesthevalueofactiveTabinthelocalstatetothedata-tabattributeoftheclickedtab.Finally,thetemplatepropertycontainsthemarkupusedtorenderthecomponent.
Ifyou'renotusingsinglefilecomponentsinVue(.vueextension),youneedtoconvertthecomponentnametokebabcaseinthetemplateproperty.Forexample,intheAppcomponentshownearlier,BalancesBarwaschangedto<balances-bar>inthetemplate.
TheBalancesBarThe/components/BalancesBarfoldercontainstwocomponentfiles:BalanceCard.jsandBalancesBar.js.TheBalancesBarcomponentpersistsacrosstheTRANSACTIONSandCHARTStabsandislocateddirectlyunderthetabcontrol.ItcontainsfouroftheBalanceCardcomponents,oneforeachbalancetype:initialraw,currentraw,initialcooked,andcurrentcooked.Thefirstandthirdcardsrepresentingtheinitialbalancescontaininputssothebalancecanbechanged.ThesecondandfourthcardsrepresentingthecurrentbalancesarecalculateddynamicallyintheWasmmodule(usingthegetFinalBalanceForType()function).Thefollowingsnippet,takenfromtheBalancesBarcomponent,demonstratesVue'sbindingsyntax:
<balance-card
title="InitialRawBalance"
:value="balances.initialRaw"
:onChange="amount=>onBalanceChange(amount,'initialRaw')">
</balance-card>
The:precedingthevalueandonChangeattributesindicatethatthesepropertiesareboundtotheVuecomponent.Ifthevalueofbalances.initialRawchanges,thevaluedisplayedintheBalanceCardwillupdateaswell.TheonBalanceChange()functionforthiscardupdatesthevalueofbalances.initialRawinglobalstate.
getFormattedTransactions(){<br/>constgetDisplayAmount=(type,amount)=>{<br/>if(amount===0)returnaccounting.formatMoney(amount);<br/>returnaccounting.formatMoney(amount,{<br/>format:{pos:'%s%v',neg:'%s(%v)'}<br/>});<br/>};<br/><br/>constgetDisplayDate=transactionDate=>{<br/>if(!transactionDate)return'';<br/>constparsedTime=d3.timeParse('%Y-%m-%d')(transactionDate);<br/>returnd3.timeFormat('%m/%d/%Y')(parsedTime);<br/>};<br/><br/>return$store.state.transactions.map(<br/>({<br/>type,<br/>rawAmount,<br/>cookedAmount,<br/>transactionDate,<br/>...transaction<br/>})=>({<br/>...transaction,<br/>type,<br/>rawAmount:getDisplayAmount(type,rawAmount),<br/>cookedAmount:getDisplayAmount(type,cookedAmount),<br/>transactionDate:getDisplayDate(transactionDate)<br/>})<br/>);<br/>}
TheprecedinggetFormattedTransactions()functionshownappliesformattingtotherawAmount,cookedAmount,andtransactionDatefieldswithineachtransactionrecord.Thisisdonetoensurethevaluebeingdisplayedincludesadollarsign(foramounts)andispresentedinauser-friendlyformat.
TheChartsTabThe/components/ChartsTabfoldercontainstwocomponentfiles:ChartsTab.jsandPieChart.js.TheChartsTabcomponentcontainstwoinstancesofthePieChartcomponent,oneforincomeandoneforexpenses.EachPieChartcomponentdisplayseithertheraworcookedpercentagesbycategory.Theusercanswitchbetweenraworcookedviewsviabuttonsdirectlyabovethechart.ThedrawChart()methodinPieChart.jsusesD3torenderthepiechartandlegend.ItusesD3'sbuilt-inanimationstoanimateeachpieceofthepiewhenloading:arc.append('path').attr('fill',d=>colorScale(d.data.category)).transition().delay((d,i)=>i*100).duration(500).attrTween('d',d=>{consti=d3.interpolate(d.startAngle+0.1,d.endAngle);returnt=>{d.endAngle=i(t);returnarcPath(d);};});
Theprecedingsnippet,takenfromdrawChart()inPieChart.js,definestheanimationforthepiepieceinonlyafewlinesofcode.Ifyou'reinterestedinlearningmoreaboutD3'scapabilities,checkoutsometheexamplesathttps://bl.ocks.org.That'sitforthecomponentsreview;let'stryrunningtheapplication.
RunningtheapplicationYou'vewrittenandcompiledtheCcodeandaddedthefrontendlogic.It'stimetostarttheapplicationandinteractwithit.Inthissection,wewillvalidateyourapplication's/srcfolder,runtheapplication,andtestoutthefeaturestoensureeverythingisworkingcorrectly.
├──/assets<br/>│├──main.wasm<br/>│└──memory.wasm<br/>├──/components<br/>│├──/BalancesBar<br/>││├──BalanceCard.js<br/>││└──BalancesBar.js<br/>│├──/ChartsTab<br/>││├──ChartsTab.js<br/>││└──PieChart.js<br/>│├──/TransactionsTab<br/>││├──ConfirmationModal.js<br/>│|├──TransactionModal.js<br/>│|├──TransactionsTab.js<br/>│|└──TransactionsTable.js<br/>│└──App.js<br/>├──/store<br/>│├──api.js<br/>│├──initializeWasm.js<br/>│├──store.js<br/>│└──WasmTransactions.js<br/>├──index.html<br/>├──main.js<br/>└──styles.css
Ifeverythingmatchesup,you'rereadytoproceed.
Startitup!Tostarttheapplication,openupaterminalinthe/cook-the-booksfolderandrunthefollowingcommand:
npmstart
browser-syncthedevelopmentdependencyweinstalledinthefirstsectionofthischapter,actsasalocalserver(liketheservelibrary).Itmakestheapplicationaccessibleinthebrowserfromtheportspecifiedinthepackage.jsonfile(inthiscase,4000).Ifyounavigatetohttp://localhost:4000/index.htmlinyourbrowser,youshouldseethis:
ApplicationoninitialloadWe'reusingbrowser-syncinsteadofservebecauseitwatchesforchangesinyourfilesandautomaticallyreloadstheapplicationifyoumakeachange.Toseethisinaction,trychangingthecontentsofthetitlebarinApp.jsfromCooktheBookstoBroiltheBooks.Thebrowserwillrefreshandyou'llseetheupdatedtextinthetitlebar.
TestingitoutToensureeverythingisworkingcorrectly,let'stestouttheapplication.Eachofthefollowingsectionsdescribesanactionandexpectedbehaviorforaparticularfunctionoftheapplication.Followalongtoseeifyou'regettingtheexpectedresults.Ifyourunintoanissue,youcanalwaysreferbacktothe/chapter-07-cook-the-booksfolderinthelearn-webassemblyrepository.
ChanginginitialbalancesTrychangingtheinputvaluesontheINITIALRAWBALANCEandINITIALCOOKEDBALANCEBalanceCardcomponents.TheCURRENTRAWBALANCEandCURRENTCOOKEDBALANCEcardvaluesshouldupdatetoreflectyourchanges.
CreatinganewtransactionMakeanoteofthecurrentrawandcookedbalances,thenpresstheblueAddbuttonatthebottom-rightcornerofthewindow.ItshouldloadtheTransactionModalcomponent.Populatetheinputs,makeanoteoftheType,RawAmount,andCookedAmountyouentered,thenpresstheSavebutton.
Thebalancesshouldhaveupdatedtoreflectthenewamounts.IfyoupickedWithdrawalfortheType,thebalancesshoulddecrease,otherwise,theyincrease(forDeposit)asshowninthefollowingscreenshot:
TransactionModalwhenaddinganewtransaction
DeletinganexistingtransactionPickarowwithintheTransactionsTablecomponent,notetheamounts,andpressthebuttonthatlookslikeatrashcanforthatrecord.TheConfirmationModalcomponentshouldappear.WhenyoupresstheYesbutton,thetransactionrecordshouldnolongerbepresentinthetableandthecurrentbalancesshouldupdatetoreflecttheamountsassociatedwiththedeletedtransactionasshowninthefollowingscreenshot:
Confirmationmodalshownafterdeletebuttonispressed
EditinganexistingtransactionFollowthesameprocedureasyoudidforcreatinganewtransaction,exceptchangetheexistingamounts.Checkthecurrentbalancestoensuretheyreflecttheupdatedtransactionamounts.
TestingtheChartstabSelecttheChartstabtoloadtheChartsTabcomponent.PressthebuttonsineachPieChartcomponenttoswitchbetweentherawandcookedviews.Thepiechartsshouldre-renderwiththeupdatedvalues:
WrapupCongratulations,youjustbuiltanapplicationthatusesWebAssembly!Tellyourfriends!NowthatyouunderstandthecapabilitiesandlimitationsofWebAssembly,it'stimetoexpandourhorizonsandusesomeoftheexcellentfeaturesEmscriptenprovides.
SummaryInthischapter,webuiltanaccountingapplicationfromscratchthatusesWebAssemblywithoutanyoftheextrafeaturesEmscriptenprovides.ByadheringtotheCoreSpecification,wedemonstratedthelimitationsofWebAssemblyinitscurrentform.However,wewereabletoperformcomputationquicklythroughtheuseofWasmmodules,whichiswellsuitedforaccounting.WeusedVuetosplitourapplicationintocomponents,UIkitforthedesignandlayout,andD3tocreatepiechartsfromourtransactionsdata.InChapter8,PortingaGamewithEmscripten,we'lltakefulladvantageofEmscriptentoportanexistingC++codebasetoWebAssembly.
Questions1. WhydidweuseVueforthisapplication(insteadofReactorAngular)?2. WhydidweuseCinsteadofC++forthisproject?3. WhydidweneedtosetupamockAPIusingjsonstore.ioinsteadofstoring
thedatalocallyinaJSONfile?4. Whatisthenameofthedatastructureweusedformanagingtransactionsin
theCfile?5. Whichfunctionsdidweneedfromthememory.wasmfileandwhatarethey
usedfor?6. WhydidwecreateawrapperclassaroundtheWasmmodule?7. Whydidwemakethe$storeobjectglobal?8. Whichlibrariescouldyouuseinaproductionapplicationformanaging
globalstate?9. Whyareweusingbrowser-sync,insteadofserve,toruntheapplication?
PortingaGamewithEmscriptenAsdemonstratedinChapter7,CreatinganApplicationfromScratch,WebAssemblyisstillrelativelylimitedinitscurrentform.EmscriptenprovidespowerfulAPIsforextendingWebAssembly'scapabilitiestoaddfunctionalitytoyourapplication.CompilingtoaWebAssemblymoduleandJavaScriptgluecode(insteadofanexecutable)can,insomecases,onlyrequireminorchangestotheexistingCorC++source.
Inthischapter,we'regoingtotakeacodebasewritteninC++thatgetscompiledtoatraditionalexecutable,andupdatethecodesothatitcanbecompiledtoWasm/JavaScript.We'llalsoaddsomeadditionalfeaturesfortighterintegrationwiththebrowser.
Bytheendofthischapter,you'llknowhowtodothefollowing:
UpdateaC++codebasetocompiletoaWasmmodule/JavaScriptgluecode(insteadofanativeexecutable)UseEmscripten'sAPIstoaddbrowserintegrationtoaC++applicationBuildamulti-fileC++projectwiththeproperemccflagsRunandtestaC++applicationinthebrowserusingemrun
OverviewofthegameInthischapter,we'retakingaTetrisclonewritteninC++andupdatingthecodetointegrateEmscriptenandcompiletoWasm/JS.ThecodebaseinitsoriginalformcompiledtoanexecutableutilizesSDL2andcanbeloadedfromthecommandline.Inthissection,we'regoingtobrieflyreviewwhatTetrisis,howtogetthecode(withouthavingtowriteitfromscratch),andhowtogetitrunning.
WhatisTetris?InTetris,themainobjectiveofthegameistorotateandmovepieces(Tetriminos)ofvariousshapeswithinaplayingfield(wellormatrix)tocreatearowofblockswithoutgaps.Whenafullrowiscreated,itisdeletedfromtheplayingfieldandyourscoreisincreasedbyone.Inourversionofthegame,therewon'tbeawincondition(althoughitwouldbesimpletoaddit).
It'simportanttounderstandtherulesandmechanicsofthegamebecausethecodeusesalgorithmsforconceptssuchascollisiondetectionandscoring.Understandingthegoalofafunctionhelpsyouunderstandthecodewithin.IrecommendyougiveitatryonlineifyouneedtobrushuponyourTetrisskills.Youcanplayitathttps://emulatoronline.com/nes-games/classic-tetris/withouthavingtoinstallAdobeFlash.ItlooksjustliketheoriginalNintendoVersion:
ClassicTetrisatEmulatorOnline.com
Theversionwe'llbeworkingwithwon'tcontainthepiececounters,levels,orpoints(we'restickingtolinecounts),butitwilloperateinthesameway.
ThesourceofthesourceItturnsoutthatasearchforTetrisC++providesamultitudeoftutorialsandexamplerepositoriestochoosefrom.IntheinterestofstickingtotheformattingandnamingconventionsthatI'vebeenusinguptothispoint,Icombinedtheseresourcestocreatemyownversionofthegame.TheFurtherreadingsectionattheendofthischapterhaslinkstotheseresourcesifyou'reinterestedinlearningmore.Theconceptsandprocessforportingacodebaseareapplicable,regardlessofthesource.Onthatnote,let'stakeabriefstep-asidetodiscussportingingeneral.
AnoteaboutportingPortinganexistingcodebasetoEmscriptenisnotalwaysasimpletask.ThereareseveralvariablestotakeintoaccountwhenevaluatingwhetheraC,C++,orRustapplicationisamenabletoconversion.Forexample,gamesthatmakeuseofseveralthird-partylibrariesorevenafewthird-partylibrariesthatareofconsiderablecomplexitymayrequireasignificantamountofeffort.Emscriptenprovidesthefollowingcommonlyusedlibrariesoutofthebox:
asio:Anetworkandlow-levelI/OprogramminglibraryBullet:Areal-timecollisiondetectionandmulti-physicssimulationlibraryCocos2d:Asuiteofopensource,cross-platform,gamedevelopmenttoolsFreeType:AlibraryusedtorenderfontsHarfBuzz:AnOpenTypetextshapingenginelibpng:TheofficialPNGreferencelibraryOgg:AmultimediacontainerformatSDL2:Alibrarydesignedtoprovidelow-levelaccesstoaudio,akeyboard,amouse,ajoystick,andgraphicshardwareSDL2_image:AnimagefileloadinglibrarySDL2_mixer:Asamplemulti-channelaudiomixerlibrarySDL2_net:Asmallsamplecross-platformnetworkinglibrarySDL2_ttf:AsamplelibrarythatallowsyoutouseTrueTypefontsinyourSDLapplicationsVorbis:Ageneralpurposeaudioandmusicencodingformatzlib:Alosslessdatacompressionlibrary
Ifthelibraryisn'talreadyported,youwillneedtodoityourself.Thiswouldbenefitthecommunity,butrequiresasignificantinvestmentoftimeandresources.OurTetrisexampleonlyusesSDL2,whichmakestheportingprocessrelativelysimple.
GettingthecodeThecodeforthischapterislocatedinthe/chapter-08-tetrisfolderofthelearn-webassemblyrepository.Therearetwodirectorieswithin/chapter-08-tetris:the/output-nativefolder,whichcontainstheoriginal(pre-ported)codeandthe/output-wasmfolder,whichcontainstheportedcode.
IfyouwanttouseVSCode'sTaskfeatureforthenativebuildstep,you'llneedtoopenthe/chapter-08-tetris/output-nativefolderinVSCode,notthetop-level/learn-webassemblyfolder.
BuildingthenativeprojectThe/cmakefolderandCMakeLists.txtfilewithinthe/output-nativefolderarerequiredtobuildtheproject.TheREADME.mdfilecontainsinstructionstogetthecodeupandrunningoneachplatform.Buildingtheprojectisn'tnecessarytoworkthroughtheportingprocess.Theprocessforinstallingtherequireddependenciesandgettingtheprojecttobuildsuccessfullyonyourplatformcanbetime-consumingandcomplex.Ifyoustillwishtoproceed,youcanbuildtheexecutablethroughVSCode'sTaskfeaturebyselectingTasks|RunTask...fromthemenuandselectingBuildExecutablefromthelistafterfollowingtheinstructionsintheREADME.mdfile.
ThegameinactionIfyouweresuccessfulinbuildingtheproject,youshouldbeabletorunitbyselectingTasks|RunTask...fromtheVSCodemenuandselectingtheStartExecutabletaskfromthelist.Ifeverythingwassuccessful,youshouldsee
somethinglikethis:
Compiledgamerunningnatively
Ourversionofthegamedoesn'thavealosingcondition;itjustincrementstheROWScountbyoneforeachrowyouclear.IfoneoftheTetriminostouchesthetopoftheboard,thegameisoverandtheboardresets.It'sarudimentaryimplementationofthegame,butadditionalfeaturesincreasethecomplexityandamountofcoderequired.Let'sreviewthecodebaseinmoredetail.
ThecodebaseindepthNowthatyouhavethecodeavailable,you'llneedtofamiliarizeyourselfwiththecodebase.Withouthavingagoodunderstandingofthecodeyouwanttoport,you'llhaveamuchhardertimeportingitsuccessfully.Inthischapter,we'regoingtowalkthrougheachoftheC++classandheaderfilesanddescribetheirrolesintheapplication.
BreakingthecodeintoobjectsC++wasdesignedaroundanobject-orientedparadigm,whichiswhattheTetriscodebaseusestosimplifymanagementoftheapplication.ThecodebaseconsistsofC++classfiles
(.cpp)andheaderfiles(.h)thatrepresentobjectswithinthecontextofthegame.IusedthegameplaysummaryfromtheWhatisTetris?sectiontoextrapolatewhichobjectsIneeded.
Thegamepieces(Tetriminos)andplayingfield(referredtoasawellormatrix)aregoodcandidatesforclasses.Maybelessintuitively,butstilljustasvalid,isthegameitself.Classesdon'tnecessarilyneedtobeasconcreteasactualobjects—they'reexcellentforstoringsharedcode.I'mabigfanoflesstyping,soIoptedtousePiecetorepresentaTetriminoandBoardfortheplayingfield(althoughthewordwellisshorter,itjustdoesn'tquitefit).Icreatedaheaderfiletostoreglobalvariables(constants.h),aGameclasstomanagegameplay,andamain.cppfile,whichactsastheentrypointforthegame.Here'sthecontentsofthe/srcfolder:
├──board.cpp
├──board.h
├──constants.h
├──game.cpp
├──game.h
├──main.cpp
├──piece.cpp
└──piece.h
Eachfile(withtheexceptionofmain.cppandconstants.h)hasaclass(.cpp)andheader(.h)file.Headerfilesallowyoutoreusecodeacrossmultiplefilesandpreventcodeduplication.TheFurtherreadingsectioncontainsresourcesforyoutolearnmoreaboutheaderfilesifyou'reinterested.Theconstants.hfileisusedinalmostalloftheotherfileswithintheapplication,solet'sreviewthatfirst.
TheconstantsfileRatherthanhaveconfusingmagicnumberssprinkledthroughoutthecodebase,Ioptedforaheaderfilecontainingtheconstantswe'llbeusing(constants.h).Thecontentsofthisfileareshownhere:#ifndefTETRIS_CONSTANTS_H#defineTETRIS_CONSTANTS_H
namespaceConstants{constintBoardColumns=10;constintBoardHeight=720;constintBoardRows=20;constintBoardWidth=360;constintOffset=BoardWidth/BoardColumns;constintPieceSize=4;constintScreenHeight=BoardHeight+50;}
#endif//TETRIS_CONSTANTS_H
The#ifndefstatementinthefirstlineofthefileisan#includeguard,whichpreventstheheaderfilefrombeingincludedmultipletimesduringcompilation.Theseguardsareusedinalloftheapplication'sheaderfiles.Thepurposeofeachoftheseconstantswillbecomeclearwhenwestepthrougheachoftheclasses.Iincludeditfirsttoprovidecontextaroundthevariouselementsizesandhowtheyrelatetoeachother.
Let'smoveontothevariousclassesthatrepresentaspectsofthegame.ThePiececlassrepresentsanobjectatthelowestlevel,sowe'llstartthereandworkourwayuptotheBoardandGameclasses.
ThepiececlassThepiece,orTetrimino,istheelementthatcanbemovedandrotatedontheboard.TherearesevenkindsofTetriminos—eachisrepresentedbyaletterandhasacorrespondingcolor:
Tetriminocolors,takenfromWikipedia
Weneedawaytodefineeachpieceintermsofshape,color,andcurrentorientation.Eachpiecehasfourdifferentorientations(at90degreeincrements),whichresultsin28totalvariationsforallpieces.Thecolordoesn'tchange,sothatonlyneedstobeassignedonce.Withthatinmind,let'sfirsttakealookattheheaderfile(piece.h):
#ifndefTETRIS_PIECE_H
#defineTETRIS_PIECE_H
#include<SDL2/SDL.h>
#include"constants.h"
classPiece{
public:
enumKind{I=0,J,L,O,S,T,Z};
explicitPiece(Kindkind);
voiddraw(SDL_Renderer*renderer);
voidmove(intcolumnDelta,introwDelta);
voidrotate();
boolisBlock(intcolumn,introw)const;
intgetColumn()const;
intgetRow()const;
private:
Kindkind_;
intcolumn_;
introw_;
intangle_;
};
#endif//TETRIS_PIECE_H
ThegameusesSDL2torenderthevariousgraphicalelementsandhandlekeyboardinput,whichiswhywe'repassingaSDL_Rendererintothedraw()function.You'llseehowSDL2isusedintheGameclass,butfornowjustbeawareofitsinclusion.TheheaderfiledefinestheinterfaceforthePiececlass;let'sreviewtheimplementationinpiece.cpp.We'llwalkthrougheachsectionofcodeanddescribethefunctionality.
Theconstructoranddraw()functionThefirstsectionofcodedefinestheconstructorofthePiececlassandthedraw()function:
#include"piece.h"
usingnamespaceConstants;
Piece::Piece(Piece::Kindkind):
kind_(kind),
column_(BoardColumns/2-PieceSize/2),
row_(0),
angle_(0){
}
voidPiece::draw(SDL_Renderer*renderer){
switch(kind_){
caseI:
SDL_SetRenderDrawColor(renderer,
/*Cyan:*/45,254,254,255);
break;
caseJ:
SDL_SetRenderDrawColor(renderer,
/*Blue:*/11,36,251,255);
break;
caseL:
SDL_SetRenderDrawColor(renderer,
/*Orange:*/253,164,41,255);
break;
caseO:
SDL_SetRenderDrawColor(renderer,
/*Yellow:*/255,253,56,255);
break;
caseS:
SDL_SetRenderDrawColor(renderer,
/*Green:*/41,253,47,255);
break;
caseT:
SDL_SetRenderDrawColor(renderer,
/*Purple:*/126,15,126,255);
break;
caseZ:
SDL_SetRenderDrawColor(renderer,
/*Red:*/252,13,28,255);
break;
}
for(intcolumn=0;column<PieceSize;++column){
for(introw=0;row<PieceSize;++row){
if(isBlock(column,row)){
SDL_Rectrect{
(column+column_)*Offset+1,
(row+row_)*Offset+1,
Offset-2,
Offset-2
};
SDL_RenderFillRect(renderer,&rect);
}
}
}
}
Theconstructorinitializestheclasswithdefaultvalues.TheBoardColumnsandPieceSizevaluesareconstantsfromtheconstants.hfile.BoardColumnsrepresentstheamountofcolumnsthatcanfitonaboard,whichis10inthiscase.ThePieceSizeconstantrepresentstheareaorblockthatapiecetakesupincolumns,whichis4.Theinitialvalueassignedtotheprivatecolumns_variablerepresentsthecenteroftheboard.
Thedraw()functionloopsthroughallofthepossiblerowsandcolumnsontheboardandfillsinanycellsthatarepopulatedbyapiecewiththecolorthatcorrespondstoitskind.ThedeterminationforwhetheracellispopulatedbyapieceisperformedintheisBlock()function,whichwe'lldiscussnext.
Themove(),rotate(),andisBlock()functionsThesecondsectioncontainsthelogictomoveorrotatethepieceanddetermineitscurrentlocation:
voidPiece::move(intcolumnDelta,introwDelta){
column_+=columnDelta;
row_+=rowDelta;
}
voidPiece::rotate(){
angle_+=3;
angle_%=4;
}
boolPiece::isBlock(intcolumn,introw)const{
staticconstchar*Shapes[][4]={
//I
{
"*"
"*"
"*"
"*",
""
"****"
""
"",
"*"
"*"
"*"
"*",
""
"****"
""
"",
},
//J
{
"*"
"*"
"**"
"",
""
"*"
"***"
"",
"**"
"*"
"*"
"",
""
""
"***"
"*",
},
...
};
returnShapes[kind_][angle_][column+row*PieceSize]=='*';
}
intPiece::getColumn()const{
returncolumn_;
}
intPiece::getRow()const{
returnrow_;
}
Themove()functionupdatesthevaluesoftheprivatecolumn_androw_variables,whichdictatesthepiece'slocationontheboard.Therotate()functionsetsthevalueoftheprivateangle_variabletoeither0,1,2,or3(whichiswhy%=4isused).
Determinationforwhichkindofpieceisshown,itslocation,androtationisperformedintheisBlock()function.IomittedallbutthefirsttwoelementsoftheShapesmulti-dimensionalarraytoavoidclutteringupthefile,buttheremainingfivepiecekindsarepresentintheactualcode.Iwilladmitthatthisisn'tthemostelegantimplementation,butitsuitsourpurposesjustfine.
Theprivatekind_andangle_valuesarespecifiedasdimensionsintheShapesarraytopickthefourcorrespondingchar*elements.Thesefourelementsrepresentthefourpossibleorientationsofthepiece.Iftheindexofcolumn+row*PieceSizeinthestringisanasterisk,thepieceispresentinthespecifiedrowandcolumn.IfyoudecidetoworkthroughoneoftheTetristutorialsavailableontheweb(orlookatoneofthemanyTetrisrepositoriesonGitHub),you'llfindthatthereareseveraldifferentwaystocalculatewhetheracellispopulatedbyapiece.Ichosethismethodbecauseit'seasiertovisualizethepieces.
ThegetColumn()andgetRow()functionsThefinalsectionofcodecontainsfunctionstogettherowandcolumnofthepiece:
intPiece::getColumn()const{
returncolumn_;
}
intPiece::getRow()const{
returnrow_;
}
Thesefunctionssimplyreturnthevalueoftheprivatecolumn_orrow_variable.NowthatyouhaveabetterunderstandingofthePiececlass,let'smoveontotheBoard.
TheBoardclassTheBoardcontainsinstancesofthePiececlassandneedstodetectcollisionsamongthepieces,whenrowsarefilled,andwhenthegameisover.Let'sstartwiththecontentsoftheheaderfile(board.h):
#ifndefTETRIS_BOARD_H
#defineTETRIS_BOARD_H
#include<SDL2/SDL.h>
#include<SDL2/SDL2_ttf.h>
#include"constants.h"
#include"piece.h"
usingnamespaceConstants;
classBoard{
public:
Board();
voiddraw(SDL_Renderer*renderer,TTF_Font*font);
boolisCollision(constPiece&piece)const;
voidunite(constPiece&piece);
private:
boolisRowFull(introw);
boolareFullRowsPresent();
voidupdateOffsetRow(intfullRow);
voiddisplayScore(SDL_Renderer*renderer,TTF_Font*font);
boolcells_[BoardColumns][BoardRows];
intcurrentScore_;
};
#endif//TETRIS_BOARD_H
TheBoardhasadraw()functionlikethePiececlassaswellasseveralotherfunctionsformanagingrowsandkeepingtrackofwhichcellsarepopulatedontheboard.TheSDL2_ttflibraryisusedtorendertheROWS:textatthebottomofthewindowwiththecurrentscore(countofrowscleared).Now,let'stakealookateachsectionoftheimplementationfile(board.cpp).
Theconstructoranddraw()functionThefirstsectionofcodedefinestheconstructoroftheBoardclassandthedraw()function:
#include<sstream>
#include"board.h"
usingnamespaceConstants;
Board::Board():cells_{{false}},currentScore_(0){}
voidBoard::draw(SDL_Renderer*renderer,TTF_Font*font){
displayScore(renderer,font);
SDL_SetRenderDrawColor(
renderer,
/*LightGray:*/140,140,140,255);
for(intcolumn=0;column<BoardColumns;++column){
for(introw=0;row<BoardRows;++row){
if(cells_[column][row]){
SDL_Rectrect{
column*Offset+1,
row*Offset+1,
Offset-2,
Offset-2
};
SDL_RenderFillRect(renderer,&rect);
}
}
}
}
TheBoardconstructorinitializesthevaluesoftheprivatecells_andcurrentScore_variablestodefaultvalues.Thecells_variableisatwo-dimensionalarrayofBooleans,withthefirstdimensionrepresentingcolumnsandthesecondrows.Ifapieceoccupiesaspecificcolumnandrow,thecorrespondingvalueinthearrayistrue.Thedraw()functionbehavessimilarlytothedraw()functionofPieceinthatitfillscellsthatcontainpieceswithcolor.However,thisfunctiononlyfillsincellsthatareoccupiedbypiecesthathavereachedthebottomoftheboardwithalightgraycolor,regardlessofwhatkindofpieceitis.
TheisCollision()functionThesecondsectionofcodecontainslogictodetectcollisions:
boolBoard::isCollision(constPiece&piece)const{
for(intcolumn=0;column<PieceSize;++column){
for(introw=0;row<PieceSize;++row){
if(piece.isBlock(column,row)){
intcolumnTarget=piece.getColumn()+column;
introwTarget=piece.getRow()+row;
if(
columnTarget<0
||columnTarget>=BoardColumns
||rowTarget<0
||rowTarget>=BoardRows
){
returntrue;
}
if(cells_[columnTarget][rowTarget])returntrue;
}
}
}
returnfalse;
}
TheisCollision()functionloopsthrougheachcellontheboarduntilitreachesonepopulatedbythe&piecepassedasanargument.Ifthepieceisabouttocollidewitheithersideoftheboardorithasreachedthebottom,thefunctionreturnstrue,otherwiseitreturnsfalse.
Theunite()functionThethirdsectionofcodecontainslogictouniteapiecewiththetoprowwhenitcomestorest:
voidBoard::unite(constPiece&piece){
for(intcolumn=0;column<PieceSize;++column){
for(introw=0;row<PieceSize;++row){
if(piece.isBlock(column,row)){
intcolumnTarget=piece.getColumn()+column;
introwTarget=piece.getRow()+row;
cells_[columnTarget][rowTarget]=true;
}
}
}
//Continuouslyloopsthrougheachoftherowsuntilnofullrowsare
//detectedandensuresthefullrowsarecollapsedandnon-fullrows
//areshiftedaccordingly:
while(areFullRowsPresent()){
for(introw=BoardRows-1;row>=0;--row){
if(isRowFull(row)){
updateOffsetRow(row);
currentScore_+=1;
for(intcolumn=0;column<BoardColumns;++column){
cells_[column][0]=false;
}
}
}
}
}
boolBoard::isRowFull(introw){
for(intcolumn=0;column<BoardColumns;++column){
if(!cells_[column][row])returnfalse;
}
returntrue;
}
boolBoard::areFullRowsPresent(){
for(introw=BoardRows-1;row>=0;--row){
if(isRowFull(row))returntrue;
}
returnfalse;
}
voidBoard::updateOffsetRow(intfullRow){
for(intcolumn=0;column<BoardColumns;++column){
for(introwOffset=fullRow-1;rowOffset>=0;--rowOffset){
cells_[column][rowOffset+1]=
cells_[column][rowOffset];
}
}
}
Theunite()functionandthecorrespondingisRowFull(),areFullRowsPresent(),and
updateOffsetRow()functionsperformseveraloperations.Itupdatestheprivatecells_variablewiththerowsandcolumnsthatthespecified&pieceargumentoccupiesbysettingtheappropriatearraylocationtotrue.Italsoclearsanyfullrows(allcolumnsfilled)fromtheboardbysettingthecorrespondingcells_arraylocationstofalseandincrementsthecurrentScore_.Aftertherowiscleared,thecells_arrayisupdatedtoshifttherowabovetheclearedrowdownby1.
ThedisplayScore()functionThefinalsectionofcodedisplaysthescoreatthebottomofthegamewindow:
voidBoard::displayScore(SDL_Renderer*renderer,TTF_Font*font){
std::stringstreammessage;
message<<"ROWS:"<<currentScore_;
SDL_Colorwhite={255,255,255};
SDL_Surface*surface=TTF_RenderText_Blended(
font,
message.str().c_str(),
white);
SDL_Texture*texture=SDL_CreateTextureFromSurface(
renderer,
surface);
SDL_RectmessageRect{20,BoardHeight+15,surface->w,surface->h};
SDL_FreeSurface(surface);
SDL_RenderCopy(renderer,texture,nullptr,&messageRect);
SDL_DestroyTexture(texture);
}
ThedisplayScore()functionusestheSDL2_ttflibrarytodisplaythecurrentscoreatthebottomofthewindow(underneaththeboard).TheTTF_Font*fontargumentispassedinfromtheGameclasstoavoidinitializingthefonteverytimethescoreisupdated.ThestringstreammessagevariableisusedtocreatethetextvalueandsetittoaCchar*withintheTTF_RenderText_Blended()function.TherestofthecodedrawsthetextonaSDL_Recttoensurethatit'sproperlydisplayed.
That'sitfortheBoardclass;let'smoveontotheGametoseehowitallfitstogether.
TheGameclassTheGameclasscontainstheloopingfunctionthatenablesyoutomovepiecesaroundtheboardwithkeypresses.Here'sthecontentsoftheheaderfile(game.h):
#ifndefTETRIS_GAME_H
#defineTETRIS_GAME_H
#include<SDL2/SDL.h>
#include<SDL2/SDL2_ttf.h>
#include"constants.h"
#include"board.h"
#include"piece.h"
classGame{
public:
Game();
~Game();
boolloop();
private:
Game(constGame&);
Game&operator=(constGame&);
voidcheckForCollision(constPiece&newPiece);
voidhandleKeyEvents(SDL_Event&event);
SDL_Window*window_;
SDL_Renderer*renderer_;
TTF_Font*font_;
Boardboard_;
Piecepiece_;
uint32_tmoveTime_;
};
#endif//TETRIS_GAME_H
Theloop()functioncontainsthegamelogicandmanagesstatebasedonevents.Thefirsttwolinesundertheprivate:headerpreventmorethanoneinstanceofthegamefrombeingcreated,whichcouldcauseamemoryleak.Theprivatemethodsreducetheamountofcodelinesintheloop()function,whichsimplifiesmaintenanceanddebugging.Let'smoveontotheimplementationingame.cpp.
TheconstructoranddestructorThefirstsectionofcodedefinestheactionstoperformwhentheclassinstanceisloaded(constructor)andunloaded(destructor):
#include<cstdlib>
#include<iostream>
#include<stdexcept>
#include"game.h"
usingnamespacestd;
usingnamespaceConstants;
Game::Game():
//Createanewrandompiece:
piece_{static_cast<Piece::Kind>(rand()%7)},
moveTime_(SDL_GetTicks())
{
if(SDL_Init(SDL_INIT_VIDEO)!=0){
throwruntime_error(
"SDL_Init(SDL_INIT_VIDEO):"+string(SDL_GetError()));
}
SDL_CreateWindowAndRenderer(
BoardWidth,
ScreenHeight,
SDL_WINDOW_OPENGL,
&window_,
&renderer_);
SDL_SetWindowPosition(
window_,
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED);
SDL_SetWindowTitle(window_,"Tetris");
if(TTF_Init()!=0){
throwruntime_error("TTF_Init():"+string(TTF_GetError()));
}
font_=TTF_OpenFont("PressStart2P.ttf",18);
if(font_==nullptr){
throwruntime_error("TTF_OpenFont:"+string(TTF_GetError()));
}
}
Game::~Game(){
TTF_CloseFont(font_);
TTF_Quit();
SDL_DestroyRenderer(renderer_);
SDL_DestroyWindow(window_);
SDL_Quit();
}
Theconstructorrepresentstheentrypointfortheapplication,soalloftherequiredresourcesareallocatedandinitializedwithinit.TheTTF_OpenFont()functionisreferencingaTrueTypefontfiledownloadedfromGoogleFonts
namedPressStart2P.Youcanviewthefontathttps://fonts.google.com/specimen/Press+Start+2P.It'spresentinthe/resourcesfolderoftherepositoryandgetscopiedintothesamefolderastheexecutablewhentheprojectisbuilt.IfatanypointanerroroccurswheninitializingtheSDL2resources,aruntime_erroristhrownwithdetailsoftheerror.Thedestructor(~Game())freesuptheresourcesweallocatedforSDL2andSDL2_ttfbeforetheapplicationexits.Thisisdonetoavoidamemoryleak.
Theloop()functionThefinalsectionofcoderepresentstheGame::loop:
boolGame::loop(){
SDL_Eventevent;
while(SDL_PollEvent(&event)){
switch(event.type){
caseSDL_KEYDOWN:
handleKeyEvents(event);
break;
caseSDL_QUIT:
returnfalse;
default:
returntrue;
}
}
SDL_SetRenderDrawColor(renderer_,/*DarkGray:*/58,58,58,255);
SDL_RenderClear(renderer_);
board_.draw(renderer_,font_);
piece_.draw(renderer_);
if(SDL_GetTicks()>moveTime_){
moveTime_+=1000;
PiecenewPiece=piece_;
newPiece.move(0,1);
checkForCollision(newPiece);
}
SDL_RenderPresent(renderer_);
returntrue;
}
voidGame::checkForCollision(constPiece&newPiece){
if(board_.isCollision(newPiece)){
board_.unite(piece_);
piece_=Piece{static_cast<Piece::Kind>(rand()%7)};
if(board_.isCollision(piece_))board_=Board();
}else{
piece_=newPiece;
}
}
voidGame::handleKeyEvents(SDL_Event&event){
PiecenewPiece=piece_;
switch(event.key.keysym.sym){
caseSDLK_DOWN:
newPiece.move(0,1);
break;
caseSDLK_RIGHT:
newPiece.move(1,0);
break;
caseSDLK_LEFT:
newPiece.move(-1,0);
break;
caseSDLK_UP:
newPiece.rotate();
break;
default:
break;
}
if(!board_.isCollision(newPiece))piece_=newPiece;
}
Theloop()functionreturnsaBooleanaslongastheSDL_QUITeventhasn'tfired.Every1second,thedraw()functionsforthePieceandBoardinstancesareexecuted,andthepiecelocationsontheboardareupdatedaccordingly.Theleft,right,anddownarrowkeyscontrolthepiece'smovementwhiletheuparrowkeyrotatesthepieceby90degrees.AppropriateresponsestokeypressesarehandledinthehandleKeyEvents()function.ThecheckForCollision()functiondeterminesifanewinstanceoftheactivepiececollidedwitheithersideoftheboardorcametorestontopoftheotherpieces.Ifitdid,anewpieceiscreated.Thelogicforclearingtherows(viatheunite()functionofBoard)isalsohandledinthisfunction.We'realmostdone!Let'smoveontothemain.cppfile.
ThemainfileThere'snoheaderfileassociatedwithmain.cppbecauseitsonlypurposeistoactasanentrypointtotheapplication.Infact,thefileisonlysevenlineslong:
#include"game.h"
intmain(){
Gamegame;
while(game.loop());
return0;
}
Thewhilestatementisexitedwhentheloop()functionreturnsfalse,whichoccurswhentheSDL_QUITeventfires.AllthisfileisdoingiscreatinganewinstanceofGameandstartingtheloop.That'sitforthecodebase;let'sstartporting!
PortingtoEmscriptenYouhaveagoodunderstandingofthecodebase,sonowit'stimetostartportingitoverwithEmscripten.Fortunately,we'reabletoleveragesomeofthebrowser'sfeaturestosimplifythecodeandcompletelyremoveathird-partylibrary.Inthissection,we'regoingtoupdatethecodetocompiletoaWasmmoduleandJavaScriptgluefileandupdatesomeofthefunctionalitytoutilizethebrowser.
PreparingforportingThe/output-wasmfoldercontainstheendresult,butIrecommendthatyoucreateacopyofthe/output-nativefoldersothatyoucanfollowalongwiththeportingprocess.ThereareVSCodeTaskssetupforbothnativecompilationandEmscriptencompilation.Ifyougetstuck,youcanalwaysreferencethe/output-wasmcontents.MakesureyouopenyourcopiedfolderinVSCode(File|Openandselectyourcopiedfolder),otherwiseyouwon'tbeabletousetheTasksfeature.
What'schanging?ThisgameisanidealcandidateforportingbecauseitusesSDL2,awidelyusedlibrarywithanexistingEmscriptenport.IncludingSDL2inthecompilationsteprequiresonlyoneadditionalargumentpassedtotheemcccommand.AnEmscriptenportoftheSDL2_ttflibraryalsoexists,butkeepingitinthecodebasedoesn'tmakemuchsense.Itssolepurposeistorenderthescore(amountofrowscleared)astext.WewouldneedtoincludetheTTFfilewiththeapplicationandcomplicatethebuildprocess.EmscriptenprovidesthemeansforusingJavaScriptcodewithinourC++,sowe'regoingtotakeamuchsimplerroute:showthescoreintheDOM.
Inadditiontochangingtheexistingcode,we'llneedtocreateanHTMLandCSSfilefordisplayingandstylingthegameinthebrowser.TheJavaScriptcodewewritewillbeminimal—wejustneedtoloadtheEmscriptenmoduleandallourfunctionalityishandledintheC++codebase.We'llalsoneedtoaddafew<div>elementsandlaythemoutaccordinglytodisplaythescore.Let'sstartporting!
AddingthewebassetsCreateafolderinyourprojectfoldernamed/public.Addanewfilenamedindex.htmltothe/publicfolderandpopulateitwiththefollowingcontents:
<!doctypehtml>
<htmllang="en-us">
<head>
<title>Tetris</title>
<linkrel="stylesheet"type="text/css"href="styles.css"/>
</head>
<body>
<divclass="wrapper">
<h1>Tetris</h1>
<div>
<canvasid="canvas"></canvas>
<divclass="scoreWrapper">
<span>ROWS:</span><spanid="score"></span>
</div>
</div>
</div>
<scripttype="application/javascript"src="index.js"></script>
<scripttype="application/javascript">
Module({canvas:(()=>document.getElementById('canvas'))()})
</script>
</body>
</html>
Theindex.jsfilebeingloadedinthefirst<script>tagdoesn'texistyet;that'llbegeneratedinthecompilationstep.Let'saddsomestylestotheelements.Createastyles.cssfileinthe/publicfolderandpopulateitwiththefollowingcontents:
@importurl("https://fonts.googleapis.com/css?family=Press+Start+2P");
*{
font-family:"PressStart2P",sans-serif;
}
body{
margin:24px;
}
h1{
font-size:36px;
}
span{
color:white;
font-size:24px;
}
.wrapper{
display:flex;
align-items:center;
flex-direction:column;
}
.titleWrapper{
display:flex;
align-items:center;
justify-content:center;
}
.header{
font-size:24px;
margin-left:16px;
}
.scoreWrapper{
background-color:#3A3A3A;
border-top:1pxsolidwhite;
padding:16px0;
width:360px;
}
span:first-child{
margin-left:16px;
margin-right:8px;
}
SincethePressStart2Pfontwe'reusingishostedonGoogleFonts,wecanimportitforuseonthesite.TheCSSrulesinthisfilehandlesimplelayoutandstyling.That'sitfortheweb-relatedfilesweneededtocreate.Now,it'stimetoupdatetheC++code.
PortingtheexistingcodeWeonlyneedtoeditafewfilestogetEmscriptenworkingcorrectly.Forthesakeofsimplicityandcompactness,onlytheaffectedsectionsofcodewillbeincluded(ratherthantheentirefile).Let'sworkthroughthefilesinthesameorderastheprevioussectionandstartwithconstants.h.
UpdatingtheconstantsfileWe'lldisplaytherowsclearedcountontheDOMinsteadofinthegamewindowitself,soyoucandeletetheScreenHeightconstantfromthefile.Wenolongerneedadditionalspacetoaccommodateforthescoretext:
namespaceConstants{
constintBoardColumns=10;
constintBoardHeight=720;
constintBoardRows=20;
constintBoardWidth=360;
constintOffset=BoardWidth/BoardColumns;
constintPieceSize=4;
//constintScreenHeight=BoardHeight+50;<-----Deletethisline
}
NochangesneedtobemadetothePiececlassfiles(piece.cpp/piece.h).However,wewillneedtoupdatetheBoardclass.Let'sstartwiththeheaderfile(board.h).Startingwiththebottomandworkingourwayup,let'supdatethedisplayScore()function.Inthe<body>sectionoftheindex.htmlfile,there'sa<span>elementwithid="score".We'regoingtoupdatethiselementusingtheemscripten_run_scriptcommandtodisplaythecurrentscore.Asaresult,thedisplayScore()functionbecomesmuchshorter.Thebeforeandafterisshownasfollows.
HereistheoriginalversionoftheBoardclass'sdisplayScore()function:
voidBoard::displayScore(SDL_Renderer*renderer,TTF_Font*font){
std::stringstreammessage;
message<<"ROWS:"<<currentScore_;
SDL_Colorwhite={255,255,255};
SDL_Surface*surface=TTF_RenderText_Blended(
font,
message.str().c_str(),
white);
SDL_Texture*texture=SDL_CreateTextureFromSurface(
renderer,
surface);
SDL_RectmessageRect{20,BoardHeight+15,surface->w,surface->h};
SDL_FreeSurface(surface);
SDL_RenderCopy(renderer,texture,nullptr,&messageRect);
SDL_DestroyTexture(texture);
}
HereistheportedversionofthedisplayScore()function:
voidBoard::displayScore(intnewScore){
std::stringstreamaction;
action<<"document.getElementById('score').innerHTML="<<newScore;
emscripten_run_script(action.str().c_str());
}
Theemscripten_run_scriptactionsimplyfindsthe<span>elementontheDOMandsetstheinnerHTMLtothecurrentscore.Wecan'tusetheEM_ASM()functionherebecauseEmscriptendoesn'trecognizethedocumentobject.SincewehaveaccesstotheprivatecurrentScore_variableintheclass,we'regoingtomovethedisplayScore()callinthedraw()functionintotheunite()function.ThislimitstheamountofcallstodisplayScore()toensurethatthefunctioniscalledonlywhenthescorehasactuallychanged.Weonlyneedtoaddonelineofcodetoaccomplishthis.Here'swhattheunite()functionlookslikenow:
voidBoard::unite(constPiece&piece){
for(intcolumn=0;column<PieceSize;++column){
for(introw=0;row<PieceSize;++row){
if(piece.isBlock(column,row)){
intcolumnTarget=piece.getColumn()+column;
introwTarget=piece.getRow()+row;
cells_[columnTarget][rowTarget]=true;
}
}
}
//Continuouslyloopsthrougheachoftherowsuntilnofullrowsare
//detectedandensuresthefullrowsarecollapsedandnon-fullrows
//areshiftedaccordingly:
while(areFullRowsPresent()){
for(introw=BoardRows-1;row>=0;--row){
if(isRowFull(row)){
updateOffsetRow(row);
currentScore_+=1;
for(intcolumn=0;column<BoardColumns;++column){
cells_[column][0]=false;
}
}
}
displayScore(currentScore_);//<-----Addthisline
}
}
Sincewe'renolongerusingtheSDL2_ttflibrary,wecanupdatethedraw()functionsignatureandremovethedisplayScore()functioncall.Here'stheupdateddraw()function:
voidBoard::draw(SDL_Renderer*renderer/*,TTF_Font*font*/){
//^^^^^^^^^^^^^^<--Removethisargument
//displayScore(renderer,font);<-----Deletethisline
SDL_SetRenderDrawColor(
renderer,
/*LightGray:*/140,140,140,255);
for(intcolumn=0;column<BoardColumns;++column){
for(introw=0;row<BoardRows;++row){
if(cells_[column][row]){
SDL_Rectrect{
column*Offset+1,
row*Offset+1,
Offset-2,
Offset-2
};
SDL_RenderFillRect(renderer,&rect);
}
}
}
}
ThedisplayScore()functioncallwasremovedfromthefirstlineofthefunctionandtheTTF_Font*fontargumentwasremovedaswell.Let'saddacalltodisplayScore()intheconstructortoensurethattheinitialvalueissetto0whenthegameendsandanewonebegins:
Board::Board():cells_{{false}},currentScore_(0){
displayScore(0);//<-----Addthisline
}
That'sitfortheclassfile.SincewechangedthesignaturesforthedisplayScore()anddraw()functions,andremovedthedependencyforSDL2_ttf,we'llneedtoupdatetheheaderfile.Removethefollowinglinesfromboard.h:
#ifndefTETRIS_BOARD_H
#defineTETRIS_BOARD_H
#include<SDL2/SDL.h>
//#include<SDL2/SDL2_ttf.h><-----Deletethisline
#include"constants.h"
#include"piece.h"
usingnamespaceConstants;
classBoard{
public:
Board();
voiddraw(SDL_Renderer*renderer/*,TTF_Font*font*/);
//^^^^^^^^^^^^^^<--Removethis
boolisCollision(constPiece&piece)const;
voidunite(constPiece&piece);
private:
boolisRowFull(introw);
boolareFullRowsPresent();
voidupdateOffsetRow(intfullRow);
voiddisplayScore(SDL_Renderer*renderer,TTF_Font*font);
//^^^^^^^^^^^^^^<--Removethis
boolcells_[BoardColumns][BoardRows];
intcurrentScore_;
};
#endif//TETRIS_BOARD_H
We'removingrightalong!Thefinalchangeweneedtomakeisthealsothebiggestone.TheexistingcodebasehasaGameclassthatmanagestheapplicationlogicandamain.cppfilethatcallstheGame.loop()functioninthemain()function.TheloopingmechanismisawhileloopthatcontinuestorunaslongastheSDL_QUITeventhasn'tfired.WeneedtochangeourapproachtoaccommodateforEmscripten.
Emscriptenprovidesanemscripten_set_main_loopfunctionthatacceptsanem_callback_funcloopingfunction,fps,andasimulate_infinite_loopflag.Wecan'tincludetheGameclassandpassGame.loop()astheem_callback_funcargument,becausethebuildwillfail.Instead,we'regoingtoeliminatetheGameclasscompletelyandmovethelogicintothemain.cppfile.Copythecontentsofgame.cppintomain.cpp(overwritingtheexistingcontents)anddeletetheGameclassfiles(game.cpp/game.h).Sincewe'renotdeclaringaclassforGame,removetheGame::prefixesfromthefunctions.Theconstructoranddestructorarenolongervalid(they'renolongerpartofaclass),soweneedtomovethatlogictoadifferentlocation.Wealsoneedtoreorderthefiletoensurethatourcalledfunctionscomebeforethecallingfunctions.Thefinalresultlookslikethis:
#include<emscripten/emscripten.h>
#include<SDL2/SDL.h>
#include<stdexcept>
#include"constants.h"
#include"board.h"
#include"piece.h"
usingnamespacestd;
usingnamespaceConstants;
staticSDL_Window*window=nullptr;
staticSDL_Renderer*renderer=nullptr;
staticPiececurrentPiece{static_cast<Piece::Kind>(rand()%7)};
staticBoardboard;
staticintmoveTime;
voidcheckForCollision(constPiece&newPiece){
if(board.isCollision(newPiece)){
board.unite(currentPiece);
currentPiece=Piece{static_cast<Piece::Kind>(rand()%7)};
if(board.isCollision(currentPiece))board=Board();
}else{
currentPiece=newPiece;
}
}
voidhandleKeyEvents(SDL_Event&event){
PiecenewPiece=currentPiece;
switch(event.key.keysym.sym){
caseSDLK_DOWN:
newPiece.move(0,1);
break;
caseSDLK_RIGHT:
newPiece.move(1,0);
break;
caseSDLK_LEFT:
newPiece.move(-1,0);
break;
caseSDLK_UP:
newPiece.rotate();
break;
default:
break;
}
if(!board.isCollision(newPiece))currentPiece=newPiece;
}
voidloop(){
SDL_Eventevent;
while(SDL_PollEvent(&event)){
switch(event.type){
caseSDL_KEYDOWN:
handleKeyEvents(event);
break;
caseSDL_QUIT:
break;
default:
break;
}
}
SDL_SetRenderDrawColor(renderer,/*DarkGray:*/58,58,58,255);
SDL_RenderClear(renderer);
board.draw(renderer);
currentPiece.draw(renderer);
if(SDL_GetTicks()>moveTime){
moveTime+=1000;
PiecenewPiece=currentPiece;
newPiece.move(0,1);
checkForCollision(newPiece);
}
SDL_RenderPresent(renderer);
}
intmain(){
moveTime=SDL_GetTicks();
if(SDL_Init(SDL_INIT_VIDEO)!=0){
throwstd::runtime_error("SDL_Init(SDL_INIT_VIDEO)");
}
SDL_CreateWindowAndRenderer(
BoardWidth,
BoardHeight,
SDL_WINDOW_OPENGL,
&window,
&renderer);
emscripten_set_main_loop(loop,0,1);
SDL_DestroyRenderer(renderer);
renderer=nullptr;
SDL_DestroyWindow(window);
window=nullptr;
SDL_Quit();
return0;
}
ThehandleKeyEvents()andcheckForCollision()functionshaven'tchanged;wesimplymovedthemtothetopofthefile.Theloop()functionreturntypewaschangedfrombooltovoidasrequiredbyemscripten_set_main_loop.Finally,thecodefromtheconstructoranddestructorwasmovedintothemain()functionandanyreferencestoSDL2_ttfwereremoved.Insteadofthewhilestatementthatcalledtheloop()functionofGame,wehavetheemscripten_set_main_loop(loop,0,1)call.Wechangedthe#includestatementsatthetopofthefiletoaccommodateforEmscripten,SDL2,andourBoardandPiececlasses.That'sitforchanges—nowit'stimetoconfigurethebuildandtestoutthegame.
BuildingandrunningthegameWiththecodeupdatedandtherequiredwebassetspresent,it'stimetobuildandtestoutthegame.Thecompilationstepissimilartothepreviousexamplesinthisbook,butwe'regoingtouseadifferenttechniquetorunthegame.Inthissection,we'regoingtoconfigurethebuildtasktoaccommodatefortheC++filesandruntheapplicationusingafeatureprovidedbyEmscripten.
BuildingwithVSCodetasksWe'regoingtoconfigurethebuildintwoways:withVSCodetasksandaMakefile.MakefilesareniceifyouprefertouseadifferenteditorthanVSCode.The/.vscode/tasks.jsonfilealreadycontainsthetasksyou'llneedtobuildtheproject.TheEmscriptenbuildstepisthedefault(asetofnativebuildtasksisalsopresent).Let'swalkthrougheachtaskinthetasksarrayandreviewwhat'stakingplace.Thefirsttaskdeletesanyexistingcompiledoutputfilespriortobuilding:{"label":"RemoveExistingWebFiles","type":"shell","command":"rimraf","options":{"cwd":"${workspaceRoot}/public"},"args":["index.js","index.wasm"]}
Thesecondtaskperformsthebuildwiththeemcccommand:
{
"label":"BuildWebAssembly",
"type":"shell",
"command":"emcc",
"args":[
"--bind","src/board.cpp","src/piece.cpp","src/main.cpp",
"-std=c++14",
"-O3",
"-s","WASM=1",
"-s","USE_SDL=2",
"-s","MODULARIZE=1",
"-o","public/index.js"
],
"group":{
"kind":"build",
"isDefault":true
},
"problemMatcher":[],
"dependsOn":["RemoveExistingWebFiles"]
}
Therelatedargumentsareplacedonthesameline.Theonlynewandunfamiliaradditiontotheargsarrayisthe--bindargumentwiththecorresponding.cppfiles.ThistellsEmscriptenthatallthefilesafter--bindarerequiredtobuildtheproject.TestoutthebuildbyselectingTasks|RunBuildTask...fromthemenuorusingthekeyboardshortcutCmd/Ctrl+Shift+B.Ittakesafewsecondstobuild,buttheterminalwillletyouknowwhenthecompilationprocessiscomplete.Ifsuccessful,youshouldseeanindex.jsandindex.wasmfileinthe/publicfolder.
BuildingwithaMakefileIfyouprefernottouseVSCode,youcanuseaMakefiletoaccomplishthesamegoalastheVSCodetasks.CreateafilenamedMakefileinyourprojectfolderandpopulateitwiththefollowingcontents(makesurethatthefileisusingtabs,notspaces):
#Thisallowsyoutojustrunthe"make"commandwithoutspecifying
#arguments:
.DEFAULT_GOAL:=build
#Specifieswhichfilestocompileaspartoftheproject:
CPP_FILES=$(wildcardsrc/*.cpp)
#FlagstouseforEmscriptenemcccompilecommand:
FLAGS=-std=c++14-O3-sWASM=1-sUSE_SDL=2-sMODULARIZE=1\
--bind$(CPP_FILES)
#Nameofoutput(the.wasmfileiscreatedautomatically):
OUTPUT_FILE=public/index.js
#Thisisthetargetthatcompilesourexecutable
compile:$(CPP_FILES)
emcc$(FLAGS)-o$(OUTPUT_FILE)
#Removestheexistingindex.jsandindex.wasmfiles:
clean:
rimraf$(OUTPUT_FILE)
rimrafpublic/index.wasm
#Removestheexistingfilesandbuildstheproject:
build:cleancompile
@echo"BuildComplete!"
TheoperationsbeingperformedareidenticaltotheVSCodetasks,justinadifferentformatusingmoreuniversaltooling.Thedefaultbuildstepissetinthefile,soyoucanrunthefollowingcommandwithinyourprojectfoldertocompiletheproject:
make
NowthatyouhaveacompiledWasmfileandJavaScriptgluecode,let'stryrunningthegame.
RunningthegameInsteadofusingserveorbrowser-sync,we'regoingtouseabuilt-infeatureofEmscripten'stoolchain,emrun.Itprovidestheaddedbenefitofcapturingstdoutandstderr(ifyoupassthe--emrunlinkerflagtotheemcccommand)andprintingthemtotheterminalifdesired.We'renotgoingtousethe--emrunflag,buthavingalocalwebserveravailablewithouthavingtoinstallanyadditionaldependenciesisaniceaddedfeaturetobeawareof.Openupaterminalinstancewithinyourprojectfolderandrunthefollowingcommandtostartthegame:emrun--browserchrome--no_emrun_detectpublic/index.html
Youcanspecifyfirefoxforthebrowserifthat'swhatyou'reusingfordevelopment.The--no_emrun_detectflaghidesamessageintheterminalstatingthattheHTMLpageisnotemruncapable.Ifyounavigatetohttp://localhost:6931/index.html,youshouldseethefollowing:
Tetrisrunninginthebrowser
Tryrotatingandmovingthepiecestoensurethateverythingisworkingcorrectly.TheROWScountshouldincrementbyonewhenyou'vesuccessfullyclearedarow.Youmayalsonoticethatifyou'retooclosetotheedgeoftheboard,youwon'tbeabletorotatesomeofthepieces.Congratulations,you'vesuccessfullyportedaC++gameovertoEmscripten!
SummaryInthischapter,weportedaTetrisclonewritteninC++thatusedSDL2toEmscriptensoitcouldberuninthebrowserwithWebAssembly.WecoveredtherulesofTetrisandhowtheymaptothelogicwithintheexistingcodebase.WealsoreviewedeachfileintheexistingcodebaseindividuallyandwhichchangeshadtobemadetosuccessfullycompiletoaWasmfileandJavaScriptgluecode.Afterupdatingtheexistingcode,wecreatedtherequiredHTMLandCSSfiles,thenconfiguredabuildstepwiththeappropriateemccflags.Oncebuilt,thegamewasrunusingEmscripten'semruncommand.
InChapter9,IntegratingwithNode.js,we'regoingtodiscusshowtointegrateWebAssemblyintoNode.jsandthebenefitsthisintegrationprovides.
Questions1. WhatarethepiecescalledinTetris?2. WhatisonereasonforchoosingnottoportanexistingC++codebaseto
Emscripten?3. Whattooldidweusetocompilethegamenatively(forexample,toan
executable)?4. Whatisthepurposeoftheconstants.hfile?5. WhywereweabletoeliminatetheSDL2_ttflibrary?6. WhichEmscriptenfunctiondidweusetostartrunningthegame?7. Whichargumentdidweaddtotheemcccommandtobuildthegameand
whatpurposedoesitserve?8. WhatadvantagedoesemrunofferoveratoollikeserveandBrowsersync?
FurtherreadingHeaderFilesinC++:https://www.sitesbay.com/cpp/cpp-header-filesSDL2TetrisonGitHub:https://github.com/andwn/sdl2-tetrisTetrisonGitHub:https://github.com/abesary/tetrisTetris-LinuxonGitHub:https://github.com/abesary/tetris-linux
IntegratingwithNode.js
ThemodernwebleansheavilyonNode.jsforbothdevelopmentandserver-sidemanagement.Withtheadventofincreasinglycomplexbrowserapplicationsthatperformcomputationallyexpensiveoperations,performanceincreasescanbeincrediblybeneficial.Inthischapter,we'regoingtodescribethevariouswaysyoucanintegrateWebAssemblywithNode.jsthroughtheuseofvariousexamples.
Ourgoalforthischapteristounderstandthefollowing:
TheadvantagesofintegratingWebAssemblywithNode.jsHowtointeractwiththeNode.jsWebAssemblyAPIHowtoutilizeWasmmodulesinaprojectthatusesWebpackHowtowriteunittestsforWebAssemblymodulesusingnpmlibraries
WhyNode.js?InChapter3,SettingUpaDevelopmentEnvironment,Node.jswasdescribedasanasynchronousevent-drivenJavaScriptruntime,whichisthedefinitiontakenfromtheofficialwebsite.WhatNode.jsrepresents,however,isaprofoundshiftinthewaywebuildandmanagewebapplications.Inthissection,wewilldiscusstherelationshipbetweenWebAssemblyandNode.js,andwhythetwotechnologiescomplementeachothersowell.
SeamlessintegrationNode.jsrunsonGoogle'sV8JavaScriptengine,whichpowersGoogleChrome.SinceV8'sWebAssemblyimplementationadherestotheCoreSpecification,youcaninteractwithaWebAssemblymoduleusingthesameAPIasthebrowser.Insteadofperformingafetchcallfora.wasmfile,youcanuseNode.js'sfsmoduletoreadthecontentsintoabuffer,thencallinstantiate()ontheresult.
ComplementarytechnologiesJavaScripthaslimitationsontheserversideaswell.ExpensivecomputationorworkingwithlargenumberscanbeoptimizedwithWebAssembly'ssuperiorperformance.Asascriptinglanguage,JavaScriptexcelsatautomatingsimpletasks.YoucouldwriteascripttocompileC/C++toaWasmfile,copyittoabuildfolder,andseethechangesreflectedinthebrowserifyou'reusingatoollikeBrowsersync.
DevelopmentwithnpmNode.jshasanextensiveecosystemoftoolsandlibrariesintheformofnpm.SvenSauleauandothermembersoftheopensourcecommunityhavecreatedwebassemblyjs,anextensivesuiteoftoolingforWebAssemblybuiltwithNode.js.Thewebassemblyjssiteathttps://webassembly.js.orgincludesthetaglineToolchainforWebAssembly.Therearecurrentlyover20npmpackagestoperformvarioustasksandaidindevelopment,suchasanESLintplugin,anASTvalidator,andaformatter.AssemblyScript,aTypeScripttoWebAssemblycompiler,allowsyoutowriteperformantcodethatcompilestoaWasmmodulewithouthavingtolearnCorC++.TheNode.jscommunityisclearlyvestedinWebAssembly'ssuccess.
Server-sideWebAssemblywithExpressNode.jscanbeusedinseveralwaystoaddvaluetoaWebAssemblyproject.Inthissection,we'regoingtowalkthroughanexampleNode.jsapplicationthatintegratesWebAssembly.TheapplicationusesExpresswithsomesimpleroutestocallfunctionsfromacompiledWasmmodule.
OverviewoftheprojectTheprojectreusessomeofthecodefromtheapplicationwebuiltinChapter7,CreatinganApplicationfromScratch(CooktheBooks)todemonstratehowNode.jscanbeusedwithWebAssembly.Thecodeforthissectionislocatedinthe/chapter-09-node/server-examplefolderinthelearn-webassemblyrepository.We'regoingtoreviewportionsoftheapplicationdirectlyapplicabletoNode.js.Thefollowingstructurerepresentsthefilestructurefortheproject:├──/lib│└──main.c├──/src|├──Transaction.js|├──/assets|│├──db.json|│├──main.wasm|│└──memory.wasm|├──assign-routes.js|├──index.js|└──load-assets.js├──package.json├──package-lock.json└──requests.js
Withregardtodependencies,theapplicationusestheexpressandbody-parserlibrariestosetuproutesandparseJSONfromthebodyofrequests.Fordatamanagement,ituseslowdb,alibrarythatprovidesmethodsforreadingandupdatingaJSONfile.TheJSONfileislocatedin/src/assets/db.jsonandcontainsdatathatwasslightlymodifiedfromtheCooktheBooksdataset.We'reusingnodemontowatchforchangesinthe/srcfolderandreloadtheapplicationautomatically.We'reusingrimraftomanagefiledeletion.Thelibraryisincludedasadependencyintheeventthatyoudidn'tinstallitgloballyinChapter3,SettingUpaDevelopmentEnvironment.Finally,thenode-fetchlibraryallowsustousethefetchAPItomakeHTTPrequestswhentestingtheapplication.
TosimplifyfunctionalityinboththeJavaScriptandCfiles,therawAmountandcookedAmountfieldswerereplacedwithasingleamountfield,andthecategoryfieldisnowcategoryId,whichmapstoacategoriesarrayindb.json.
ExpressconfigurationTheapplicationisloadedin/src/index.js.Thecontentsofthisfileareshownasfollows:
constexpress=require('express');
constbodyParser=require('body-parser');
constloadAssets=require('./load-assets');
constassignRoutes=require('./assign-routes');
//IfyouprefacethenpmstartcommandwithPORT=[YourPort]on
//macOS/UbuntuorsetPORT=[YourPort]onWindows,itwillchangetheport
//thattheserverisrunningon,soPORT=3001willruntheappon
//port3001:
constPORT=process.env.PORT||3000;
conststartApp=async()=>{
constapp=express();
//Usebody-parserforparsingJSONinthebodyofarequest:
app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());
//InstantiatetheWasmmoduleandlocaldatabase:
constassets=awaitloadAssets();
//SetuproutesthatcaninteractwithWasmandthedatabase:
assignRoutes(app,assets);
//Starttheserverwiththespecifiedport:
app.listen(PORT,(err)=>{
if(err)returnPromise.reject(err);
returnPromise.resolve();
});
};
startApp()
.then(()=>console.log(`Serverisrunningonport${PORT}`))
.catch(err=>console.error(`Anerroroccurred:${err}`));
ThisfilesetsupanewExpressapp,addsthebody-parsermiddleware,loadsthemockdatabaseandWasminstance,andassignsroutes.Let'smoveontodiscussingthedifferencebetweeninstantiatingaWasmmoduleinthebrowserandNode.js.
InstantiatingaWasmmodulewithNode.jsTheWasmfilesareinstantiatedin/src/load-assets.js.We'reusingthememory.wasmfilefromCooktheBooks,butthe/assets/main.wasmfileiscompiledfromaslightlydifferentversionofmain.c,whichislocatedinthe/libfolder.TheloadWasm()functionperformsthesameoperationastheWasminitializationcodefromCooktheBooks,butthemethodforpassinginthebufferSourcetoWebAssembly.instantiate()isdifferent.Let'sexaminethisfurtherbyreviewingaportionofthecodeintheloadWasm()functionoftheload-assets.jsfile:constfs=require('fs');constpath=require('path');
constassetsPath=path.resolve(__dirname,'assets');
constgetBufferSource=fileName=>{constfilePath=path.resolve(assetsPath,fileName);returnfs.readFileSync(filePath);//<-Replacesthefetch()and.arrayBuffer()};
//We'reusingasync/awaitbecauseitsimplifiesthePromisesyntaxconstloadWasm=async()=>{constwasmMemory=newWebAssembly.Memory({initial:1024});constmemoryBuffer=getBufferSource('memory.wasm');constmemoryInstance=awaitWebAssembly.instantiate(memoryBuffer,{env:{memory:wasmMemory}});...
Toelaborateonthedifferences,here'ssomecodethatinstantiatesamoduleusingfetch:
fetch('main.wasm')
.then(response=>{
if(response.ok)returnresponse.arrayBuffer();
thrownewError('UnabletofetchWebAssemblyfile');
})
.then(bytes=>WebAssembly.instantiate(bytes,importObj));
WhenusingNode.js,thefetchcallisreplacedbythefs.readFileSync()functionandthearrayBuffer()functionisnolongerrequiredbecausefs.readFileSync()returnsabufferthatcanbepasseddirectlyintotheinstantiate()function.OncetheWasmmoduleisinstantiated,wecanstartinteractingwiththeinstance.
CreatingamockdatabaseTheload-assets.jsfilealsocontainsamethodforcreatingamockdatabaseinstance:
constloadDb=()=>{
constdbPath=path.resolve(assetsPath,'db.json');
constadapter=newFileSync(dbPath);
returnlow(adapter);
};
TheloadDb()functionloadsthecontentsof/assets/db.jsonintoaninstanceoflowdb.Thedefaultfunctionexportedfromload-assets.jscallstheloadWasm()andloadDb()functionsandreturnsanobjectcontainingthemockdatabaseandWasminstance:
module.exports=asyncfunctionloadAssets(){
constdb=loadDb();
constwasmInstance=awaitloadWasm();
return{
db,
wasmInstance
};
};
Goingforward,I'llusethetermdatabasetorefertothelowdbinstancethataccessesthedb.jsonfile.Nowthattheassetsareloaded,let'sreviewhowtheapplicationinteractswiththem.
InteractingwiththeWebAssemblymoduleInteractionwiththedatabaseandWasminstancetakesplaceacrosstwofilesinthe/srcfolder:Transaction.jsandassign-routes.js.Inourexampleapplication,allcommunicationwiththeAPIisperformedviaHTTPrequests.Sendingarequesttoaspecificendpointwilltriggersomeinteractionwiththedatabase/Wasminstanceontheserver.Let'sstartbyreviewingTransaction.js,whichinteractsdirectlywiththedatabaseandWasminstance.
WrappinginteractioninTransaction.jsJustaswithCooktheBooks,there'saclassthatwrapstheWasminteractioncodeandprovidesacleaninterface.ThecontentsofTransaction.jsareverysimilartothecontentsof/src/store/WasmTransactions.jsfromCooktheBooks.MostofthechangesaccommodateforthecategoryIdbeingpresentinatransactionrecordandasingleamountfield(nomorerawandcookedamounts).Additionalfunctionalitywasaddedtointeractwiththedatabase.Forexample,here'safunctionthateditsanexistingtransaction,bothinthedatabaseandthelinkedlistfromtheWasminstance:
getValidAmount(transaction){
const{amount,type}=transaction;
returntype==='Withdrawal'?-Math.abs(amount):amount;
}
edit(transactionId,contents){
constupdatedTransaction=this.db.get('transactions')
.find({id:transactionId})
.assign(contents)
.write();
const{categoryId,...transaction}=updatedTransaction;
constamount=this.getValidAmount(transaction);
this.wasmInstance._editTransaction(transactionId,categoryId,amount);
returnupdatedTransaction;
}
Theedit()functionupdatesthedatabaserecordthatcorrespondstothetransactionIdargumentwiththevaluesinthecontentsargument.this.dbisthedatabaseinstancethatwascreatedintheload-assets.jsfile.SincethecategoryIdfieldisavailableontheupdatedTransactionrecord,wecanpassitdirectlytothis.wasmInstance._editTransaction().ItgetspassedintotheconstructorwhenanewinstanceofTransactioniscreated.
Transactionoperationsinassign-routes.jsTheassign-routes.jsfiledefinesroutesandaddsthemtotheexpressinstance(app)createdinindex.js.InExpress,routescanbedefineddirectlyonapp(forexample,app.get()),orthroughtheuseofaRouter.Inthiscase,aRouterwasusedtoaddmultiplemethodstothesameroutepath.Thefollowingcode,takenfromtheassign-routes.jsfile,createsaRouterinstanceandaddstworoutes:aGETroutethatreturnsalltransactions,andaPOSTroutethatcreatesanewtransaction:
module.exports=functionassignRoutes(app,assets){
const{db,wasmInstance}=assets;
consttransaction=newTransaction(db,wasmInstance);
consttransactionsRouter=express.Router();
transactionsRouter
.route('/')
.get((req,res)=>{
consttransactions=transaction.findAll();
res.status(200).send(transactions);
})
.post((req,res)=>{
const{body}=req;
if(!body){
returnres.status(400).send('Bodyofrequestisempty');
}
constnewRecord=transaction.add(body);
res.status(200).send(newRecord);
});
...
//SetthebasepathforallroutesontransactionsRouter:
app.use('/api/transactions',transactionsRouter);
}
Theapp.use()functionattheendofthesnippetspecifiesthatallroutesdefinedonthetransactionsRouterinstanceareprefixedwith/api/transactions.Ifyouwererunningtheapplicationlocallyonport3000,youcouldnavigatetohttp://localhost:3000/api/transactionsinyourbrowserandseeanarrayofallthetransactionsinJSONformat.
Asyoucanseefromthebodyoftheget()andpost()functions,interactionswith
anytransactionrecordsarebeingdelegatedtotheTransactioninstancecreatedinline3.Thatcompletesourreviewofpertinentsectionsofthecodebase.Eachofthefilescontaincommentsdescribingthefile'sfunctionalityandpurpose,soyoumaywanttoreviewthosebeforemovingontothenextsection.Inthenextsection,we'llbuild,run,andinteractwiththeapplication.
BuildingandrunningtheapplicationBeforewebuildandtestouttheproject,you'llneedtoinstallthenpmdependencies.Openaterminalwithinthe/server-examplefolderandrunthefollowingcommand:
npminstall
Oncethat'scomplete,you'rereadytomoveontothebuildstep.
BuildingtheapplicationInthecaseofthisapplication,buildingreferstocompilingthelib/main.ctoa.wasmfileusingtheemcccommand.SincethisisaNode.jsproject,wecanusethescriptskeyinourpackage.jsonfiletodefineTasks.YoucanstilluseVSCode'sTasksfeaturebecauseitautomaticallydetectsthescriptsfromyourpackage.jsonfileandpresentstheminthelistoftaskswhenyouselectTasks|RunTask...fromthemenu.Thefollowingcodecontainsthecontentsofthescriptssectioninthisproject'spackage.jsonfile:
"scripts":{
"prebuild":"rimrafsrc/assets/main.wasm",
"build":"emcclib/main.c-Os-sWASM=1-sSIDE_MODULE=1
-sBINARYEN_ASYNC_COMPILATION=0-sALLOW_MEMORY_GROWTH=1
-osrc/assets/main.wasm",
"start":"nodesrc/index.js",
"watch":"nodemonsrc/*--exec'npmstart'"
},
Thebuildscriptwassplitacrossmultiplelinesfordisplaypurposes,soyou'dhavetocombinethoselinesforvalidJSON.TheprebuildscriptremovestheexistingWasmfile,andthebuildscriptrunstheemcccommandwiththerequiredflagstocompilelib/main.candoutputtheresulttosrc/assets/main.wasm.Torunthescript,openaterminalwithinthe/server-examplefolderandrunthefollowingcommand:
npmrunbuild
Ifthe/src/assetsfoldercontainsafilenamedmain.wasm,thebuildcompletedsuccessfully.Ifanerrorhasoccurred,theterminalshouldprovideadescriptionoftheerror,aswellasastacktrace.
Youcancreatenpmscriptsthatrunbeforeorafteraspecificscriptbycreatinganentrywiththesamenameandprefixingitwithpreorpost.Forexample,ifyouwantedtorunascriptafterthebuildscripthascompleted,youcancreateascriptnamed"postbuild"andspecifythecommandyouwanttorun.
StartingandtestingouttheapplicationIfyou'remakingchangestotheapplicationortryingtofixabug,youcouldusethewatchscripttowatchforanychangestothecontentsofthe/srcfolderandautomaticallyrestarttheapplicationifachangewasmade.Sincewe'rejustrunningandtestingouttheapplication,wecanusethestartcommandinstead.Intheterminal,ensureyou'reinthe/server-examplefolderandrunthefollowingcommand:
npmstart
YoushouldseeamessagethatsaysServerisrunningonport3000.You'renowabletosendHTTPrequeststotheserver.Totesttheapplication,openanewterminalinstancewithintheserver-exampledirectoryandrunthefollowingcommand:
node./requests.js1
ThisshouldlogouttheresponsebodyoftheGETcalltothe/api/transactionsendpoint.Therequests.jsfilecontainsfunctionalitythatallowsyoutomakerequeststoalloftheavailableroutes.ThegetFetchActionForId()functionreturnsanobjectwithanendpointandoptionsvalue,whichcorrespondstoarouteintheassign-routes.jsfile.TheactionIdisanarbitrarynumbertosimplifytestingandreducetheamountoftypingforrunningcommands.Forexample,youcouldrunthefollowingcommand:
node./requests.js5
ItwilllogoutthesumofalltransactionsfortheComputer&Internetcategory.Youcanpassanadditionalargumenttothenodecommandifyouwantthetotalforadifferentcategory.TogetthesumofalltransactionsintheInsurancecategory,runthiscommand:
node./requests.js53
Trygoingthrougheachoftherequests(thereareeightintotal).Ifyoumakearequestthatadds,removes,oreditsatransaction,youshouldseethechangesinthe/src/assets/db.jsonfile.That'sitfortheNode.jsexampleproject.Inthenextsection,we'llutilizeWebpacktoloadandinteractwithaWasmmodule.
Client-sideWebAssemblywithWebpackWebapplicationscontinuetogrowincomplexityandsize.SimplyservingupafewhandwrittenHTML,CSS,andJavaScriptfilesisnotfeasibleforlargeapplications.Tomanagethiscomplexity,webdevelopersusebundlerstoallowformodularization,ensurebrowsercompatibility,andreducethesizeofJavaScriptfiles.Inthissection,we'regoingtobeusingapopularbundler,Webpack,toutilizeWasmwithoutusingemcc.
OverviewoftheprojectTheexampleWebpackapplicationextendsthefunctionalityoftheCcodewewroteintheCompilingCwithoutthegluecodesectionofChapter5,CreatingandLoadingaWebAssemblyModule.Insteadofshowingabluerectanglebouncingaroundaredbackground,we'llshowanalieninaspaceshipbouncingaroundtheHorseheadNebula.Thecollisiondetectionfunctionalityhasbeenmodifiedtoaccommodateforbouncingwithinarectangle,sothemovementofthespaceshipwillberandom.Thecodeforthissectionislocatedinthe/chapter-09-node/webpack-examplefolderinthelearn-webassemblyrepository.Thefilestructurefortheprojectisshowninthefollowingcode:
├──/src
│├──/assets
││├──background.jpg
││└──spaceship.svg
│├──App.js
│├──index.html
│├──index.js
│├──main.c
│└──styles.css
├──package.json
├──package-lock.json
└──webpack.config.js
We'llreviewtheWebpackconfigurationfileinalatersection.Fornow,let'stakeamomenttodiscussWebpackinmoredetail.
WhatisWebpack?TheJavaScriptecosystemhasbeenrapidlyevolvingoverthepastseveralyears,resultinginnewframeworksandlibrariespoppingupconstantly.BundlerscameaboutasawaytoenabledeveloperstosplitaJavaScriptapplicationintoseveralfileswithouthavingtoworryaboutmanagingglobalnamespaces,scriptloadingorder,oranincrediblylonglistof<script>tagsintheHTMLfile.Abundlercombinesallofthefilesintooneandresolvesanynamingcollisions.
Webpackis,atthetimeofwriting,oneofthemostpopularbundlersforfrontenddevelopment.ItdoesmuchmorethancombineJavaScriptfiles,however.Italsoperformscomplextaskssuchascode-splittingandtreeshaking(dead-codeelimination).Webpackwasdesignedwithapluginarchitecture,whichresultedinamassiveamountofcommunity-developedplugins.AsearchforWebpackonnpmcurrentlyreturnsover12,000packages!Thisexhaustivelistofplugins,alongwithitspowerfulbuilt-infeatureset,makesWebpackafull-fledgedbuildtool.
InstallingandconfiguringWebpackBeforewebegintheapplicationwalk-through,openupaterminalwithinthe/webpack-examplefolderandrunthefollowingcommand:
npminstall
DependenciesoverviewTheapplicationusesVersion4ofWebpack(themostrecentversionasofwritingthis)tobuildourapplication.WeneedtouseWebpackpluginstoloadthevariousfiletypesusedintheapplicationandBabeltoutilizenewerJavaScriptfeatures.ThefollowingsnippetliststhedevDependencieswe'reusingintheproject(takenfrompackage.json):
...
"devDependencies":{
"@babel/core":"^7.0.0-rc.1",
"@babel/preset-env":"^7.0.0-rc.1",
"babel-loader":"^8.0.0-beta.4",
"cpp-wasm-loader":"0.7.7",
"css-loader":"1.0.0",
"file-loader":"1.1.11",
"html-loader":"0.5.5",
"html-webpack-plugin":"3.2.0",
"mini-css-extract-plugin":"0.4.1",
"rimraf":"2.6.2",
"webpack":"4.16.5",
"webpack-cli":"3.1.0",
"webpack-dev-server":"3.1.5"
},
...
Ispecifiedexactversionsforsomeofthelibrariestoensuretheapplicationbuildsandrunssuccessfully.Anylibrarieswithanameendingin-loaderor-pluginareusedinconjunctionwithWebpack.Thecpp-wasm-loaderlibraryallowsustoimportaCorC++filedirectly,withouthavingtocompileittoWasmfirst.Webpack4hasbuilt-insupportforimporting.wasmfiles,butyoucan'tspecifyanimportObjargument,whichisrequiredformodulesgeneratedwithEmscripten.
Configuringloadersandpluginsinwebpack.config.jsWe'reusingseveraldifferentfiletypesinadditiontoJavaScriptfortheapplication:CSS,SVG,HTML,andsoon.Installingthe-loaderdependenciesisonlypartoftheequation—youalsoneedtotellWebpackhowtoloadthem.Youalsoneedtospecifyconfigurationdetailsforanypluginsyouhaveinstalled.Youcanspecifytheloadingandconfigurationdetailsinawebpack.config.jsfileintherootfolderofyourproject.Thefollowingsnippetcontainsthecontentsof/webpack-example/webpack.config.js:
constHtmlWebpackPlugin=require('html-webpack-plugin');
constMiniCssExtractPlugin=require('mini-css-extract-plugin');
module.exports={
module:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
use:{
loader:'babel-loader',
options:{
//Weneedthistouseasync/await:
presets:[
[
'@babel/preset-env',{
targets:{node:'10'}
}
]
]
}
}
},
{
test:/\.html$/,
use:{
loader:'html-loader',
options:{minimize:true}
}
},
{
test:/\.css$/,
use:[MiniCssExtractPlugin.loader,'css-loader']
},
{
test:/\.(c|cpp)$/,
use:{
loader:'cpp-wasm-loader',
options:{
emitWasm:true
}
}
},
{
test:/\.(png|jpg|gif|svg)$/,
use:{
loader:'file-loader',
options:{
name:'assets/[name].[ext]'
}
}
}
]
},
plugins:[
newHtmlWebpackPlugin({
template:'./src/index.html',
filename:'./index.html'
}),
//Thisisusedforbundling(buildingforproduction):
newMiniCssExtractPlugin({
filename:'[name].css',
chunkFilename:'[id].css'
})
]
};
TherulessectiontellsWebpackwhichloadertouseforafileextension.ThefourthiteminthearrayhandlesC/C++files(notethetestfieldvaluecontainingc|cpp).TheHtmlWebpackPlugintakesthecontentsof/src/index.html,addsanyrequired<script>tags,minifiesit,andcreatesanindex.htmlinthebuildfolder,whichdefaultsto/dist.TheMiniCssExtractPlugincopiesanyimportedCSSintoasingleCSSfileinthe/distfolder.We'llreviewhowtobuildtheprojectinalatersection,solet'smoveontotheapplicationcode,startingwiththeCfile.
TheCcodeSincewe'reallowedtoimportCandC++filesdirectly,theCfileislocatedwithinthe/srcfolder.Thisfile,main.c,containslogictomanagecollisiondetectionandmovethespaceshiparoundthe<canvas>.Thecodeisbasedonthewithout-glue.cfilewecreatedinChapter5,CreatingandLoadingaWebAssemblyModule.We'renotgoingtoreviewtheentirefile,onlythesectionsthathavechangedandmeritexplanation.Let'sbeginwiththedefinitionsanddeclarationssection,whichincludesanewstruct:Bounds.
DefinitionsanddeclarationsThecodecontainingthedefinitionsanddeclarationssectionsisshownasfollows:
typedefstructBounds{
intwidth;
intheight;
}Bounds;
//We'reusingtheterm"Rect"torepresenttherectanglethe
//imageoccupies:
typedefstructRect{
intx;
inty;
intwidth;
intheight;
//Horizontaldirectionoftravel(L/R):
charhorizDir;
//Verticaldirectionoftravel(U/D):
charvertDir;
}Rect;
structBoundsbounds;
structRectrect;
NewpropertieswereaddedtotheexistingRectdefinitiontoaccommodateforflexiblesizingandtrackingmovementinthexandydirections.Wedefinedanewstruct,Bounds,andremovedtheexisting#definestatementsbecausethe<canvas>elementisnolongerasquarewithstaticdimensions.Anewinstanceofbothelementsisdeclaredwhenthemoduleloads.Thedimensionalpropertiesoftheseinstancesareassignedinthestart()function,whichwe'llcovernext.
Thestart()functionTheupdatedstart()function,whichactsastheentrypointtothemodule,isshownasfollows:
EMSCRIPTEN_KEEPALIVE
voidstart(intboundsWidth,intboundsHeight,intrectWidth,
intrectHeight){
rect.x=0;
rect.y=0;
rect.horizDir='R';
rect.vertDir='D';
rect.width=rectWidth;
rect.height=rectHeight;
bounds.width=boundsWidth;
bounds.height=boundsHeight;
setIsRunning(true);
}
AnyfunctionsthatarecalledfromJavaScriptareprependedwiththeEMSCRIPTEN_KEEPALIVEstatement.We'renowpassingthewidthandheightofboththeBoundsandRectelementsasargumentstothestart()function,whichweassigntothelocalboundsandrectvariables.Thisallowsustoeasilychangethedimensionsofeitheronewithouthavingtomakeanychangestothecollisiondetectionlogic.Inthecontextofthisapplication,therectrepresentstherectangleinwhichthespaceshipimageresides.Wesetthedefaulthorizontalandverticaldirectionfortherectsotheimageinitiallymovestotherightanddown.Let'smoveontotherectmovement/collisiondetectioncode.
TheupdateRectLocation()functionThecoderelatedtocollisiondetectionandtheRectmovementishandledintheupdateRectLocation()function,whichisshownasfollows:
/**
*Updatestherectanglelocationby+/-1pxinthexorybasedon
*thecurrentlocation.
*/
voidupdateRectLocation(){
//Determineiftheboundingrectanglehas"bumped"intoeither
//theleft/rightsideortop/bottomside.Dependingonwhichside,
//flipthedirection:
intxBouncePoint=bounds.width-rect.width;
if(rect.x==xBouncePoint)rect.horizDir='L';
if(rect.x==0)rect.horizDir='R';
intyBouncePoint=bounds.height-rect.height;
if(rect.y==yBouncePoint)rect.vertDir='U';
if(rect.y==0)rect.vertDir='D';
//Ifthedirectionhaschangedbasedonthexandy
//coordinates,ensurethexandypointsupdate
//accordingly:
inthorizIncrement=1;
if(rect.horizDir=='L')horizIncrement=-1;
rect.x=rect.x+horizIncrement;
intvertIncrement=1;
if(rect.vertDir=='U')vertIncrement=-1;
rect.y=rect.y+vertIncrement;
}
TheprimarydifferencebetweenthiscodeandthecodewewroteinChapter5,CreatingandLoadingaWebAssemblyModule,isthecollisiondetectionlogic.Insteadofsimplytrackingthelocationoftherectinstancehorizontallyandchangingdirectionwhenithitstherightboundary,thefunctionnowtracksthehorizontalandverticaldirectionsandmanageseachindependently.Althoughthisisn'tthemostperformantalgorithm,itdoesachievethegoalofensuringthespaceshipchangesdirectionwhenitencounterstheedgeofthe<canvas>.
TheJavaScriptcodeTheonlyproductiondependencywe'reusingfortheapplicationisVue.Althoughtheapplicationconsistsofasinglecomponent,Vuemakesmanagingdata,functions,andthecomponentlife-cyclemuchsimplerthantryingtodoitmanually.Theindex.jsfilecontainstheVueinitializationcode,whiletherenderingandapplicationlogicisin/src/App.js.Thisfilehasalotofmovingparts,sowe'regoingtoreviewthecodeinchunks,aswedidintheprevioussection.Let'sstartwiththeimportstatements.
TheimportstatementsThefollowingcodedemonstratestheWebpackloadersinaction:
//Thisisloadedusingthecss-loaderdependency:
import'./styles.css';
//Thisisloadedusingthecpp-wasm-loaderdependency:
importwasmfrom'./main.c';
//Theseareloadedusingthefile-loaderdependency:
importbackgroundImagefrom'./assets/background.jpg';
importspaceshipImagefrom'./assets/spaceship.svg';
Theloadersweconfiguredinthewebpack.config.jsfileunderstandhowtohandleCSS,C,andimagefiles.Nowthatwehavetherequiredresourcesavailable,wecanstartdefiningourcomponentstate.
ComponentstateThefollowingcodeinitializesthelocalstateinthedata()functionforourcomponent:
exportdefault{
data(){
return{
instance:null,
bounds:{width:800,height:592},
rect:{width:200,height:120},
speed:5
};
},
...
Althoughtheboundsandrectpropertiesneverchange,wedefinedtheminthelocalstatetokeepallthedatausedbythecomponentinasinglelocation.Thespeedpropertydictateshowquicklythespaceshipmovesacrossthe<canvas>andhasarangeof1to10.Theinstancepropertyisinitializedtonull,butwillbeusedtoaccessthecompiledWasmmodule'sexportedfunctions.Let'smoveontotheWasminitializationcodethatcompilestheWasmfileandpopulatesthe<canvas>.
WasminitializationThecodetocompiletheWasmfileandpopulatethe<canvas>elementisshownasfollows:
methods:{
//CreateanewImageinstancetopassintothedrawImagefunction
//forthe<canvas>element'scontext:
loadImage(imageSrc){
constloadedImage=newImage();
loadedImage.src=imageSrc;
returnnewPromise((resolve,reject)=>{
loadedImage.onload=()=>resolve(loadedImage);
loadedImage.onerror=()=>reject();
});
},
//Compile/loadthecontentsofmain.candassigntheresulting
//Wasmmoduleinstancetothecomponentsthis.instanceproperty:
asyncinitializeWasm(){
constctx=this.$refs.canvas.getContext('2d');
//CreateImageinstancesofthebackgroundandspaceship.
//Thesearerequiredtopassintothectx.drawImage()function:
const[bouncer,background]=awaitPromise.all([
this.loadImage(spaceshipImage),
this.loadImage(backgroundImage)
]);
//CompiletheCcodetoWasmandassigntheresulting
//module.exportstothis.instance:
const{width,height}=this.bounds;
returnwasm
.init(imports=>({
...imports,
_jsFillRect(x,y,w,h){
ctx.drawImage(bouncer,x,y,w,h);
},
_jsClearRect(){
ctx.drawImage(background,0,0,width,height);
}
}))
.then(module=>{
this.instance=module.exports;
returnPromise.resolve();
});
},
...
Thereareadditionalfunctionsdefinedinthemethodskeyofthecomponent,butfornowwe'llfocusonthecodethatcompilestheimportedCfiletoWasm.AfterImageinstancesarecreatedforthespaceshipandbackgroundimages,themain.cfile(importedas.wasm)iscompiledtoaWasmmoduleandtheresultingexportsis
assignedtothis.instance.Oncetheseoperationscomplete,thestart()functioncanbecalledfromtheexportedWasmmodule.SincetheinitializeWasm()functioncallsthe<canvas>element'sgetContext()function,thecomponentneedstobemountedbeforethisfunctioncanbecalled.Let'sreviewtherestofthemethodsdefinitionsandthemounted()eventhandler.
ComponentmountingTheremainingmethodsdefinitionsandmounted()eventhandlerfunctionareshownasfollows:
...
//Loopingfunctiontomovethespaceshipacrossthecanvas.
loopRectMotion(){
setTimeout(()=>{
this.instance.moveRect();
if(this.instance.getIsRunning())this.loopRectMotion();
},15-this.speed);
},
//Pauses/resumesthespaceship'smovementwhenthebuttonis
//clicked:
onActionClick(event){
constnewIsRunning=!this.instance.getIsRunning();
this.instance.setIsRunning(newIsRunning);
event.target.innerHTML=newIsRunning?'Pause':'Resume';
if(newIsRunning)this.loopRectMotion();
}
},
mounted(){
this.initializeWasm().then(()=>{
this.instance.start(
this.bounds.width,
this.bounds.height,
this.rect.width,
this.rect.height
);
this.loopRectMotion();
});
},
OncetheWasmmoduleiscompiled,thestart()functionisaccessibleonthis.instance.Theboundsandrectdimensionsarepassedintothestart()function,andthentheloopRectFunction()iscalledtostartmovingthespaceship.TheonActionClick()eventhandlerfunctionpausesorresumesthemovementofthespaceshipbasedonwhetherornotit'scurrentlyinmotion.
TheloopRectMotion()functionsinthesamewayastheexamplecodefromChapter5,CreatingandLoadingaWebAssemblyModule,exceptthespeedisnowadjustable.The15-this.speedcalculation,whichdictatesthetimeoutlength,maylookalittlestrange.Sincethemovementspeedoftheimageisbasedontheamountoftimethatelapsesbetweenfunctioncalls,increasingthisnumber
wouldactuallyslowdownthespaceship.Consequently,this.speedissubtractedfrom15,whichwaschosenbecauseit'sslightlygreaterthan10butwon'tturnthespaceshipintoablurifthis.speedisincreasedtothemaximum.That'sitforthecomponentlogic;let'smoveontotherenderingsectionofthecodewherethetemplateisdefined.
ComponentrenderingThecontentsofthetemplateproperty,whichdictateswhattorender,areshownasfollows:
template:`
<divclass="flexcolumn">
<h1>SPACEWASM!</h1>
<canvas
ref="canvas"
:height="bounds.height"
:width="bounds.width">
</canvas>
<divclass="flexcontrols">
<div>
<buttonclass="defaultText"@click="onActionClick">
Pause
</button>
</div>
<divclass="flexcolumn">
<labelclass="defaultText"for="speed">Speed:{{speed}}</label>
<input
v-model="speed"
id="speed"
type="range"
min="1"
max="10"
step="1">
</div>
</div>
</div>
Sincewe'reusingVue,wecanbindtheattributesandeventhandlersofHTMLelementstopropertiesandmethodsdefinedinourcomponent.InadditiontoaPAUSE/RESUMEbutton,there'sarange<input>thatallowsyoutochangethespeed.Byslidingittotheleftorright,you'reabletoslowdownorspeedupthespaceshipandseethechangesreflectedimmediately.Thatconcludesourreview;let'sseehowWebpackcanbeusedtobuildorruntheapplication.
<strong>npmrunbuild</strong>
Itmaytakeaminutetobuildtheprojectthefirsttimeyourunit.ThiscanbeattributedtotheWasmcompilationstep.However,subsequentbuildsshouldbemuchfaster.Ifthebuildwassuccessful,youshouldseeanewlycreated/distfolderwiththesecontents:├──/assets│├──background.jpg│└──spaceship.svg├──index.html├──main.css├──main.js└──main.wasm
TestingthebuildLet'stryoutthebuildtoensureeverythingisworkingcorrectly.Runthefollowingcommandinyourterminalinstancetostarttheapplication:serve-l8080dist
Ifyounavigatetohttp://127.0.0.1:8080/index.htmlinyourbrowser,youshouldseethis:
Webpackapplicationrunninginthebrowser
Thespaceshipimage(takenfromhttps://commons.wikimedia.org/wiki/File:Alien_Spaceship_-_SVG_Vector.svg)bouncesaroundwithintheboundsoftheHorseheadNebulabackgroundimage(takenfromhttps://commons.wikimedia.org/wiki/File:Horsehead_Nebula_Christmas_2017_Deography.jpg).WhenthePAUSEbuttonispressed,thebutton'scaptionchangestoRESUMEandtheshipstopsmoving.PressingthebuttonagainwillchangethecaptionbacktoPAUSEandtheshipwillstartmovingagain.AdjustingtheSPEEDsliderincreasesordecreasesthespeedoftheship.
RunningthestartscriptTheapplicationhasthewebpack-dev-serverlibraryinstalled,whichoperateslikeBrowsersync.ThelibraryusesLiveReloading,whichautomaticallyupdatestheapplicationwhenyoumakeanychangestothefilesin/src.Sincewe'reusingaWebpackloaderforCandC++files,theautomaticupdateeventwilltriggerifyouchangetheCfileaswell.Runthefollowingthecommandtostarttheapplicationandwatchforchanges:npmstart
Abrowserwindowshouldopenautomaticallywhenthebuildcompletes,andthendirectyoutotherunningapplication.Toseethelive-reloadingfeatureinaction,trysettingthevalueoftheisRunningvariableinthesetIsRunning()functioninmain.ctofalseinsteadofnewIsRunning:
EMSCRIPTEN_KEEPALIVE
voidsetIsRunning(boolnewIsRunning){
//isRunning=newIsRunning;
//Setthevaluetoalwaysfalse:
isRunning=false;
}
Thespaceshipshouldbestuckintheupper-leftcorner.Ifyouchangeitback,thespaceshipstartsmovingagain.Inthenextsection,wewillwriteunittestsinJavaScripttotestWebAssemblymodules.
TestingWebAssemblymoduleswithJestWell-testedcodepreventsregressionbugs,simplifiesrefactoring,andalleviatessomeofthefrustrationsthatgoalongwithaddingnewfeatures.Onceyou'vecompiledaWasmmodule,youshouldwriteteststoensureit'sfunctioningasexpected,evenifyou'vewrittentestsforC,C++,orRustcodeyoucompileditfrom.Inthissection,we'lluseJest,aJavaScripttestingframework,totestthefunctionsinacompiledWasmmodule.
ThecodebeingtestedAllofthecodeusedinthisexampleislocatedinthe/chapter-09-node/testing-examplefolder.Thecodeandcorrespondingtestsareverysimpleandarenotrepresentativeofreal-worldapplications,butthey'reintendedtodemonstratehowtouseJestfortesting.Thefollowingcoderepresentsthefilestructureofthe/testing-examplefolder:├──/src|├──/__tests__|│└──main.test.js|└──main.c├──package.json└──package-lock.json
ThecontentsoftheCfilethatwe'lltest,/src/main.c,isshownasfollows:
intaddTwoNumbers(intleftValue,intrightValue){
returnleftValue+rightValue;
}
floatdivideTwoNumbers(floatleftValue,floatrightValue){
returnleftValue/rightValue;
}
doublefindFactorial(floatvalue){
inti;
doublefactorial=1;
for(i=1;i<=value;i++){
factorial=factorial*i;
}
returnfactorial;
}
Allthreefunctionsinthefileareperformingsimplemathematicaloperations.Thepackage.jsonfileincludesascripttocompiletheCfiletoaWasmfilefortesting.RunthefollowingcommandtocompiletheCfile:npmrunbuild
Thereshouldbeafilenamedmain.wasminthe/srcdirectory.Let'smoveontodescribingthetestingconfigurationstep.
TestingconfigurationTheonlydependencywe'lluseforthisexampleisJest,aJavaScripttestingframeworkbuiltbyFacebook.Jestisanexcellentchoicefortestingbecauseitincludesmostofthefeaturesyou'llneedoutofthebox,suchascoverage,assertions,andmocking.Inmostcases,youcanuseitwithzeroconfiguration,dependingonthecomplexityofyourapplication.Ifyou'reinterestedinlearningmore,checkoutJest'swebsiteathttps://jestjs.io.Openaterminalinstanceinthe/chapter-09-node/testing-examplefolderandrunthefollowingcommandtoinstallJest:
npminstall
Inthepackage.jsonfile,therearethreeentriesinthescriptssection:build,pretest,andtest.Thebuildscriptexecutestheemcccommandwiththerequiredflagstocompile/src/main.cto/src/main.wasm.Thetestscriptexecutesthejestcommandwiththe--verboseflag,whichprovidesadditionaldetailsforeachofthetestsuites.Thepretestscriptsimplyrunsthebuildscripttoensure/src/main.wasmexistspriortorunninganytests.
TestsfilereviewLet'swalkthroughthetestfile,locatedat/src/__tests__/main.test.js,andreviewthepurposeofeachsectionofcode.Thefirstsectionofthetestfileinstantiatesthemain.wasmfileandassignstheresulttothelocalwasmInstancevariable:
constfs=require('fs');
constpath=require('path');
describe('main.wasmTests',()=>{
letwasmInstance;
beforeAll(async()=>{
constwasmPath=path.resolve(__dirname,'..','main.wasm');
constbuffer=fs.readFileSync(wasmPath);
constresults=awaitWebAssembly.instantiate(buffer,{
env:{
memoryBase:0,
tableBase:0,
memory:newWebAssembly.Memory({initial:1024}),
table:newWebAssembly.Table({initial:16,element:'anyfunc'}),
abort:console.log
}
});
wasmInstance=results.instance.exports;
});
...
Jestprovideslife-cyclemethodstoperformanysetuporteardownactionspriortorunningtests.Youcanspecifyfunctionstorunbeforeorafterallofthetests(beforeAll()/afterAll()),orbeforeoraftereachtest(beforeEach()/afterEach()).WeneedacompiledinstanceoftheWasmmodulefromwhichwecancallexportedfunctions,soweputtheinstantiationcodeinthebeforeAll()function.
We'rewrappingtheentiretestsuiteinadescribe()blockforthefile.Jestusesadescribe()functiontoencapsulatesuitesofrelatedtestsandtest()orit()torepresentasingletest.Here'sasimpleexampleofthisconcept:
constadd=(a,b)=>a+b;
describe('theaddfunction',()=>{
test('returns6when4and2arepassedin',()=>{
constresult=add(4,2);
expect(result).toEqual(6);
});
test('returns20when12and8arepassedin',()=>{
constresult=add(12,8);
expect(result).toEqual(20);
});
});
Thenextsectionofcodecontainsallthetestsuitesandtestsforeachexportedfunction:
...
describe('the_addTwoNumbersfunction',()=>{
test('returns300when100and200arepassedin',()=>{
constresult=wasmInstance._addTwoNumbers(100,200);
expect(result).toEqual(300);
});
test('returns-20when-10and-10arepassedin',()=>{
constresult=wasmInstance._addTwoNumbers(-10,-10);
expect(result).toEqual(-20);
});
});
describe('the_divideTwoNumbersfunction',()=>{
test.each([
[10,100,10],
[-2,-10,5],
])('returns%fwhen%fand%farepassedin',(expected,a,b)=>{
constresult=wasmInstance._divideTwoNumbers(a,b);
expect(result).toEqual(expected);
});
test('returns~3.77when20.75and5.5arepassedin',()=>{
constresult=wasmInstance._divideTwoNumbers(20.75,5.5);
expect(result).toBeCloseTo(3.77,2);
});
});
describe('the_findFactorialfunction',()=>{
test.each([
[120,5],
[362880,9.2],
])('returns%pwhen%pispassedin',(expected,input)=>{
constresult=wasmInstance._findFactorial(input);
expect(result).toEqual(expected);
});
});
});
Thefirstdescribe()block,forthe_addTwoNumbers()function,hastwotest()instancestoensurethatthefunctionreturnsthesumofthetwonumberspassedinasarguments.Thenexttwodescribe()blocks,forthe_divideTwoNumbers()and_findFactorial()functions,useJest's.eachfeature,whichallowsyoutorunthesametestwithdifferentdata.Theexpect()functionallowsyoutomakeassertionsonthevaluepassedinasanargument.The.toBeCloseTo()assertioninthelast_divideTwoNumbers()testcheckswhethertheresultiswithintwodecimalplacesof3.77.Therestusethe.toEqual()assertiontocheckforequality.
WritingtestswithJestisrelativelysimple,andrunningthemiseveneasier!Let'stryrunningourtestsandreviewingsomeoftheCLIflagsthatJestprovides.
RunningthetestsTorunthetests,openaterminalinstanceinthe/chapter-09-node/testing-examplefolderandrunthefollowingcommand:
npmtest
Youshouldseethefollowingoutputinyourterminal:
main.wasmTests
the_addTwoNumbersfunction
✓returns300when100and200arepassedin(4ms)✓returns-20when-10and-10arepassedinthe_divideTwoNumbersfunction
✓returns10when100and10arepassedin✓returns-2when-10and5arepassedin(1ms)✓returns~3.77when20.75and5.5arepassedinthe_findFactorialfunction
✓returns120when5ispassedin(1ms)✓returns362880when9.2ispassedin
TestSuites:1passed,1total
Tests:7passed,7total
Snapshots:0total
Time:1.008s
Ranalltestsuites.
Ifyouhavealargenumberoftests,youcouldremovethe--verboseflagfromthetestscriptinpackage.jsonandonlypasstheflagtothenpmtestcommandifneeded.ThereareseveralotherCLIflagsyoucanpasstothejestcommand.Thefollowinglistcontainssomeofthemorecommonlyusedflags:
--bail:Exitsthetestsuiteimmediatelyuponthefirstfailingtestsuite--coverage:Collectstestcoverageanddisplaysitintheterminalafterthetestshaverun--watch:Watchesfilesforchangesandrerunstestsrelatedtochangedfiles
Youcanpasstheseflagstothenpmtestcommandbyaddingthemaftera--.Forexample,ifyouwantedtousethe--bailflag,you'drunthiscommand:
npmtest----bail
YoucanviewtheentirelistofCLIoptionsontheofficialsiteathttps://jestjs.io/docs/en/cli.
SummaryInthischapter,wediscussedtheadvantagesofintegratingWebAssemblywithNode.jsanddemonstratedhowNode.jscouldbeusedontheserverandclientside.WeevaluatedanExpressapplicationthatusesaWasmmoduletoperformcalculationsonaccountingtransactions.Wethenreviewedabrowser-basedapplicationthatutilizesWebpacktoimportandcallfunctionsfromaCfilewithouthavingtowriteanyWasminstantiationcode.Finally,wesawhowtheJesttestingframeworkcanbeleveragedtotestacompiledmoduleandensureit'sfunctioningcorrectly.InChapter10,AdvancedToolsandUpcomingFeatures,we'llcoveradvancedtoolsanddiscussthefeaturesthatareonthehorizonforWebAssembly.
Questions1. WhatisoneoftheadvantagesofintegratingWebAssemblywithNode.js?2. WhatlibrarydoestheExpressapplicationusetoreadandwritedatatoa
JSONfile?3. Whatisthedifferencebetweenloadingamoduleinthebrowserandin
Node.js?4. Whattechniquecanyouusetorunannpmscriptbeforeorafteranexisting
npmscript?5. WhatisthenameofthetaskWebpackperformstoeliminatedeadcode?6. WhatisthepurposeofaloaderinWebpack?7. Whatisthedifferencebetweenthedescribe()andtest()functionsinJest?8. HowdoyoupassadditionalCLIflagstothenpmtestcommand?
FurtherreadingExpress:https://expressjs.comWebpack:https://webpack.js.orgJestAPI:https://jestjs.io/docs/en/api
AdvancedToolsandUpcomingFeaturesWebAssembly'secosystemisconstantlygrowingandevolving.DevelopershaveseenthepotentialforWebAssembly.TheybuildtoolstoimprovethedevelopmentexperienceoroutputWasmmodulesfromtheirlanguageofchoice(albeitwithsomelimitations).
Inthischapter,we'llevaluatetheunderlyingtechnologiesthatmakeWebAssemblytick.We'llalsoreviewtoolsyoucanuseinthebrowserandcoveranadvancedusecasethatutilizesWebWorkers.Finally,we'llquicklyreviewupcomingfeaturesandproposalsthatareontheroadmapforWebAssembly.
Ourgoalforthischapteristounderstandthefollowing:
HowWABTandBinaryenfitintothebuildprocessandwhattheycanbeusedforHowtocompileaWebAssemblymoduleusingLLVM(ratherthanEmscripten)OnlinetoolssuchasWasmFiddleandotherusefultoolingonlineHowtoutilizeWebWorkerstorunWebAssemblyinparallelUpcomingfeatures(proposedandinprogress)thatwillbeintegratedintoWebAssemblyinthefuture
WABTandBinaryenWABTandBinaryenallowdeveloperstoworkwithsourcefilesanddeveloptoolingforWebAssembly.Ifyou'reinterestedinworkingwithWebAssemblyatalowerlevel,thesetoolsprovidethemeansforaccomplishingsuchagoal.Inthissection,we'llevaluatethesetoolsingreaterdetailandreviewthepurposeandcapabilitiesofeachone.
WABT–theWebAssemblybinarytoolkitWABT'sfocusisonthemanipulationofWebAssemblybinary(.wasm)filesandtext(.wat)files,aswellasconversionbetweenthetwoformats.WABTprovidestoolstotranslateWattoWasm(wat2wasm)andviceversa(wasm2wat),aswellasatooltoconvertaWasmfiletoaCsourceandheaderfile(wasm2c).YoucanviewtheentirelistoftoolsintheREADMEfileoftheWABTGitHubrepositoryathttps://github.com/WebAssembly/wabt.
OneexampleusecaseofWABTistheWebAssemblyToolkitforVSCodeextensionweinstalledinChapter3,SettingUpaDevelopmentEnvironment.TheextensiondependsonWABTtoviewthetextformatassociatedwitha.wasmfile.Therepositoryprovideslinkstowat2wasmandwasm2watdemos,whichyoucanusetotestthevalidityofaWatprogramorinteractwithacompiledbinaryusingJavaScript.ThefollowingscreenshotcontainstheWatandJavaScriptinstantiationcodefromthewat2wasmdemo:
WatandJavaScriptloadingcodefromwat2wasm's"simple"example
Inline3oftheJSpanel,youmayhavenoticedthattheaddTwo()functionfromwasmInstance.exportsisn'tprefixedwitha_.Emscriptenaddsthe_automaticallyinthecompilationprocess.Youcouldomitthe_byconvertingthe.wasmfiletoa.wat,updatingthefunctionnames,andconvertingitbackto.wasmusingtheWABT,althoughthiswouldn'tbeverypractical.TheWABTsimplifiestheprocessoftransformingtextformattobinaryformatandviceversa.IfyouwanttobuildcompilationtoolingforWebAssembly,you'duseBinaryen,whichwewillcovernext.
BinaryenBinaryen'sGitHubpageathttps://github.com/WebAssembly/binaryendescribesBinaryenasacompilerandtoolchaininfrastructurelibraryforWebAssembly,writteninC++.ItaimstomakecompilingtoWebAssemblyeasy,fast,andeffective.ItachievestheseaimsbyprovidingasimpleCAPI,aninternalIR,andanoptimizer.JustaswiththeWABT,BinaryenprovidesanextensivesuiteoftoolsfordevelopingWebAssemblytooling.ThefollowinglistdescribesasubsetofthetoolsthatBinaryenprovides:
wasm-shell:ToolcapableofloadingandinterpretingWebAssemblyasm2wasm:Compilesasm.jscodetoaWasmmodulewasm2js:CompilesaWasmmoduletoJavaScriptwasm-merge:CombinesmultipleWasmfilesintoonewasm.js:JavaScriptlibrarythatincludestheBinaryeninterpreter,asm2wasm,theWatparser,andotherBinaryentoolsbinaryen.js:JavaScriptlibrarythatprovidesaJavaScriptinterfacefortheBinaryentoolchain
Thewasm.jsandbinaryen.jstoolsareofparticularinterestforJavaScriptdevelopersinterestedinbuildingWebAssemblytooling.Thebinaryen.jslibraryisavailableasannpmpackage(https://www.npmjs.com/package/binaryen).
Anexcellentexampleofbinaryen.jsusageisAssemblyScript(https://github.com/AssemblyScript/assemblyscript).AssemblyScriptisastrictlytypedsubsetofTypeScriptthatgeneratesWebAssemblymodules.ThelibrarycomespackagedwithaCLItoquicklyscaffoldnewprojectsandmanagethebuildstep.IntheCompilingwithLLVMsection,we'llcoverhowtocompileWasmmodulesusingLLVM.
CompilingwithLLVMInChapter1,WhatisWebAssembly?,wediscussedtherelationshipbetweenEmscripten'sEMSDKandLLVM.EmscriptenusesLLVMandClangtocompileC/C++downtoLLVMbitcode.TheEmscriptencompiler(emcc)compilesthatbitcodetoasm.js,whichispassedtoBinaryentogenerateaWasmfile.Ifyou'reinterestedinusingLLVM,youcancompileC/C++toWasmwithoutinstallingtheEMSDK.Inthissection,wewillreviewtheprocessforenablingWasmcompilationusingLLVM.AftercompilingsomeexampleC++codetoaWasmfile,we'lltryitoutinthebrowser.
TheinstallationprocessIfyouwanttocompileWebAssemblymodulesusingLLVM,severaltoolsneedtobeinstalledandconfigured.Gettingthesetoolsworkingtogethercorrectlycanbeanarduousandtime-consumingprocess.Fortunately,someonewentthroughthetroubleofmakingthisprocessmuchsimpler.DanielWirtzcreatedannpmpackagenamedwebassembly(https://www.npmjs.com/package/webassembly)thatcanperformthefollowingoperations(withthecorrespondingCLIcommands):
CompileC/C++codetoaWebAssemblymodule(wacompile)LinkmultipleWebAssemblymodulestoone(walink)DecompileaWebAssemblymoduletotextformat(wadisassemble)AssembleWebAssemblytextformattoamodule(waassemble)
ThelibraryisusingBinaryen,Clang,LLVM,andadditionalLLVMtoolsbehindthescenes.We'llinstallthispackagegloballytoensurewehaveaccesstothewacommand.Toinstall,openaterminalinstanceandrunthefollowingcommand:
npminstall-gwebassembly
Itmaytakeafewminutestoinstallanyrequireddependencies.Oncecomplete,runthefollowingcommandtovalidatetheinstallation:
wa
Youshouldseethefollowinginterminal:
TheexamplecodeTotestoutthecompiler,we'regoingtouseaslightlymodifiedversionofthewithout-glue.cfilefromtheInteractingwithJavaScriptwithoutgluecodesectionofChapter5,CreatingandLoadingaWebAssemblyModule.Thecodeforthissectionislocatedinthe/chapter-10-advanced-tools/compile-with-llvmdirectoryofthelearn-webassemblyrepository.Followthefollowinginstructionstocreatethefilesnecessaryforthecompilertest.Let'sstartwiththeC++file.
TheC++fileCreateanewdirectoryinyour/book-examplesdirectorynamed/compile-with-llvm.Createanewfileinthe/compile-with-llvmdirectorynamedmain.cppandpopulateitwiththefollowingcontents:
#include<stdbool.h>
#defineBOUNDS255
#defineRECT_SIDE50
#defineBOUNCE_POINT(BOUNDS-RECT_SIDE)
boolisRunning=true;
typedefstructRect{
intx;
inty;
chardirection;
}Rect;
structRectrect;
voidupdateRectLocation(){
if(rect.x==BOUNCE_POINT)rect.direction='L';
if(rect.x==0)rect.direction='R';
intincrementer=1;
if(rect.direction=='L')incrementer=-1;
rect.x=rect.x+incrementer;
rect.y=rect.y+incrementer;
}
extern"C"{
externintjsClearRect();
externintjsFillRect(intx,inty,intwidth,intheight);
__attribute__((visibility("default")))
voidmoveRect(){
jsClearRect();
updateRectLocation();
jsFillRect(rect.x,rect.y,RECT_SIDE,RECT_SIDE);
}
__attribute__((visibility("default")))
boolgetIsRunning(){
returnisRunning;
}
__attribute__((visibility("default")))
voidsetIsRunning(boolnewIsRunning){
isRunning=newIsRunning;
}
__attribute__((visibility("default")))
voidinit(){
rect.x=0;
rect.y=0;
rect.direction='R';
setIsRunning(true);
}
}
Thecodeinthisfileisalmostidenticaltothecontentsofwithout-glue.cfromChapter5,CreatingandLoadingaWebAssemblyModule.Thecommentshavebeenremovedfromthefileandtheimported/exportedfunctionsarewrappedinanextern"C"block.The__attribute__((visibility("default")))linesaremacrostatements(similartoEMSCRIPTEN_KEEPALIVE)thatensurethefunctionsaren'tremovedfromthecompiledoutputduringthedead-codeeliminationstep.Justaswithpriorexamples,we'llinteractwiththecompiledWasmmodulethroughanHTMLfile.Let'screatethatnext.
TheHTMLfileCreateafilenamedindex.htmlinthe/compile-with-llvmdirectoryandpopulateitwiththefollowingcontents:
<!doctypehtml>
<htmllang="en-us">
<head>
<title>LLVMTest</title>
</head>
<body>
<h1>LLVMTest</h1>
<canvasid="myCanvas"width="255"height="255"></canvas>
<divstyle="margin-top:16px;">
<buttonid="actionButton"style="width:100px;height:24px;">
Pause
</button>
</div>
<scripttype="application/javascript">
constcanvas=document.querySelector('#myCanvas');
constctx=canvas.getContext('2d');
constimportObj={
env:{
memoryBase:0,
tableBase:0,
memory:newWebAssembly.Memory({initial:256}),
table:newWebAssembly.Table({initial:8,element:'anyfunc'}),
abort:console.log,
jsFillRect:function(x,y,w,h){
ctx.fillStyle='#0000ff';
ctx.fillRect(x,y,w,h);
},
jsClearRect:function(){
ctx.fillStyle='#ff0000';
ctx.fillRect(0,0,255,255);
}
}
};
WebAssembly.instantiateStreaming(fetch('main.wasm'),importObj)
.then(({instance})=>{
constm=instance.exports;
m.init();
constloopRectMotion=()=>{
setTimeout(()=>{
m.moveRect();
if(m.getIsRunning())loopRectMotion();
},20)
};
document.querySelector('#actionButton')
.addEventListener('click',event=>{
constnewIsRunning=!m.getIsRunning();
m.setIsRunning(newIsRunning);
event.target.innerHTML=newIsRunning?'Pause':'Start';
if(newIsRunning)loopRectMotion();
});
loopRectMotion();
});
</script>
</body>
</html>
Thecontentsofthisfileareverysimilartothewithout-glue.htmlfilefromChapter5,CreatingandLoadingaWebAssemblyModule.InsteadofusingtheloadWasm()functionfromthe/common/load-wasm.jsfile,we'reusingtheWebAssembly.instantiateStreaming()function.Thisallowsustoomitanadditional<script>elementandservethefilesdirectlyfromthe/compile-with-llvmdirectory.
The_isomittedfromthejsFillRectandjsClearRectfunctionspassedintotheimportObj.Wecanomitthe_forthefunctionspresentontheinstance.exportsobjectaswell.LLVMdoesn'tprefixanyofthedata/functionspassedinoroutofthemodulewitha_.Inthenextsection,we'llcompilemain.cppandinteractwiththeresultantWasmfileinthebrowser.
CompilingandrunningtheexampleWeinstalledthewebassemblynpmpackagewiththe-gflag,sothewacommandshouldbeavailableintheterminal.Openaterminalinstanceinthe/compile-with-llvmdirectoryandrunthefollowingcommand:
wacompilemain.cpp-omain.wasm
Youshouldseeafilenamedmain.wasmappearinthecompile-with-llvmfolderofVSCode'sfileexplorer.ToensuretheWasmmodulecompiledcorrectly,runthefollowingcommandwithinthe/compile-with-llvmdirectory:
serve-l8080
Ifyounavigatetohttp://127.0.0.1:8080/index.htmlinyourbrowser,youshouldseethefollowing:
LLVMcompiledmodulerunninginthebrowser
OnlinetoolingTheinstallationandconfigurationprocessforcompilingWebAssemblymoduleslocallyis,admittedly,alittlecumbersome.Fortunately,thereareseveralonlinetoolsavailablethatallowyoutodevelopandinteractwithWebAssemblyinthebrowser.Inthissection,we'llreviewthosetoolsanddiscussthefunctionalityeachoneprovides.
WasmFiddleIntheConnectingthedotswithWasmFiddlesectioninChapter2,ElementsofWebAssembly-Wat,Wasm,andtheJavaScriptAPI,weusedWasmFiddletocompileasimpleCfunctiontoWasmandinteractwithitusingJavaScript.WasmFiddleprovidesaC/C++editor,JavaScripteditor,Wat/x86viewer,andJavaScriptoutputpanel.Youcanalsointeractwiththe<canvas>ifdesired.WasmFiddleusesLLVMtogeneratetheWasmmodules,whichiswhytheimportsandexportsaren'tprefixedwitha_.YoucaninteractwithWasmFiddleathttps://wasdk.github.io/WasmFiddle.
WebAssemblyExplorerWebAssemblyExplorer,locatedathttps://mbebenita.github.io/WasmExplorer,providessimilarfunctionalitytoWasmFiddle.ItallowsyoutocompileCorC++toaWasmmoduleandviewthecorrespondingWat.However,WebAssemblyExplorerprovidesadditionalfunctionalitynotpresentinWasmFiddle.Forexample,youcancompileCorC++toWasmandviewthecorrespondingFirefoxx86andLLVMx86code.Youcanselectfromalistofcodeexamplesandspecifytheoptimizationlevel(-Oflaginemcc).ItalsoprovidesabuttonthatallowsyoutoimportthecodeintoWasmFiddle:
ScreenshotofWebAssemblyExplorer
WebAssemblyStudioWebAssemblyStudio,locatedathttps://webassembly.studio,isafeature-richeditoranddevelopmentenvironment.YoucancreateC,Rust,andAssemblyScriptprojects.ItprovidesthecapabilitiestobuildandruncodewithinthebrowserandintegrateswellwithGitHub.WebAssemblyStudioenablesyoutobuildawebapplicationwithouthavingtoinstallandconfiguretherequiredWebAssemblytoolinglocally:
ScreenshotofWebAssemblyStudio
Inthenextsection,we'lldemonstratehowtoaddparallelismtoyourWebAssemblyapplicationwithWebWorkers.
ParallelWasmwithWebWorkersTheprocessofbuildingacomplexapplicationthatperformsheavycomputationorotherresource-intensiveworkcanbenefitgreatlyfromusingthreads.Threadsallowyoutoperformoperationsinparallelbydividingfunctionalityamongtasksthatrunindependently.Atofwritingthis,supportforthreadsinWebAssemblyisintheFeatureProposalphase.Inthisphase,thespecificationhasn'tbeenwrittenandthefeatureisn'timplemented.Fortunately,JavaScriptprovidesthreadingcapabilitiesintheformofWebWorkers.Inthissection,we'lldemonstratehowtouseJavaScript'sWebWorkersAPItointeractwithWasmmodulesinseparatethreads.
WebWorkersandWebAssemblyWebWorkersallowyoutoutilizethreadsinthebrowser,whichcanimprovetheperformanceofyourapplicationbyoffloadingsomeofthelogicfromthemain(UI)thread.WorkerthreadsarealsocapableofperformingI/OusingXMLHttpRequest.Workerthreadscommunicatewiththemainthreadbypostingmessagestoaneventhandler.
WebWorkersallowustoloadWasmmodulesintoseparatethreadsandperformoperationsthatdon'thindertheperformanceoftheUI.WebWorkersdohavesomelimitations.They'reunabletodirectlymanipulatetheDOMoraccesssomeofthemethodsandpropertiesonthewindowobject.Themessagespassedbetweenthreadsmustbeserializedobjects,whichmeansyoucan'tpassfunctions.Nowthatyouknowwhataworkeris,let'sdiscusshowtocreateone.
CreatingaworkerBeforeyoucancreateaworker,youneedaJavaScriptfilewithcodethatrunsintheworkerthread.Youcanseeasimpleexampleofaworkerdefinitionfileathttps://github.com/mdn/simple-web-worker/blob/gh-pages/worker.js.Thefileshouldcontainamessageeventlistenerthatperformsoperationswhenmessagesarereceivedfromotherthreadsandrespondsaccordingly.
Oncethatfileiscreated,you'rereadytouseitwithaworker.AworkeriscreatedbypassingaURLargumenttotheWorker()constructor.TheURLcanbeastringrepresentingthenameofthefilewithyourworkerdefinitioncode,orconstructedusingaBlob.TheBlobtechniquecanbeusefulifyou'refetchingtheworkerdefinitioncodefromaserver.Theexampleapplicationdemonstrateshowtousebothapproaches.Let'smoveontotheprocessofintegratingWebAssemblywithWebWorkers.
TheWebAssemblyworkflowInordertoutilizeWasmmodulesinseparatethreads,theWasmfilemustbecompiledinthemainthreadandinstantiatedinaWebWorker.Let'sreviewthisprocessinmoredetail:
1. AnewWebWorker(we'llrefertoitaswasmWorker)iscreatedusingtheWorker()constructor.
2. Afetchcallismadetoretrievea.wasmfileandthearrayBuffer()functioniscalledontheresponse.
3. TheresolvedvalueofthearrayBuffer()functionispassedtotheWebAssembly.compile()function.
4. TheWebAssembly.compile()functionresolveswithaWebAssembly.Moduleinstance,whichisincludedinthebodyofamessagepostedtothewasmWorkerusingthepostMessage()function.
5. WithinwasmWorker,theWebAssembly.ModuleinstancefromthemessagebodyispassedtotheWebAssembly.instantiate()function,whichresolveswithaWebAssembly.Instance.
6. TheWebAssembly.InstanceexportsobjectisassignedtoalocalvariableinwasmWorkerandisusedtocallWasmfunctions.
TocallafunctionfromthewasmWorkerWasminstance,youpostamessagetotheworkerthreadwithanyargumentstopasstotheWasmfunction.Then,wasmWorkerexecutesthefunctionandpassestheresultsbacktothemainthread.That'sthecruxofhowthreadsareutilizedinthecontextofWebWorkers.Beforewemoveontotheexampleapplication,youmayneedtoaddressalimitationthatGoogleChromeimposes.FollowtheinstructionsintheLimitationsinGoogleChromesectiontoensuretheexampleapplicationworkssuccessfully.
LimitationsinGoogleChromeGoogleChromeplacesarestrictiononwhatcanbeincludedinthebodyofaWebWorker'spostMessage()function.IfyoutriedtosendacompiledWebAssembly.Moduletoaworker,you'dgetanerrorandtheoperationwouldbeunsuccessful.Youcanoverridethisbysettingaflag.Toenablethisfunctionality,openGoogleChromeandenterchrome://flagsintheaddressbar.Typecloninginthesearchboxatthetopofthepage.YoushouldseealistitemtitledWebAssemblystructuredcloningsupport.SelecttheEnabledoptionfromthedropdownnexttothelistitemandpresstheRELAUNCHNOWbuttonwhenprompted:
UpdatingtheWebAssemblyflaginGoogleChrome
AfterChromerestarts,youcanruntheexampleapplicationwithoutissue.Ifyou'reusingMozillaFirefox,noactionisrequired.Itsupportsthisfeaturebydefault.Let'smoveontotheexampleapplicationthatdemonstratestheuseofWebAssemblyinthreads.
OverviewofthecodeTheexampleapplicationisn'tmuchofanapplication.It'sasimpleformthatacceptstwoinputvaluesandreturnsthesumordifferenceofthesetwovalues.TheaddandsubtractoperationsareeachexportedfromtheirownWasmmoduleinstantiatedinaworkerthread.Theexamplemaybecontrived,butiteffectivelydemonstrateshowtointegrateWebAssemblyintoWebWorkers.
Thecodeforthissectionislocatedinthe/chapter-10-advanced-tools/parallel-wasmdirectoryofthelearn-webassemblyrepository.Thefollowingsectionswalkthrougheachsectionofthecodebaseanddescribehowtobuildtheapplicationfromscratch.Ifyouwishtofollowalong,createafolderinyour/book-examplesdirectorynamed/parallel-wasm.
TheCcodeTheexampleusestwoworkerthreads:oneforadditionandanotherforsubtraction.Consequently,we'llneedtwoseparateWasmmodules.Createafoldernamed/libinyour/parallel-wasmdirectory.Withinthe/libdirectory,createafilenamedadd.candpopulateitwiththefollowingcontents:
intcalculate(intfirstVal,intsecondVal){
returnfirstVal+secondVal;
}
Createanotherfilein/libnamedsubtract.candpopulateitwiththefollowingcontents:
intcalculate(intfirstVal,intsecondVal){
returnfirstVal-secondVal;
}
Notethatthefunctionnameinbothfilesiscalculate.Thiswasdonesowedon'thavetowriteanyconditionallogicwithintheworkercodetodeterminetheWasmfunctiontocall.Thealgebraicoperationistiedtoaworker,sowhenweneedtoaddtwonumbers,the_calculate()functionwillbecalledintheaddWorker.ThiswillbecomeclearerwhenwereviewtheJavaScriptportionofthecode,whichwe'llcovernext.
TheJavaScriptcodeBeforewedigintotheJavaScriptcode,createafoldernamed/srcinyour/parallel-wasmdirectory.Let'sstartwiththefilecontainingthecodethatrunsintheworkerthread.
Definingthreadexecutioninworker.jsCreateanewfileinthe/srcdirectorynamedworker.jsandpopulateitwiththefollowingcontents:
varwasmInstance=null;
self.addEventListener('message',event=>{
/**
*OncetheWebAssemblycompilationiscomplete,thispostsamessage
*backwithwhetherornottheinstantiationwassuccessful.Ifthe
*payloadisnull,thecompilationsucceeded.
*/
constsendCompilationMessage=(error=null)=>{
self.postMessage({
type:'COMPILE_WASM_RESPONSE',
payload:error
});
};
const{type,payload}=event.data;
switch(type){
//InstantiatesthecompiledWasmmoduleandpostsamessagebackto
//themainthreadindicatingiftheinstantiationwassuccessful:
case'COMPILE_WASM_REQUEST':
constimportObj={
env:{
memoryBase:0,
tableBase:0,
memory:newWebAssembly.Memory({initial:256}),
table:newWebAssembly.Table({initial:2,element:'anyfunc'}),
abort:console.log
}
};
WebAssembly.instantiate(payload,importObj)
.then(instance=>{
wasmInstance=instance.exports;
sendCompilationMessage();
})
.catch(error=>{
sendCompilationMessage(error);
});
break;
//Callsthe`calculate`methodassociatedwiththeinstance(addor
//subtract,andpoststheresultbacktothemainthread:
case'CALC_REQUEST':
const{firstVal,secondVal}=payload;
constresult=wasmInstance._calculate(firstVal,secondVal);
self.postMessage({
type:'CALC_RESPONSE',
payload:result
});
break;
default:
break;
}
},false);
Thecodeisencapsulatedwithintheeventlistenerforthemessageevent(self.addEventListener(...)),whichisraisedwhenthepostMessage()functioniscalledonthecorrespondingworker.Theeventparameterintheeventlistener'scallbackfunctioncontainsadatapropertywiththecontentsofthemessage.AllofthemessagespassedbetweenthreadsintheapplicationfollowtheFluxStandardAction(FSA)convention.Objectsthatadheretothisconventionhaveatypeandpayloadproperty,wheretypeisastringandpayloadcanbeofanytype.YoucanreadmoreabouttheFSAathttps://github.com/redux-utilities/flux-standard-action.
YoucanuseanyformatorstructureforthedatayoupassusingthepostMessage()function,aslongasthedataisserializable.
Theswitchstatementexecutesanactionbasedonthemessage'stypevalue,whichisastring.Ifthetypeis'COMPILE_WASM_REQUEST',theWebAssembly.instantiate()functioniscalledwiththepayloadfromthemessageandimportObj.TheexportsobjectoftheresultisassignedtothelocalwasmInstancevariableforlateruse.Ifthetypeis'CALC_REQUEST',thewasmInstance._calculate()functioniscalledwiththefirstValandsecondValvaluesfromthepayloadobject.Thecalculationcodeshouldshedsomelightonwhythefunctionwasnamed_calculate()insteadof_add()or_subtract().Byusingageneralname,theworkerdoesn'tcarewhatoperationit'sperforming,itjustcallsthefunctiontogettheresult.
Inbothcases,theworkerpostsamessagebacktothemainthreadusingthepostMessage()function.IusedaREQUEST/RESPONSEconventionforthetypepropertyvalue.Thisallowsyoutoquicklyidentifywhichthreadthemessagesareoriginatingfrom.Messagessentfromthemainthreadendwith_REQUESTinthetypewhileresponsescomingfromtheworkerthreadsendwith_RESPONSE.Let'smoveontotheWebAssemblyinteractioncode.
InteractingwithWasminWasmWorker.jsCreateanewfileinthe/srcdirectorynamedWasmWorker.jsandpopulateitwiththefollowingcontents:/***WebWorkerassociatedwithaninstantiatedWasmmodule.*@class*/exportdefaultclassWasmWorker{constructor(workerUrl){this.worker=newWorker(workerUrl);this.listenersByType={};this.addListeners();}
//Addalistenerassociatedwiththe`type`valuefromthe//Workermessage:addListenerForType(type,listener){this.listenersByType[type]=listener;}
//Addeventlistenersforerrorandmessagehandling.addListeners(){this.worker.addEventListener('error',event=>{console.error(`%cError:${event.message}`,'color:red;');},false);
//Ifahandlerwasspecifiedusingthe`addListener`method,//firethatmethodifthe`type`matches:this.worker.addEventListener('message',event=>{if(event.datainstanceofObject&&event.data.hasOwnProperty('type')&&event.data.hasOwnProperty('payload')
){const{type,payload}=event.data;if(this.listenersByType[type]){this.listenersByType[type](payload);}}else{console.log(event.data);}},false);}
//FetchestheWasmfile,compilesit,andpassesthecompiledresult//tothecorrespondingworker.Thecompiledmoduleisinstantiated//intheworker.initialize(name){returnfetch(`calc-${name}.wasm`).then(response=>response.arrayBuffer()).then(bytes=>WebAssembly.compile(bytes)).then(wasmModule=>{this.worker.postMessage({type:'COMPILE_WASM_REQUEST',payload:wasmModule});returnPromise.resolve();});}
//Postsamessagetotheworkerthreadtocallthe`calculate`//methodfromtheWasminstance:calculate(firstVal,secondVal){this.worker.postMessage({type:'CALC_REQUEST',payload:{firstVal,secondVal}});
}}
TheWasmWorkerclassmanagesaworkerthreadassociatedwithaWasmfile.IntheWasmWorkerconstructor,anewWorkeriscreatedanddefaulteventlistenersareaddedfortheerrorandmessageevents.Theinitialize()functionfetchesthe.wasmfileassociatedwiththenameargument,compilesit,andsendstheresultantWebAssembly.Moduleinstancetotheworkerthreadtobeinstantiated.
TheaddListenerForType()functionisusedtospecifyacallbackfunction(listener)toexecutewhenthetypefieldinthemessageresponsematchesthetypeargumentpassedtothefunction.Thisisrequiredtocapturetheresultofthe_calculate()functionfromtheworkerthread.
Finally,thecalculate()functioninWasmWorkerpostsamessagetotheworkerthreadwiththefirstValandsecondValargumentspassedinfromthe<input>elementsonthe<form>.Let'smoveontotheapplicationloadingcodetoseehowWasmWorkerinteractswiththeUI.
Loadingtheapplicationinindex.jsCreateanewfileinthe/srcdirectorynamedindex.jsandpopulateitwiththefollowingcontents:
importWasmWorkerfrom'./WasmWorker.js';
/**
*Ifyouadd?blob=truetotheendoftheURL(e.g.
*http://localhost:8080/index.html?blob=true),theworkerwillbe
*createdfromaBlobratherthanaURL.Thisreturnsthe
*URLtousefortheWorkereitherasastringorcreatedfromaBlob.
*/
constgetWorkerUrl=async()=>{
consturl=newURL(window.location);
constisBlob=url.searchParams.get('blob');
varworkerUrl='worker.js';
document.title='WasmWorker(StringURL)';
//CreateaBlobinstancefromthetextcontentsof`worker.js`:
if(isBlob==='true'){
constresponse=awaitfetch('worker.js');
constresults=awaitresponse.text();
constworkerBlob=newBlob([results]);
workerUrl=window.URL.createObjectURL(workerBlob);
document.title='WasmWorker(BlobURL)';
}
returnPromise.resolve(workerUrl);
};
/**
*InstantiatestheWasmmoduleassociatedwiththespecifiedworker
*andaddseventlistenerstothe"Add"and"Subtract"buttons.
*/
constinitializeWorker=async(wasmWorker,name)=>{
awaitwasmWorker.initialize(name);
wasmWorker.addListenerForType('CALC_RESPONSE',payload=>{
document.querySelector('#result').value=payload;
});
document.querySelector(`#${name}`).addEventListener('click',()=>{
constinputs=document.querySelectorAll('input');
var[firstInput,secondInput]=inputs.values();
wasmWorker.calculate(+firstInput.value,+secondInput.value);
});
};
/**
*Spawns(2)workers:oneassociatedwithcalc-add.wasmandanother
*withcalc-subtract.wasm.Addsaneventlistenertothe"Reset"
*buttontoclearalltheinputvalues.
*/
constloadPage=async()=>{
document.querySelector('#reset').addEventListener('click',()=>{
constinputs=document.querySelectorAll('input');
inputs.forEach(input=>(input.value=0));
});
constworkerUrl=awaitgetWorkerUrl();
constaddWorker=newWasmWorker(workerUrl);
awaitinitializeWorker(addWorker,'add');
constsubtractWorker=newWasmWorker(workerUrl);
awaitinitializeWorker(subtractWorker,'subtract');
};
loadPage()
.then(()=>console.log('%cPageloaded!','color:green;'))
.catch(error=>console.error(error));
TheapplicationentrypointistheloadPage()function.Beforewedigintotheworkerinitializationcode,let'sdiscussthegetWorkerUrl()function.Earlierinthissection,welearnedthatyoucanpassastringrepresentingafilenameoraURLcreatedfromaBlobtotheWorker()constructor.Thefollowingexamplecodedemonstratesthefirsttechnique:
varworker=newWorker('worker.js');
Thesecondtechniqueisdemonstratedintheif(isBlob==='true')blockofthegetWorkerUrl()function.Ifthecurrentwindow.locationvalueendswith?blob=true,theURLpassedtotheWorker()constructoriscreatedfromaBlob.Theonlynoticeabledifferenceisthedocument.titlevalue,whichupdatestoreflecttheURLtype.Let'sjumpbacktotheloadPage()functiontodiscusstheinitializationcode.
AfteraneventlistenerisaddedtotheResetbuttonintheloadPage()function,twoWasmWorkerinstancesarecreated:addWorkerandsubtractWorker.EachworkerispassedtotheinitializeWorker()functionasthewasmWorkerargument.IninitializeWorker(),thewasmWorker.initialize()functioniscalledtoinstantiatetheWasmmodule.ThewasmWorker.addListenerForType()functioniscalledtosetthevalueoftheResult<input>tothevaluereturnedfromthe_calculate()functioninthecorrespondingworker.Finally,aneventlistenerisaddedtotheclickeventofthe<button>thateitheraddsorsubtractsthefirstValandsecondVal<input>values(basedonthenameargument).That'sitfortheJavaScriptcode.Let'screateanHTMLandCSSfile,thenmoveontothebuildstep.
ThewebassetsWeneedanHTMLfiletoactastheentrypointtotheapplication.Createafileinthe/srcdirectorynamedindex.htmlandpopulateitwiththefollowingcontents:
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>WasmWorkers</title>
<linkrel="stylesheet"type="text/css"href="styles.css"/>
</head>
<body>
<formclass="valueForm">
<divclass="valueForm">
<labelfor="firstVal">FirstValue:</label>
<inputid="firstVal"type="number"value="0"/>
</div>
<divclass="valueForm">
<labelfor="secondVal">SecondValue:</label>
<inputid="secondVal"type="number"value="0"/>
</div>
<divclass="valueForm">
<labelfor="result">Result:</label>
<inputid="result"type="number"value="0"readonly/>
</div>
</form>
<div>
<buttonid="add">Add</button>
<buttonid="subtract">Subtract</button>
<buttonid="reset">Reset</button>
</div>
<scripttype="module"src="index.js"></script>
</body>
</html>
Theapplicationconsistsofa<form>withthree<input>elementsandablockofthree<button>elements.Thefirsttwo<input>elementscorrespondtothefirstValandsecondValpropertiesincludedinthepayloadsenttoeitherworkerthread.Thefinal<input>isread-onlyanddisplaystheresultofeitheroperation.
Theblockof<button>elementsbelowthe<form>performoperationsonthe<input>values.Thefirsttwo<button>elementssendthe<input>valuestoeithertheaddWorkerorsubtractWorkerthread(dependingonwhichbuttonwaspressed).Thefinal<button>setsallofthe<input>valuesto0.
Theapplicationisinitializedinthe<script>taginthelastlinebeforethe</body>closingtag.JustaswithCooktheBooks,thetype="module"attributeallowsusto
usetheimport/exportsyntaxavailableinnewerbrowsers.Finally,weneedtoaddsomestylestotheapplication.Createafileinthe/srcdirectorynamedstyles.cssandpopulateitwiththefollowingcontents:
*{
font-family:sans-serif;
font-size:14px;
}
body{
margin:16px;
}
form.valueForm{
display:table;
}
div.valueForm{
display:table-row;
}
label,input{
display:table-cell;
margin-bottom:16px;
}
label{
font-weight:bold;
padding-right:16px;
}
button{
border:1pxsolidblack;
border-radius:4px;
cursor:pointer;
font-weight:bold;
height:24px;
margin-right:4px;
width:80px;
}
button:hover{
background:lightgray;
}
That'sthelastfileweneedtocreate,butnotthelastonerequiredtoruntheapplication.WestillneedtogenerateWasmfilesfromtheCfilesinthe/libdirectory.Let'smoveontothebuildstep.
BuildingandrunningtheapplicationWiththecodewritten,it'stimetobuildandtesttheapplication.Aftercompletingthebuildstep,we'llinteractwiththerunningapplicationandreviewhowtotroubleshootWebWorkersusingthebrowser'sdevelopmenttools.
CompilingtheCfilesWeneedtocompileeachCfiletoaseparate.wasmfile,whichmeansthecommandneededtoperformthecompilationstepisverbose.Toperformthebuild,openaterminalinstanceinyour/parallel-wasmdirectoryandrunthefollowingcommands:
#First,compiletheadd.cfile:
emcc-Os-sWASM=1-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0lib/add.c-osrc/calc-add.wasm
#Next,compilethesubtract.cfile
emcc-Os-sWASM=1-sSIDE_MODULE=1-sBINARYEN_ASYNC_COMPILATION=0lib/subtract.c-osrc/calc-subtract.wasm
Youshouldseetwonewfilesinthe/srcdirectory:calc-add.wasmandcalc-subtract.wasm.Withtherequiredfilesinplace,it'stimetotestouttheapplication.
InteractingwiththeapplicationOpenaterminalinstanceinthe/parallel-wasmdirectoryandrunthefollowingcommand:serve-l8080src
Ifyounavigatetohttp://127.0.0.1:8080/index.htmlinyourbrowser,youshouldsee
this:
WasmWorkersapplicationrunninginthebrowserTrychangingthevaluesintheFirstValueandSecondValueinputsandpressingtheAddandSubtractbuttons.TheResultinputshouldupdatewiththecalculatedresult.Ifyounavigateto
http://127.0.0.1:8080/index.html?blob=true,theURLargumentpassedtotheWorker()constructorwilluseaBlobinsteadofthefilename.ThetabshouldchangetoreflectthattheBlobtechniqueisusedtoconstructtheURL:
TabtitleupdatedtoreflecttheBlobURLtechnique
DebuggingWebWorkersYoucansetbreakpointsandinteractwithworkerthreadsusingthebrowser'sdevelopmenttools.InGoogleChrome,openDeveloperToolsandselecttheSourcestab.Thefilelistpanelshouldcontaintwoinstancesofworker.js.ThedebuggerpanelcontainsaThreadssectionwiththemainthreadandtwoworker.jsthreads.ThefollowingscreenshotindicatesthethreaddebuggingelementswithintheChromeDeveloperToolspanelfortherunningapplication:
ThreaddebuggingtoolsintheChromeDeveloperToolspanel
InFirefox,workerdebuggingisdoneinseparateDeveloperToolswindows.Toseethisinaction,openDeveloperToolsinFirefoxandselecttheDebuggerpanel.Clickononeoftheworker.jslistitemsintheWorkerspanel.AnewDeveloperToolswindowshouldappearthatcorrespondswiththeselectedworker.ThefollowingscreenshotshowsaseparateDeveloperToolswindowforoneoftheworker.jsinstancesselectedfromtheWorkerspanel:
ThreaddebuggingtoolsintheFirefoxDeveloperToolspanelInthenextsection,we'lldiscusssomeoftheupcomingfeaturesofWebAssembly.
UpcomingfeaturesThereareseveralupcomingWebAssemblyfeaturesinvariousphasesofthestandardizationprocess.Someofthemaremoreimpactfulthanothers,butallofthemarevaluableimprovements.Inthissection,we'lldescribethestandardizationprocessandreviewasubsetofthefeaturesthatrepresentasignificantshiftinWebAssembly'scapabilities.MostofthecontentinthissectionwasreferencedfromColinEberhardt'sblogposttitledThefutureofWebAssembly-Alookatupcomingfeaturesandproposals.Thepostcanbefoundathttps://blog.scottlogic.com/2018/07/20/wasm-future.html.
ThestandardizationprocessTheWebAssemblyW3CProcessdocumentationathttps://github.com/WebAssembly/meetings/blob/master/process/phases.mddescribesthesixphases(from0to5)ofthestandardizationprocess.Thefollowinglistprovidesbriefdescriptionsofeachofthesephases:
Phase0.Pre-Proposal:AWebAssemblyCommunityGroup(CG)memberhasanidea,andtheCGvotesonwhethertomoveittoPhase1.Phase1.FeatureProposal:Thepre-proposalprocesshassucceededandarepositoryiscreatedintheWebAssemblyorganizationonGitHubtodocumentthefeature.Phase2.ProposedSpecTextAvailable:Thefullproposedspectextisavailable,possibleimplementationsareprototyped,andatestsuiteisadded.Phase3.ImplementationPhase:Embeddersimplementthefeature,therepositoryisupdatedtoincluderevisionstotheformalization,andthespecisupdatedtoincludeimplementationofthefeatureinthereferenceinterpreter.Phase4.StandardizetheFeature:IftwoormoreWebVMsandatleastonetoolchainimplementthefeature,thefeatureisfullyhandedofftotheWebAssemblyWorkingGroup(WG).Phase5.TheFeatureisStandardized:TheWGmembershavereachedconsensusthatthefeatureiscomplete.
Nowthatyou'refamiliarwiththephasesassociatedwiththestandardizationprocess,let'smoveontothethreadsproposal.
ThreadsIntheprevioussection,weusedWebWorkerstomoveWasmmodulesintoworkerthreads,whichallowedustocallWasmfunctionswithoutblockingthemainthread.However,passingmessagesbetweenworkerthreadshasperformancelimitations.Inanefforttoaddressthisissue,athreadsfeaturewasproposedforWebAssembly.
Theproposal,currentlyinPhase1,isdescribedindetailathttps://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md.Pertheproposaldocumentation,thethreadsfeatureaddsanewsharedlinearmemorytypeandsomenewoperationsforatomicmemoryaccess.Thisproposalisrelativelylimitedinscope.Eberhardtprovidesthefollowingelaborationinhisblogpost:
"Notably,thisproposaldoesnotintroduceamechanismforcreatingthreads(whichhascausedalotofdebate)insteadthisfunctionalityissuppliedbythehost.WithinthecontextofwasmexecutedbythebrowserthiswillbethefamiliarWebWorkers."
Althoughthefeaturewouldn'tallowforthecreationofthreads,itprovidesasimplerwayofsharingdatabetweentheworkerthreadswecreateinJavaScript.
HostbindingsThehostbindingsproposal,whichisalsoinPhase1,wouldaddressasignificantlimitationofWebAssemblywhenusedinthebrowser:DOMmanipulation.Theproposaldocumentationathttps://github.com/WebAssembly/host-bindings/blob/master/proposals/host-bindings/Overview.mdprovidesthefollowinglistofgoalsforthisfeature:
Ergonomics:AllowWebAssemblymodulestocreate,passaround,call,andmanipulateJavaScript+DOMobjectsSpeed:AllowJS/DOMorotherhostcallstobewelloptimizedPlatformconsistency:AllowWebIDLtobeusedtoannotateWasmimports/exports(viaatool)Incrementalism:Provideastrategythatispolyfillable
ImprovingWebAssembly'sinteroperabilitywithJavaScriptandWebAPIswouldsimplifythedevelopmentprocessconsiderably.Itwouldalsoeliminatetheneedforthe"glue"codethattoolssuchasEmscriptenprovide.
GarbagecollectionThegarbagecollection(GC)proposaliscurrentlyinPhase1.WediscussedgarbagecollectionintheWhataretheLimitations?sectionofChapter1,WhatisWebAssembly?Theproposaldocumentationathttps://github.com/WebAssembly/gc/blob/master/proposals/gc/Overview.mdprovidesanextensiveoverviewofthefeatureanddescribestheelementsthatneedtobeaddedtothespecification.Eberhardtprovidesthefollowingdescriptionoftheproposalinhisblogpost:"ThisproposaladdsGCcapabilitiestoWebAssembly.Interestingly,itwillnothaveitsownGC,insteaditwillintegratewiththeGCprovidedbythehostenvironment.Thismakesalotofsenseasthis,andvariousotherproposals(hostbindings,referencetypes),aredesignedtoimprovetheinteropwiththehost,makingiteasiertosharestateandcallAPIs.HavingasingleGCtomanagememorymakesthismucheasier."
Thisfeaturewillrequireagreatdealofefforttoimplement,butaddingittoWebAssemblywillbeworththeeffort.Let'swrapupthissectionwithafeaturecurrentlyintheimplementationphase:referencetypes.
ReferencetypesReferencetypes,currentlyinPhase3,formthebasisforthehostbindingsandGCfeatures.Theproposaldocumentationathttps://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.mddescribestheadditionofanewtype,anyref,whichcanbeusedasbothavaluetypeandatableelementtype.TheanyreftypeallowsyoutopassaJavaScriptobjecttoaWasmmodule.Eberhardtdescribestheimplicationsofthisfeatureinhisblogpost:"Thewasmmodulecan'treallydomuchwiththeobjectviatheanyreftype.What'smoreimportantisthatthemoduleisholdingareferencetoagarbagecollectedobjectontheJSheap,meaningtheyneedtobetracedduringwasmexecution.Thisproposalisseenasastepping-stonetowardsthemoresignificantgarbagecollectionproposal."
ThereareseveralotherexcitingfeaturesinthepipelineforWebAssembly.TheWebAssemblyCGandWGaredevotingtheirtimeandresourcestomakingthesefeaturesareality.YoucanviewalloftheproposalsattheWebAssemblyorganizationpageonGitHub,locatedathttps://github.com/WebAssembly.
SummaryInthischapter,wereviewedadvancedtoolsandanalternatecompilationmethodforWebAssembly.WelearnedaboutWABTandBinaryen'sroleintheWebAssemblydevelopmentprocessandthefunctionalitytheyprovide.WecompiledaWasmmodulewithLLVMthroughtheuseoftheWebAssemblynpmpackageandinteractedwiththeresultinthebrowser.WereviewedsomeoftheWebAssemblytoolingavailableonlineandcreatedasimpleapplicationthatusesWebWorkerstostoreWasmmodulesinseparatethreads.Finally,wediscussedtheupcomingfeaturesofWebAssemblyandthestandardizationprocess.Nowthatyou'vegainedagreaterunderstandingofWebAssembly,gooutthereandbuildsomething!
QuestionsWhatdoesWABTstandfor?WhatthreeelementsdoesBinaryenprovidetomakecompilingtoWebAssemblyeasy,fast,andeffective?WhatisthemaindifferencebetweenmodulescompiledusingEmscriptenversusLLVMwithregardtotheimportObj/exports?WhichonlinetoolallowsyoutouseAssemblyScript?WhatarethetwotypesofargumentsyoucanpasstotheWorker()constructor?Whatconventionwasusedforpassingmessagesbetweenthemainthreadandworkerthreads?HowmanyphasesareintheWebAssemblystandardizationprocess?Whatisthenameofthenewtypedefinedinthereferencetypesfeature?
FurtherreadingAcrashcourseinmemorymanagement:https://hacks.mozilla.org/2017/06/a-crash-course-in-memory-management
MDNWebWorkersAPI:https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
WebAssembly-WebWorkers:https://medium.com/@c.gerard.gallant/webassembly-web-workers-f2ba637c3e4a
OtherBooksYouMayEnjoyIfyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:
Angular6forEnterprise-ReadyWebApplicationsDoguhanUluca
ISBN:9781786462909
Createfull-stackwebapplicationsusingAngularandRESTfulAPIsMasterAngularfundamentals,RxJS,CLItools,unittesting,GitHub,andDockerDesignandarchitectresponsive,secureandscalableappstodeployonAWSAdoptaminimalist,value-firstapproachtodeliveringyourappwithKanbanGetintroducedtoautomatedtestingwithcontinuousintegrationonCircleCIOptimizeNginxandNode.jswebserverswithloadtestingtools
MasteringTheFasterWebwithPHP,MySQL,andJavaScript
AndrewCaya
ISBN:9781788392211
Install,confgure,anduseproflingandbenchmarkingtestingtoolsUnderstandhowtorecognizeoptimizabledatastructuresandfunctionstoeffectivelyoptimizeaPHP7applicationDiagnosebadSQLqueryperformanceanddiscoverwaystooptimizeitGraspmodernSQLtechniquestooptimizecomplexSQLqueriesIdentifyandsimplifyoverlycomplexJavaScriptcodeExploreandimplementUIdesignprinciplesthateffectivelyenhancetheperformanceCombinewebtechnologiestoboostwebserverperformance
Leaveareview-letotherreadersknowwhatyouthinkPleaseshareyourthoughtsonthisbookwithothersbyleavingareviewonthesitethatyouboughtitfrom.IfyoupurchasedthebookfromAmazon,pleaseleaveusanhonestreviewonthisbook'sAmazonpage.Thisisvitalsothatotherpotentialreaderscanseeanduseyourunbiasedopiniontomakepurchasingdecisions,wecanunderstandwhatourcustomersthinkaboutourproducts,andourauthorscanseeyourfeedbackonthetitlethattheyhaveworkedwithPackttocreate.Itwillonlytakeafewminutesofyourtime,butisvaluabletootherpotentialcustomers,ourauthors,andPackt.Thankyou!