Skip to content

Commit 539ce7e

Browse files
Add multi-threading example
1 parent 7496901 commit 539ce7e

File tree

12 files changed

+438
-0
lines changed

12 files changed

+438
-0
lines changed

Examples/Multithreading/.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"originHash" : "e66f4c272838a860049b7e3528f1db03ee6ae99c2b21c3b6ea58a293be4db41b",
3+
"pins" : [
4+
{
5+
"identity" : "chibi-ray",
6+
"kind" : "remoteSourceControl",
7+
"location" : "https://github.com/kateinoigakukun/chibi-ray",
8+
"state" : {
9+
"revision" : "c8cab621a3338dd2f8e817d3785362409d3b8cf1"
10+
}
11+
}
12+
],
13+
"version" : 3
14+
}

Examples/Multithreading/Package.swift

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// swift-tools-version: 6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "Example",
7+
dependencies: [
8+
.package(path: "../../"),
9+
.package(url: "https://github.com/kateinoigakukun/chibi-ray", revision: "c8cab621a3338dd2f8e817d3785362409d3b8cf1"),
10+
],
11+
targets: [
12+
.executableTarget(
13+
name: "MyApp",
14+
dependencies: [
15+
.product(name: "JavaScriptKit", package: "JavaScriptKit"),
16+
.product(name: "JavaScriptEventLoop", package: "JavaScriptKit"),
17+
.product(name: "ChibiRay", package: "chibi-ray"),
18+
]
19+
),
20+
]
21+
)

