1- // Assuming existing imports and setup in src/plugin.js
1+ // src/plugin.js
2+ /**
3+ * ArtoolkitPlugin
4+ * - maintains plugin lifecycle (init, enable, disable, dispose)
5+ * - optionally runs detection inside a Worker (src/worker/worker.js)
6+ * - subscribes to engine:update to send frames (by id) to the worker
7+ * - emits ar:markerFound / ar:markerUpdated / ar:markerLost on the engine eventBus
8+ *
9+ * Note: the worker stub is intentionally simple and returns a fake detection.
10+ */
11+ export class ArtoolkitPlugin {
12+ constructor ( options = { } ) {
13+ this . options = options ;
14+ this . core = null ;
15+ this . enabled = false ;
216
3- let detectionWorker ;
17+ // Worker and handlers
18+ this . _worker = null ;
19+ this . _onWorkerMessage = this . _onWorkerMessage . bind ( this ) ;
420
5- function enableWorker ( ) {
6- detectionWorker = new Worker ( new URL ( './worker/worker.js' , import . meta . url ) ) ;
21+ // Engine update subscription
22+ this . _onEngineUpdate = this . _onEngineUpdate . bind ( this ) ;
723
8- detectionWorker . postMessage ( { type : 'init' } ) ;
24+ // Marker state tracking: Map<id, { lastSeen: number, visible: boolean }>
25+ this . _markers = new Map ( ) ;
926
10- // Capture engine:update event to post processFrame messages
11- core . eventBus . on ( 'engine:update' , ( frame ) => {
12- if ( frame ) {
13- detectionWorker . postMessage ( { type : 'processFrame' , payload : { frameId : frame . id } } ) ;
27+ // configuration
28+ this . workerEnabled = options . worker !== false ; // default true
29+ this . lostThreshold = options . lostThreshold ?? 5 ; // frames to consider lost
1430 }
15- } ) ;
1631
17- detectionWorker . addEventListener ( 'message' , ( ev ) => {
18- const { type, payload } = ev . data || { } ;
19- if ( type === 'ready' ) {
20- console . log ( 'Worker is ready' ) ;
21- } else if ( type === 'detectionResult' ) {
22- payload . detections . forEach ( detection => {
23- core . eventBus . emit ( 'ar:markerUpdated' , {
24- id : detection . id ,
25- poseMatrix : new Float32Array ( detection . poseMatrix ) ,
26- confidence : detection . confidence ,
27- corners : detection . corners
28- } ) ;
29- } ) ;
32+ async init ( core ) {
33+ this . core = core ;
34+ // Nothing heavy here; defer worker setup to enable()
35+ return this ;
3036 }
31- } ) ;
32- }
3337
34- // Call enableWorker when you want to start the worker
35- // Example: enableWorker();
38+ async enable ( ) {
39+ if ( ! this . core ) throw new Error ( 'Plugin not initialized' ) ;
40+ if ( this . enabled ) return this ;
41+ this . enabled = true ;
42+
43+ // subscribe to engine update to send frames to worker
44+ this . core . eventBus . on ( 'engine:update' , this . _onEngineUpdate ) ;
45+
46+ // start worker if configured
47+ if ( this . workerEnabled ) {
48+ this . _startWorker ( ) ;
49+ }
50+
51+ // start a simple interval to sweep lost markers by frame count (optional)
52+ this . _sweepInterval = setInterval ( ( ) => this . _sweepMarkers ( ) , 100 ) ; // adjust as needed
53+ return this ;
54+ }
55+
56+ async disable ( ) {
57+ if ( ! this . enabled ) return this ;
58+ this . enabled = false ;
59+
60+ this . core . eventBus . off ( 'engine:update' , this . _onEngineUpdate ) ;
61+
62+ if ( this . _worker ) {
63+ this . _stopWorker ( ) ;
64+ }
65+
66+ if ( this . _sweepInterval ) {
67+ clearInterval ( this . _sweepInterval ) ;
68+ this . _sweepInterval = null ;
69+ }
70+
71+ return this ;
72+ }
73+
74+ dispose ( ) {
75+ return this . disable ( ) ;
76+ }
77+
78+ // Engine frame handler: forward frame info to the worker
79+ _onEngineUpdate ( frame ) {
80+ // frame is expected to be an object provided by the capture system, e.g.:
81+ // { id: number, timestamp, imageBitmap?, width, height, sourceRef }
82+ if ( ! frame ) return ;
83+
84+ // Send lightweight message to worker (worker may accept ImageBitmap later)
85+ if ( this . _worker ) {
86+ try {
87+ this . _worker . postMessage ( { type : 'processFrame' , payload : { frameId : frame . id } } ) ;
88+ } catch ( err ) {
89+ // worker may be terminated; ignore
90+ console . warn ( 'Artoolkit worker postMessage failed' , err ) ;
91+ }
92+ } else {
93+ // No worker: we could run detection inline in future
94+ }
95+ }
96+
97+ // Worker lifecycle
98+ _startWorker ( ) {
99+ 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' } ) ;
104+ }
105+
106+ _stopWorker ( ) {
107+ if ( ! this . _worker ) return ;
108+ this . _worker . removeEventListener ( 'message' , this . _onWorkerMessage ) ;
109+ try {
110+ this . _worker . terminate ( ) ;
111+ } catch ( e ) {
112+ // ignore
113+ }
114+ this . _worker = null ;
115+ }
116+
117+ _onWorkerMessage ( ev ) {
118+ const { type, payload } = ev . data || { } ;
119+ if ( type === 'ready' ) {
120+ // Worker initialized
121+ this . core ?. eventBus ?. emit ( 'ar:workerReady' , { } ) ;
122+ } else if ( type === 'detectionResult' ) {
123+ // payload: { detections: [ { id, confidence, poseMatrix (array), corners, frameId } ] }
124+ if ( ! payload || ! Array . isArray ( payload . detections ) ) return ;
125+ for ( const d of payload . detections ) {
126+ const id = d . id ;
127+ const now = Date . now ( ) ;
128+ const poseMatrix = new Float32Array ( d . poseMatrix || [ ] ) ;
129+ const confidence = d . confidence ?? 0 ;
130+ const corners = d . corners ?? [ ] ;
131+
132+ const prev = this . _markers . get ( id ) ;
133+ if ( ! prev || ! prev . visible ) {
134+ // newly found
135+ this . _markers . set ( id , { lastSeen : now , visible : true , lostCount : 0 } ) ;
136+ this . core . eventBus . emit ( 'ar:markerFound' , { id, poseMatrix, confidence, corners, timestamp : now } ) ;
137+ } else {
138+ // updated
139+ prev . lastSeen = now ;
140+ prev . lostCount = 0 ;
141+ this . _markers . set ( id , prev ) ;
142+ this . core . eventBus . emit ( 'ar:markerUpdated' , { id, poseMatrix, confidence, corners, timestamp : now } ) ;
143+ }
144+ }
145+ } else if ( type === 'error' ) {
146+ console . error ( 'Artoolkit worker error' , payload ) ;
147+ this . core ?. eventBus ?. emit ( 'ar:workerError' , payload ) ;
148+ }
149+ }
150+
151+ // sweep markers and emit lost events for markers not seen recently
152+ _sweepMarkers ( ) {
153+ const now = Date . now ( ) ;
154+ for ( const [ id , state ] of this . _markers . entries ( ) ) {
155+ 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
158+ if ( deltaMs > ( this . lostThreshold * 200 ) ) {
159+ // emit lost
160+ this . _markers . delete ( id ) ;
161+ this . core . eventBus . emit ( 'ar:markerLost' , { id, timestamp : now } ) ;
162+ }
163+ }
164+ }
165+
166+ // public helper to get marker state
167+ getMarkerState ( markerId ) {
168+ return this . _markers . get ( markerId ) || null ;
169+ }
170+ }
0 commit comments