FreeWRL / FreeX3D 4.3.0
desktop.c
1/****************************************************************************
2 This file is part of the FreeWRL/FreeX3D Distribution.
3
4 Copyright 2009 CRC Canada. (http://www.crc.gc.ca)
5
6 FreeWRL/FreeX3D is free software: you can redistribute it and/or modify
7 it under the terms of the GNU Lesser Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 FreeWRL/FreeX3D is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with FreeWRL/FreeX3D. If not, see <http://www.gnu.org/licenses/>.
18****************************************************************************/
19
20/*desktop.c theme: traditional desktop- and browser plugin-specific code
21including former !FRONTEND_HANDLES_DISPLAY_THREAD and !FRONTEND_GETS_FILES code
22(versus: sandbox configs like android, ios, winRT where the frontend has its own displaythread and io_http>download_url )
23The functions in here emulate FEGF+FEHDT for desktop, so desktop works like sandbox apps
24- desktop io_http > download_url is called from in here
25- _displayThread loop is created and run in here
26- desktop console-program startup
27- desktop browser-plugin startup
28- dequeue_get_enqueue called once per displaythread to dequeue URL from backend,
29 do URL2BLOB = URL2FILE+FILE2BLOB by spawning download, load tasks,
30 and enqueuing the BLOB results to the backend
31
32Tips for freewrl developers:
33 don't call resource_fetch or download_url() -which are synchronous calls -from backend modules
34 (it took a lot of work to get them out of there) instead develop 'async' so the frontend can deliver
35 results whenever downloads arrive.
36 externProto: If you are trying to repair tests 17.wrl, 17.x3d, or externProto
37 you should refactor externProto parsing and runtime code to allow asynchronous/delay loading
38 of externProto definitions.
39
40EMULATING FEGF in ML - more detail..
41terminology
42FE - frontend
43BE - backend
44ML - middle layer
45FEGF (FRONTEND GETS FILES)
46 - sandboxed Android, IOS and winRT download internet resources in frontend code, in asynchronous tasks so as not to block the UI thread.
47 - (versus desktop MLGF - see code below and io_http.c)
48FEHDT (FRONTEND_HANDLES_DISPLAY_THREAD)
49 - android, winRT run their own displaythread, and own the swapbuffers and opengl/directx context
50 - (versus desktop console and plugins that launch a displaythread which loops and does swapbuffers -see _displayThread below)
51BLOB - binary large object - a text string blob for vrml/x3d
52TEXBLOB - width,height,RGBA blob for textures
53Scenery: vrml/x3d for main scene, inline, externProto, string
54Imagery: image files .jpg, .ico, .png etc
55Scripts: .js, shader
56
57BE (BackEnd) worker threads:
58Scenery and scripts: _inputParsingThread()
59Imagery: _textureThread()
60
61download2local - downloads an internet file to a local file
62local2blob - reads a local disk file into BLOB or TEXBLOB
63INITSCENE = {URL -> BE -> initScene -> enqueueURL}
64-- killOldworld, new resItem with baseURL pushed on stack to populate _parentResource fields
65-- URL enqueued for worker thread (which may in turn enqueue it for FE thread's URL2LOCAL)
66FILE2BLOB = {localURL -> ML -> local2blob -> enqueueBlob -> BE}
67W3DX - web3d stage .zip^^
68
69Typical workflows for WinRT FEGF configuration:
701. URLBox -> INITSCENE -> FE -> URL? download2Local -> FILE2BLOB
712. Picker/OpenWith -> AccessCache -> INITSCENE -> FE -> cacheLookup -> found ? use : filePicker -> download/copy2Local -> FILE2BLOB
723. Picker/OpenWith -> W3DX? -> ML -> localStage -> manifest -> mainsceneURL -> INITSCENE -> ML-> manifestLookup -> FILE2BLOB
73
74Desktop configurations would be refactored to move URL2LOCAL AND LOCAL2BLOB into the ML:
75- resource push, pop at end of io_http.c would be moved to resources.c
76- io_files.c and io_http.c would be moved to ML -out of the core library
77- core library would work in URLs and BLOBS
78- to avoid yet more threads to run the ML, the BE would enqueue a task with a function pointer for URL2LOCAL,
79 for each URL it enqueues, if the function pointer is non-null it runs it, else does nothing.
80 ML would populate the function pointer. After ML does URL2LOCAL, if successful,
81 it enqueus another function task LOCAL2BLOB which the worker thread runs.
82
83Android may be diskless for some types of files -no local file intermediary- and if so does URL2BLOB in one step in the FE.
84Some different possible workflows for different configurations and scenarios:
851 ------------------------------------------(BLOB) ------------P-> SCENERY || JS || SHADER
862 --------------------------LOCAL FILE -L-> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
873 URL --D-----------------> LOCAL FILE -L-> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
884 URL --D-> LOCAL ZIP --U-> LOCAL FILE -L-> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
895 ----------LOCAL ZIP --U-> LOCAL FILE -L-> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
906 URL ---------D+L------------------------> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
91
92D-download, U-unzip, L-load, I-convert to Image w,h,rgba, P-Process: parse/texture
93BLOB and TEXBLOB are the common/goal states
94Two workflows for TEXBLOB:
95LI-> (TEXBLOB) -P-> TEXTURE
96- when your image libary loads from file
97L-> (BLOB) I (TEXBLOB) -P-> TEXTURE
98- when your image library loads from BLOB
99
100I think I did .x3z + minizip in the wrong layer. It should be in a middle layer, not in the BE. You would never Anchor to a .x3z or .w3dx.
101
102^^
103Stage is a missing concept from web3d.org. A scene file, with all its .js files, its inline/extern Proto and anchor scenes aka scenery,
104all its imagery, as a self-contained set or 'stage'
105- like .x3z minizip bundling of doc.x3d + images, except more general
106- freewrl could be modified to aggregate files it downloads into a local cache of files, and add w3dx.manifest
107 that would list the original URL of the mainscene, and its stage relative path, and a lookup table of all
108 the other resources original URLs and their stage relative path. Then zip it and rename it to .w3dx.
109RULE: the BE must never know. ML unzips, gets the mainscene original URL and passes to the BE. The BE then requests
110 the mainscene by it's original URL, and ML looks it up in the manifest table, gets the stage-relative path,
111 and prepends the path of the stage.
112w3dx.manifest:
113<stage>
114<file type="mainscene" url="" path=""/>
115<file type="scene" url="" path=""/>
116<file type="scenery" url="" path=""/>
117<file type="imagery" url="" path=""/>
118...
119</stage>
120
121*/
122
123#include <config.h>
124#include <system.h>
125#include <system_threads.h>
126#include <resources.h>
127#include <libFreeWRL.h>
128#include <internal.h>
129#include <io_http.h>
130#include "main/MainLoop.h"
131
132
133
134void startNewHTMLWindow(char *url);
135
136void launch_in_web_browser(void *res){
137 char * url;
138 url = fwl_resitem_getURL(res);
139 if(url){
140 //platforms can just stub this until implemented
141 startNewHTMLWindow(url);
142 }
143
144}
145
150//#define DEBUG_RES printf
151bool resource_fetch(void *res)
152{
153 int type, status;
154 char *url;
155 //char* pound;
156
157 ASSERT(res);
158 type = fwl_resitem_getType(res);
159 url = fwl_resitem_getURL(res);
160 status = fwl_resitem_getStatus(res);
161 //if(0) printf("fetching resource: %s, %s resource %s\n", resourceTypeToString(type), resourceStatusToString(status) ,url);
162
163 //switch (res->type) {
164 switch(type) {
165
166 case rest_invalid:
167 //res->status = ress_invalid;
168 ERROR_MSG("resource_fetch: can't fetch an invalid resource: %s\n", url); //res->URLrequest);
169 fwl_resitem_setStatus(res,ress_invalid);
170 break;
171
172 case rest_url:
173 switch (status) {
174 case ress_none:
175 case ress_starts_good:
176 DEBUG_RES ("resource_fetch, calling download_url\n");
177 //pound = NULL;
178 //pound = strchr(res->parsed_request, '#');
179 //if (pound != NULL) {
180 // *pound = '\0';
181 // /* copy the name out, so that Anchors can go to correct Viewpoint */
182 // pound++;
183 // res->afterPoundCharacters = STRDUP(pound);
184 //}
185
186 download_url(res);
187 break;
188 default:
189 /* error */
190 break;
191 }
192 break;
193
194 case rest_file:
195 status = fwl_resitem_getStatus(res);
196 switch (status) {
197 case ress_none:
198 case ress_starts_good:
199 if (do_file_exists(url)){ //res->parsed_request)) {
200 if (do_file_readable(url)){ //res->parsed_request)) {
201 //res->status = ress_downloaded;
202 fwl_resitem_setStatus(res,ress_downloaded);
203 //res->actual_file = STRDUP(url); //res->parsed_request);
204 fwl_resitem_setActualFile(res,url);
205 } else {
206 //res->status = ress_failed;
207 fwl_resitem_setStatus(res,ress_failed);
208 ERROR_MSG("resource_fetch: wrong permission to read file: %s\n", url); //res->parsed_request);
209 }
210 } else {
211 //res->status = ress_failed;
212 fwl_resitem_setStatus(res,ress_failed);
213 // a little too noisy, if MF url and first url isn't found, it makes it look like there's a problem
214 // meanwhile subsequent SF urls in the MFUrl might succeed.
215 //ERROR_MSG("resource_fetch: can't find file: %s\n", url); //res->parsed_request);
216 }
217
218 break;
219 default:
220 /* error */
221 break;
222 }
223 break;
224
225 case rest_multi:
226 case rest_string:
227 /* Nothing to do */
228 break;
229 }
230 DEBUG_RES ("resource_fetch (end): network=%s type=%s status=%s"
231 " request=<%s> base=<%s> url=<%s> [parent %p, %s]\n",
232 BOOL_STR(res->network), resourceTypeToString(res->type),
233 resourceStatusToString(res->status), res->URLrequest,
234 res->URLbase, res->parsed_request,
235 res->parent, (res->parent ? res->parent->URLbase : "N/A"));
236 //return (res->status == ress_downloaded);
237 return fwl_resitem_getStatus(res) == ress_downloaded;
238}
239
240
241void frontenditem_enqueue_tg(s_list_t *item, void *tg);
242s_list_t *frontenditem_dequeue_tg(void *tg);
243s_list_t *frontenditem_dequeue();
244
245int checkReplaceWorldRequest();
246int checkExitRequest();
247enum {
248 url2file_task_chain,
249 url2file_task_spawn,
250} url2file_task_tactic;
251
252enum {
253 file2blob_task_chain,
254 file2blob_task_spawn,
255 file2blob_task_enqueue,
256} file2blob_task_tactic;
257
258
259//int file2blob(resource_item_t *res);
260int url2file(void *res){
261 int status, retval = 0;
262 resource_fetch(res); //URL2FILE
263 status = fwl_resitem_getStatus(res);
264 if(status == ress_downloaded){
265 //queue for loading
266 retval = 1;
267 }
268 return retval;
269}
270
271void file2blob_task(s_list_t *item);
272extern int async_thread_count;
273static void *thread_download_async (void *args){
274 int downloaded; //, tactic;
275 void *tg;
276 s_list_t *item = (s_list_t *)args;
277 //resource_item_t *res = (resource_item_t *)item->elem;
278 void *res = (void*)item->elem;
279 async_thread_count++;
280 //printf("{%d}",async_thread_count);
281 tg = fwl_resitem_getGlobal(res);
282 if(fwl_setCurrentHandle(tg, __FILE__, __LINE__));
283
284 downloaded = url2file(res);
285
286 //tactic = file2blob_task_chain;
287 if(downloaded)
288 file2blob_task(item); //ml_new(res));
289 else{
290 resitem_enqueue(item); //for garbage collection
291 }
292 async_thread_count--;
293 return NULL;
294}
295void downloadAsync (s_list_t *item) {
296 //resource_item_t *res = (resource_item_t *)item->elem;
297 void *res = (void *)item->elem;
298 pthread_t * thread;
299 thread = fwl_resitem_getDownloadThread(res);
300 //if(!res->_loadThread) res->_loadThread = malloc(sizeof(pthread_t));
301 if(!thread) thread = malloc(sizeof(pthread_t));
302 //pthread_create ((pthread_t*)res->_loadThread, NULL,&thread_download_async, (void *)item);
303 fwl_resitem_setDownloadThread(res,thread);
304 pthread_create (thread, NULL,&thread_download_async, (void *)item);
305}
306
307
308//this is for simulating frontend_gets_files in configs that run _displayThread: desktop and browser plugins
309//but can be run from any thread as long as you know the freewrl instance/context/tg/gglobal* for the resitem and frontenditem queues, replaceWorldRequest etc
310#define MAX_SPAWNED_PER_PASS 15 //in desktop I've had 57 spawned threads at once, with no problems. In case there's a problem this will limit spawned-per-pass, which will indirectly limit spawned-at-same-time
311void frontend_dequeue_get_enqueue(void *tg){
312 int count_this_pass;
313 s_list_t *item = NULL;
314 void *res = NULL;
315 fwl_setCurrentHandle(tg, __FILE__, __LINE__); //set the freewrl instance - will apply to all following calls into the backend. This allows you to call from any thread.
316 count_this_pass = 0; //approximately == number of spawned threads running at one time when doing file2blob_task_spawn
317 while( max(count_this_pass,async_thread_count) < MAX_SPAWNED_PER_PASS && !checkExitRequest() && !checkReplaceWorldRequest() && (item = frontenditem_dequeue()) != NULL ){
318 count_this_pass++;
319 //download_url((resource_item_t *) item->elem);
320 res = item->elem;
321 if(fwl_resitem_getStatus(res) != ress_downloaded){
322 int tactic = url2file_task_spawn;//url2file_task_spawn;
323 if(fwl_resitem_getMediaType(res) == resm_external){
324 //if anchroring to something besides scene or viewpoint, send it to a web-browser to display
325 launch_in_web_browser(res);
326 fwl_resitem_setStatus(res,ress_none); //how to tell backend to delete res now, done?
327 resitem_enqueue(item);
328 }else{
329 if(tactic == url2file_task_chain){
330 //int more_multi;
331 resource_fetch(res); //URL2FILE
332 //if(1){
333 //Multi_URL in backend
334 resitem_enqueue(item);
335 //}else{
336 // //Multi_URL loop moved here (middle layer ML),
337 // more_multi = (res->status == ress_failed) && (res->m_request != NULL);
338 // if(more_multi){
339 // //still some hope via multi_string url, perhaps next one
340 // res->status = ress_invalid; //downgrade ress_fail to ress_invalid
341 // res->type = rest_multi; //should already be flagged
342 // //must consult BE to convert relativeURL to absoluteURL via baseURL
343 // //(or could we absolutize in a batch in resource_create_multi0()?)
344 // resource_identify(res->parent, res); //should increment multi pointer/iterator
345 // frontenditem_enqueue(item);
346 // }
347 //}
348 }else if(tactic == url2file_task_spawn){
349 downloadAsync(item); //res already has res->tg with global context
350 }
351 }
352 }
353 if(fwl_resitem_getStatus(res) == ress_downloaded){
354 file2blob_task(item);
355 }
356 }
357 //fwl_clearCurrentHandle(); don't unset, in case we are in a BE/ML thread ie _displayThread
358}
359
360#ifdef SSR_SERVER
361void SSR_reply(void * tg);
362void dequeue_SSR_request(void * tg);
363#endif
364char *get_key_val(char *key);
365void _displayThread(void *globalcontext)
366{
367 /* C CONTROLLER - used in configurations such as C main programs, and browser plugins with no loop of their own
368 - usually the loop, create gl context, and swapbuffers stay together in the same layer
369 MVC - Model-View-Controller design pattern: do the Controller activities here:
370 1) tell Model (scenegraph, most of libfreewrl) to update itself
371 2) tell View to poll-model-and-update-itself (View: GUI UI/statusbarHud/Console)
372 -reason for MVC: no callbacks are needed from Model to UI(View), so easy to change View
373 -here everything is in C so we don't absolutely need MVC style, but we are preparing MVC in C
374 to harmonize with Android, IOS etc where the UI(View) and Controller are in Objective-C or Java and Model(state) in C
375
376 Non-blocking UI thread - some frontends don't allow you to block the display thread. They will be in
377 different code like objectiveC, java, C#, but here we try and honor the idea by allowing
378 looping to continue while waiting for worker threads to flush and/or exit by polling the status of the workers
379 rather than using mutex conditions.
380 */
381 int more;
382
383#ifdef SSR_SERVER
384 int run_ssr;
385 run_ssr = FALSE;
386#endif //SSR_SERVER
387
388 fwl_setCurrentHandle(globalcontext, __FILE__, __LINE__);
389 ENTER_THREAD("display");
390#ifdef SSR_SERVER
391 if(!run_ssr) {
392 //if this is ssr server running, it does a few quirky things like doing slow looping
393 char *running_ssr = get_key_val("SSR");
394 if(running_ssr)
395 if(!strcmp(running_ssr,"true"))
396 run_ssr = TRUE;
397 //printf("in desktop.c run_ssr = %d\n",run_ssr);
398 }
399#endif
400 do{
401 //if(frontendGetsFiles()==2)
402#ifdef SSR_SERVER
403 if(run_ssr){
404 SSR_reply(globalcontext);
405 dequeue_SSR_request(globalcontext);
406 }
407#endif
408#ifdef _MSC_VER
409 //win32 message pump - works here for desktop freewrl and npapi, ActiveX plugins,
410 // because those all use _DisplayThread here.
411 // (winGLES2 project which uses EGL 'front end' has its own win32 message pump,
412 // and doesn't call this _displayThread)
413 fwMessageLoop();
414#endif
415 frontend_dequeue_get_enqueue(globalcontext); //this is non-blocking (returns immediately) if queue empty
416 more = fwl_draw();
417 /* swap the rendering area */
418 if(more)
419 if(0) FW_GL_SWAPBUFFERS;
420 } while (more);
421 // moved to fwl_draw for disabler finalizeRenderSceneUpdateScene(); //Model end
422 //printf("Ending display thread gracefully\n");
423 return;
424}
425#ifdef _MSC_VER
426void sync();
427#endif
428//#if !defined (FRONTEND_HANDLES_DISPLAY_THREAD)
429void fwl_initializeDisplayThread()
430{
431 int ret;
432 ttglobal tg = gglobal();
433 /* Synchronize trace/error log... */
434 fflush(stdout);
435 fflush(stderr);
436 sync();
437 ASSERT(TEST_NULL_THREAD(gglobal()->threads.DispThrd));
438
439
441 //pthread_mutex_init( &tg->threads.mutex_resource_tree, NULL );
442 //pthread_mutex_init( &tg->threads.mutex_resource_list, NULL );
443 //pthread_mutex_init( &tg->threads.mutex_texture_list, NULL );
444 //pthread_cond_init( &tg->threads.resource_list_condition, NULL );
445 //pthread_cond_init( &tg->threads.texture_list_condition, NULL );
446 //pthread_mutex_init(&tg->threads.mutex_frontend_list,NULL);
447
448
449 ret = pthread_create(&tg->threads.DispThrd, NULL, (void *) _displayThread, tg);
450 switch (ret) {
451 case 0:
452 break;
453 case EAGAIN:
454 ERROR_MSG("initializeDisplayThread: not enough system resources to create a process for the new thread.");
455 return;
456 }
457
458
459// OLD_IPHONE_AQUA #if !defined(TARGET_AQUA) && !defined(_MSC_VER)
460#if !defined(_MSC_VER) && !defined(TARGET_AQUA)
461 if (gglobal()->internalc.global_trace_threads) {
462 TRACE_MSG("initializeDisplayThread: waiting for display to become initialized...\n");
463 while (IS_DISPLAY_INITIALIZED == FALSE) {
464 usleep(50);
465 }
466 }
467#endif
468}
469
470//#endif /* FRONTEND_HANDLES_DISPLAY_THREAD */
471
472
473//desktop plugin
474void fwl_spawnRenderingThread(void *globalcontext){
475 //if(!params->frontend_handles_display_thread){
476 /* OK the display is now initialized,
477 create the display thread and wait for it
478 to complete initialization */
479 fwl_initializeDisplayThread();
480
481 //usleep(50);
482 //set_thread2global(tg,tg->threads.DispThrd ,"display thread");
483 //}
484
485}
486
487//desktop console
488void fwl_startFreeWRL(const char *url)
489{
490 ttglobal tg = gglobal();
491 //ConsoleMessage ("yes, really, FWL_STARTFREEWRL called is called\n");
492
493 /* Give the main argument to the resource handler */
494 if (url != NULL) {
495 //char* suff = NULL;
496 //char* local_name = NULL;
497 //splitpath_local_suffix(url, &local_name, &suff);
498 //if(url) tg->Mainloop.url = strdup(url);
499 //tg->Mainloop.scene_name = local_name;
500 //tg->Mainloop.scene_suff = suff;
501
502 fwl_resource_push_single_request(url);
503 DEBUG_MSG("request sent to parser thread, main thread joining display thread...\n");
504 } else {
505 DEBUG_MSG("no request for parser thread, main thread joining display thread...\n");
506 }
507 //this is for simulating frontend_gets_files for testing. Do not set FRONTEND_GETS_FILES.
508 //this tests an alternate method.
509 // you need to put an http:// file on the command line (this is hardwired for io_http gets only, not local
510 //if(frontendGetsFiles()==1){
511 // for(;;){
512 // frontend_dequeue_get_enqueue(ttg); //this is non-blocking (returns immediately) if queue empty
513 // sleep(100);
514 // if(checkExitRequest()) break;
515 // }
516 // sleep(200); //wait for backend threads to wind down
517 //}else{
518 /* now wait around until something kills this thread. */
519 //pthread_join(gglobal()->threads.DispThrd, NULL);
520 _displayThread(tg);
521 //}
522}
Definition Viewer.h:139