Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions demo/electron/car/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@ npm run build
npm install
```

3. **Rebuild native modules for Electron:**
3. **Native modules — no manual rebuild needed:**

```bash
npm run rebuild
```
rclnodejs ships prebuilt binaries for Electron and selects the matching one at
runtime from `ROS_DISTRO` + Linux codename + architecture, so just make sure
ROS 2 is sourced (next step) before launching. Do not run `electron-rebuild`
against rclnodejs — it recompiles from source and bypasses the prebuilt binary.
The Forge `rebuildConfig` in `package.json` already excludes `rclnodejs` from
the automatic rebuild step.

4. **Start the demo:**
```bash
Expand Down Expand Up @@ -291,13 +294,12 @@ Extend the joystick commands by modifying the switch statement in `main.js` and
- Ensure ROS2 is properly sourced before running npm start
- Check that rclnodejs is built correctly

2. **Native Module Rebuild Issues**

```bash
npm run rebuild
# or manually:
./node_modules/.bin/electron-rebuild
```
2. **Native module fails to load**
- Ensure ROS 2 is sourced so rclnodejs can match its prebuilt binary
(`ROS_DISTRO` must be set; prebuilt binaries are provided for Ubuntu).
- As a last resort, force a from-source rebuild by setting
`RCLNODEJS_FORCE_BUILD=1` before `npm start` (needs a compiler toolchain and
network access).

3. **Topic Not Appearing**
- Verify ROS2 daemon is running: `ros2 daemon status`
Expand Down
6 changes: 5 additions & 1 deletion demo/electron/car/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"main": "main.js",
"scripts": {
"start": "electron-forge start",
"rebuild": "electron-rebuild",
"package": "electron-forge package",
"make": "electron-forge make"
},
Expand Down Expand Up @@ -39,6 +38,11 @@
"unpack": "**/node_modules/rclnodejs/**"
}
},
"rebuildConfig": {
"ignoreModules": [
"rclnodejs"
]
},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
Expand Down
22 changes: 16 additions & 6 deletions demo/electron/manipulator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,27 @@ An interactive Electron application demonstrating a two-joint robotic manipulato
npm install
```

3. **Rebuild native modules for Electron**:
3. **Source your ROS 2 environment** (required so the matching prebuilt binary is selected):

```bash
npm run rebuild
source /opt/ros/$ROS_DISTRO/setup.bash # or your ROS 2 installation path
```

rclnodejs ships prebuilt native binaries for Electron, so no compilation is needed.
The binary is selected at runtime from `ROS_DISTRO`, the Linux distro codename, and
the CPU architecture, so `ROS_DISTRO` must be set before launching the app. If no
matching prebuilt binary is available for your platform, rclnodejs falls back to
building from source.

> Note: do not run `electron-rebuild` against rclnodejs — it recompiles the addon
> from source and bypasses the prebuilt binary. The Electron Forge `rebuildConfig`
> in `package.json` already excludes `rclnodejs` from the automatic rebuild step.

## 📜 Available Scripts

- **`npm start`** - Launch the application in development mode
- **`npm run package`** - Package the application into a standalone executable folder
- **`npm run make`** - Create platform-specific installers (requires system tools like `zip`, `dpkg`)
- **`npm run rebuild`** - Rebuild native modules after dependency changes

## 🚀 Quick Start

Expand All @@ -54,9 +64,9 @@ An interactive Electron application demonstrating a two-joint robotic manipulato
npm start
Comment on lines +25 to 28
```

- ✅ **No ROS2 environment required**
- ✅ **Works without external setup**
- ✅ **No ROS2 topic publishing required**
- ✅ **Pure visualization and manual control**
- ⚠️ ROS 2 must still be sourced so the prebuilt native binary is selected (otherwise rclnodejs builds from source)
- ⚠️ No ROS2 topic publishing (local mode only)

### Option 2: Manual ROS2 Setup (Recommended for ROS2 Integration)
Expand Down Expand Up @@ -308,7 +318,7 @@ manipulator/
2. **Build errors with Electron**

- This demo currently uses Electron 40.1.0
- If you change Electron or other native-module dependencies, rerun `npm run rebuild`
- rclnodejs loads its prebuilt Electron binary at runtime; if it fails to load, ensure ROS 2 is sourced so the matching prebuild is selected. To force a from-source rebuild of rclnodejs, set `RCLNODEJS_FORCE_BUILD=1`
- The versions recorded in `package.json` and `package-lock.json` are the tested baseline for this demo

