Coverage Report

Created: 2026-01-23 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpac/src/scenegraph/vrml_route.c
Line
Count
Source
1
/*
2
 *      GPAC - Multimedia Framework C SDK
3
 *
4
 *      Authors: Jean Le Feuvre
5
 *      Copyright (c) Telecom ParisTech 2000-2023
6
 *          All rights reserved
7
 *
8
 *  This file is part of GPAC / Scene Graph sub-project
9
 *
10
 *  GPAC is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU Lesser General Public License as published by
12
 *  the Free Software Foundation; either version 2, or (at your option)
13
 *  any later version.
14
 *
15
 *  GPAC is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU Lesser General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU Lesser General Public
21
 *  License along with this library; see the file COPYING.  If not, write to
22
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
 *
24
 */
25
26
#include <gpac/internal/scenegraph_dev.h>
27
/*MPEG4 & X3D tags (for node tables & script handling)*/
28
#include <gpac/nodes_mpeg4.h>
29
#include <gpac/nodes_x3d.h>
30
31
#ifndef GPAC_DISABLE_VRML
32
33
GF_Route* gf_sg_route_exists(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromField, GF_Node *toNode, u32 toField);
34
35
GF_EXPORT
36
GF_Route *gf_sg_route_new(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromField, GF_Node *toNode, u32 toField)
37
0
{
38
0
  GF_Route *r;
39
0
  if (!sg || !toNode || !fromNode) return NULL;
40
41
0
  if ( (r = gf_sg_route_exists(sg, fromNode, fromField, toNode, toField)) )
42
0
    return r;
43
44
0
  GF_SAFEALLOC(r, GF_Route)
45
0
  if (!r) return NULL;
46
0
  r->FromNode = fromNode;
47
0
  r->FromField.fieldIndex = fromField;
48
0
  r->ToNode = toNode;
49
0
  r->ToField.fieldIndex = toField;
50
0
  r->graph = sg;
51
52
0
  if (!fromNode->sgprivate->interact) {
53
0
    GF_SAFEALLOC(fromNode->sgprivate->interact, struct _node_interactive_ext);
54
0
    if (!fromNode->sgprivate->interact) {
55
0
      GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[VRML] Failed to create interact storage\n"));
56
0
      gf_free(r);
57
0
      return NULL;
58
0
    }
59
0
  }
60
0
  if (!fromNode->sgprivate->interact->routes) fromNode->sgprivate->interact->routes = gf_list_new();
61
0
  gf_list_add(fromNode->sgprivate->interact->routes, r);
62
0
  gf_list_add(sg->Routes, r);
63
0
  return r;
64
0
}
65
66
GF_Route* gf_sg_route_exists(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromField, GF_Node *toNode, u32 toField)
67
0
{
68
0
  u32 i = 0;
69
0
  GF_Route* rt;
70
0
  if ( !fromNode->sgprivate->interact || !fromNode->sgprivate->interact->routes )
71
0
    return NULL;
72
73
0
  while ( (rt = (GF_Route*)gf_list_enum(fromNode->sgprivate->interact->routes, &i) )) {
74
0
    if ( rt->FromField.fieldIndex == fromField && rt->ToNode == toNode && rt->ToField.fieldIndex == toField )
75
0
      return rt;
76
0
  }
77
0
  return NULL;
78
0
}
79
80
GF_EXPORT
81
void gf_sg_route_del(GF_Route *r)
82
0
{
83
0
  GF_SceneGraph *sg;
84
85
  /*remove declared routes*/
86
0
  gf_list_del_item(r->graph->Routes, r);
87
  /*remove route from node - do this regardless of setup state since the route is registered upon creation*/
88
0
  if (r->FromNode && r->FromNode->sgprivate->interact && r->FromNode->sgprivate->interact->routes) {
89
0
    gf_list_del_item(r->FromNode->sgprivate->interact->routes, r);
90
0
    if (!gf_list_count(r->FromNode->sgprivate->interact->routes)) {
91
0
      gf_list_del(r->FromNode->sgprivate->interact->routes);
92
0
      r->FromNode->sgprivate->interact->routes = NULL;
93
0
    }
94
0
  }
95
  /*special case for script events: notify desdctruction*/
96
0
  if (r->ToNode && (r->ToField.fieldType==GF_SG_VRML_SCRIPT_FUNCTION) && r->ToField.on_event_in) {
97
0
    r->is_setup = 0;
98
0
    r->FromNode = NULL;
99
0
    if (!r->graph->pOwningProto) r->ToField.on_event_in(r->ToNode, r);
100
0
  }
101
102
0
  r->is_setup = 0;
103
0
  sg = r->graph;
104
0
  while (sg->parent_scene) sg = sg->parent_scene;
105
0
  gf_list_add(sg->routes_to_destroy, r);
106
0
  gf_list_del_item(sg->routes_to_activate, r);
107
0
}
108
109
GF_EXPORT
110
GF_Err gf_sg_route_del_by_id(GF_SceneGraph *sg,u32 routeID)
111
0
{
112
0
  GF_Route *r;
113
0
  if(!sg) return GF_BAD_PARAM;
114
0
  r = gf_sg_route_find(sg, routeID);
115
0
  if (!r) return GF_BAD_PARAM;
116
0
  gf_sg_route_del(r);
117
0
  return GF_OK;
118
0
}
119
120
void gf_sg_destroy_routes(GF_SceneGraph *sg)
121
0
{
122
0
  while (gf_list_count(sg->routes_to_destroy) ) {
123
0
    GF_Route *r = (GF_Route *)gf_list_get(sg->routes_to_destroy, 0);
124
0
    gf_list_rem(sg->routes_to_destroy, 0);
125
0
    gf_sg_route_unqueue(sg, r);
126
0
    if (r->name) gf_free(r->name);
127
0
    gf_free(r);
128
0
  }
129
0
}
130
131
132
void gf_sg_route_queue(GF_SceneGraph *sg, GF_Route *r)
133
0
{
134
0
  u32 now;
135
0
  if (!sg) return;
136
137
  /*get the top level scene (that's the only reliable one regarding simulatioin tick)*/
138
0
  while (sg->parent_scene) sg = sg->parent_scene;
139
  /*a single route may not be activated more than once in a simulation tick*/
140
0
  now = 1 + sg->simulation_tick;
141
0
  if (r->lastActivateTime >= now) return;
142
0
  r->lastActivateTime = now;
143
0
  gf_list_add(sg->routes_to_activate, r);
144
0
}
145
146
/*activate all routes in the order they where triggered*/
147
GF_EXPORT
148
void gf_sg_activate_routes(GF_SceneGraph *sg)
149
0
{
150
0
  GF_Route *r;
151
0
  GF_Node *targ;
152
0
  if (!sg) return;
153
154
0
  sg->simulation_tick++;
155
0
  gf_sg_destroy_routes(sg);
156
157
0
  while (gf_list_count(sg->routes_to_activate)) {
158
0
    r = (GF_Route *)gf_list_get(sg->routes_to_activate, 0);
159
0
    gf_list_rem(sg->routes_to_activate, 0);
160
0
    if (r) {
161
0
      targ = r->ToNode;
162
0
      if (gf_sg_route_activate(r)) {
163
#ifdef GF_SELF_REPLACE_ENABLE
164
        if (sg->graph_has_been_reset) {
165
          sg->graph_has_been_reset = 0;
166
          return;
167
        }
168
#endif
169
0
        if (r->is_setup) gf_node_changed(targ, &r->ToField);
170
0
      }
171
0
    }
172
0
  }
173
0
}
174
175
void gf_sg_route_unqueue(GF_SceneGraph *sg, GF_Route *r)
176
0
{
177
  /*get the top level scene*/
178
0
  while (sg->parent_scene) sg = sg->parent_scene;
179
  /*remove route from queue list*/
180
0
  gf_list_del_item(sg->routes_to_activate, r);
181
0
}
182
183
GF_EXPORT
184
GF_Route *gf_sg_route_find(GF_SceneGraph *sg, u32 RouteID)
185
0
{
186
0
  GF_Route *r;
187
0
  u32 i=0;
188
0
  while ((r = (GF_Route*)gf_list_enum(sg->Routes, &i))) {
189
0
    if (r->ID == RouteID) return r;
190
0
  }
191
0
  return NULL;
192
0
}
193
194
GF_EXPORT
195
GF_Route *gf_sg_route_find_by_name(GF_SceneGraph *sg, char *name)
196
0
{
197
0
  GF_Route *r;
198
0
  u32 i;
199
0
  if (!sg || !name) return NULL;
200
201
0
  i=0;
202
0
  while ((r = (GF_Route*)gf_list_enum(sg->Routes, &i))) {
203
0
    if (r->name && !strcmp(r->name, name)) return r;
204
0
  }
205
0
  return NULL;
206
0
}
207
208
GF_EXPORT
209
GF_Err gf_sg_route_set_id(GF_Route *route, u32 ID)
210
0
{
211
0
  GF_Route *ptr;
212
0
  if (!route || !ID) return GF_BAD_PARAM;
213
214
0
  ptr = gf_sg_route_find(route->graph, ID);
215
0
  if (ptr) return GF_BAD_PARAM;
216
0
  route->ID = ID;
217
0
  return GF_OK;
218
0
}
219
220
#if 0 //unused
221
u32 gf_sg_route_get_id(GF_Route *route)
222
{
223
  return route->ID;
224
}
225
#endif
226
227
GF_EXPORT
228
GF_Err gf_sg_route_set_name(GF_Route *route, char *name)
229
0
{
230
0
  GF_Route *ptr;
231
0
  if (!name || !route) return GF_BAD_PARAM;
232
0
  ptr = gf_sg_route_find_by_name(route->graph, name);
233
0
  if (ptr) return GF_BAD_PARAM;
234
0
  if (route->name) gf_free(route->name);
235
0
  route->name = gf_strdup(name);
236
0
  return GF_OK;
237
0
}
238
239
GF_EXPORT
240
char *gf_sg_route_get_name(GF_Route *route)
241
0
{
242
0
  return route->name;
243
0
}
244
245
void gf_sg_route_setup(GF_Route *r)
246
0
{
247
0
  gf_node_get_field(r->FromNode, r->FromField.fieldIndex, &r->FromField);
248
0
  gf_node_get_field(r->ToNode, r->ToField.fieldIndex, &r->ToField);
249
0
  switch (r->FromField.fieldType) {
250
0
  case GF_SG_VRML_MFNODE:
251
0
    if (r->ToField.fieldType != GF_SG_VRML_MFNODE) return;
252
0
    break;
253
0
  case GF_SG_VRML_SFNODE:
254
0
    if (r->ToField.fieldType != GF_SG_VRML_SFNODE) return;
255
0
    break;
256
0
  }
257
0
  r->is_setup = 1;
258
0
}
259
260
/*send event out of proto - all ISed fields are ignored*/
261
void gf_node_event_out_proto(GF_Node *node, u32 FieldIndex)
262
0
{
263
0
  u32 i;
264
0
  GF_Route *r;
265
0
  if (!node) return;
266
267
0
  if (!node->sgprivate->interact) return;
268
269
  //search for routes to activate in the order they where declared
270
0
  i=0;
271
0
  while ((r = (GF_Route*)gf_list_enum(node->sgprivate->interact->routes, &i))) {
272
0
    if (r->IS_route) continue;
273
0
    if (r->FromNode != node) continue;
274
0
    if (r->FromField.fieldIndex != FieldIndex) continue;
275
0
    gf_sg_route_queue(node->sgprivate->scenegraph, r);
276
0
  }
277
0
}
278
279
Bool gf_sg_route_activate(GF_Route *r)
280
0
{
281
0
  Bool ret;
282
  /*URL/String conversion clone*/
283
0
  void VRML_FieldCopyCast(void *dest, u32 dst_field_type, void *orig, u32 ori_field_type);
284
0
  gf_assert(r->FromNode);
285
0
  if (!r->is_setup) {
286
0
    gf_sg_route_setup(r);
287
0
    if (!r->is_setup) return 0;
288
    /*special case when initing ISed routes on eventOuts: skip*/
289
0
    if (r->IS_route) {
290
0
      if (r->FromField.eventType == GF_SG_EVENT_OUT) return 0;
291
0
      if (r->ToField.eventType == GF_SG_EVENT_OUT) return 0;
292
0
    }
293
0
    if (r->IS_route && ((r->ToNode->sgprivate->tag==TAG_MPEG4_Script)
294
0
#ifndef GPAC_DISABLE_X3D
295
0
                        || (r->ToNode->sgprivate->tag==TAG_X3D_Script)
296
0
#endif
297
0
                       ) && ((r->ToField.eventType==GF_SG_EVENT_IN) /*|| (r->ToField.eventType==GF_SG_EVENT_FIELD)*/)
298
0
            && r->FromField.eventType==GF_SG_EVENT_IN) {
299
0
      return 0;
300
0
    }
301
0
  }
302
0
#ifndef GPAC_DISABLE_LOG
303
0
  if (gf_log_tool_level_on(GF_LOG_INTERACT, GF_LOG_DEBUG)) {
304
0
    if (r->IS_route) {
305
0
      GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[VRML Event] executing %s.%s IS %s.%s", gf_node_get_name(r->FromNode), r->FromField.name, gf_node_get_name(r->ToNode), r->ToField.name));
306
0
    } else {
307
0
      GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[VRML Event] executing ROUTE %s.%s TO %s.%s", gf_node_get_name(r->FromNode), r->FromField.name, gf_node_get_name(r->ToNode), r->ToField.name));
308
0
    }
309
0
    if (r->FromField.fieldType==GF_SG_VRML_SFBOOL) {
310
0
      GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("\tBOOL VAL: %d\n", *((SFBool*)r->FromField.far_ptr)));
311
0
    } else if (r->FromField.fieldType==GF_SG_VRML_SFINT32) {
312
0
      GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("\tINT VAL: %d\n", *((SFInt32*)r->FromField.far_ptr)));
313
0
    } else {
314
0
      GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("\n"));
