Переглянути джерело

Add webassembly support.

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
rayburgemeestre 6 роки тому
джерело
коміт
4d572219ec
7 змінених файлів з 415 додано та 94 видалено
  1. +4
    -0
      .idea/encodings.xml
  2. +4
    -0
      Makefile
  3. +22
    -2
      docker_api/api.py
  4. +3
    -0
      docker_api/run_dev.sh
  5. +83
    -92
      src/App.vue
  6. +57
    -0
      src/dom_example
  7. +242
    -0
      src/pong_example

+ 4
- 0
.idea/encodings.xml Переглянути файл

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

+ 4
- 0
Makefile Переглянути файл

@@ -18,6 +18,10 @@ build:
dev:
npm run dev

.PHONY: api_dev
api_dev:
cd docker_api && bash run_dev.sh

.PHONY: docker_api
docker_api:
cd docker_api && bash build.sh

+ 22
- 2
docker_api/api.py Переглянути файл

@@ -1,5 +1,7 @@
import binascii
import os
import exec_helpers
import re

from flask import request, url_for
from flask_api import FlaskAPI, status, exceptions
@@ -23,10 +25,22 @@ def compile():
stderr = ''
retcode = 0
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)
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:
try:
ret = executor.check_call(cmd, shell=True, timeout=2)
ret = executor.check_call(cmd, shell=True, timeout=10)
if ret:
retcode = ret.exit_code
output = ret.stdout_str
@@ -43,12 +57,18 @@ def compile():
output = f.read()
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 {
'stdout': stdout,
'stderr': stderr,
'command': cmd,
'retcode': retcode,
'javascript': output
'javascript': output,
'wasm': binascii.b2a_base64(output_wasm).decode("utf-8") if wasm else ""
}, status.HTTP_201_CREATED

if __name__ == "__main__":

+ 3
- 0
docker_api/run_dev.sh Переглянути файл

@@ -0,0 +1,3 @@
#!/bin/bash

docker run -p 5000:5000 -it cheerp:latest

+ 83
- 92
src/App.vue Переглянути файл

@@ -24,13 +24,11 @@
<a class="navbar-link">
Load example
</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>

@@ -63,7 +61,9 @@

<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 class="row">
@@ -83,10 +83,12 @@
<div class="row">
<div class="tabs">
<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>
</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 class="row">
<div class="tabs">
@@ -112,85 +114,14 @@
const _ = require('lodash');
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) {
return _.find(window.frames, frame => frame.name === name);
@@ -206,6 +137,10 @@ __Z7webMainv();`.trim();
type: Number,
default: 1,
},
js_tabs: {
type: Number,
default: 1,
},
cpp_code: {
type: String,
default: cpp_code,
@@ -217,18 +152,31 @@ __Z7webMainv();`.trim();
js_code: {
type: String,
},
wasm_code: {
type: String,
},
compiler_output: {
type: String,
},
compiler_flags: {
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: {
type: Boolean,
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: {
EditorComponent
@@ -239,6 +187,22 @@ __Z7webMainv();`.trim();
iframe.document.body.innerHTML = '';
iframe.document.write(this.html_code);
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("<\/script>");
},
@@ -252,7 +216,7 @@ __Z7webMainv();`.trim();
},
compile() {
this.output_tabs = 1;
// axios.post('//localhost:5000/compile', {
//axios.post('//localhost:5000/compile', {
axios.post('https://cheerp.cppse.nl/api/compile', {
flags: this.compiler_flags,
source: this.cpp_code
@@ -266,7 +230,26 @@ __Z7webMainv();`.trim();
'STDERR:\n------------------------------\n' +
response.data.stderr;
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))
.catch(function (error) {
console.log(error);
@@ -280,6 +263,14 @@ __Z7webMainv();`.trim();
}
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: {
html_code(new_val) {

+ 57
- 0
src/dom_example Переглянути файл

@@ -0,0 +1,57 @@
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 }

+ 242
- 0
src/pong_example Переглянути файл

@@ -0,0 +1,242 @@
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 }

Завантаження…
Відмінити
Зберегти