3. **No ROS2 messages received**
Expand Down
6 changes: 5 additions & 1 deletion demo/electron/manipulator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"main": "main.js",
"scripts": {
"start": "electron-forge start",
"rebuild": "electron-rebuild",
"package": "electron-forge package",
"make": "electron-forge make"
},
Expand Down Expand Up @@ -43,6 +42,11 @@
"unpack": "**/node_modules/rclnodejs/**"
}
},
"rebuildConfig": {
"ignoreModules": [
"rclnodejs"
]
},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
Expand Down
11 changes: 7 additions & 4 deletions demo/electron/topics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ A minimal Electron application demonstrating basic ROS2 topic communication usin
npm install
```

4. **Rebuild rclnodejs for Electron**:
```bash
./node_modules/.bin/electron-rebuild
```
4. **Native modules — no manual rebuild needed**:

rclnodejs ships prebuilt binaries for Electron and picks the matching one at
runtime from `ROS_DISTRO` + Linux codename + architecture (ROS 2 was sourced in
step 2). Do not run `electron-rebuild` against rclnodejs — it recompiles from
source and bypasses the prebuilt binary; the Forge `rebuildConfig` already
excludes `rclnodejs`.

## 🚀 Running the Demo

Expand Down
6 changes: 5 additions & 1 deletion demo/electron/topics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"main": "main.js",
"scripts": {
"start": "electron-forge start",
"rebuild": "electron-rebuild",
"package": "electron-forge package",
"make": "electron-forge make"
},
Expand Down Expand Up @@ -36,6 +35,11 @@
"unpack": "**/node_modules/rclnodejs/**"
}
},
"rebuildConfig": {
"ignoreModules": [
"rclnodejs"
]
},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
Expand Down
20 changes: 10 additions & 10 deletions demo/electron/turtle_tf2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ The demo uses the following key dependencies:
npm install
```

3. **Rebuild native modules**:
3. **Native modules — no manual rebuild needed**:

```bash
npm run rebuild
```

This step is crucial for ensuring that rclnodejs and other native dependencies are properly compiled for your system.
rclnodejs ships prebuilt binaries for Electron and selects the matching one at
runtime from `ROS_DISTRO` + Linux codename + architecture, so make sure ROS 2 is
sourced (next step) before launching. Do not run `electron-rebuild` against
rclnodejs — it recompiles from source and bypasses the prebuilt binary; the Forge
`rebuildConfig` in `package.json` already excludes `rclnodejs`.

4. **Source ROS2 environment**:

Expand All @@ -111,7 +111,7 @@ The demo uses the following key dependencies:

### Method 1: Complete Demo

Start the full demo with all components (ensure you have run `npm install && npm run rebuild` first):
Start the full demo with all components (ensure you have run `npm install` first):