315
0
    }
316
0
  }
317
0
#endif
318
319
0
  ret = 1;
320
0
  switch (r->FromField.fieldType) {
321
0
  case GF_SG_VRML_SFNODE:
322
0
    if (* (GF_Node **) r->ToField.far_ptr != * (GF_Node **) r->FromField.far_ptr) {
323
0
      GF_Node *n = * (GF_Node **) r->ToField.far_ptr;
324
      /*delete instance*/
325
0
      if (n) gf_node_unregister(n, r->ToNode);
326
      /*and use the node*/
327
0
      * (GF_Node **) r->ToField.far_ptr = * (GF_Node **) r->FromField.far_ptr;
328
0
      n = * (GF_Node **) r->FromField.far_ptr;
329
0
      gf_node_register(n, r->ToNode);
330
0
    }
331
0
    break;
332
333
  /*move all pointers to dest*/
334
0
  case GF_SG_VRML_MFNODE:
335
0
  {
336
0
    GF_ChildNodeItem *last = NULL;
337
0
    GF_ChildNodeItem *orig = *(GF_ChildNodeItem **)r->FromField.far_ptr;
338
339
    /*empty list*/
340
0
    gf_node_unregister_children(r->ToNode, *(GF_ChildNodeItem **)r->ToField.far_ptr );
341
0
    *(GF_ChildNodeItem **)r->ToField.far_ptr = NULL;
342
343
0
    while (orig) {
344
0
      gf_node_list_add_child_last( (GF_ChildNodeItem **)r->ToField.far_ptr, orig->node, &last);
345
0
      gf_node_register(orig->node, r->ToNode);
346
0
      orig = orig->next;
347
0
    }
348
0
  }
349
0
  break;
350
351
0
  default:
352
0
    if (r->ToField.fieldType==r->FromField.fieldType) {
353
      /*if unchanged don't invalidate dst node*/
354
0
      if (gf_sg_vrml_field_equal(r->ToField.far_ptr, r->FromField.far_ptr, r->FromField.fieldType)) {
355
0
        ret = 0;
356
0
      } else {
357
0
        gf_sg_vrml_field_copy(r->ToField.far_ptr, r->FromField.far_ptr, r->FromField.fieldType);
358
0
      }
359
0
    }
360
    /*typecast URL <-> string if needed*/
361
0
    else {
362
0
      VRML_FieldCopyCast(r->ToField.far_ptr, r->ToField.fieldType, r->FromField.far_ptr, r->FromField.fieldType);
363
0
    }
364
0
    break;
365
0
  }
