Skip to content

Commit fa0df81

Browse files
authored
Using Jolt's math in the multi threaded conveyor belt contact listener (#212)
Fixed conveyor belt example to be thread safe
1 parent a0b6ec7 commit fa0df81

File tree

6 files changed

+54
-35
lines changed

6 files changed

+54
-35
lines changed

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ if (ENABLE_MULTI_THREADING)
5959
-s PTHREAD_POOL_SIZE=16)
6060
set(CMAKE_CXX_FLAGS "-pthread -s SHARED_MEMORY")
6161
set(OUTPUT_BASE_NAME "${OUTPUT_FOLDER}jolt-physics.multithread")
62+
set(WEBIDL_PARAMS "--thread_safe")
6263
else()
6364
set(MULTI_THREADED_FLAG "")
6465
set(ENVIRONMENT_FLAG "-s ENVIRONMENT='web,node'")
6566
set(OUTPUT_BASE_NAME "${OUTPUT_FOLDER}jolt-physics")
67+
set(WEBIDL_PARAMS "")
6668
endif()
6769

6870
# Enable SIMD flags
@@ -199,7 +201,7 @@ add_custom_command(
199201
OUTPUT glue.cpp glue.js jolt.idl
200202
BYPRODUCTS parser.out WebIDLGrammar.pkl
201203
COMMAND cat ${JOLT_IDL_FILE} > jolt.idl
202-
COMMAND ${PYTHON} ${WEBIDL_BINDER_SCRIPT} jolt.idl glue
204+
COMMAND ${PYTHON} ${WEBIDL_BINDER_SCRIPT} jolt.idl glue ${WEBIDL_PARAMS}
203205
DEPENDS ${JOLT_IDL_FILE}
204206
COMMENT "Generating JoltPhysics.js bindings"
205207
VERBATIM)

Examples/conveyor_belt_threaded.html

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
<script src="js/example.js"></script>
2121

