66 * - subscribes to engine:update to send frames (by id) to the worker
77 * - emits ar:markerFound / ar:markerUpdated / ar:markerLost on the engine eventBus
88 *
9- * Note: the worker stub is intentionally simple and returns a fake detection .
9+ * Works both in browsers (global Worker) and in Node (worker_threads.Worker) .
1010 */
1111export class ArtoolkitPlugin {
1212 constructor ( options = { } ) {
@@ -45,7 +45,7 @@ export class ArtoolkitPlugin {
4545
4646 // start worker if configured
4747 if ( this . workerEnabled ) {
48- this . _startWorker ( ) ;
48+ await this . _startWorker ( ) ;
4949 }
5050
5151 // start a simple interval to sweep lost markers by frame count (optional)
@@ -84,6 +84,7 @@ export class ArtoolkitPlugin {
8484 // Send lightweight message to worker (worker may accept ImageBitmap later)
8585 if ( this . _worker ) {
8686 try {
87+ // In Node worker_threads, worker.postMessage exists too
8788 this . _worker . postMessage ( { type : 'processFrame' , payload : { frameId : frame . id } } ) ;
8889 } catch ( err ) {
8990 // worker may be terminated; ignore
@@ -94,28 +95,73 @@ export class ArtoolkitPlugin {
9495 }
9596 }
9697
97- // Worker lifecycle
98- _startWorker ( ) {
98+ // Worker lifecycle (cross-platform)
99+ async _startWorker ( ) {
99100 if ( this . _worker ) return ;
100- // spawn worker relative to this module
101- this . _worker = new Worker ( new URL ( './worker/worker.js' , import . meta. url ) ) ;
102- this . _worker . addEventListener ( 'message' , this . _onWorkerMessage ) ;
103- this . _worker . postMessage ( { type : 'init' } ) ;
101+
102+ // Browser environment: global Worker exists
103+ if ( typeof Worker !== 'undefined' ) {
104+ // Works in browsers and bundlers that support new URL(...) for workers
105+ this . _worker = new Worker ( new URL ( './worker/worker.js' , import . meta. url ) ) ;
106+ } else {
107+ // Node environment: use worker_threads.Worker
108+ // dynamically import to avoid bundling node-only module into browser builds
109+ const { Worker : NodeWorker } = await import ( 'node:worker_threads' ) ;
110+ // Convert the worker module URL into a filesystem path suitable for worker_threads
111+ const workerUrl = new URL ( './worker/worker.js' , import . meta. url ) ;
112+
113+ // Robust conversion to platform path:
114+ // fileURLToPath handles Windows correctly and avoids duplicate drive letters.
115+ const { fileURLToPath } = await import ( 'node:url' ) ;
116+ const workerPath = fileURLToPath ( workerUrl ) ;
117+
118+ // Create worker as ES module
119+ this . _worker = new NodeWorker ( workerPath , { type : 'module' } ) ;
120+ }
121+
122+ // Attach message handler (same for both environments)
123+ if ( this . _worker . addEventListener ) {
124+ this . _worker . addEventListener ( 'message' , this . _onWorkerMessage ) ;
125+ } else if ( this . _worker . on ) {
126+ this . _worker . on ( 'message' , this . _onWorkerMessage ) ;
127+ }
128+
129+ // If worker supports postMessage init, send init
130+ try {
131+ this . _worker . postMessage ?. ( { type : 'init' } ) ;
132+ } catch ( e ) {
133+ // ignore
134+ }
104135 }
105136
106137 _stopWorker ( ) {
107138 if ( ! this . _worker ) return ;
108- this . _worker . removeEventListener ( 'message' , this . _onWorkerMessage ) ;
139+
140+ // Remove handler
141+ if ( this . _worker . removeEventListener ) {
142+ this . _worker . removeEventListener ( 'message' , this . _onWorkerMessage ) ;
143+ } else if ( this . _worker . off ) {
144+ this . _worker . off ( 'message' , this . _onWorkerMessage ) ;
145+ }
146+
109147 try {
110- this . _worker . terminate ( ) ;
148+ // terminate/close depending on environment
149+ if ( typeof Worker !== 'undefined' ) {
150+ this . _worker . terminate ( ) ;
151+ } else {
152+ // Node worker_threads
153+ this . _worker . terminate ?. ( ) ;
154+ }
111155 } catch ( e ) {
112156 // ignore
113157 }
114158 this . _worker = null ;
115159 }
116160
117161 _onWorkerMessage ( ev ) {
118- const { type, payload } = ev . data || { } ;
162+ // worker_threads messages arrive as the raw payload; browser workers wrap in event.data
163+ const data = ev && ev . data !== undefined ? ev . data : ev ;
164+ const { type, payload } = data || { } ;
119165 if ( type === 'ready' ) {
120166 // Worker initialized
121167 this . core ?. eventBus ?. emit ( 'ar:workerReady' , { } ) ;
@@ -153,10 +199,7 @@ export class ArtoolkitPlugin {
153199 const now = Date . now ( ) ;
154200 for ( const [ id , state ] of this . _markers . entries ( ) ) {
155201 const deltaMs = now - ( state . lastSeen || 0 ) ;
156- // converted threshold: if not seen within lostThreshold * frameInterval (~100ms here) mark lost
157- // simple heuristic: if lastSeen is older than lostThreshold * 200ms mark lost
158202 if ( deltaMs > ( this . lostThreshold * 200 ) ) {
159- // emit lost
160203 this . _markers . delete ( id ) ;
161204 this . core . eventBus . emit ( 'ar:markerLost' , { id, timestamp : now } ) ;
162205 }
0 commit comments