366
367
  //don't notify dest change for generic function since the dest is not a node
368
0
  if (r->ToField.fieldType==GF_SG_VRML_GENERIC_FUNCTION) {
369
0
    ret = 0;
370
0
  }
371
372
0
#ifndef GPAC_DISABLE_LOG
373
0
  if (gf_log_tool_level_on(GF_LOG_INTERACT, GF_LOG_DEBUG)) {
374
0
    GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[VRML Route] field copy/casted\n"));
375
0
  }
376
0
#endif
377
378
  //if this is a supported eventIn call watcher
379
0
  if (r->ToField.on_event_in) {
380
0
    r->ToField.on_event_in(r->ToNode, r);
381
0
  }
382
  //if this is a script eventIn call directly script
383
0
  else if (((r->ToNode->sgprivate->tag==TAG_MPEG4_Script)
384
0
#ifndef GPAC_DISABLE_X3D
385
0
            || (r->ToNode->sgprivate->tag==TAG_X3D_Script)
386
0
#endif
387
0
           ) && ((r->ToField.eventType==GF_SG_EVENT_IN) /*|| (r->ToField.eventType==GF_SG_EVENT_FIELD)*/) ) {
388
0
    gf_sg_script_event_in(r->ToNode, &r->ToField);
389
0
  }
