Add loadable examples support, and add the pong example from the official wiki. Make compiler flags configurable. Put examples in separate files. Change max. delay for waiting for output from 2 sec to 10 secs.master
<?xml version="1.0" encoding="UTF-8"?> | |||||
<project version="4"> | |||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" /> | |||||
</project> |
dev: | dev: | ||||
npm run dev | npm run dev | ||||
.PHONY: api_dev | |||||
api_dev: | |||||
cd docker_api && bash run_dev.sh | |||||
.PHONY: docker_api | .PHONY: docker_api | ||||
docker_api: | docker_api: | ||||
cd docker_api && bash build.sh | cd docker_api && bash build.sh |
import binascii | |||||
import os | import os | ||||
import exec_helpers | import exec_helpers | ||||
import re | |||||
from flask import request, url_for | from flask import request, url_for | ||||
from flask_api import FlaskAPI, status, exceptions | from flask_api import FlaskAPI, status, exceptions | ||||
stderr = '' | stderr = '' | ||||
retcode = 0 | retcode = 0 | ||||
output = '' | output = '' | ||||
output_wasm = '' | |||||
flags = flags.replace("\n", " ") | |||||
wasm = 'cheerp-mode=wasm' in flags | |||||
# Simple way to disable malicious flags (hopefully) | |||||
if not re.match('^[\s\ta-zA-Z0-9=\-_\.]*$', flags): | |||||
flags = "" | |||||
cmd = "/opt/cheerp/bin/clang {} /tmp/test.cpp -o /tmp/test.js".format(flags) | cmd = "/opt/cheerp/bin/clang {} /tmp/test.cpp -o /tmp/test.js".format(flags) | ||||
if wasm: | |||||
cmd = "/opt/cheerp/bin/clang {} /tmp/test.cpp -cheerp-wasm-loader=/tmp/test.js -o /tmp/test.wasm".format(flags) | |||||
with exec_helpers.Subprocess() as executor: | with exec_helpers.Subprocess() as executor: | ||||
try: | try: | ||||
ret = executor.check_call(cmd, shell=True, timeout=2) | |||||
ret = executor.check_call(cmd, shell=True, timeout=10) | |||||
if ret: | if ret: | ||||
retcode = ret.exit_code | retcode = ret.exit_code | ||||
output = ret.stdout_str | output = ret.stdout_str | ||||
output = f.read() | output = f.read() | ||||
f.close() | f.close() | ||||
if wasm and os.path.exists("/tmp/test.wasm"): | |||||
with open("/tmp/test.wasm", "rb") as f: | |||||
output_wasm = f.read() | |||||
f.close() | |||||
return { | return { | ||||
'stdout': stdout, | 'stdout': stdout, | ||||
'stderr': stderr, | 'stderr': stderr, | ||||
'command': cmd, | 'command': cmd, | ||||
'retcode': retcode, | 'retcode': retcode, | ||||
'javascript': output | |||||
'javascript': output, | |||||
'wasm': binascii.b2a_base64(output_wasm).decode("utf-8") if wasm else "" | |||||
}, status.HTTP_201_CREATED | }, status.HTTP_201_CREATED | ||||
if __name__ == "__main__": | if __name__ == "__main__": |
#!/bin/bash | |||||
docker run -p 5000:5000 -it cheerp:latest |
<a class="navbar-link"> | <a class="navbar-link"> | ||||
Load example | Load example | ||||
</a> | </a> | ||||
<div class="navbar-dropdown"> | |||||
<a class="navbar-item">Example 1 (TODO)</a> | |||||
<a class="navbar-item">Example 2 (TODO)</a> | |||||
<a class="navbar-item">Example 3 (TODO)</a> | |||||
<hr class="navbar-divider"> | |||||
<a class="navbar-item">Example 4 (TODO)</a> | |||||
</div> | |||||
<div class="navbar-dropdown"> | |||||
<div v-for="item in examples"> | |||||
<a class="navbar-item" v-on:click="load_example(item)">{{ item.title }}</a> | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<editor-component v-if="editor_tabs == 1" v-model="cpp_code" name="cpp" language="cpp" height="50vh"/> | <editor-component v-if="editor_tabs == 1" v-model="cpp_code" name="cpp" language="cpp" height="50vh"/> | ||||
<div v-if="editor_tabs == 2" class="settings">SETTINGS HERE...</div> | |||||
<div v-if="editor_tabs == 2" class="settings"> | |||||
<textarea class="textarea" v-model="compiler_flags"></textarea> | |||||
</div> | |||||
</div> | </div> | ||||
<div class="row"> | <div class="row"> | ||||
<div class="row"> | <div class="row"> | ||||
<div class="tabs"> | <div class="tabs"> | ||||
<ul> | <ul> | ||||
<li class="is-active"><a>Javascript</a></li> | |||||
<li v-bind:class="{'is-active': js_tabs == 1}" v-on:click="js_tabs = 1"><a>Javascript</a></li> | |||||
<li v-bind:class="{'is-active': js_tabs == 2}" v-on:click="js_tabs = 2" v-if="wasm_code"><a>Webassembly</a></li> | |||||
</ul> | </ul> | ||||
</div> | </div> | ||||
<editor-component v-model="js_code" name="js" language="javascript" height="50vh"/> | |||||
<editor-component v-if="js_tabs == 1" v-model="js_code" name="js" language="javascript" height="50vh"/> | |||||
<editor-component v-if="js_tabs == 2 && wasm_code" v-model="wasm_code" name="wasm" language="javascript" height="80vh"/> | |||||
</div> | </div> | ||||
<div class="row"> | <div class="row"> | ||||
<div class="tabs"> | <div class="tabs"> | ||||
const _ = require('lodash'); | const _ = require('lodash'); | ||||
const axios = require('axios'); | const axios = require('axios'); | ||||
const cpp_code = ` | |||||
#include <cheerp/client.h> | |||||
#include <cheerp/clientlib.h> | |||||
// We need to extend the client namespace to declare our | |||||
// custom JavaScript function | |||||
namespace client | |||||
{ | |||||
// The name should be the same as the JavaScript one | |||||
// The parameters needs to be a const client::String reference | |||||
// so that implicit conversion from const char* is supported | |||||
void changeTitle(const String& str); | |||||
} | |||||
import { dom_example } from './dom_example' | |||||
import { pong_example } from './pong_example' | |||||
using namespace client; | |||||
void webMain() | |||||
{ | |||||
Element* titleElement=document.getElementById("pagetitle"); | |||||
String* oldText=titleElement->get_textContent(); | |||||
changeTitle("Literal C++ string"); | |||||
}`.trim(); | |||||
const html_code = ` | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<title>Cheerp test</title> | |||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"><\/script> | |||||
<script> | |||||
// Use jQuery to make a (trivial) change to the page | |||||
function changeTitle(str) | |||||
{ | |||||
$("#pagetitle").text(str); | |||||
} | |||||
<\/script> | |||||
</head> | |||||
<body> | |||||
<h1 id="pagetitle">Boring static text</h1> | |||||
</body> | |||||
</html>`.trim(); | |||||
const js_code = ` | |||||
"use strict"; | |||||
/*Compiled using Cheerp (R) by Leaning Technologies Ltd*/ | |||||
var aSlot=null;var oSlot=0;var nullArray=[null];var nullObj={d:nullArray,o:0}; | |||||
function __Z7webMainv(){ | |||||
var LretConstructor=null; | |||||
LretConstructor="pagetitle"; | |||||
LretConstructor=document.getElementById(LretConstructor); | |||||
LretConstructor.textContent; | |||||
LretConstructor="Literal C++ string"; | |||||
changeTitle(LretConstructor); | |||||
return; | |||||
} | |||||
function _cheerpCreate_ZN6client6StringC2EPKc(Larg0,Marg0){ | |||||
var tmp0=0,Lgeptoindexphi=0,LretConstructor$pi=null,tmp3=null; | |||||
LretConstructor$pi=String(); | |||||
tmp0=Larg0[Marg0]|0; | |||||
if((tmp0&255)===0){ | |||||
return String(LretConstructor$pi); | |||||
}else{ | |||||
Lgeptoindexphi=0; | |||||
} | |||||
while(1){ | |||||
tmp3=String.fromCharCode(tmp0<<24>>24); | |||||
LretConstructor$pi=LretConstructor$pi.concat(tmp3); | |||||
Lgeptoindexphi=Lgeptoindexphi+1|0; | |||||
tmp0=Larg0[Marg0+Lgeptoindexphi|0]|0; | |||||
if((tmp0&255)===0){ | |||||
break; | |||||
} | |||||
} | |||||
return String(LretConstructor$pi); | |||||
} | |||||
var _$pstr=new Uint8Array([112,97,103,101,116,105,116,108,101,0]); | |||||
var _$pstr1=new Uint8Array([76,105,116,101,114,97,108,32,67,43,43,32,115,116,114,105,110,103,0]); | |||||
__Z7webMainv();`.trim(); | |||||
let cpp_code = dom_example.cpp | |||||
let js_code = dom_example.js | |||||
let wasm_code = dom_example.wasm | |||||
let html_code = dom_example.html | |||||
let compiler_flags = dom_example.flags | |||||
function findIframeByName(name) { | function findIframeByName(name) { | ||||
return _.find(window.frames, frame => frame.name === name); | return _.find(window.frames, frame => frame.name === name); | ||||
type: Number, | type: Number, | ||||
default: 1, | default: 1, | ||||
}, | }, | ||||
js_tabs: { | |||||
type: Number, | |||||
default: 1, | |||||
}, | |||||
cpp_code: { | cpp_code: { | ||||
type: String, | type: String, | ||||
default: cpp_code, | default: cpp_code, | ||||
js_code: { | js_code: { | ||||
type: String, | type: String, | ||||
}, | }, | ||||
wasm_code: { | |||||
type: String, | |||||
}, | |||||
compiler_output: { | compiler_output: { | ||||
type: String, | type: String, | ||||
}, | }, | ||||
compiler_flags: { | compiler_flags: { | ||||
type: String, | type: String, | ||||
default: '-cheerp-no-type-optimizer -cheerp-pretty-code -cheerp-no-native-math -cheerp-no-math-imul -cheerp-no-math-fround -O3 -target cheerp' | |||||
default: compiler_flags | |||||
}, | }, | ||||
do_update_iframe: { | do_update_iframe: { | ||||
type: Boolean, | type: Boolean, | ||||
default: false | default: false | ||||
} | |||||
}, | |||||
examples: { | |||||
type: Array, | |||||
default: function () { | |||||
return [ | |||||
{ title: 'DOM example', cpp_code: dom_example.cpp, 'js_code': dom_example.js, 'wasm_code': dom_example.wasm, 'html_code': dom_example.html, flags: dom_example.flags }, | |||||
{ title: 'Pong WASM example', cpp_code: pong_example.cpp, 'js_code': pong_example.js, 'wasm_code': pong_example.wasm, 'html_code': pong_example.html, flags: pong_example.flags }, | |||||
] | |||||
} | |||||
}, | |||||
}, | }, | ||||
components: { | components: { | ||||
EditorComponent | EditorComponent | ||||
iframe.document.body.innerHTML = ''; | iframe.document.body.innerHTML = ''; | ||||
iframe.document.write(this.html_code); | iframe.document.write(this.html_code); | ||||
iframe.document.write("<script>"); | iframe.document.write("<script>"); | ||||
// wasm | |||||
iframe.document.write("function fetchBuffer(path) {\n"); | |||||
iframe.document.write(" return new Promise( (resolve, reject) => {\n"); | |||||
iframe.document.write(" var wasm = '" + this.wasm_code.trim() + "';\n"); | |||||
iframe.document.write(" wasm = atob(wasm)\n"); | |||||
iframe.document.write("\n"); | |||||
iframe.document.write(" var len = wasm.length;\n"); | |||||
iframe.document.write(" var bytes = new Uint8Array(len);\n"); | |||||
iframe.document.write(" for (var i = 0; i < len; i++) {\n"); | |||||
iframe.document.write(" bytes[i] = wasm.charCodeAt(i);\n"); | |||||
iframe.document.write(" }\n"); | |||||
iframe.document.write(" resolve(bytes.buffer);\n"); | |||||
iframe.document.write(" });\n"); | |||||
iframe.document.write("}\n"); | |||||
iframe.document.write(this.js_code); | iframe.document.write(this.js_code); | ||||
iframe.document.write("<\/script>"); | iframe.document.write("<\/script>"); | ||||
}, | }, | ||||
}, | }, | ||||
compile() { | compile() { | ||||
this.output_tabs = 1; | this.output_tabs = 1; | ||||
// axios.post('//localhost:5000/compile', { | |||||
//axios.post('//localhost:5000/compile', { | |||||
axios.post('https://cheerp.cppse.nl/api/compile', { | axios.post('https://cheerp.cppse.nl/api/compile', { | ||||
flags: this.compiler_flags, | flags: this.compiler_flags, | ||||
source: this.cpp_code | source: this.cpp_code | ||||
'STDERR:\n------------------------------\n' + | 'STDERR:\n------------------------------\n' + | ||||
response.data.stderr; | response.data.stderr; | ||||
this.compiler_output = str; | this.compiler_output = str; | ||||
this.js_code = response.data.javascript; | |||||
var js = response.data.javascript; | |||||
var idx = js.indexOf('function fetchBuffer'), jdx = -1; | |||||
if (idx != -1) { | |||||
var level = 0; | |||||
for (var i=idx; ; i++) { | |||||
if (js[i] == '{') { | |||||
level++; | |||||
} | |||||
else if (js[i] == '}') { | |||||
level--; | |||||
if (!level) { | |||||
jdx = i; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
this.js_code = js.substr(0, idx) + js.substr(jdx + 1); | |||||
this.wasm_code = response.data.wasm; | |||||
}.bind(this)) | }.bind(this)) | ||||
.catch(function (error) { | .catch(function (error) { | ||||
console.log(error); | console.log(error); | ||||
} | } | ||||
catch (e) {} | catch (e) {} | ||||
}, | }, | ||||
load_example(ex) { | |||||
this.cpp_code = ex.cpp_code | |||||
this.js_code = ex.js_code | |||||
this.wasm_code = ex.wasm_code | |||||
this.html_code = ex.html_code | |||||
this.compiler_flags = ex.flags | |||||
return true; | |||||
} | |||||
}, | }, | ||||
watch: { | watch: { | ||||
html_code(new_val) { | html_code(new_val) { |
const cpp_code = ` | |||||
#include <cheerp/client.h> | |||||
#include <cheerp/clientlib.h> | |||||
// We need to extend the client namespace to declare our | |||||
// custom JavaScript function | |||||
namespace client | |||||
{ | |||||
// The name should be the same as the JavaScript one | |||||
// The parameters needs to be a const client::String reference | |||||
// so that implicit conversion from const char* is supported | |||||
void changeTitle(const String& str); | |||||
} | |||||
using namespace client; | |||||
void webMain() | |||||
{ | |||||
Element* titleElement=document.getElementById("pagetitle"); | |||||
String* oldText=titleElement->get_textContent(); | |||||
changeTitle("Literal C++ string"); | |||||
}`.trim(); | |||||
const html_code = ` | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<title>Cheerp test</title> | |||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"><\/script> | |||||
<script> | |||||
// Use jQuery to make a (trivial) change to the page | |||||
function changeTitle(str) | |||||
{ | |||||
$("#pagetitle").text(str); | |||||
} | |||||
<\/script> | |||||
</head> | |||||
<body> | |||||
<h1 id="pagetitle">Boring static text</h1> | |||||
</body> | |||||
</html>`.trim(); | |||||
const js_code = ``.trim(); | |||||
const flags = ` | |||||
-cheerp-pretty-code | |||||
-cheerp-no-type-optimizer | |||||
-cheerp-no-native-math | |||||
-cheerp-no-math-imul | |||||
-cheerp-no-math-fround | |||||
-O3 | |||||
-target cheerp`.trim() | |||||
const wasm_code = ''; | |||||
export const dom_example = { cpp: cpp_code, js: js_code, wasm: wasm_code, html: html_code, flags: flags } |
const cpp_code = ` | |||||
#include <cheerp/clientlib.h> | |||||
#include <cheerp/client.h> | |||||
#include <math.h> | |||||
static constexpr int width = 400; | |||||
static constexpr int height = 320; | |||||
// Forward declaration for the main loop of the game, compiled to WebAssembly | |||||
void mainLoop(); | |||||
// All the graphics code should stay on the JS side. It is possible to tag whole classes with the [[cheerp::genericjs]] tag. | |||||
// All members and methods of this class will be compiled to standard JavaScript. | |||||
class [[cheerp::genericjs]] Graphics | |||||
{ | |||||
private: | |||||
// When compiling to standard JavaScript it is possible to use DOM objects like any other C++ object. | |||||
static client::HTMLCanvasElement* canvas; | |||||
static client::CanvasRenderingContext2D* canvasCtx; | |||||
static int width; | |||||
static int height; | |||||
// This method is the handler for requestAnimationFrame. The browser will call this | |||||
// in sync with its graphics loop, usually at 60 fps. | |||||
static void rafHandler() | |||||
{ | |||||
mainLoop(); | |||||
client::requestAnimationFrame(cheerp::Callback(rafHandler)); | |||||
} | |||||
public: | |||||
// Define this method later on, we need to declare the Platform class first | |||||
static void keyDownHandler(client::KeyboardEvent* e); | |||||
static void keyUpHandler(client::KeyboardEvent* e); | |||||
static void initializeCanvas(int w, int h) | |||||
{ | |||||
width = w; | |||||
height = h; | |||||
canvas = (client::HTMLCanvasElement*)client::document.getElementById("pongcanvas"); | |||||
canvas->set_width(w); | |||||
canvas->set_height(h); | |||||
client::document.get_body()->appendChild(canvas); | |||||
canvasCtx = (client::CanvasRenderingContext2D*)canvas->getContext("2d"); | |||||
client::requestAnimationFrame(cheerp::Callback(rafHandler)); | |||||
// Listen for keydown events | |||||
client::document.addEventListener("keydown", cheerp::Callback(keyDownHandler)); | |||||
client::document.addEventListener("keyup", cheerp::Callback(keyUpHandler)); | |||||
} | |||||
static void drawRect(int x, int y, int w, int h, int rgb) | |||||
{ | |||||
int r = rgb&0xff; | |||||
int g = (rgb>>8)&0xff; | |||||
int b = (rgb>>16)&0xff; | |||||
canvasCtx->set_fillStyle(client::String("").concat("rgb(", r, ",", g, ",", b, ")")); | |||||
canvasCtx->fillRect(x, y, w, h); | |||||
} | |||||
static void drawCircle(int x, int y, int radius, int rgb) | |||||
{ | |||||
int r = rgb&0xff; | |||||
int g = (rgb>>8)&0xff; | |||||
int b = (rgb>>16)&0xff; | |||||
canvasCtx->set_fillStyle(client::String("").concat("rgb(", r, ",", g, ",", b, ")")); | |||||
canvasCtx->beginPath(); | |||||
canvasCtx->arc(x,y,radius,0,2*M_PI); | |||||
canvasCtx->fill(); | |||||
} | |||||
static void debugOutput(const char* str) | |||||
{ | |||||
canvasCtx->set_font("24px sans-serif"); | |||||
canvasCtx->set_fillStyle("rgb(255,255,255)"); | |||||
canvasCtx->fillText(str, 0, height - 24); | |||||
} | |||||
}; | |||||
// This whole class will be compiled to Wasm code by default since we are using the -cheerp-mode=wasm | |||||
// command line option. This is a game entity so it's better to get as much performance as we can. | |||||
class Platform | |||||
{ | |||||
private: | |||||
bool moveLeft; | |||||
bool moveRight; | |||||
int x; | |||||
int y; | |||||
int width; | |||||
int height; | |||||
public: | |||||
Platform(int x, int y, int width, int height):moveLeft(false),moveRight(false),x(x),y(y),width(width),height(height) | |||||
{ | |||||
} | |||||
int getX() const | |||||
{ | |||||
return x; | |||||
} | |||||
int getY() const | |||||
{ | |||||
return y; | |||||
} | |||||
int getWidth() const | |||||
{ | |||||
return width; | |||||
} | |||||
int getHeight() const | |||||
{ | |||||
return width; | |||||
} | |||||
void render() const | |||||
{ | |||||
Graphics::drawRect(x, y, width, height, 0xffffff); | |||||
} | |||||
void setMoveLeft(bool val) | |||||
{ | |||||
moveLeft = val; | |||||
} | |||||
void setMoveRight(bool val) | |||||
{ | |||||
moveRight = val; | |||||
} | |||||
void update() | |||||
{ | |||||
if (moveLeft) { | |||||
x -= 5; | |||||
} | |||||
if (moveRight) { | |||||
x += 5; | |||||
} | |||||
} | |||||
}; | |||||
class Ball | |||||
{ | |||||
private: | |||||
int x; | |||||
int y; | |||||
int vx; | |||||
int vy; | |||||
public: | |||||
Ball(int x, int y, int vx, int vy):x(x),y(y),vx(vx),vy(vy) | |||||
{ | |||||
} | |||||
void update() | |||||
{ | |||||
x += vx; | |||||
y += vy; | |||||
} | |||||
// Returns true if the ball gets out of the field | |||||
bool collide(const Platform& platform, int maxX, int maxY) | |||||
{ | |||||
// If we collided with the bottom side, we lost | |||||
if(y >= maxY) | |||||
return true; | |||||
// Check left and right side collisions | |||||
if(x <= 0 || x >= maxX) | |||||
vx = -vx; | |||||
// Check top side collision | |||||
if(y <= 0) | |||||
vy = -vy; | |||||
// Check collision with the top side of the plaform | |||||
if(platform.getX() < x && (platform.getX() + platform.getWidth()) > x && | |||||
platform.getY() < y && (platform.getY() + platform.getHeight()) > y) | |||||
{ | |||||
vy = -vy; | |||||
} | |||||
return false; | |||||
} | |||||
void render() | |||||
{ | |||||
Graphics::drawCircle(x, y, 5, 0xffffff); | |||||
} | |||||
}; | |||||
// Define global instances for game entities. A more serious game | |||||
// would manage these objects dynamically | |||||
Platform platform(width / 2 - 15, height - 20, 30, 7); | |||||
Ball ball(width / 2, height / 2, 2, -2); | |||||
void Graphics::keyDownHandler(client::KeyboardEvent* e) | |||||
{ | |||||
if(e->get_keyCode() == 37) | |||||
platform.setMoveLeft(true); | |||||
else if(e->get_keyCode() == 39) | |||||
platform.setMoveRight(true); | |||||
} | |||||
void Graphics::keyUpHandler(client::KeyboardEvent* e) | |||||
{ | |||||
if(e->get_keyCode() == 37) | |||||
platform.setMoveLeft(false); | |||||
else if(e->get_keyCode() == 39) | |||||
platform.setMoveRight(false); | |||||
} | |||||
void mainLoop() | |||||
{ | |||||
// Reset the background to black | |||||
Graphics::drawRect(0, 0, width, height, 0x000000); | |||||
// Draw the platform | |||||
platform.render(); | |||||
platform.update(); | |||||
// Update the ball state | |||||
ball.update(); | |||||
// Check for collisions | |||||
bool hasLost = ball.collide(platform, width, height); | |||||
if(hasLost) | |||||
Graphics::debugOutput("You lost!"); | |||||
// Render the ball | |||||
ball.render(); | |||||
} | |||||
// This function is the entry point of the program. Since we will be compiling this with the -cheerp-mode=wasm option, it will | |||||
// be compiled to WebAssembly by default. | |||||
void webMain() | |||||
{ | |||||
Graphics::initializeCanvas(width, height); | |||||
}`.trim(); | |||||
const html_code = ` | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<title>Cheerp test</title> | |||||
</head> | |||||
<body> | |||||
<canvas id="pongcanvas"></canvas> | |||||
</body> | |||||
</html> | |||||
`.trim(); | |||||
const js_code = ``.trim() | |||||
const wasm_code = ``.trim(); | |||||
const flags = ` | |||||
-cheerp-pretty-code | |||||
-cheerp-no-type-optimizer | |||||
-cheerp-no-native-math | |||||
-cheerp-no-math-imul | |||||
-cheerp-no-math-fround | |||||
-target cheerp | |||||
-cheerp-mode=wasm | |||||
-O2 | |||||
`.trim() | |||||
export const pong_example = { cpp: cpp_code, js: js_code, wasm: wasm_code, html: html_code, flags: flags } |