Examples/Multithreading/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Multithreading example
2+
3+
Install Development Snapshot toolchain `DEVELOPMENT-SNAPSHOT-2024-07-08-a` from [swift.org/install](https://www.swift.org/install/) and run the following commands:
4+
5+
```sh
6+
$ swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasip1-threads.artifactbundle.zip
7+
$ ./build.sh
8+
$ npx serve
9+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { instantiate } from "./instantiate.js"
2+
import * as WasmImportsParser from 'https://esm.run/wasm-imports-parser/polyfill.js';
3+
4+
// TODO: Remove this polyfill once the browser supports the WebAssembly Type Reflection JS API
5+
// https://chromestatus.com/feature/5725002447978496
6+
globalThis.WebAssembly = WasmImportsParser.polyfill(globalThis.WebAssembly);
7+
8+
class ThreadRegistry {
9+
workers = new Map();
10+
nextTid = 1;
11+
12+
constructor({ configuration }) {
13+
this.configuration = configuration;
14+
}
15+
16+
spawnThread(worker, module, memory, startArg) {
17+
const tid = this.nextTid++;
18+
this.workers.set(tid, worker);
19+
worker.postMessage({ module, memory, tid, startArg, configuration: this.configuration });
20+
return tid;
21+
}
22+
23+
listenMessageFromWorkerThread(tid, listener) {
24+
const worker = this.workers.get(tid);
25+
worker.onmessage = (event) => {
26+
listener(event.data);
27+
};
28+
}
29+
30+
postMessageToWorkerThread(tid, data) {
31+
const worker = this.workers.get(tid);
32+
worker.postMessage(data);
33+
}
34+
35+
terminateWorkerThread(tid) {
36+
const worker = this.workers.get(tid);
37+
worker.terminate();
38+
this.workers.delete(tid);
39+
}
40+
}
41+
42+
async function start(configuration = "release") {
43+
const response = await fetch(`./.build/${configuration}/MyApp.wasm`);
44+
const module = await WebAssembly.compileStreaming(response);
45+
const memoryImport = WebAssembly.Module.imports(module).find(i => i.module === "env" && i.name === "memory");
46+
if (!memoryImport) {
47+
throw new Error("Memory import not found");
48+
}
49+
if (!memoryImport.type) {
50+
throw new Error("Memory import type not found");
51+
}
52+
const memoryType = memoryImport.type;
53+
const memory = new WebAssembly.Memory({ initial: memoryType.minimum, maximum: memoryType.maximum, shared: true });
54+
const threads = new ThreadRegistry({ configuration });
55+
const { instance, swiftRuntime, wasi } = await instantiate({
56+
module,
57+
threadChannel: threads,
58+
addToImports(importObject) {
59+
importObject["env"] = { memory }
60+
importObject["wasi"] = {
61+
"thread-spawn": (startArg) => {
62+
const worker = new Worker("Sources/JavaScript/worker.js", { type: "module" });
63+
return threads.spawnThread(worker, module, memory, startArg);
64+
}
65+
};
66+
},
67+
configuration
68+
});
69+
wasi.initialize(instance);
70+
71+
swiftRuntime.main();
72+
}
73+
74+
start();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { WASI, File, OpenFile, ConsoleStdout, PreopenDirectory } from 'https://esm.run/@bjorn3/browser_wasi_shim@0.3.0';
2+
3+
export async function instantiate({ module, addToImports, threadChannel, configuration }) {
4+
const args = ["main.wasm"]
5+
const env = []
6+
const fds = [
7+
new OpenFile(new File([])), // stdin
8+
ConsoleStdout.lineBuffered((stdout) => {
9+
console.log(stdout);
10+
}),
11+
ConsoleStdout.lineBuffered((stderr) => {
12+
console.error(stderr);
13+
}),
14+
new PreopenDirectory("/", new Map()),
15+
];
16+
const wasi = new WASI(args, env, fds);
17+
18+
const { SwiftRuntime } = await import(`/.build/${configuration}/JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs`);
19+
const swiftRuntime = new SwiftRuntime({ sharedMemory: true, threadChannel });
20+
const importObject = {
21+
wasi_snapshot_preview1: wasi.wasiImport,
22+
javascript_kit: swiftRuntime.wasmImports,
23+
};
24+
addToImports(importObject);
25+
const instance = await WebAssembly.instantiate(module, importObject);
26+
27+
swiftRuntime.setInstance(instance);
28+
return { swiftRuntime, wasi, instance };
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { instantiate } from "./instantiate.js"
2+
3+
self.onmessage = async (event) => {
4+
const { module, memory, tid, startArg, configuration } = event.data;
5+
const { instance, wasi, swiftRuntime } = await instantiate({
6+
module,
7+
threadChannel: {
8+
postMessageToMainThread: (message) => {
9+
// Send the job to the main thread
10+
postMessage(message);
11+
},
12+
listenMessageFromMainThread: (listener) => {
13+
self.onmessage = (event) => listener(event.data);
14+
}
15+
},
16+
addToImports(importObject) {
17+
importObject["env"] = { memory }
18+
importObject["wasi"] = {
19+
"thread-spawn": () => { throw new Error("Cannot spawn a new thread from a worker thread"); }
20+
};
21+
},
22+
configuration
23+
});
24+
25+
swiftRuntime.setInstance(instance);
26+
wasi.inst = instance;
27+
swiftRuntime.startThread(tid, startArg);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import ChibiRay
2+
3+
func createDemoScene() -> Scene {
4+
return Scene(
5+
width: 800,
6+
height: 800,
7+
fov: 90,
8+
elements: [
9+
.sphere(
10+
Sphere(
11+
center: Point(x: 1.0, y: -1.0, z: -7.0),
12+
radius: 1.0,
13+
material: Material(
14+
color: Color(red: 0.8, green: 0.2, blue: 0.4),
15+
albedo: 0.6,
16+
surface: .reflective(reflectivity: 0.9)
17+
)
18+
)
19+
),
20+
.sphere(
21+
Sphere(
22+
center: Point(x: -2.0, y: 1.0, z: -10.0),
23+
radius: 3.0,
24+
material: Material(
25+
color: Color(red: 1.0, green: 0.4, blue: 0.4),
26+
albedo: 0.7,
27+
surface: .diffuse
28+
)
29+
)
30+
),
31+
.sphere(
32+
Sphere(
33+
center: Point(x: 3.0, y: 2.0, z: -5.0),
34+
radius: 2.0,
35+
material: Material(
36+
color: Color(red: 0.4, green: 0.4, blue: 0.8),
37+
albedo: 0.5,
38+
surface: .refractive(index: 2, transparency: 0.9)
39+
)
40+
)
41+
),
42+
.plane(
43+
Plane(
44+
origin: Point(x: 0.0, y: -2.0, z: -5.0),
45+
normal: Vector3(x: 0.0, y: -1.0, z: 0.0),
46+
material: Material(
47+
color: Color(red: 1.0, green: 1.0, blue: 1.0),
48+
albedo: 0.18,
49+
surface: .reflective(reflectivity: 0.5)
50+
)
51+
)
52+
),
53+
.plane(
54+
Plane(
55+
origin: Point(x: 0.0, y: 0.0, z: -20.0),
56+
normal: Vector3(x: 0.0, y: 0.0, z: -1.0),
57+
material: Material(
58+
color: Color(red: 0.2, green: 0.3, blue: 1.0),
59+
albedo: 0.38,
60+
surface: .diffuse
61+
)
62+
)
63+
)
64+
],
65+
lights: [
66+
.spherical(
67+
SphericalLight(
68+
position: Point(x: 5.0, y: 10.0, z: -3.0),
69+
color: Color(red: 1.0, green: 1.0, blue: 1.0),
70+
intensity: 16000
71+
)
72+
),
73+
.spherical(
74+
SphericalLight(
75+
position: Point(x: -3.0, y: 3.0, z: -5.0),
76+
color: Color(red: 0.3, green: 0.3, blue: 1.0),
77+
intensity: 1000
78+
)
79+
),
80+
.directional(
81+
DirectionalLight(
82+
direction: Vector3(x: 0.0, y: -1.0, z: -1.0),
83+
color: Color(red: 0.8, green: 0.8, blue: 0.8),
84+
intensity: 0.2
85+
)
86+
)
87+
],
88+
shadowBias: 1e-13,
89+
maxRecursionDepth: 10
90+
)
91+
}
92+
93+
extension Scene: @retroactive @unchecked Sendable {}

0 commit comments

Comments
 (0)