```bash
# Source ROS2 first
Expand Down Expand Up @@ -345,14 +345,14 @@ You can observe the following behavior by:
- Try restarting the Electron application

6. **"electron: not found" or native module errors**
- Make sure you ran `npm run rebuild` after `npm install`
- Make sure ROS 2 is sourced so rclnodejs can match its prebuilt binary (`ROS_DISTRO` must be set; prebuilt binaries are provided for Ubuntu)
- Ensure Node.js version is compatible (20.20.2 or higher)
- Try deleting `node_modules` and running `npm install && npm run rebuild` again
- Try deleting `node_modules` and running `npm install` again; to force a from-source rebuild of rclnodejs set `RCLNODEJS_FORCE_BUILD=1`

7. **"THREE is not defined" or script loading errors**
- Ensure Three.js is properly installed: `npm install three@0.155.0`
- Check that `node_modules/three/build/three.min.js` exists
- If issues persist, try reinstalling: `rm -rf node_modules && npm install && npm run rebuild`
- If issues persist, try reinstalling: `rm -rf node_modules && npm install`

8. **WSL (Windows Subsystem for Linux) specific issues**
- Install audio libraries: `sudo apt install libasound2t64 libasound2-dev`
Expand Down
6 changes: 6 additions & 0 deletions demo/electron/turtle_tf2/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
const { app, BrowserWindow, ipcMain } = require('electron');
const rclnodejs = require('rclnodejs');

// Allow WebGL to work on systems without a usable GPU (e.g. WSL2, headless,
// or VMs) where Chromium blocklists hardware WebGL. Without this, the 3D
// renderer fails to create a WebGL context and the visualization cannot start.
app.commandLine.appendSwitch('ignore-gpu-blocklist');
app.commandLine.appendSwitch('enable-unsafe-swiftshader');

Comment on lines +16 to +21
let mainWindow;
let turtleTf2Nodes = {};

Expand Down
6 changes: 5 additions & 1 deletion demo/electron/turtle_tf2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"main": "main.js",
"scripts": {
"start": "electron-forge start",
"rebuild": "electron-rebuild",
"package": "electron-forge package",
"make": "electron-forge make"
},
Expand Down Expand Up @@ -42,6 +41,11 @@
"unpack": "**/node_modules/rclnodejs/**"
}
},
"rebuildConfig": {
"ignoreModules": [
"rclnodejs"
]
},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
Expand Down
22 changes: 19 additions & 3 deletions demo/electron/turtle_tf2/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,27 @@ document.addEventListener('DOMContentLoaded', function () {
versionDiv.innerText = 'Electron: ' + process.versions.electron;
document.body.appendChild(versionDiv);

initializeScene();
setupEventListeners();
setupKeyboardControls();
// Register ROS2 status listeners first so the loading screen always reflects
// initialization progress, even if 3D scene setup fails below.
setupROSListeners();

try {
initializeScene();
setupEventListeners();
setupKeyboardControls();
} catch (err) {
console.error('Failed to initialize 3D scene:', err);
const loadingScreen = document.getElementById('loading-screen');
if (loadingScreen) {
const loadingText = loadingScreen.querySelector('div:last-child');
if (loadingText) {
loadingText.textContent =
'Failed to initialize 3D view (WebGL unavailable): ' + err.message;
loadingText.style.color = '#ff4444';
}
}
Comment on lines +70 to +77
}

updateStatus();

// Don't automatically hide loading screen - wait for ROS2 initialization
Expand Down
58 changes: 48 additions & 10 deletions lib/native_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,51 @@ const debug = createDebug('rclnodejs');

let nativeModule = null;

// Copy a matched prebuilt binary into build/Release so that standard binary
// resolution (and packaged Electron apps where ROS_DISTRO may be unset at
// runtime) can still locate it. Non-destructive: never overwrites an existing
// build output, so local source builds are preserved.
function mirrorPrebuiltToBuildRelease(srcPath) {
try {
const destDir = path.join(__dirname, '..', 'build', 'Release');
const destPath = path.join(destDir, 'rclnodejs.node');
Comment on lines +37 to +44
if (fs.existsSync(destPath)) {
return;
}
fs.mkdirSync(destDir, { recursive: true });
fs.copyFileSync(srcPath, destPath);
debug(`Mirrored prebuilt binary to ${destPath}`);
Comment on lines +41 to +52
} catch (e) {
debug('Could not mirror prebuilt binary to build/Release:', e.message);
}
}

// Build the native addon from source. When running under Electron, target
// Electron's headers/ABI so the resulting binary matches the runtime instead
// of producing a Node-targeted binary (the addon links libuv directly, so the
// host runtime matters).
function buildFromSource() {
const env = { ...process.env };

if (detectPrebuildRuntime() === 'electron' && process.versions.electron) {
// node-gyp targets Electron via --target (Electron version) + --dist-url
// (Electron headers); it reads these from npm_config_* env vars.
env.npm_config_target = process.versions.electron;
env.npm_config_dist_url = 'https://electronjs.org/headers';
env.npm_config_arch = process.arch;
debug(`Building from source for Electron ${process.versions.electron}`);
} else {
debug('Building from source for Node.js');
Comment on lines +69 to +77
}

childProcess.execSync('npm run rebuild', {
stdio: 'inherit',
cwd: path.join(__dirname, '..'),
timeout: 300000, // 5 minute timeout
env,
});
}
Comment on lines +66 to +86

// Simplified loader: only use prebuilt binaries with exact Ubuntu/ROS2/arch match
// Note: Prebuilt binaries are only supported on Linux (Ubuntu) platform
function customFallbackLoader() {
Expand Down Expand Up @@ -79,6 +124,7 @@ function customFallbackLoader() {

if (fs.existsSync(candidatePath)) {
debug(`Found ${runtime} prebuilt binary: ${candidate}`);
mirrorPrebuiltToBuildRelease(candidatePath);
return require(candidatePath);
}
Comment on lines 122 to 142

Expand Down Expand Up @@ -106,11 +152,7 @@ function loadNativeAddon() {
// Trigger actual compilation
try {
debug('Running forced node-gyp rebuild...');
childProcess.execSync('npm run rebuild', {
stdio: 'inherit',
cwd: path.join(__dirname, '..'),
timeout: 300000, // 5 minute timeout
});
buildFromSource();

// Load the newly built binary
nativeModule = bindings('rclnodejs');
Expand Down Expand Up @@ -172,11 +214,7 @@ function loadNativeAddon() {
// Trigger actual compilation
try {
debug('Running node-gyp rebuild...');
childProcess.execSync('npm run rebuild', {
stdio: 'inherit',
cwd: path.join(__dirname, '..'),
timeout: 300000, // 5 minute timeout
});
buildFromSource();

// Try to load the newly built binary
nativeModule = bindings('rclnodejs');
Expand Down
Loading