@@ -9,10 +9,23 @@ const BOSCAWEB_STATE_PROGRESS = 1;
9
9
const BOSCAWEB_STATE_READY = 2 ;
10
10
const BOSCAWEB_STATE_FAILED = 3 ;
11
11
12
+ // Can technically be configured with Module["INITIAL_MEMORY"], but we don't have
13
+ // access to that as early as we need it. It seems to be unset though, so using
14
+ // default should be safe.
15
+ const BOSCAWEB_INITIAL_MEMORY = 33554432 ;
16
+ const BOSCAWEB_MAXIMUM_MEMORY = 2147483648 ; // 2 GB
17
+ const BOSCAWEB_MEMORY_PAGE_SIZE = 65536 ;
18
+
19
+ const BOSCAWEB_COMPATIBILITY_OK = 0 ;
20
+ const BOSCAWEB_COMPATIBILITY_WARNING = 1 ;
21
+ const BOSCAWEB_COMPATIBILITY_FAILURE = 2 ;
22
+
12
23
class BoscaWeb {
13
24
constructor ( ) {
14
25
this . initializing = true ;
15
26
this . engine = new Engine ( GODOT_CONFIG ) ;
27
+ this . _allocatedMemory = 0 ;
28
+ this . memory = this . _allocateWasmMemory ( ) ;
16
29
17
30
this . _bootOverlay = document . getElementById ( 'boot-overlay' ) ;
18
31
this . _boot_initialState = document . getElementById ( 'boot-menu' ) ;
@@ -34,61 +47,165 @@ class BoscaWeb {
34
47
35
48
this . _compat_passedState = document . getElementById ( 'boot-compat-passed' ) ;
36
49
this . _compat_failedState = document . getElementById ( 'boot-compat-failed' ) ;
50
+ this . _compat_failedHeaderError = document . getElementById ( 'boot-compat-failed-error' ) ;
51
+ this . _compat_failedHeaderWarning = document . getElementById ( 'boot-compat-failed-warning' ) ;
37
52
this . _compat_failedList = document . getElementById ( 'boot-compat-list' ) ;
38
53
this . _compat_tryfixButton = document . getElementById ( 'boot-compat-tryfix' ) ;
39
54
this . _compat_tryfixButton . addEventListener ( 'click' , ( ) => {
40
55
this . tryFixCompatibility ( ) ;
41
56
} ) ;
42
57
43
- this . _compatible = false ;
44
- this . _compatFixable = ( GODOT_CONFIG [ 'serviceWorker' ] && GODOT_CONFIG [ 'ensureCrossOriginIsolationHeaders' ] && 'serviceWorker' in navigator ) ;
58
+ this . _compatLevel = BOSCAWEB_COMPATIBILITY_OK ;
59
+ this . _compatFixable = ( this . memory && GODOT_CONFIG [ 'serviceWorker' ] && GODOT_CONFIG [ 'ensureCrossOriginIsolationHeaders' ] && 'serviceWorker' in navigator ) ;
45
60
46
61
// Hidden by default to show native error messages, e.g. if JavaScript
47
62
// is disabled in the browser.
48
63
this . _bootOverlay . style . visibility = 'visible' ;
49
64
this . setState ( BOSCAWEB_STATE_INITIAL ) ;
50
65
}
51
66
67
+ _allocateWasmMemory ( ) {
68
+ // We will try to allocate as much as possible, starting with the limit that we actually require.
69
+ // In Safari this is likely to fail, so we try less and less. This is not guaranteed to work, but
70
+ // at least it gives user a chance.
71
+ const reductionSteps = [ 1 , 0.75 , 0.5 , 0.25 ] ;
72
+ let reductionIndex = 0 ;
73
+
74
+ let wasmMemory = null ;
75
+ let sizeMessage = '' ;
76
+ while ( wasmMemory == null && reductionIndex < reductionSteps . length ) {
77
+ const reduction = reductionSteps [ reductionIndex ] ;
78
+ this . _allocatedMemory = BOSCAWEB_MAXIMUM_MEMORY * reduction ;
79
+ sizeMessage = `${ this . _humanizeSize ( BOSCAWEB_INITIAL_MEMORY ) } out of ${ this . _humanizeSize ( this . _allocatedMemory ) } ` ;
80
+
81
+ // This can fail if we hit the browser's limit.
82
+ try {
83
+ wasmMemory = new WebAssembly . Memory ( {
84
+ initial : BOSCAWEB_INITIAL_MEMORY / BOSCAWEB_MEMORY_PAGE_SIZE ,
85
+ maximum : reduction * BOSCAWEB_MAXIMUM_MEMORY / BOSCAWEB_MEMORY_PAGE_SIZE ,
86
+ shared : true
87
+ } ) ;
88
+ } catch ( err ) {
89
+ console . error ( err ) ;
90
+ wasmMemory = null ;
91
+ }
92
+
93
+ reductionIndex += 1 ;
94
+ }
95
+
96
+ if ( wasmMemory == null ) {
97
+ console . error ( `Failed to allocate WebAssembly memory (${ sizeMessage } ); check the limits.` ) ;
98
+ return null ;
99
+ }
100
+ if ( ! ( wasmMemory . buffer instanceof SharedArrayBuffer ) ) {
101
+ console . error ( `Trying to allocate WebAssembly memory (${ sizeMessage } ), but returned buffer is not SharedArrayBuffer; this indicates that threading is probably not supported.` ) ;
102
+ return null ;
103
+ }
104
+
105
+ console . log ( `Successfully allocated WebAssembly memory (${ sizeMessage } ).` ) ;
106
+ return wasmMemory ;
107
+ }
108
+
109
+ _checkMissingFeatures ( ) {
110
+ const missingFeatures = Engine . getMissingFeatures ( {
111
+ threads : GODOT_THREADS_ENABLED ,
112
+ } ) ;
113
+
114
+ return missingFeatures . map ( ( item ) => {
115
+ const itemParts = item . split ( ' - ' ) ;
116
+ return {
117
+ 'name' : itemParts [ 0 ] ,
118
+ 'description' : itemParts [ 1 ] || '' ,
119
+ }
120
+ } ) ;
121
+ }
122
+
52
123
checkCompatibility ( ) {
53
124
this . _bootButton . classList . remove ( 'boot-init-suppressed' ) ;
54
125
this . _compat_passedState . style . display = 'none' ;
55
126
this . _compat_failedState . style . display = 'none' ;
127
+ this . _compat_failedHeaderError . style . display = 'none' ;
128
+ this . _compat_failedHeaderWarning . style . display = 'none' ;
56
129
this . _compat_tryfixButton . style . display = 'none' ;
57
130
this . _compat_failedList . style . display = 'none' ;
58
131
this . _setErrorText ( this . _compat_failedList , '' ) ;
59
132
60
- const missingFeatures = Engine . getMissingFeatures ( {
61
- threads : GODOT_THREADS_ENABLED ,
62
- } ) ;
133
+ this . _compatLevel = BOSCAWEB_COMPATIBILITY_OK ;
63
134
64
- if ( missingFeatures . length > 0 ) {
65
- this . _compatible = false ;
66
- this . _bootButton . classList . add ( 'boot-init-suppressed' ) ;
67
- this . _compat_failedState . style . display = 'flex' ;
68
- this . _compat_failedList . style . display = 'block' ;
69
- this . _compat_tryfixButton . style . display = ( this . _compatFixable ? 'inline-block' : 'none' ) ;
135
+ // Check memory allocation.
136
+ if ( this . memory == null ) {
137
+ this . _lowerCompatibilityLevel ( BOSCAWEB_COMPATIBILITY_FAILURE ) ;
138
+ this . _addCompatibilityLevelReason ( 'Your browser does not allow enough memory' ) ;
70
139
71
- const sectionHeader = document . createElement ( 'strong' ) ;
72
- sectionHeader . textContent = 'Your browser is missing following features: ' ;
73
- this . _compat_failedList . appendChild ( sectionHeader ) ;
140
+ const reasonDescription = document . createElement ( 'span' ) ;
141
+ reasonDescription . textContent = `Bosca requested maximum limit of ${ this . _humanizeSize ( BOSCAWEB_MAXIMUM_MEMORY ) } , but was refused.` ;
142
+ this . _compat_failedList . appendChild ( reasonDescription ) ;
143
+ }
144
+ else if ( this . _allocatedMemory < BOSCAWEB_MAXIMUM_MEMORY ) {
145
+ this . _lowerCompatibilityLevel ( BOSCAWEB_COMPATIBILITY_WARNING ) ;
146
+ this . _addCompatibilityLevelReason ( 'Your browser does not allow enough memory' ) ;
147
+
148
+ const reasonDescription = document . createElement ( 'span' ) ;
149
+ reasonDescription . textContent = `Bosca requested maximum limit of ${ this . _humanizeSize ( BOSCAWEB_MAXIMUM_MEMORY ) } , but was only allowed ${ this . _humanizeSize ( this . _allocatedMemory ) } .` ;
150
+ this . _compat_failedList . appendChild ( reasonDescription ) ;
151
+ }
152
+
153
+ // Check for missing browser feature.
154
+ const missingFeatures = this . _checkMissingFeatures ( ) ;
155
+ if ( missingFeatures . length > 0 ) {
156
+ this . _lowerCompatibilityLevel ( BOSCAWEB_COMPATIBILITY_FAILURE ) ;
157
+ this . _addCompatibilityLevelReason ( 'Your browser is missing following features' ) ;
74
158
75
- const sectionList = document . createElement ( 'span' ) ;
76
- this . _compat_failedList . appendChild ( sectionList ) ;
159
+ const reasonDescription = document . createElement ( 'span' ) ;
160
+ this . _compat_failedList . appendChild ( reasonDescription ) ;
77
161
missingFeatures . forEach ( ( item , index ) => {
78
- const itemParts = item . split ( ' - ' ) ;
79
-
80
162
const annotatedElement = document . createElement ( 'abbr' ) ;
81
- annotatedElement . textContent = itemParts [ 0 ] ;
82
- annotatedElement . title = itemParts [ 1 ] ;
83
- sectionList . appendChild ( annotatedElement ) ;
163
+ annotatedElement . textContent = item . name ;
164
+ annotatedElement . title = item . description ;
165
+ reasonDescription . appendChild ( annotatedElement ) ;
84
166
85
167
if ( index < missingFeatures . length - 1 ) {
86
- sectionList . appendChild ( document . createTextNode ( ", " ) ) ;
168
+ reasonDescription . appendChild ( document . createTextNode ( ", " ) ) ;
87
169
}
88
170
} ) ;
89
- } else {
90
- this . _compatible = true ;
91
- this . _compat_passedState . style . display = 'flex' ;
171
+ }
172
+
173
+ switch ( this . _compatLevel ) {
174
+ case BOSCAWEB_COMPATIBILITY_OK :
175
+ this . _compat_passedState . style . display = 'flex' ;
176
+ break ;
177
+
178
+ case BOSCAWEB_COMPATIBILITY_WARNING :
179
+ this . _bootButton . classList . add ( 'boot-init-suppressed' ) ;
180
+ this . _compat_failedState . style . display = 'flex' ;
181
+ this . _compat_failedHeaderWarning . style . display = 'inline-block' ;
182
+ this . _compat_failedList . style . display = 'block' ;
183
+ this . _compat_tryfixButton . style . display = ( this . _compatFixable ? 'inline-block' : 'none' ) ;
184
+ break ;
185
+
186
+ case BOSCAWEB_COMPATIBILITY_FAILURE :
187
+ this . _bootButton . classList . add ( 'boot-init-suppressed' ) ;
188
+ this . _compat_failedState . style . display = 'flex' ;
189
+ this . _compat_failedHeaderError . style . display = 'inline-block' ;
190
+ this . _compat_failedList . style . display = 'block' ;
191
+ this . _compat_tryfixButton . style . display = ( this . _compatFixable ? 'inline-block' : 'none' ) ;
192
+ break ;
193
+ }
194
+ }
195
+
196
+ _addCompatibilityLevelReason ( message ) {
197
+ if ( this . _compat_failedList . hasChildNodes ( ) ) {
198
+ this . _compat_failedList . appendChild ( document . createElement ( 'br' ) ) ;
199
+ }
200
+
201
+ const reasonHeader = document . createElement ( 'strong' ) ;
202
+ reasonHeader . textContent = `${ message } : ` ;
203
+ this . _compat_failedList . appendChild ( reasonHeader ) ;
204
+ }
205
+
206
+ _lowerCompatibilityLevel ( level ) {
207
+ if ( this . _compatLevel < level ) {
208
+ this . _compatLevel = level ;
92
209
}
93
210
}
94
211
@@ -205,4 +322,20 @@ class BoscaWeb {
205
322
this . setState ( BOSCAWEB_STATE_FAILED ) ;
206
323
this . initializing = false ;
207
324
}
325
+
326
+ _humanizeSize ( size ) {
327
+ const labels = [ 'B' , 'KB' , 'MB' , 'GB' , 'TB' , ] ;
328
+
329
+ let label = labels [ 0 ] ;
330
+ let value = size ;
331
+
332
+ let index = 0 ;
333
+ while ( value >= 1024 && index < labels . length ) {
334
+ index += 1 ;
335
+ value = value / 1024 ;
336
+ label = labels [ index ] ;
337
+ }
338
+
339
+ return `${ value . toFixed ( 2 ) } ${ label } ` ;
340
+ }
208
341
}
0 commit comments