390
  //check if ISed or not - this will notify the node of any changes
391
0
  else {
392
0
    gf_sg_proto_propagate_event(r->ToNode, r->ToField.fieldIndex, r->FromNode);
393
    /*if not an ISed field, propagate (otherwise ROUTE is executed just below)*/
394
0
    if (r->ToField.eventType != GF_SG_EVENT_EXPOSED_FIELD)
395
0
      gf_sg_proto_propagate_event(r->ToNode, r->ToField.fieldIndex, r->FromNode);
396
    /*only happen on proto, an eventOut may route to an eventOut*/
397
0
    if (r->IS_route && r->ToField.eventType==GF_SG_EVENT_OUT)
398
0
      gf_node_event_out(r->ToNode, r->ToField.fieldIndex);
399
0
  }
400
401
  /*and signal routes on exposed fields if field changed*/
402
0
  if (r->ToField.eventType == GF_SG_EVENT_EXPOSED_FIELD) {
403
0
    if (r->IS_route)
404
0
      gf_node_event_out_proto(r->ToNode, r->ToField.fieldIndex);
405
0
    else
406
0
      gf_node_event_out(r->ToNode, r->ToField.fieldIndex);
407
0
  }
408
409
0
#ifndef GPAC_DISABLE_LOG
410
0
  if (gf_log_tool_level_on(GF_LOG_INTERACT, GF_LOG_DEBUG)) {
411
0
    GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[VRML Route] done executing (res %d)\n", ret));
412
0
  }
