const _ = require('lodash'); | const _ = require('lodash'); | ||||
const axios = require('axios'); | const axios = require('axios'); | ||||
import { dom_example } from './examples/dom_example' | |||||
import { pong_example } from './examples/pong_example' | |||||
import { call_cpp_example } from './examples/call_cpp_example' | |||||
import { perf_example } from './examples/perf_example' | |||||
var context = require.context("./examples", true, /\.js$/); | |||||
var examples = []; | |||||
var dom_example_index = -1; | |||||
context.keys().forEach(function (key) { | |||||
if (key.indexOf('dom.js') != -1) { | |||||
dom_example_index = examples.length; | |||||
console.log("FOUND: " + dom_example_index); | |||||
} | |||||
examples.push(context(key)['example']) | |||||
}); | |||||
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 | |||||
let cpp_code = examples[dom_example_index].cpp_code, | |||||
js_code = examples[dom_example_index].js_code, | |||||
wasm_code = examples[dom_example_index].wasm_code, | |||||
html_code = examples[dom_example_index].html_code, | |||||
compiler_flags = examples[dom_example_index].flags; | |||||
function findIframeByName(name) { | function findIframeByName(name) { | ||||
return _.find(window.frames, frame => frame.name === name); | return _.find(window.frames, frame => frame.name === name); | ||||
examples: { | examples: { | ||||
type: Array, | type: Array, | ||||
default: function () { | 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 }, | |||||
{ title: 'Call C++ example', cpp_code: call_cpp_example.cpp, 'js_code': call_cpp_example.js, 'wasm_code': call_cpp_example.wasm, 'html_code': call_cpp_example.html, flags: call_cpp_example.flags }, | |||||
{ title: 'JS vs Compiled JS perf', cpp_code: perf_example.cpp, 'js_code': perf_example.js, 'wasm_code': perf_example.wasm, 'html_code': perf_example.html, flags: perf_example.flags }, | |||||
] | |||||
return examples | |||||
} | } | ||||
}, | }, | ||||
share_link: { | share_link: { | ||||
}, | }, | ||||
}, | }, | ||||
watch: { | watch: { | ||||
html_code(new_val) { | |||||
}, | |||||
cpp_code(new_val) { | |||||
}, | |||||
js_code(new_val) { | |||||
}, | |||||
html_code(new_val) {}, | |||||
cpp_code(new_val) {}, | |||||
js_code(new_val) {}, | |||||
}, | }, | ||||
created: function() { | created: function() { | ||||
var hashchange_fun = function() { | var hashchange_fun = function() { |
const cpp_code = ` | |||||
#include <cheerp/client.h> | |||||
#include <cheerp/clientlib.h> | |||||
using namespace client; | |||||
// The class can of course have any name | |||||
// The [[cheerp::jsexport]] attribute tells Cheerp to make | |||||
// the class available to JavaScript code | |||||
class [[cheerp::jsexport]] JsBridge | |||||
{ | |||||
private: | |||||
// The class is allowed to have member variables | |||||
// but they should all be trivially destructible | |||||
int callCount; | |||||
public: | |||||
JsBridge():callCount(0) | |||||
{ | |||||
} | |||||
int addAndReturn(int a) | |||||
{ | |||||
console.log("Called C++ code"); | |||||
callCount+=a; | |||||
return callCount; | |||||
} | |||||
}; | |||||
// An entry point, even if empty, is still required | |||||
void webMain() | |||||
{ | |||||
}`.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> | |||||
var jsBridge=null; | |||||
function callCPPCode() | |||||
{ | |||||
if(!jsBridge) | |||||
jsBridge=new JsBridge(); | |||||
var ret=jsBridge.addAndReturn(3); | |||||
$("#clickText").text("C++ code returned "+ret); | |||||
return false; | |||||
} | |||||
</script> | |||||
</head> | |||||
<body> | |||||
<a id="clickText" href="javascript:void(0)" onclick="callCPPCode()">Click to call C++ code</a> | |||||
</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 call_cpp_example = { cpp: cpp_code, js: js_code, wasm: wasm_code, html: html_code, flags: flags } |
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> | |||||
<!-- MARKER: Include javascript here. --> | |||||
</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 = ``.trim(); | |||||
const html_code = ``.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 <cmath> | |||||
#include <cheerp/client.h> | |||||
#include <cheerp/clientlib.h> | |||||
class [[cheerp::jsexport]] JsBridge | |||||
{ | |||||
public: | |||||
JsBridge() = default; | |||||
size_t test() | |||||
{ | |||||
volatile size_t counter = 0; | |||||
for (int i = 0; i < 50000; ++i) { | |||||
for (int j = 0; j < 50000; ++j) { | |||||
counter++; | |||||
} | |||||
} | |||||
return sqrt(counter); | |||||
} | |||||
}; | |||||
void webMain() {} | |||||
`.trim(); | |||||
const html_code = ` | |||||
<html> | |||||
<head> | |||||
<script> | |||||
function test() { | |||||
var counter = 0 | |||||
for (var i = 0; i < 50000; ++i) { | |||||
for (var j = 0; j < 50000; ++j) { | |||||
counter++; | |||||
} | |||||
} | |||||
return Math.sqrt(counter); | |||||
} | |||||
function benchmark() { | |||||
var jsBridge = new JsBridge(); | |||||
{ | |||||
document.write("Running native JS..<br>"); | |||||
var start = new Date().getTime(); | |||||
var ret = test(); | |||||
var time = new Date().getTime() - start; | |||||
document.write("Result: " + ret + "<br>"); | |||||
document.write("Execution time: " + (time / 1000.0) + " secs<br>"); | |||||
} | |||||
document.write("<br>"); | |||||
{ | |||||
document.write("Running compiled JS..<br>"); | |||||
var start = new Date().getTime(); | |||||
var ret = jsBridge.test(); | |||||
var time = new Date().getTime() - start; | |||||
document.write("Result: " + ret + "<br>"); | |||||
document.write("Execution time: " + (time / 1000.0) + " secs<br>"); | |||||
} | |||||
} | |||||
</script> | |||||
</head> | |||||
<body> | |||||
Benchmark started... <br/><br/> | |||||
<script> | |||||
benchmark(); | |||||
</script> | |||||
<br/> | |||||
Benchmark finished... <br/> | |||||
</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 perf_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> | |||||
<!-- MARKER: Include javascript here. --> | |||||
</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 } |