11/**
22 * Default Profile Policy Plugin
3- * Automatically detects device type and sets appropriate performance profiles
4- * Provides device profile configuration for AR processing
3+ * Automatically computes a capability-based device profile at runtime.
4+ * Backward-compatible: legacy DEVICE_PROFILES mappings still supported.
55 */
66
7- import { RESOURCES , DEVICE_PROFILES } from "../../src/core/components.js" ;
7+ import { RESOURCES , DEVICE_PROFILES , QUALITY_TIERS } from "../../src/core/components.js" ;
88
99export const defaultProfilePlugin = {
1010 id : "profile:default" ,
11- name : "Default Profile Policy" ,
11+ name : "Default Profile Policy (auto) " ,
1212 type : "profile" ,
1313
1414 /**
15- * Initialize the plugin
15+ * Initialize the plugin: compute auto profile and publish it
1616 */
1717 async init ( context ) {
18- // Detect device profile and set it as a resource
19- const profile = this . detectProfile ( ) ;
18+ const profile = await this . _computeAutoProfile ( ) ;
2019 context . ecs . setResource ( RESOURCES . DEVICE_PROFILE , profile ) ;
20+ // Emit a profile-applied event for observers (optional)
21+ context ?. eventBus ?. emit ?. ( "profile:applied" , { profile } ) ;
2122 } ,
2223
2324 /**
24- * Detect the appropriate device profile based on user agent and hardware
25- * @returns {Object } Device profile configuration
25+ * Preferred: detect a structured capability-based profile
2626 */
27- detectProfile ( ) {
28- const isMobile = this . _isMobileDevice ( ) ;
29- const profileLabel = isMobile ? "phone-normal" : "desktop-normal" ;
27+ async detectProfile ( ) {
28+ return this . _computeAutoProfile ( ) ;
29+ } ,
30+
31+ /**
32+ * Compute a capability-based profile
33+ * Returns a structured profile with backward-compatible fields.
34+ */
35+ async _computeAutoProfile ( ) {
36+ const caps = this . _getCaps ( ) ;
37+ const bench = await this . _microBenchmark ( 8 ) ; // ~8ms probe
38+ const score = this . _scoreCaps ( caps , bench ) ;
39+ const tierInfo = this . _pickTier ( score ) ;
40+ const [ w , h ] = tierInfo . capture ;
41+
42+ // Backward-compat top-level fields used in older examples:
43+ const legacyCompatible = {
44+ label : `auto-${ tierInfo . tier } ` ,
45+ sourceWidth : w ,
46+ sourceHeight : h ,
47+ displayWidth : w ,
48+ displayHeight : h ,
49+ canvasWidth : w ,
50+ canvasHeight : h ,
51+ maxDetectionRate : 60 ,
52+ } ;
53+
54+ // New structured fields:
55+ const structured = {
56+ qualityTier : tierInfo . tier , // QUALITY_TIERS value
57+ score,
58+ caps,
59+ capture : {
60+ sourceWidth : w ,
61+ sourceHeight : h ,
62+ displayWidth : w ,
63+ displayHeight : h ,
64+ fpsHint : 30 ,
65+ } ,
66+ processing : {
67+ budgetMsPerFrame : tierInfo . budget ,
68+ complexity : tierInfo . complexity ,
69+ } ,
70+ } ;
71+
72+ return { ...legacyCompatible , ...structured } ;
73+ } ,
74+
75+ /**
76+ * Get device capability signals (defensive checks for non-browser envs)
77+ */
78+ _getCaps ( ) {
79+ const nav = typeof navigator !== "undefined" ? navigator : { } ;
80+ const win = typeof window !== "undefined" ? window : { } ;
81+ const scr = typeof screen !== "undefined" ? screen : { } ;
82+
83+ const userAgentHint = typeof nav . userAgent === "string" ? nav . userAgent : "" ;
84+ const cores = Math . max ( 1 , Number ( nav . hardwareConcurrency || 2 ) ) ;
85+ const memoryGB = Math . max ( 0.5 , Number ( nav . deviceMemory || 2 ) ) ;
86+ const webgl2 = ! ! win . WebGL2RenderingContext ;
87+ const wasmSIMD = typeof WebAssembly === "object" && typeof WebAssembly . validate === "function" ;
88+ const screenLongSide = Math . max ( Number ( scr . width || 0 ) , Number ( scr . height || 0 ) ) || 0 ;
89+
90+ let torch = false ;
91+ let focusMode = "unknown" ;
92+ try {
93+ const getSC = nav . mediaDevices ?. getSupportedConstraints ?. bind ( nav . mediaDevices ) ;
94+ const sc = getSC ? getSC ( ) : { } ;
95+ torch = ! ! sc ?. torch ;
96+ focusMode = sc ?. focusMode ? "supported" : "unknown" ;
97+ } catch {
98+ // ignore
99+ }
100+
101+ return {
102+ userAgentHint,
103+ cores,
104+ memoryGB,
105+ webgl2,
106+ wasmSIMD,
107+ screenLongSide,
108+ camera : { torch, focusMode } ,
109+ } ;
110+ } ,
111+
112+ /**
113+ * Very small CPU probe to approximate budget
114+ */
115+ async _microBenchmark ( msTarget = 8 ) {
116+ if ( typeof performance === "undefined" || typeof performance . now !== "function" ) {
117+ return 0 ;
118+ }
119+ const start = performance . now ( ) ;
120+ let acc = 0 ;
121+ while ( performance . now ( ) - start < msTarget ) {
122+ // Cheap floating math
123+ for ( let i = 0 ; i < 1000 ; i ++ ) acc += Math . sqrt ( i + ( acc % 5 ) ) ;
124+ // Safety: don't run too long if timers behave oddly
125+ if ( performance . now ( ) - start > msTarget * 2 ) break ;
126+ }
127+ return acc ;
128+ } ,
30129
31- return this . getProfile ( profileLabel ) ;
130+ /**
131+ * Convert caps + bench signal into a 0..100 score
132+ */
133+ _scoreCaps ( caps , benchSignal ) {
134+ let score = 0 ;
135+ // Cores: up to 6 cores -> 30 pts
136+ score += Math . min ( 30 , ( caps . cores || 0 ) * 5 ) ;
137+ // Memory: up to ~7.5 GB -> 30 pts
138+ score += Math . min ( 30 , ( caps . memoryGB || 0 ) * 4 ) ;
139+ // WebGL2: 10 pts
140+ if ( caps . webgl2 ) score += 10 ;
141+ // WASM SIMD hint: 10 pts
142+ if ( caps . wasmSIMD ) score += 10 ;
143+ // Screen long side: up to ~6000 px -> 10 pts (rough indicator of class)
144+ score += Math . min ( 10 , Math . floor ( ( caps . screenLongSide || 0 ) / 600 ) ) ;
145+
146+ // Normalize bench signal into ~0..10
147+ if ( typeof benchSignal === "number" ) {
148+ const norm = Math . max ( 0 , Math . log10 ( Math . max ( 10 , benchSignal ) ) ) ;
149+ score += Math . min ( 10 , 5 + norm ) ;
150+ }
151+
152+ score = Math . round ( Math . max ( 0 , Math . min ( 100 , score ) ) ) ;
153+ return score ;
154+ } ,
155+
156+ /**
157+ * Map score to a quality tier and capture/budget hints
158+ */
159+ _pickTier ( score ) {
160+ if ( score >= 85 ) {
161+ return { tier : QUALITY_TIERS . ULTRA , capture : [ 1280 , 720 ] , budget : 12 , complexity : "high" } ;
162+ }
163+ if ( score >= 65 ) {
164+ return { tier : QUALITY_TIERS . HIGH , capture : [ 960 , 540 ] , budget : 10 , complexity : "high" } ;
165+ }
166+ if ( score >= 45 ) {
167+ return { tier : QUALITY_TIERS . MEDIUM , capture : [ 800 , 450 ] , budget : 8 , complexity : "medium" } ;
168+ }
169+ return { tier : QUALITY_TIERS . LOW , capture : [ 640 , 360 ] , budget : 6 , complexity : "low" } ;
32170 } ,
33171
34172 /**
35- * Get profile configuration by label
36- * @param {string } label - Profile label
37- * @returns {Object } Profile configuration
173+ * Legacy mapping: return a minimal legacy profile by label
38174 */
39175 getProfile ( label ) {
40176 const profiles = {
@@ -76,45 +212,41 @@ export const defaultProfilePlugin = {
76212 } ,
77213
78214 /**
79- * Check if the current device is a mobile device
80- * @private
81- */
82- _isMobileDevice ( ) {
83- const userAgent = navigator . userAgent ;
84- return ! ! (
85- userAgent . match ( / A n d r o i d / i) ||
86- userAgent . match ( / w e b O S / i) ||
87- userAgent . match ( / i P h o n e / i) ||
88- userAgent . match ( / i P a d / i) ||
89- userAgent . match ( / i P o d / i) ||
90- userAgent . match ( / B l a c k B e r r y / i) ||
91- userAgent . match ( / W i n d o w s P h o n e / i)
92- ) ;
93- } ,
94-
95- /**
96- * Set a specific profile
97- * @param {string } label - Profile label
98- * @param {Object } context - Engine context
215+ * Keep legacy setter: If a legacy label is passed, set that profile.
99216 */
100217 setProfile ( label , context ) {
101218 const profile = this . getProfile ( label ) ;
102219 context . ecs . setResource ( RESOURCES . DEVICE_PROFILE , profile ) ;
103220 } ,
104221
105222 /**
106- * Get the current profile
107- * @param {Object } context - Engine context
108- * @returns {Object } Current device profile
223+ * Read currently applied profile
109224 */
110225 getCurrentProfile ( context ) {
111226 return context . ecs . getResource ( RESOURCES . DEVICE_PROFILE ) ;
112227 } ,
113228
114229 /**
115- * Dispose the plugin
230+ * Legacy mobile detection retained (unused by default)
231+ * @private
232+ */
233+ _isMobileDevice ( ) {
234+ const ua = ( typeof navigator !== "undefined" && navigator . userAgent ) || "" ;
235+ return ! ! (
236+ ua . match ( / A n d r o i d / i) ||
237+ ua . match ( / w e b O S / i) ||
238+ ua . match ( / i P h o n e / i) ||
239+ ua . match ( / i P a d / i) ||
240+ ua . match ( / i P o d / i) ||
241+ ua . match ( / B l a c k B e r r y / i) ||
242+ ua . match ( / W i n d o w s P h o n e / i)
243+ ) ;
244+ } ,
245+
246+ /**
247+ * Dispose hook
116248 */
117249 async dispose ( ) {
118250 // Nothing to clean up
119251 } ,
120- } ;
252+ } ;
0 commit comments