Add a proper webpack setup for wasm

This commit is contained in:
Maximilian Ammann 2021-12-09 15:06:40 +01:00
parent 1f766b0fe0
commit 316d2f4422
19 changed files with 7141 additions and 181 deletions

View File

@ -1,9 +1,13 @@
[build]
target = "x86_64-unknown-linux-gnu"
[target.wasm32-unknown-unknown]
rustflags = [
# Enabled unstable APIs from web_sys
"--cfg=web_sys_unstable_apis",
# Enables features which are required for shared-memory
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
# Enables the possibility to import memory into wasm. shared-memory option is required.
# Enables the possibility to import memory into wasm.
# Without --shared-memory it is not possible to use shared WebAssembly.Memory.
"-C", "link-args=--shared-memory --import-memory",
]

View File

@ -6,6 +6,15 @@ edition = "2021"
resolver = "2"
build = "build.rs"
[lib]
crate-type = ["cdylib", "rlib"]
[package.metadata.wasm-pack.profile.dev]
wasm-opt = false
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
[features]
web-webgl = ["wgpu/webgl"]

23
src/lib.rs Normal file
View File

@ -0,0 +1,23 @@
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
mod fps_meter;
mod platform;
mod render;
mod io;
mod setup;
#[cfg(target_arch = "wasm32")]
mod web;
fn main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
pollster::block_on(setup::setup(window, event_loop));
}

View File

@ -1,95 +1,14 @@
use log::{info, trace, warn};
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};
use crate::platform::Instant;
use crate::render::state::State;
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
mod fps_meter;
mod platform;
mod render;
mod io;
mod setup;
#[cfg(target_arch = "wasm32")]
mod web;
mod io;
use js_sys;
async fn setup(window: Window, event_loop: EventLoop<()>, u8sab: Option<js_sys::Uint8Array>) {
info!("== mapr ==");
info!("Controls:");
info!(" Arrow keys: scrolling");
info!(" PgUp/PgDown: zoom in/out");
info!(" a/z: increase/decrease the stroke width");
let mut state = State::new(&window).await;
let mut last_render_time = Instant::now();
event_loop.run(move |event, _, control_flow| {
match event {
Event::DeviceEvent {
ref event,
.. // We're not using device_id currently
} => {
trace!("{:?}", event);
state.device_input(event);
}
Event::WindowEvent {
ref event,
window_id,
} if window_id == window.id() => {
if !state.window_input(event) {
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
} => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => {
state.resize(*physical_size);
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(_) => {
warn!("{}", u8sab.as_ref().unwrap().get_index(1));
let now = Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if lost
Err(wgpu::SurfaceError::Lost) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e),
}
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
window.request_redraw();
}
_ => {}
}
});
}
fn main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
@ -100,5 +19,5 @@ fn main() {
.build(&event_loop)
.unwrap();
pollster::block_on(setup(window, event_loop, None));
pollster::block_on(setup::setup(window, event_loop));
}

79
src/setup.rs Normal file
View File

@ -0,0 +1,79 @@
use log::{info, trace};
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use crate::platform::Instant;
use crate::render::state::State;
pub async fn setup(window: winit::window::Window, event_loop: EventLoop<()>) {
info!("== mapr ==");
info!("Controls:");
info!(" Arrow keys: scrolling");
info!(" PgUp/PgDown: zoom in/out");
info!(" a/z: increase/decrease the stroke width");
let mut state = State::new(&window).await;
let mut last_render_time = Instant::now();
event_loop.run(move |event, _, control_flow| {
match event {
Event::DeviceEvent {
ref event,
.. // We're not using device_id currently
} => {
trace!("{:?}", event);
state.device_input(event);
}
Event::WindowEvent {
ref event,
window_id,
} if window_id == window.id() => {
if !state.window_input(event) {
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
} => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => {
state.resize(*physical_size);
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(_) => {
let now = Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if lost
Err(wgpu::SurfaceError::Lost) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e),
}
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
window.request_redraw();
}
_ => {}
}
});
}

View File

@ -47,6 +47,6 @@ pub async fn run() {
height: body.client_height(),
});
super::setup(window, event_loop, None).await;
super::setup::setup(window, event_loop).await;
}

View File