2222
<script id="workerScript" type="module">
23-
import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.min.js';
24-
25-
const wrapQuat = (q) => new THREE.Quaternion(q.GetX(), q.GetY(), q.GetZ(), q.GetW());
26-
const wrapVec3 = (v) => new THREE.Vector3(v.GetX(), v.GetY(), v.GetZ());
27-
const DegreesToRadians = (deg) => deg * (Math.PI / 180.0);
2823
const onWorker = globalThis.onWorker = async (Jolt, args) => {
24+
Jolt.Vec3.prototype.Clone = function () { return new Jolt.Vec3(this.GetX(), this.GetY(), this.GetZ()); };
25+
Jolt.RVec3.prototype.Clone = function () { return new Jolt.RVec3(this.GetX(), this.GetY(), this.GetZ()); };
26+
Jolt.Quat.prototype.Clone = function () { return new Jolt.Quat(this.GetX(), this.GetY(), this.GetZ(), this.GetW()); };
27+
const DegreesToRadians = (deg) => deg * (Math.PI / 180.0);
28+
29+
const cLocalSpaceVelocity = new Jolt.Vec3(0, 0, -10.0);
30+
const cLocalSpaceAngularVelocity = new Jolt.Vec3(0, DegreesToRadians(10.0), 0);
31+
2932
const { contactListenerPtr, linearBelts, angularBelt } = args;
3033
const contactListener = Jolt.wrapPointer(contactListenerPtr, Jolt.ContactListenerJS);
3134

@@ -37,44 +40,39 @@
3740

3841
const body1ID = body1.GetID().GetIndexAndSequenceNumber();
3942
const body2ID = body2.GetID().GetIndexAndSequenceNumber();
40-
const rotation1 = wrapQuat(body1.GetRotation());
41-
const rotation2 = wrapQuat(body2.GetRotation());
43+
const rotation1 = body1.GetRotation().Clone(); // Next call to GetRotation() will overwrite rotation1
44+
const rotation2 = body2.GetRotation();
4245

4346
const body1LinearBelt = linearBelts.find(belt => belt == body1ID)
4447
const body2LinearBelt = linearBelts.find(belt => belt == body2ID)
4548
if (body1LinearBelt || body2LinearBelt) {
4649
// Determine the world space surface velocity of both bodies
47-
const cLocalSpaceVelocity = new THREE.Vector3(0, 0, -10.0);
48-
const body1LinearSurfaceVelocity = body1LinearBelt ? cLocalSpaceVelocity.applyQuaternion(rotation1) : new THREE.Vector3(0, 0, 0);
49-
const body2LinearSurfaceVelocity = body2LinearBelt ? cLocalSpaceVelocity.applyQuaternion(rotation2) : new THREE.Vector3(0, 0, 0);
50+
const body1LinearSurfaceVelocity = body1LinearBelt ? rotation1.MulVec3(cLocalSpaceVelocity).Clone() : new Jolt.Vec3(0, 0, 0);
51+
const body2LinearSurfaceVelocity = body2LinearBelt ? rotation2.MulVec3(cLocalSpaceVelocity).Clone() : new Jolt.Vec3(0, 0, 0);
5052

5153
// Calculate the relative surface velocity
52-
const v = body2LinearSurfaceVelocity.sub(body1LinearSurfaceVelocity);
53-
settings.mRelativeLinearSurfaceVelocity.Set(v.x, v.y, v.z);
54+
body2LinearSurfaceVelocity.Sub(body1LinearSurfaceVelocity);
55+
settings.mRelativeLinearSurfaceVelocity = body2LinearSurfaceVelocity;
5456
}
5557

5658
const angularBodyId = angularBelt[0];
5759
const body1Angular = body1ID == angularBodyId;
5860
const body2Angular = body2ID == angularBodyId;
5961
if (body1Angular || body2Angular) {
6062
// Determine the world space angular surface velocity of both bodies
61-
const cLocalSpaceAngularVelocity = new THREE.Vector3(0, DegreesToRadians(10.0), 0);
62-
const body1AngularSurfaceVelocity = body1Angular ? cLocalSpaceAngularVelocity.applyQuaternion(rotation1) : new THREE.Vector3(0, 0, 0);
63-
const body2AngularSurfaceVelocity = body2Angular ? cLocalSpaceAngularVelocity.applyQuaternion(rotation2) : new THREE.Vector3(0, 0, 0);
63+
const body1AngularSurfaceVelocity = body1Angular ? rotation1.MulVec3(cLocalSpaceAngularVelocity).Clone() : new Jolt.Vec3(0, 0, 0);
64+
const body2AngularSurfaceVelocity = body2Angular ? rotation2.MulVec3(cLocalSpaceAngularVelocity).Clone() : new Jolt.Vec3(0, 0, 0);
6465

6566
// Note that the angular velocity is the angular velocity around body 1's center of mass, so we need to add the linear velocity of body 2's center of mass
66-
const COM1 = wrapVec3(body1.GetCenterOfMassPosition());
67-
const COM2 = wrapVec3(body2.GetCenterOfMassPosition());
68-
const body2LinearSurfaceVelocity = body2Angular ?
69-
body2AngularSurfaceVelocity.cross(COM1.clone().sub(COM2)) : new THREE.Vector3(0, 0, 0);
67+
const COM1 = body1.GetCenterOfMassPosition().Clone();
68+
const COM2 = body2.GetCenterOfMassPosition();
69+
const body2LinearSurfaceVelocity = body2Angular ? body2AngularSurfaceVelocity.Cross(COM1.Sub(COM2)).Clone() : new Jolt.Vec3(0, 0, 0);
7070

7171
// Calculate the relative angular surface velocity
72-
const rls = body2LinearSurfaceVelocity;
73-
settings.mRelativeLinearSurfaceVelocity.Set(rls.x, rls.y, rls.z);
74-
const ras = body2AngularSurfaceVelocity.sub(body1AngularSurfaceVelocity);
75-
settings.mRelativeAngularSurfaceVelocity.Set(ras.x, ras.y, ras.z);
72+
settings.mRelativeLinearSurfaceVelocity = body2LinearSurfaceVelocity;
73+
body2AngularSurfaceVelocity.Sub(body1AngularSurfaceVelocity);
74+
settings.mRelativeAngularSurfaceVelocity = body2AngularSurfaceVelocity;
7675
}
77-
7876
};
7977
contactListener.OnContactPersisted = (body1, body2, manifold, settings) => {
8078
// Same behavior as contact added

Examples/stress_test_threaded.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<title>JoltPhysics.js demo</title>
55
<meta charset="utf-8">
66
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7-
<script src="js/coi-serviceworker.min.js"></script>
7+
<script src="coi-serviceworker.min.js"></script>
88
<link rel="stylesheet" type="text/css" href="style.css">
99
</head>
1010
<body>

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ Go to the [demos page](https://jrouwe.github.io/JoltPhysics.js/) to see the proj
1313

1414
## Using
1515

16-
This library comes in 3 flavours:
16+
This library comes in 5 flavours:
1717
- `wasm-compat` - A WASM version with the WASM file (encoded in base64) embedded in the bundle
1818
- `wasm` - A WASM version with a separate WASM file
1919
- `asm` - A JavaScript version that uses [asm.js](https://developer.mozilla.org/en-US/docs/Games/Tools/asm.js)
20+
- `wasm-compat-multithread` - Same as `wasm-compat` but with multi threading and SIMD enabled.
21+
- `wasm-multithread` - Same as `wasm` but with multi threading and SIMD enabled.
2022

2123
See [falling_shapes.html](Examples/falling_shapes.html) for a example on how to use the library.
2224

@@ -46,6 +48,12 @@ import Jolt from 'jolt-physics/wasm';
4648

4749
// asm.js
4850
import Jolt from 'jolt-physics/asm';
51+
52+
// WASM embedded in the bundle, multithread and SIMD enabled
53+
import Jolt from 'jolt-physics/wasm-compat-multithread';
54+
55+
// WASM, multithread and SIMD enabled
56+
import Jolt from 'jolt-physics/wasm-multithread';
4957
```
5058

5159
You can also import esm bundles with unpkg:
@@ -95,7 +103,7 @@ Additional options that can be provided to ```build.sh```:
95103

96104
## Running
97105

98-
By default the examples use the WASM version of Jolt. This requires serving the html file using a web server rather than opening the html file directly.
106+
By default the examples use the WASM compat version of Jolt. This requires serving the html file using a web server rather than opening the html file directly.
99107

100108
Go open a terminal in this folder and run the following commands:
101109

tools/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
The files in this folder are modified versions of https://github.com/emscripten-core/emscripten/tree/main/tools and https://github.com/emscripten-core/emscripten/tree/main/third_party at version 10cb9d46cdd17e7a96de68137c9649d9a630fbc7
22

3-
They are here until this PR is merged: https://github.com/emscripten-core/emscripten/pull/22770
3+
They are here until these PRs are merged:
4+
- https://github.com/emscripten-core/emscripten/pull/22770
5+
- https://github.com/emscripten-core/emscripten/pull/22772

tools/webidl_binder.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ def getExtendedAttribute(self, _name):
5959
parser = argparse.ArgumentParser()
6060
parser.add_argument('--wasm64', action='store_true', default=False,
6161
help='Build for wasm64')
62+
parser.add_argument('--thread_safe', action='store_true', default=False,
63+
help='Emit temporaries as "static thread_local" to avoid race conditions')
6264
parser.add_argument('infile')
6365
parser.add_argument('outfile')
6466
options = parser.parse_args()
@@ -388,7 +390,7 @@ def type_to_cdec(raw):
388390

389391
def render_function(class_name, func_name, sigs, return_type, non_pointer,
390392
copy, operator, constructor, is_static, func_scope,
391-
call_content=None, const=False, array_attribute=False):
393+
call_content=None, const=False, array_attribute=False, thread_safe=False):
392394
legacy_mode = CHECKS not in ['ALL', 'FAST']
393395
all_checks = CHECKS == 'ALL'
394396

@@ -624,10 +626,13 @@ def make_call_args(i):
624626
basic_return = 'return ' if constructor or return_type != 'Void' else ''
625627
return_prefix = basic_return
626628
return_postfix = ''
629+
storage_attribute = ''
627630
if non_pointer:
628631
return_prefix += '&'
632+
if thread_safe:
633+
storage_attribute = 'thread_local '
629634
if copy:
630-
pre += ' static %s temp;\n' % type_to_c(return_type, non_pointing=True)
635+
pre += ' static %s%s temp;\n' % (storage_attribute, type_to_c(return_type, non_pointing=True))
631636
return_prefix += '(temp = '
632637
return_postfix += ', &temp)'
633638

@@ -765,7 +770,8 @@ def add_bounds_check_impl():
765770
constructor,
766771
is_static=m.isStatic(),
767772
func_scope=m.parentScope.identifier.name,
768-
const=m.getExtendedAttribute('Const'))
773+
const=m.getExtendedAttribute('Const'),
774+
thread_safe=options.thread_safe)
769775
mid_js += ['\n']
770776
if constructor:
771777
mid_js += build_constructor(name)
@@ -807,7 +813,8 @@ def add_bounds_check_impl():
807813
func_scope=interface,
808814
call_content=get_call_content,
809815
const=m.getExtendedAttribute('Const'),
810-
array_attribute=m.type.isArray())
816+
array_attribute=m.type.isArray(),
817+
thread_safe=options.thread_safe)
811818

812819
if m.readonly:
813820
mid_js += [r'''
@@ -829,7 +836,8 @@ def add_bounds_check_impl():
829836
func_scope=interface,
830837
call_content=set_call_content,
831838
const=m.getExtendedAttribute('Const'),
832-
array_attribute=m.type.isArray())
839+
array_attribute=m.type.isArray(),
840+
thread_safe=options.thread_safe)
833841
mid_js += [r'''
834842
/** @suppress {checkTypes} */
835843
Object.defineProperty(%s.prototype, '%s', { get: %s.prototype.%s, set: %s.prototype.%s });
@@ -847,7 +855,8 @@ def add_bounds_check_impl():
847855
False,
848856
False,
849857
func_scope=interface,
850-
call_content='delete self')
858+
call_content='delete self',
859+
thread_safe=options.thread_safe)
851860

852861
# Emit C++ class implementation that calls into JS implementation
853862

0 commit comments

Comments
 (0)