Add JavaScript bindings and WASM support
Co-authored-by: Matija Kecman <matijak@google.com> Co-authored-by: Sebastian Noreña Rendón <sebas.norena@creativa77.com.ar> Co-authored-by: Kyle Bayes <kylebayes@google.com> PiperOrigin-RevId: 826479070 Change-Id: I25acf36e6c20b091d8492e10798d028ae66f6733
This commit is contained in:
committed by
Copybara-Service
parent
57f7145806
commit
4086261714
@@ -0,0 +1,60 @@
|
||||
# Copyright 2025 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/wasm/dist")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -O3")
|
||||
|
||||
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/wasm)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/src)
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
|
||||
link_directories(${CMAKE_BINARY_DIR}/lib)
|
||||
|
||||
file(GLOB MUJOCO_WASM_FILES
|
||||
"benchmark_test.cc"
|
||||
"../unpack.cc"
|
||||
)
|
||||
|
||||
if(NOT MUJOCO_WASM_FILES)
|
||||
message(FATAL_ERROR "No source files found")
|
||||
endif()
|
||||
|
||||
add_compile_options(-pthread)
|
||||
|
||||
# Set Emscripten linker flags
|
||||
set(EMCC_LINKER_FLAGS
|
||||
"--bind"
|
||||
"-s ASSERTIONS=1"
|
||||
"-s ALLOW_MEMORY_GROWTH=1"
|
||||
"-s EXPORT_ES6=1"
|
||||
"-s MODULARIZE=1"
|
||||
"-s FORCE_FILESYSTEM=1"
|
||||
"-s EXPORTED_RUNTIME_METHODS=['ccall','cwrap','FS','MEMFS']"
|
||||
"-s EXPORT_NAME=loadMujoco"
|
||||
"-gsource-map"
|
||||
"-g"
|
||||
"--emit-tsd mujoco_wasm_benchmark.d.ts"
|
||||
)
|
||||
string (REPLACE ";" " " EMCC_LINKER_FLAGS_STR "${EMCC_LINKER_FLAGS}")
|
||||
|
||||
add_executable(mujoco_wasm_benchmark ${MUJOCO_WASM_FILES})
|
||||
|
||||
set_target_properties(mujoco_wasm_benchmark PROPERTIES LINK_FLAGS "${EMCC_LINKER_FLAGS_STR}")
|
||||
|
||||
target_link_libraries(mujoco_wasm_benchmark ccd lodepng mujoco tinyxml2 qhullstatic_r)
|
||||
|
||||
install(TARGETS mujoco_wasm_benchmark DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR})
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "wasm/unpack.h"
|
||||
|
||||
namespace mujoco::wasm {
|
||||
using emscripten::val;
|
||||
|
||||
EMSCRIPTEN_DECLARE_VAL_TYPE(NumberArray);
|
||||
|
||||
// TODO(matijak): Add a benchmark where the buffer shared with JS and C++ is
|
||||
// created in C++ rather than JS.
|
||||
|
||||
val BenchmarkSortNumberArray(const NumberArray& vec) {
|
||||
UNPACK_ARRAY(mjtNum, vec);
|
||||
std::sort(vec_.data(), vec_.data() + vec_.size());
|
||||
return val::array(vec_.data(), vec_.data() + vec_.size());
|
||||
}
|
||||
|
||||
val BenchmarkSortDoubleBuffer(const val& vec) {
|
||||
UNPACK_VALUE(mjtNum, vec);
|
||||
std::sort(vec_.data(), vec_.data() + vec_.size());
|
||||
return vec;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(mujoco_benchmark_functions) {
|
||||
emscripten::class_<WasmBuffer<mjtNum>>("DoubleBuffer")
|
||||
.constructor<int>()
|
||||
.class_function("FromArray", &WasmBuffer<mjtNum>::FromArray)
|
||||
.function("GetPointer", &WasmBuffer<mjtNum>::GetPointer)
|
||||
.function("GetElementCount", &WasmBuffer<mjtNum>::GetElementCount)
|
||||
.function("GetView", &WasmBuffer<mjtNum>::GetView);
|
||||
emscripten::function("SortNumberArray", &BenchmarkSortNumberArray);
|
||||
emscripten::function("SortDoubleBuffer", &BenchmarkSortDoubleBuffer);
|
||||
emscripten::register_type<NumberArray>("number[]");
|
||||
}
|
||||
|
||||
} // namespace mujoco::wasm
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'jasmine';
|
||||
|
||||
import { MainModule, DoubleBuffer } from "../dist/mujoco_wasm_benchmark"
|
||||
import loadMujoco from "../dist/mujoco_wasm_benchmark.js"
|
||||
|
||||
describe('MuJoCo WASM Benchmark Tests', () => {
|
||||
let mujoco: MainModule;
|
||||
|
||||
function isNumberArraySorted(arr: number[]): boolean {
|
||||
for (let i = 0; i < arr.length - 1; i++) {
|
||||
if (arr[i] > arr[i + 1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function verifySorted(state: any, kind: string): void {
|
||||
let array: number[] = [];
|
||||
if (kind === 'NumberArray') {
|
||||
array = state as number[];
|
||||
} else {
|
||||
const bufferView = (state as DoubleBuffer).GetView();
|
||||
array = Array.from(bufferView) as number[];
|
||||
}
|
||||
|
||||
const sorted = isNumberArraySorted(array);
|
||||
if (!sorted) {
|
||||
console.error(` Verification failed for ${kind}. Array was ${array}`);
|
||||
}
|
||||
}
|
||||
|
||||
function runBenchmark(iterations: number, items: number, kind: string) {
|
||||
let state: any;
|
||||
let func: (state: any) => any;
|
||||
if (kind === 'NumberArray') {
|
||||
state = new Array(items).map(() => Math.random())
|
||||
func = (state) => mujoco.SortNumberArray(state);
|
||||
} else if (kind === 'DoubleBuffer') {
|
||||
state = mujoco.DoubleBuffer.FromArray(
|
||||
new Array(items).map(() => Math.random()));
|
||||
func = (state) => mujoco.SortDoubleBuffer(state);
|
||||
} else {
|
||||
throw new Error(`Unsupported benchmark type: ${kind}`);
|
||||
}
|
||||
|
||||
// Warmup JIT compiler to get stable results
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const sortedState = func(state);
|
||||
verifySorted(sortedState, kind);
|
||||
}
|
||||
|
||||
// Measurement
|
||||
const totalStartTime = performance.now();
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
func(state);
|
||||
}
|
||||
const totalEndTime = performance.now();
|
||||
|
||||
if (kind === 'DoubleBuffer') {
|
||||
state.delete();
|
||||
}
|
||||
|
||||
// Report results
|
||||
const totalTime = totalEndTime - totalStartTime;
|
||||
const avgTimeMilliseconds = totalTime / iterations;
|
||||
console.log(`Benchmark: "Sort ${kind} ${iterations} iterations with ${
|
||||
items} items" - AVG time per call: ${
|
||||
avgTimeMilliseconds.toFixed(2)} ms`);
|
||||
return {
|
||||
totalTime, avgTimeMilliseconds,
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
mujoco = await loadMujoco();
|
||||
});
|
||||
|
||||
it('should benchmark NumberArray and DoubleBuffer and compare results',
|
||||
() => {
|
||||
const na1 = runBenchmark(100, 1_000, 'NumberArray');
|
||||
const na2 = runBenchmark(100, 400_000, 'NumberArray');
|
||||
|
||||
const db1 = runBenchmark(100, 1_000, 'DoubleBuffer');
|
||||
const db2 = runBenchmark(100, 400_000, 'DoubleBuffer');
|
||||
|
||||
// The actual time should be much faster than that but the intention of
|
||||
// the check is to catch huge regressions without a flakey test
|
||||
const _100ms = 100;
|
||||
|
||||
expect(db2.avgTimeMilliseconds).toBeLessThan(_100ms);
|
||||
|
||||
expect(db1.totalTime).toBeLessThan(na1.totalTime);
|
||||
expect(db2.totalTime).toBeLessThan(na2.totalTime);
|
||||
|
||||
expect(db1.avgTimeMilliseconds).toBeLessThan(na1.avgTimeMilliseconds);
|
||||
expect(db2.avgTimeMilliseconds).toBeLessThan(na2.avgTimeMilliseconds);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,274 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'jasmine';
|
||||
|
||||
import { MainModule } from "../dist/mujoco_wasm"
|
||||
import loadMujoco from "../dist/mujoco_wasm.js"
|
||||
|
||||
let mujoco: MainModule;
|
||||
|
||||
describe('Enums', () => {
|
||||
beforeAll(async () => {
|
||||
mujoco = await loadMujoco();
|
||||
});
|
||||
|
||||
it('mjtDisableBit should exist', () => {
|
||||
expect(mujoco.mjtDisableBit).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtEnableBit should exist', () => {
|
||||
expect(mujoco.mjtEnableBit).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtJoint should exist', () => {
|
||||
expect(mujoco.mjtJoint).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtGeom should exist', () => {
|
||||
expect(mujoco.mjtGeom).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtCamLight should exist', () => {
|
||||
expect(mujoco.mjtCamLight).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtLightType should exist', () => {
|
||||
expect(mujoco.mjtLightType).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtTexture should exist', () => {
|
||||
expect(mujoco.mjtTexture).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtTextureRole should exist', () => {
|
||||
expect(mujoco.mjtTextureRole).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtColorSpace should exist', () => {
|
||||
expect(mujoco.mjtColorSpace).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtIntegrator should exist', () => {
|
||||
expect(mujoco.mjtIntegrator).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtCone should exist', () => {
|
||||
expect(mujoco.mjtCone).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtJacobian should exist', () => {
|
||||
expect(mujoco.mjtJacobian).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtSolver should exist', () => {
|
||||
expect(mujoco.mjtSolver).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtEq should exist', () => {
|
||||
expect(mujoco.mjtEq).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtWrap should exist', () => {
|
||||
expect(mujoco.mjtWrap).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtTrn should exist', () => {
|
||||
expect(mujoco.mjtTrn).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtDyn should exist', () => {
|
||||
expect(mujoco.mjtDyn).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtGain should exist', () => {
|
||||
expect(mujoco.mjtGain).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtBias should exist', () => {
|
||||
expect(mujoco.mjtBias).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtObj should exist', () => {
|
||||
expect(mujoco.mjtObj).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtSensor should exist', () => {
|
||||
expect(mujoco.mjtSensor).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtStage should exist', () => {
|
||||
expect(mujoco.mjtStage).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtDataType should exist', () => {
|
||||
expect(mujoco.mjtDataType).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtConDataField should exist', () => {
|
||||
expect(mujoco.mjtConDataField).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtSameFrame should exist', () => {
|
||||
expect(mujoco.mjtSameFrame).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtLRMode should exist', () => {
|
||||
expect(mujoco.mjtLRMode).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtFlexSelf should exist', () => {
|
||||
expect(mujoco.mjtFlexSelf).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtSDFType should exist', () => {
|
||||
expect(mujoco.mjtSDFType).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtTaskStatus should exist', () => {
|
||||
expect(mujoco.mjtTaskStatus).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtState should exist', () => {
|
||||
expect(mujoco.mjtState).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtConstraint should exist', () => {
|
||||
expect(mujoco.mjtConstraint).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtConstraintState should exist', () => {
|
||||
expect(mujoco.mjtConstraintState).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtWarning should exist', () => {
|
||||
expect(mujoco.mjtWarning).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtTimer should exist', () => {
|
||||
expect(mujoco.mjtTimer).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtCatBit should exist', () => {
|
||||
expect(mujoco.mjtCatBit).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtMouse should exist', () => {
|
||||
expect(mujoco.mjtMouse).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtPertBit should exist', () => {
|
||||
expect(mujoco.mjtPertBit).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtCamera should exist', () => {
|
||||
expect(mujoco.mjtCamera).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtLabel should exist', () => {
|
||||
expect(mujoco.mjtLabel).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtFrame should exist', () => {
|
||||
expect(mujoco.mjtFrame).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtVisFlag should exist', () => {
|
||||
expect(mujoco.mjtVisFlag).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtRndFlag should exist', () => {
|
||||
expect(mujoco.mjtRndFlag).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtStereo should exist', () => {
|
||||
expect(mujoco.mjtStereo).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtPluginCapabilityBit should exist', () => {
|
||||
expect(mujoco.mjtPluginCapabilityBit).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtGridPos should exist', () => {
|
||||
expect(mujoco.mjtGridPos).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtFramebuffer should exist', () => {
|
||||
expect(mujoco.mjtFramebuffer).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtDepthMap should exist', () => {
|
||||
expect(mujoco.mjtDepthMap).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtFontScale should exist', () => {
|
||||
expect(mujoco.mjtFontScale).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtFont should exist', () => {
|
||||
expect(mujoco.mjtFont).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtGeomInertia should exist', () => {
|
||||
expect(mujoco.mjtGeomInertia).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtMeshInertia should exist', () => {
|
||||
expect(mujoco.mjtMeshInertia).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtMeshBuiltin should exist', () => {
|
||||
expect(mujoco.mjtMeshBuiltin).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtBuiltin should exist', () => {
|
||||
expect(mujoco.mjtBuiltin).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtMark should exist', () => {
|
||||
expect(mujoco.mjtMark).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtLimited should exist', () => {
|
||||
expect(mujoco.mjtLimited).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtAlignFree should exist', () => {
|
||||
expect(mujoco.mjtAlignFree).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtInertiaFromGeom should exist', () => {
|
||||
expect(mujoco.mjtInertiaFromGeom).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtOrientation should exist', () => {
|
||||
expect(mujoco.mjtOrientation).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtButton should exist', () => {
|
||||
expect(mujoco.mjtButton).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtEvent should exist', () => {
|
||||
expect(mujoco.mjtEvent).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtItem should exist', () => {
|
||||
expect(mujoco.mjtItem).toBeDefined();
|
||||
});
|
||||
|
||||
it('mjtSection should exist', () => {
|
||||
expect(mujoco.mjtSection).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extension": {
|
||||
"karma": {
|
||||
"client": {
|
||||
"pingTimeout": 300000
|
||||
},
|
||||
"browserDisconnectTimeout": 300000
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import Jasmine from 'jasmine';
|
||||
|
||||
const jasmine = new Jasmine();
|
||||
|
||||
jasmine.loadConfig({
|
||||
spec_dir: 'tests',
|
||||
spec_files: ['**/*_test.ts'],
|
||||
jsLoader: 'import',
|
||||
random: false,
|
||||
stopSpecOnExpectationFailure: false,
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('Starting Jasmine test run...');
|
||||
const result = await jasmine.execute();
|
||||
console.log(`Jasmine test run finished. Status: ${result.overallStatus}`);
|
||||
|
||||
if (result.overallStatus === 'failed') {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Test runner script failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
Copyright 2025 DeepMind Technologies Limited
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>MuJoCo WebAssembly</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { MainModule, MjData, MjModel } from "../../dist/mujoco_wasm"
|
||||
import loadMujoco from "../../dist/mujoco_wasm.js"
|
||||
|
||||
declare function loadMujoco(): Promise<MainModule>;
|
||||
|
||||
async function main() {
|
||||
const mujoco: MainModule = await loadMujoco();
|
||||
|
||||
(mujoco as any).FS.mkdir('/working');
|
||||
(mujoco as any).FS.mount((mujoco as any).MEMFS, {root: '.'}, '/working');
|
||||
|
||||
const xmlContent = `
|
||||
<mujoco model="Box falling">
|
||||
<option viscosity="1"/>
|
||||
<worldbody>
|
||||
<light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/>
|
||||
<geom name="MyFloor" type="plane" size="1 1 0.1" rgba=".9 0 0 1" user="5 4 3 2 1"/>
|
||||
<geom name="MyWall" type="plane" size="0.1 1 0.1" rgba="0 1 0 1" user="5 4 3"/>
|
||||
<body pos="0 0 1" name="MyBox">
|
||||
<joint type="free"/>
|
||||
<geom name="MyBoxGeom" type="box" size=".1 .2 .3" rgba="0 .9 0 1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>`;
|
||||
|
||||
(mujoco as any).FS.writeFile('/working/hello.xml', xmlContent);
|
||||
let model: MjModel|undefined;
|
||||
let data: MjData|undefined;
|
||||
|
||||
try {
|
||||
console.log('Hello world!: Loading model');
|
||||
model = mujoco.MjModel.loadFromXML('/working/hello.xml');
|
||||
if (!model) {
|
||||
throw new Error('Failed to load model');
|
||||
}
|
||||
data = new mujoco.MjData(model);
|
||||
if (!data) {
|
||||
throw new Error('Failed to load data');
|
||||
}
|
||||
|
||||
// Add your test code here...
|
||||
|
||||
} finally {
|
||||
model?.delete();
|
||||
data?.delete();
|
||||
(mujoco as any).FS.unmount('/working');
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { defineConfig } from "vite"
|
||||
|
||||
export default defineConfig({
|
||||
root: 'tests/sandbox',
|
||||
base: './',
|
||||
build: {
|
||||
outDir: '../../sandbox-dist',
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
open: true,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user