@ -10,7 +10,7 @@ pub fn test_fetch(web_window: &Window) {
let cb: Closure<dyn FnMut(JsValue) + 'static> = Closure::wrap(Box::new(|value: JsValue| {
info!("interval elapsed!");
}) as Box<dyn FnMut(JsValue)>);
web_window.fetch_with_str("http://localhost:5555/web/mapr.html").then(&cb);
web_window.fetch_with_str("http://localhost:5555/web/index.html").then(&cb);
cb.forget();
}
@ -51,7 +51,7 @@ pub fn test_shared_mem(memory: &JsValue) {
pub fn test_alloc() -> *mut u8 {
let mut buf: Vec<u8> = Vec::with_capacity(100);
buf.push(54);
buf.push(56);
let ptr = buf.as_mut_ptr();
std::mem::forget(buf);
return ptr;

View File

@ -17,11 +17,19 @@ while true; do
esac
done
# shellcheck disable=SC2086
cargo +nightly build --features "$webgl_flag" --target wasm32-unknown-unknown -Z build-std=std,panic_abort
function plain_build() {
cargo +nightly build --features "$webgl_flag" --target wasm32-unknown-unknown -Z build-std=std,panic_abort
# TODO: Untested: --reference-types
wasm-bindgen --target web --out-dir web/libs/mapr-pain-bindgen target/wasm32-unknown-unknown/debug/mapr.wasm
}
# TODO: Untested: --reference-types
wasm-bindgen --target web --out-dir web target/wasm32-unknown-unknown/debug/mapr.wasm
function wasm_pack_build() {
rustup run nightly ~/Downloads/wasm-pack-v0.10.1-x86_64-unknown-linux-musl/wasm-pack build . \
--dev --target web --out-dir web/libs/mapr -- \
--features "$webgl_flag" -Z build-std=std,panic_abort
}
wasm_pack_build
#xdg-open "https://localhost:5555/web/mapr.html"
python3 tools/tls-http-server.py
#python3 tools/tls-http-server.py

View File

@ -1,49 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1M1QdM9iaU8zo
H6dPft6vv/O9n5zYvLWWjhp87EOnYW8O0tPVQBsE2qMZWsW3wy3pDI6hH9Tg7opv
YkyJp0FbV+U5jJeaNupOZMMPqE6qBkUcw144D9ZsZSca2GOpsCk+aQ3mGJGuVJXK
VMN6Mj/aSdC+R1lS/PxISlAUNZJieJLQuZKhb0W128QrZWoI/wB7ulOiWG5ZwHXu
B95HOExB28snEOmhIN1+D2e3Z272mQWte0WsenbBlfhdZmpHFGsWuFF48KGp4UHZ
J/R2iGG3UzY/Ddu2qQR1+LOWQdWJI7sJDUw2Lzf03xwBGzXFII3k2aCaVR/fXTDX
Hl3H4Y/xAgMBAAECggEAAnPSZJ7KBLSzpkSfaBUIgIpxixR48/oB3yrHMv+jnsVT
0niKA23dr8AhyvsOiAEtQuU3nPioUe8Ikp9a99X6rXjQEmpTtvOdUjhM545YmjaB
gvUl9k5IkakA0X0yRB6tQDn9xSp59kkQye9BwgqiEJtFJv4iOFxSMcxHry5Wb9y6
E3gAQsKWCSCZ9gjVlUO45deaq2arIhKx1REgfYzdVbYj4zsalDCD074wA2FuBQiL
2lXmdahnB9tmR+s8otg//Piqdx23ZtNvGbBAkSiB1gxrmubVl3xO1BarQBSQIh7l
juSsYFiWaJLM109Ub1971AXtki+mWJVpEFeNoRK+7QKBgQDXbLLHxs0Gs7wUEHCJ
3PSNQhwew1KoZ7CHamOUvLjQ5pkFMd00AXAJU4uabFPHD48NtnSjSA11JHFVvexv
U/e6bGrJ1XUhhUPqkUpKK18gnmZxBawmJqAjAL/hd5N3vwOv3H/h4VR00cXRv1rp
JGDtd3VmSRmHCVt7ALJ9TdGskwKBgQDXVGtO0bsgNZoI0I2pmHinTRlL9W/UKYaU
P0COCRjEyV/AFBYJho3m2fzNIRmZ6lbFx8O8NNgr9xD4P8vJSNz0bO2VjhKVoeuT
kMiFz1Ma1wwKZf1rAhDfCNkxJV+DZouAH3977mhA7RFgvKavtUN3AmLm4Ahb3wYk
KMllWMNn6wKBgQDL0I6xTpiqL6DRRuCCvuctQpACVwA8TqYI6JJApfEhzBqYfJ3x
Vy7d1Apv1u/IlSLecXKXDVOWMh18Zs1sYwAtg+fg/7DrPv+LzVyBIdnFrbf0VM6T
5dV6aHoiYpTGJZXia/ceCadzQwkrMiVcEUwXP/91/9FEHolLrdQxk0++OwKBgGb4
q8gpa37foyWL9fKgQzJeIDa+zBd9NCo4hTpnBz0zv2IvOMqdWcaHf+joZ0G/WneC
LAwYiTedErRkbaN4AJGydXsLUb/Uir8qgXnbxzYSmGNwmltNxZuis5pY8O/5XcSL
S3+MZraFp8XG8T8dQ4Hp1jnp8JVedaVdC9I3PFq5AoGBANNUEK3XNqtGEQAqrgfF
YfDFaQq969186wLoAs8uTbwHv4I5kZQihaFzI+prg76rrx0Q6Q4lOMfWMMTelxPZ
EY0bylPWYTfpYHKsIs0wYHfWoZFParIRoxeyKRfml9kqaYfhyOYiibVb5TUcZpom
qEf8HMSmaXkwrnzgsCv14j+b
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUYtZgdz0k4plQ68PB9tQ+C4RDTAUwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTEyMDgyMDE1MTJaFw0yMjEy
MDgyMDE1MTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC1M1QdM9iaU8zoH6dPft6vv/O9n5zYvLWWjhp87EOn
YW8O0tPVQBsE2qMZWsW3wy3pDI6hH9Tg7opvYkyJp0FbV+U5jJeaNupOZMMPqE6q
BkUcw144D9ZsZSca2GOpsCk+aQ3mGJGuVJXKVMN6Mj/aSdC+R1lS/PxISlAUNZJi
eJLQuZKhb0W128QrZWoI/wB7ulOiWG5ZwHXuB95HOExB28snEOmhIN1+D2e3Z272
mQWte0WsenbBlfhdZmpHFGsWuFF48KGp4UHZJ/R2iGG3UzY/Ddu2qQR1+LOWQdWJ
I7sJDUw2Lzf03xwBGzXFII3k2aCaVR/fXTDXHl3H4Y/xAgMBAAGjUzBRMB0GA1Ud
DgQWBBQfHnK0tz0evZOugOANNZ0yURlOLjAfBgNVHSMEGDAWgBQfHnK0tz0evZOu
gOANNZ0yURlOLjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCL
zwbbceDyRl7p+Z9vgug0PtnH1qMNY3Jg4ukcQZPZEd9DTVNuCzLqUbeW4Y5Wxpwv
e7PfKUJ80OZHw0Pt8QHKReREwDdFgKOBBB5Iu2XtdxiRWLO0tMl8T9fdTgBBGi7W
+uVy1//cDhj0oWFsMiQrT3b06KIJnw6vx3BQoyFGppCz7MmAsaJCyfZMUx3wj5gF
wqtlIRMGrsfsqmJDTVjre+7rXzru2RCabtfrnvzKZNnO4cDnYhYoBhtwE5YAqY0+
K3kOmFSoI/45BxzHcH3/gOfYrtXy/z/bTe4Z3tBbwhh3AiO8+kGmqpu7tj9sqTHj
JY/GzZKA/BpLHnyBK0BH
-----END CERTIFICATE-----

View File

@ -1,16 +0,0 @@
from http.server import HTTPServer, SimpleHTTPRequestHandler
import ssl
import sys
import socketserver
class Handler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
SimpleHTTPRequestHandler.end_headers(self)
if __name__ == '__main__':
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(('0.0.0.0', 5555), Handler) as httpd:
#httpd.socket = ssl.wrap_socket(httpd.socket, certfile='tools/server.pem', server_side=True)
httpd.serve_forever()

8
web/.gitignore vendored
View File

@ -1,3 +1,5 @@
*.ts
mapr*.js
*.wasm
node_modules
libs/mapr*
dist

View File

@ -1,12 +1,12 @@
import init from "./mapr.js";
var init_output;
import init from "mapr";
onmessage = async m => {
let msg = m.data;
await fetch("http://localhost:8080/mapr.html")
if (msg.type === "init") {
init_output = await init(undefined, msg.memory);
const init_output = await init(undefined, msg.memory);
console.log(msg.address)
postMessage(init_output.get54(msg.address));
}

View File

@ -1,14 +1,10 @@
<html lang="en-US">
<head>
<title>mapr Demo</title>
</head>
<body style="margin: 0; padding: 0;">
<script type="module">
import init from "./mapr.js";
import init from "mapr";
const start = async () => {
const memory = new WebAssembly.Memory({initial: 1024, maximum: 10 * 1024, shared: true});
const init_output = await init(undefined, memory);
const fetch_worker = new Worker("./fetch-worker.js", {
const fetch_worker = new Worker(new URL('./fetch-worker.js', import.meta.url), {
type: "module",
});
@ -18,8 +14,7 @@
console.log(e)
}
//await init_output.run();
</script>
<canvas id="mapr"></canvas>
</body>
</html>
await init_output.run();
}
start();

0
web/libs/.gitkeep Normal file
View File

View File

6863
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
web/package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "web",
"version": "0.0.1",
"description": "A mapr demo",
"main": "index.js",
"scripts": {
"start": "RUSTUP_TOOLCHAIN=nightly webpack-dev-server",
"build": "RUSTUP_TOOLCHAIN=nightly webpack"
},
"repository": {
"type": "git",
"url": "https://github.com/maxammann/mapr"
},
"dependencies": {
"mapr": "file:libs/mapr",
"wasm-pack": "^0.10.1"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "^1.6.0",
"copy-webpack-plugin": "^10.0.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0",
"wasm-pack": "^0.10.1"
},
"keywords": [
"wasm",
"webgpu",
"maps"
],
"author": "",
"license": "MIT"
}

9
web/static/index.html Normal file
View File

@ -0,0 +1,9 @@
<html lang="en-US">
<head>
<title>mapr Demo</title>
</head>
<body style="margin: 0; padding: 0;">
<script src="index.js"></script>
<canvas id="mapr"></canvas>
</body>
</html>

81
web/webpack.config.js Normal file
View File

@ -0,0 +1,81 @@
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
let dist = path.join(__dirname, 'dist');
module.exports = {
mode: "development",
entry: {
index: "./index.js"
},
experiments: {
syncWebAssembly: true
},
performance: {
maxEntrypointSize: 400000,
maxAssetSize: 400000000,
},
output: {
path: dist,
filename: "[name].js"
},
devServer: {
static: {
directory: dist,
},
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
},
},
plugins: [
new CopyPlugin({
patterns: [
{ from: "static", to: "." },
],
}),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, '../'),
// Check https://rustwasm.github.io/wasm-pack/book/commands/build.html for
// the available set of arguments.
//
// Optional space delimited arguments to appear before the wasm-pack
// command. Default arguments are `--verbose`.
//args: '--log-level warn',
// Default arguments are `--typescript --target browser --mode normal`.
extraArgs: '--target web -- --features web-webgl -Z build-std=std,panic_abort',
// Optional array of absolute paths to directories, changes to which
// will trigger the build.
// watchDirectories: [
// path.resolve(__dirname, "another-crate/src")
// ],
// The same as the `--out-dir` option for `wasm-pack`
outDir: path.resolve(__dirname, './libs/mapr'),
// The same as the `--out-name` option for `wasm-pack`
// outName: "index",
// If defined, `forceWatch` will force activate/deactivate watch mode for
// `.rs` files.
//
// The default (not set) aligns watch mode for `.rs` files to Webpack's
// watch mode.
// forceWatch: true,
// If defined, `forceMode` will force the compilation mode for `wasm-pack`
//
// Possible values are `development` and `production`.
//
// the mode `development` makes `wasm-pack` build in `debug` mode.
// the mode `production` makes `wasm-pack` build in `release` mode.
// forceMode: "development",
// Controls plugin output verbosity, either 'info' or 'error'.
// Defaults to 'info'.
// pluginLogLevel: 'info'
}),
]
};