FreeWRL/FreeX3D  3.0.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
21 including 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 )
23 The 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 
32 Tips 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 
40 EMULATING FEGF in ML - more detail..
41 terminology
42 FE - frontend
43 BE - backend
44 ML - middle layer
45 FEGF (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)
48 FEHDT (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)
51 BLOB - binary large object - a text string blob for vrml/x3d
52 TEXBLOB - width,height,RGBA blob for textures
53 Scenery: vrml/x3d for main scene, inline, externProto, string
54 Imagery: image files .jpg, .ico, .png etc
55 Scripts: .js, shader
56 
57 BE (BackEnd) worker threads:
58 Scenery and scripts: _inputParsingThread()
59 Imagery: _textureThread()
60 
61 download2local - downloads an internet file to a local file
62 local2blob - reads a local disk file into BLOB or TEXBLOB
63 INITSCENE = {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)
66 FILE2BLOB = {localURL -> ML -> local2blob -> enqueueBlob -> BE}
67 W3DX - web3d stage .zip^^
68 
69 Typical workflows for WinRT FEGF configuration:
70 1. URLBox -> INITSCENE -> FE -> URL? download2Local -> FILE2BLOB
71 2. Picker/OpenWith -> AccessCache -> INITSCENE -> FE -> cacheLookup -> found ? use : filePicker -> download/copy2Local -> FILE2BLOB
72 3. Picker/OpenWith -> W3DX? -> ML -> localStage -> manifest -> mainsceneURL -> INITSCENE -> ML-> manifestLookup -> FILE2BLOB
73 
74 Desktop 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 
83 Android may be diskless for some types of files -no local file intermediary- and if so does URL2BLOB in one step in the FE.
84 Some different possible workflows for different configurations and scenarios:
85 1 ------------------------------------------(BLOB) ------------P-> SCENERY || JS || SHADER
86 2 --------------------------LOCAL FILE -L-> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
87 3 URL --D-----------------> LOCAL FILE -L-> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
88 4 URL --D-> LOCAL ZIP --U-> LOCAL FILE -L-> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
89 5 ----------LOCAL ZIP --U-> LOCAL FILE -L-> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
90 6 URL ---------D+L------------------------> (BLOB || TEXBLOB) -P-> SCENERY || JS || SHADER || TEXTURE
91 
92 D-download, U-unzip, L-load, I-convert to Image w,h,rgba, P-Process: parse/texture
93 BLOB and TEXBLOB are the common/goal states
94 Two workflows for TEXBLOB:
95 LI-> (TEXBLOB) -P-> TEXTURE
96 - when your image libary loads from file
97 L-> (BLOB) I (TEXBLOB) -P-> TEXTURE
98 - when your image library loads from BLOB
99 
100 I 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 ^^
103 Stage is a missing concept from web3d.org. A scene file, with all its .js files, its inline/extern Proto and anchor scenes aka scenery,
104 all 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.
109 RULE: 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.
112 w3dx.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 
134 void startNewHTMLWindow(char *url);
135 
136 void 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
151 bool resource_fetch(void *res)
152 {
153  int type, status;
154  char *url;
155  //char* pound;
156  DEBUG_RES("fetching resource: %s, %s resource %s\n", resourceTypeToString(res->type), resourceStatusToString(res->status) ,res->URLrequest);
157 
158  ASSERT(res);
159  type = fwl_resitem_getType(res);
160  url = fwl_resitem_getURL(res);
161  status = fwl_resitem_getStatus(res);
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 
241 void frontenditem_enqueue_tg(s_list_t *item, void *tg);
242 s_list_t *frontenditem_dequeue_tg(void *tg);
243 s_list_t *frontenditem_dequeue();
244 
245 int checkReplaceWorldRequest();
246 int checkExitRequest();
247 enum {
248  url2file_task_chain,
249  url2file_task_spawn,
250 } url2file_task_tactic;
251 
252 enum {
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);
260 int 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 
271 void file2blob_task(s_list_t *item);
272 extern int async_thread_count;
273 static 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 }
295 void 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
311 void 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
361 void SSR_reply(void * tg);
362 void dequeue_SSR_request(void * tg);
363 #endif
364 char *get_key_val(char *key);
365 void _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 
416  frontend_dequeue_get_enqueue(globalcontext); //this is non-blocking (returns immediately) if queue empty
417  more = fwl_draw();
418  /* swap the rendering area */
419  if(more)
420  if(0) FW_GL_SWAPBUFFERS;
421  } while (more);
422  // moved to fwl_draw for disabler finalizeRenderSceneUpdateScene(); //Model end
423  //printf("Ending display thread gracefully\n");
424  return;
425 }
426 #ifdef _MSC_VER
427 void sync();
428 #endif
429 //#if !defined (FRONTEND_HANDLES_DISPLAY_THREAD)
430 void fwl_initializeDisplayThread()
431 {
432  int ret;
433  ttglobal tg = gglobal();
434  /* Synchronize trace/error log... */
435  fflush(stdout);
436  fflush(stderr);
437  sync();
438  ASSERT(TEST_NULL_THREAD(gglobal()->threads.DispThrd));
439 
440 
442  //pthread_mutex_init( &tg->threads.mutex_resource_tree, NULL );
443  //pthread_mutex_init( &tg->threads.mutex_resource_list, NULL );
444  //pthread_mutex_init( &tg->threads.mutex_texture_list, NULL );
445  //pthread_cond_init( &tg->threads.resource_list_condition, NULL );
446  //pthread_cond_init( &tg->threads.texture_list_condition, NULL );
447  //pthread_mutex_init(&tg->threads.mutex_frontend_list,NULL);
448 
449 
450  ret = pthread_create(&tg->threads.DispThrd, NULL, (void *) _displayThread, tg);
451  switch (ret) {
452  case 0:
453  break;
454  case EAGAIN:
455  ERROR_MSG("initializeDisplayThread: not enough system resources to create a process for the new thread.");
456  return;
457  }
458 
459 
460 // OLD_IPHONE_AQUA #if !defined(TARGET_AQUA) && !defined(_MSC_VER)
461 #if !defined(_MSC_VER)
462  if (gglobal()->internalc.global_trace_threads) {
463  TRACE_MSG("initializeDisplayThread: waiting for display to become initialized...\n");
464  while (IS_DISPLAY_INITIALIZED == FALSE) {
465  usleep(50);
466  }
467  }
468 #endif
469 }
470 
471 //#endif /* FRONTEND_HANDLES_DISPLAY_THREAD */
472 
473 
474 //desktop plugin
475 void fwl_spawnRenderingThread(void *globalcontext){
476  //if(!params->frontend_handles_display_thread){
477  /* OK the display is now initialized,
478  create the display thread and wait for it
479  to complete initialization */
480  fwl_initializeDisplayThread();
481 
482  //usleep(50);
483  //set_thread2global(tg,tg->threads.DispThrd ,"display thread");
484  //}
485 
486 }
487 
488 //desktop console
489 void fwl_startFreeWRL(const char *url)
490 {
491  ttglobal tg = gglobal();
492  //ConsoleMessage ("yes, really, FWL_STARTFREEWRL called is called\n");
493 
494  /* Give the main argument to the resource handler */
495  if (url != NULL) {
496  //char* suff = NULL;
497  //char* local_name = NULL;
498  //splitpath_local_suffix(url, &local_name, &suff);
499  //if(url) tg->Mainloop.url = strdup(url);
500  //tg->Mainloop.scene_name = local_name;
501  //tg->Mainloop.scene_suff = suff;
502 
503  fwl_resource_push_single_request(url);
504  DEBUG_MSG("request sent to parser thread, main thread joining display thread...\n");
505  } else {
506  DEBUG_MSG("no request for parser thread, main thread joining display thread...\n");
507  }
508  //this is for simulating frontend_gets_files for testing. Do not set FRONTEND_GETS_FILES.
509  //this tests an alternate method.
510  // you need to put an http:// file on the command line (this is hardwired for io_http gets only, not local
511  //if(frontendGetsFiles()==1){
512  // for(;;){
513  // frontend_dequeue_get_enqueue(ttg); //this is non-blocking (returns immediately) if queue empty
514  // sleep(100);
515  // if(checkExitRequest()) break;
516  // }
517  // sleep(200); //wait for backend threads to wind down
518  //}else{
519  /* now wait around until something kills this thread. */
520  //pthread_join(gglobal()->threads.DispThrd, NULL);
521  _displayThread(tg);
522  //}
523 }
Definition: list.h:37
Definition: Viewer.h:174