413
0
#endif
414
415
0
  return ret;
416
0
}
417
418
419
GF_EXPORT
420
void gf_node_event_out(GF_Node *node, u32 FieldIndex)
421
0
{
422
0
  u32 i;
423
0
  GF_Route *r;
424
0
  if (!node) return;
425
426
  /*node has no routes*/
427
0
  if (!node->sgprivate->interact || !node->sgprivate->interact->routes) return;
428
429
  //search for routes to activate in the order they where declared
430
0
  i=0;
431
0
  while ((r = (GF_Route*)gf_list_enum(node->sgprivate->interact->routes, &i))) {
432
0
    if (r->FromNode != node) continue;
433
0
    if (r->FromField.fieldIndex != FieldIndex) continue;
434
435
    /*no postpone for IS routes*/
436
0
    if (r->IS_route) {
437
0
      if (gf_sg_route_activate(r))
438
0
        gf_node_changed(r->ToNode, &r->ToField);
439
0
    }
440
    //queue
441
0
    else {
442
0
      gf_sg_route_queue(node->sgprivate->scenegraph, r);
443
0
    }
444
0
  }
445
0
}
446
447
GF_EXPORT
448
void gf_node_event_out_str(GF_Node *node, const char *eventName)
449
0
{
450
0
  u32 i;
451
0
  GF_Route *r;
452
453
  /*node has no routes*/
454
0
  if (!node->sgprivate->interact || !node->sgprivate->interact->routes) return;
455
456
  //search for routes to activate in the order they where declared
457
0
  i=0;
458
0
  while ((r = (GF_Route*)gf_list_enum(node->sgprivate->interact->routes, &i))) {
459
0
    if (!r->is_setup) gf_sg_route_setup(r);
460
0
    if (stricmp(r->FromField.name, eventName)) continue;
461
462
    //no postpone
463
0
    if (r->IS_route) {
464
0
      gf_sg_route_activate(r);
465
0
    }
466
    //queue
467
0
    else {
468
0
      gf_sg_route_queue(node->sgprivate->scenegraph, r);
469
0
    }
470
0
  }
471
0
}
472
473
typedef struct
474
{
475
  GF_Route r;
476
  void ( *route_callback) (void *param, GF_FieldInfo *from_field);
477
} GF_RouteToFunction;
478
479
static void on_route_to_function(GF_Node *node, GF_Route *r)
480
0
{
481
0
  GF_RouteToFunction *rf = (GF_RouteToFunction *)r;
482
0
  rf->route_callback(r->ToNode, &r->FromField);
483
0
}
484
485
GF_EXPORT
486
void gf_sg_route_new_to_callback(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromField, void *cbk, void ( *route_callback) (void *param, GF_FieldInfo *from_field) )
487
0
{
488
0
  GF_Route *r;
489
0
  GF_RouteToFunction *rf;
490
0
  GF_SAFEALLOC(rf, GF_RouteToFunction);
491
0
  if (!rf) return;
492
0
  rf->route_callback = route_callback;
493
494
0
  r = (GF_Route *)rf;
495
0
  r->FromNode = fromNode;
496
0
  r->FromField.fieldIndex = fromField;
497
0
  gf_node_get_field(r->FromNode, fromField, &r->FromField);
498
499
0
  r->ToNode = (GF_Node *) cbk;
500
0
  r->ToField.fieldType = GF_SG_VRML_GENERIC_FUNCTION;
501
0
  r->ToField.on_event_in = on_route_to_function;
502
0
  r->ToField.eventType = GF_SG_EVENT_IN;
503
0
  r->ToField.far_ptr = NULL;
504
505
0
  r->is_setup = 1;
506
0
  r->graph = sg;
507
508
0
  if (!fromNode->sgprivate->interact) {
509
0
    GF_SAFEALLOC(fromNode->sgprivate->interact, struct _node_interactive_ext);
510
0
    if (!fromNode->sgprivate->interact) {
511
0
      GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[VRML] Failed to create interact storage\n"));
512
0
      gf_free(r);
513
0
      return;
514
0
    }
515
0
  }
516
0
  if (!fromNode->sgprivate->interact->routes) fromNode->sgprivate->interact->routes = gf_list_new();
517
0
  gf_list_add(fromNode->sgprivate->interact->routes, r);
518
0
  gf_list_add(fromNode->sgprivate->scenegraph->Routes, r);
519
0
}
520
521
522
#endif  /*GPAC_DISABLE_VRML*/
523