Skip to content

Commit a15135d

Browse files
committed
feat(worker): forward ARToolKit getMarker events from worker to main thread
1 parent b1d8c84 commit a15135d

File tree

1 file changed

+214
-138
lines changed

1 file changed

+214
-138
lines changed

src/worker/worker.js

Lines changed: 214 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -8,162 +8,238 @@ let parent = null;
88
let arController = null;
99
let arControllerInitialized = false;
1010

11+
// forwarder guard
12+
let getMarkerForwarderAttached = false;
13+
1114
// Detect environment and setup worker communication
1215
// Only try to import node:worker_threads if we're in a Node.js environment
1316
if (typeof self === 'undefined') {
14-
// We're in Node.js (no 'self' global)
15-
try {
16-
const wt = await import('node:worker_threads').catch(() => null);
17-
if (wt && wt.parentPort) {
18-
isNodeWorker = true;
19-
parent = wt.parentPort;
20-
}
21-
} catch (e) {
22-
// Fallback: not in worker_threads context
23-
isNodeWorker = false;
24-
parent = null;
17+
// We're in Node.js (no 'self' global)
18+
try {
19+
const wt = await import('node:worker_threads').catch(() => null);
20+
if (wt && wt.parentPort) {
21+
isNodeWorker = true;
22+
parent = wt.parentPort;
2523
}
24+
} catch (e) {
25+
// Fallback: not in worker_threads context
26+
isNodeWorker = false;
27+
parent = null;
28+
}
2629
}
2730

2831
function onMessage(fn) {
29-
if (isNodeWorker) {
30-
parent.on('message', (msg) => fn(msg));
31-
} else {
32-
self.addEventListener('message', (ev) => fn(ev.data));
33-
}
32+
if (isNodeWorker) {
33+
parent.on('message', (msg) => fn(msg));
34+
} else {
35+
self.addEventListener('message', (ev) => fn(ev.data));
36+
}
3437
}
3538

3639
function sendMessage(msg) {
37-
if (isNodeWorker) {
38-
parent.postMessage(msg);
39-
} else {
40-
self.postMessage(msg);
41-
}
40+
if (isNodeWorker) {
41+
parent.postMessage(msg);
42+
} else {
43+
self.postMessage(msg);
44+
}
45+
}
46+
47+
// Serialize AR.js-style getMarker event into a transferable payload
48+
function serializeGetMarkerEvent(ev) {
49+
try {
50+
const data = ev?.data || {};
51+
const marker = data.marker || {};
52+
const matrix = Array.isArray(data.matrix) ? data.matrix.slice(0, 16)
53+
: (data.matrix && data.matrix.length ? Array.from(data.matrix).slice(0, 16) : null);
54+
const vertex = marker.vertex
55+
? (Array.isArray(marker.vertex) ? marker.vertex.slice() : null)
56+
: (marker.corners ? marker.corners.flatMap(c => [c.x ?? c[0], c.y ?? c[1]]) : null);
57+
58+
return {
59+
type: data.type, // e.g., ARToolkit.PATTERN_MARKER
60+
matrix,
61+
marker: {
62+
idPatt: marker.idPatt ?? marker.patternId ?? marker.pattern_id ?? null,
63+
idMatrix: marker.idMatrix ?? null,
64+
cfPatt: marker.cfPatt ?? marker.confidence ?? null,
65+
cfMatrix: marker.cfMatrix ?? null,
66+
vertex: vertex || null
67+
}
68+
};
69+
} catch (_e) {
70+
return { type: null, matrix: null, marker: {} };
71+
}
72+
}
73+
74+
function attachGetMarkerForwarder() {
75+
if (!arController || typeof arController.addEventListener !== 'function' || getMarkerForwarderAttached) return;
76+
try {
77+
arController.addEventListener('getMarker', (event) => {
78+
const payload = serializeGetMarkerEvent(event);
79+
try { console.log('[Worker] getMarker', payload); } catch (_) {}
80+
sendMessage({ type: 'getMarker', payload });
81+
});
82+
getMarkerForwarderAttached = true;
83+
} catch (_e) {
84+
// ignore
85+
}
4286
}
4387

4488
// Initialize ARController with default dimensions if not already initialized
4589
async function initArtoolkit(width = 640, height = 480) {
46-
if (arControllerInitialized) {
47-
return true;
48-
}
49-
50-
try {
51-
// Stub implementation - in real usage, this would initialize the actual ARToolKit WASM module
52-
console.log(`[Worker] Initializing ARToolKit with dimensions ${width}x${height}`);
53-
54-
// Simulate ARController initialization
55-
arController = {
56-
loadMarker: async (patternUrl) => {
57-
console.log(`[Worker] Loading marker pattern from: ${patternUrl}`);
58-
// Simulate async marker loading
59-
await new Promise((r) => setTimeout(r, 50));
60-
// Return a simulated marker ID
61-
return Math.floor(Math.random() * 1000);
62-
},
63-
trackPatternMarkerId: (markerId, size) => {
64-
console.log(`[Worker] Tracking pattern marker ID ${markerId} with size ${size}`);
65-
return true;
66-
}
67-
};
68-
69-
arControllerInitialized = true;
70-
console.log('[Worker] ARToolKit initialized successfully');
90+
if (arControllerInitialized) return true;
91+
92+
try {
93+
// Stub implementation - in real usage, this would initialize the actual ARToolKit WASM module
94+
console.log(`[Worker] Initializing ARToolKit with dimensions ${width}x${height}`);
95+
96+
// Minimal event-capable stub; real ARController will provide these
97+
const _listeners = new Map();
98+
arController = {
99+
addEventListener: (name, fn) => {
100+
if (!_listeners.has(name)) _listeners.set(name, []);
101+
_listeners.get(name).push(fn);
102+
},
103+
removeEventListener: (name, fn) => {
104+
if (!_listeners.has(name)) return;
105+
_listeners.set(name, _listeners.get(name).filter(x => x !== fn));
106+
},
107+
dispatchEvent: (ev) => {
108+
const name = ev?.type || ev?.name;
109+
const list = name ? (_listeners.get(name) || []) : [];
110+
list.forEach(h => h(ev));
111+
},
112+
loadMarker: async (patternUrl) => {
113+
console.log(`[Worker] Loading marker pattern from: ${patternUrl}`);
114+
await new Promise((r) => setTimeout(r, 50)); // simulate async
115+
return Math.floor(Math.random() * 1000); // simulated marker id
116+
},
117+
trackPatternMarkerId: (markerId, size) => {
118+
console.log(`[Worker] Tracking pattern marker ID ${markerId} with size ${size}`);
71119
return true;
72-
} catch (err) {
73-
console.error('[Worker] Failed to initialize ARToolKit:', err);
74-
arControllerInitialized = false;
75-
return false;
76-
}
120+
},
121+
// no-op; real ARController will process canvas/imageData and emit getMarker events
122+
process: (_source) => {}
123+
};
124+
125+
arControllerInitialized = true;
126+
console.log('[Worker] ARToolKit initialized successfully');
127+
128+
// If the real ARController is used, this will forward its getMarker events
129+
attachGetMarkerForwarder();
130+
131+
return true;
132+
} catch (err) {
133+
console.error('[Worker] Failed to initialize ARToolKit:', err);
134+
arControllerInitialized = false;
135+
return false;
136+
}
77137
}
78138

79139
onMessage(async (ev) => {
80-
const { type, payload } = ev || {};
81-
try {
82-
if (type === 'init') {
83-
sendMessage({ type: 'ready' });
84-
} else if (type === 'loadMarker') {
85-
// Handle marker loading request
86-
console.log('[Worker] Received loadMarker message:', payload);
87-
const { patternUrl, size, requestId } = payload || {};
88-
89-
if (!patternUrl) {
90-
console.error('[Worker] loadMarker: missing patternUrl');
91-
sendMessage({
92-
type: 'loadMarkerResult',
93-
payload: { ok: false, error: 'Missing patternUrl parameter', requestId }
94-
});
95-
return;
96-
}
97-
98-
try {
99-
// Ensure ARController is initialized before loading marker
100-
if (!arControllerInitialized) {
101-
console.log('[Worker] Initializing ARToolKit with default dimensions before loading marker');
102-
const initSuccess = await initArtoolkit();
103-
if (!initSuccess) {
104-
throw new Error('Failed to initialize ARToolKit');
105-
}
106-
}
107-
108-
// Load the marker pattern
109-
console.log(`[Worker] Loading marker from ${patternUrl}`);
110-
const markerId = await arController.loadMarker(patternUrl);
111-
console.log(`[Worker] Marker loaded with ID: ${markerId}`);
112-
113-
// Track the pattern marker with specified size (default to 1)
114-
const markerSize = size !== undefined ? size : 1;
115-
console.log(`[Worker] Tracking pattern marker ${markerId} with size ${markerSize}`);
116-
arController.trackPatternMarkerId(markerId, markerSize);
117-
118-
sendMessage({
119-
type: 'loadMarkerResult',
120-
payload: { ok: true, markerId, size: markerSize, requestId }
121-
});
122-
console.log('[Worker] Marker loading completed successfully');
123-
} catch (err) {
124-
console.error('[Worker] Error loading marker:', err);
125-
sendMessage({
126-
type: 'loadMarkerResult',
127-
payload: { ok: false, error: err?.message || String(err), requestId }
128-
});
129-
}
130-
} else if (type === 'processFrame') {
131-
// Browser: payload.imageBitmap may exist
132-
const { imageBitmap, frameId } = payload || {};
133-
134-
// If ImageBitmap present, we could run detection on it.
135-
// In this stub, we just simulate a small delay, then close the ImageBitmap.
136-
if (imageBitmap && typeof imageBitmap.close === 'function') {
137-
// simulate async processing
138-
await new Promise((r) => setTimeout(r, 10));
139-
// optional: read pixels via OffscreenCanvas if needed
140-
try {
141-
// Always close the ImageBitmap to free resources
142-
imageBitmap.close();
143-
} catch (e) {
144-
// ignore
145-
}
146-
} else {
147-
// No ImageBitmap (Node mode or fallback). Simulate async detection.
148-
await new Promise((r) => setTimeout(r, 10));
149-
}
150-
151-
const fakeMatrix = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]);
152-
const result = {
153-
detections: [
154-
{
155-
id: 'demo',
156-
confidence: 0.9,
157-
poseMatrix: Array.from(fakeMatrix),
158-
corners: [{x:0,y:0},{x:1,y:0},{x:1,y:1},{x:0,y:1}],
159-
frameId
160-
}
161-
]
162-
};
163-
164-
sendMessage({ type: 'detectionResult', payload: result });
140+
const { type, payload } = ev || {};
141+
try {
142+
if (type === 'init') {
143+
sendMessage({ type: 'ready' });
144+
} else if (type === 'loadMarker') {
145+
// Handle marker loading request
146+
console.log('[Worker] Received loadMarker message:', payload);
147+
const { patternUrl, size, requestId } = payload || {};
148+
149+
if (!patternUrl) {
150+
console.error('[Worker] loadMarker: missing patternUrl');
151+
sendMessage({
152+
type: 'loadMarkerResult',
153+
payload: { ok: false, error: 'Missing patternUrl parameter', requestId }
154+
});
155+
return;
156+
}
157+
158+
try {
159+
// Ensure ARController is initialized before loading marker
160+
if (!arControllerInitialized) {
161+
console.log('[Worker] Initializing ARToolKit with default dimensions before loading marker');
162+
const initSuccess = await initArtoolkit();
163+
if (!initSuccess) throw new Error('Failed to initialize ARToolKit');
164+
}
165+
166+
// Load the marker pattern
167+
console.log(`[Worker] Loading marker from ${patternUrl}`);
168+
const markerId = await arController.loadMarker(patternUrl);
169+
console.log(`[Worker] Marker loaded with ID: ${markerId}`);
170+
171+
// Track the pattern marker with specified size (default to 1)
172+
const markerSize = size !== undefined ? size : 1;
173+
console.log(`[Worker] Tracking pattern marker ${markerId} with size ${markerSize}`);
174+
arController.trackPatternMarkerId(markerId, markerSize);
175+
176+
sendMessage({
177+
type: 'loadMarkerResult',
178+
payload: { ok: true, markerId, size: markerSize, requestId }
179+
});
180+
console.log('[Worker] Marker loading completed successfully');
181+
} catch (err) {
182+
console.error('[Worker] Error loading marker:', err);
183+
sendMessage({
184+
type: 'loadMarkerResult',
185+
payload: { ok: false, error: err?.message || String(err), requestId }
186+
});
187+
}
188+
} else if (type === 'processFrame') {
189+
// Browser: payload.imageBitmap may exist
190+
const { imageBitmap, frameId } = payload || {};
191+
192+
// If ImageBitmap present, we could run detection on it.
193+
// In this stub, we just simulate a small delay, then close the ImageBitmap.
194+
if (imageBitmap && typeof imageBitmap.close === 'function') {
195+
// simulate async processing
196+
await new Promise((r) => setTimeout(r, 10));
197+
try {
198+
// Always close the ImageBitmap to free resources
199+
imageBitmap.close();
200+
} catch (_e) {
201+
// ignore
165202
}
166-
} catch (err) {
167-
sendMessage({ type: 'error', payload: { message: err?.message || String(err) } });
203+
} else {
204+
// No ImageBitmap (Node mode or fallback). Simulate async detection.
205+
await new Promise((r) => setTimeout(r, 10));
206+
}
207+
208+
// Simulated detection result (keeps pipeline functional)
209+
const fakeMatrix = new Float32Array([
210+
1,0,0,0,
211+
0,1,0,0,
212+
0,0,1,0,
213+
0,0,0,1
214+
]);
215+
const demoDetection = {
216+
id: 'demo',
217+
confidence: 0.9,
218+
poseMatrix: Array.from(fakeMatrix),
219+
corners: [{x:0,y:0},{x:1,y:0},{x:1,y:1},{x:0,y:1}],
220+
frameId
221+
};
222+
223+
// Post existing structured detectionResult
224+
sendMessage({ type: 'detectionResult', payload: { detections: [demoDetection] } });
225+
226+
// Also forward an AR.js-style getMarker event to the main thread
227+
const vertex = demoDetection.corners.flatMap(c => [c.x, c.y]);
228+
const getMarkerPayload = {
229+
type: 0, // ARToolkit.PATTERN_MARKER (placeholder numeric id)
230+
matrix: demoDetection.poseMatrix,
231+
marker: {
232+
idPatt: demoDetection.id,
233+
cfPatt: demoDetection.confidence,
234+
vertex
235+
}
236+
};
237+
try { console.log('[Worker] getMarker (derived from detectionResult)', getMarkerPayload); } catch (_e) {}
238+
sendMessage({ type: 'getMarker', payload: getMarkerPayload });
239+
240+
// If a real ARController is used and emits 'getMarker', the forwarder will send actual events as well.
168241
}
242+
} catch (err) {
243+
sendMessage({ type: 'error', payload: { message: err?.message || String(err) } });
244+
}
169245
});

0 commit comments

Comments
 (0)