The open source OpenXR runtime
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

u/space: Add default Space Overseer

+694
+2
src/xrt/auxiliary/util/CMakeLists.txt
··· 73 73 u_pretty_print.h 74 74 u_prober.c 75 75 u_prober.h 76 + u_space_overseer.c 77 + u_space_overseer.h 76 78 u_string_list.cpp 77 79 u_string_list.h 78 80 u_string_list.hpp
+575
src/xrt/auxiliary/util/u_space_overseer.c
··· 1 + // Copyright 2023, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief A implementation of the @ref xrt_space_overseer interface. 6 + * 7 + * @author Jakob Bornecrantz <jakob@collabora.com> 8 + * @ingroup aux_util 9 + */ 10 + 11 + #include "xrt/xrt_space.h" 12 + #include "xrt/xrt_device.h" 13 + #include "xrt/xrt_tracking.h" 14 + 15 + #include "math/m_space.h" 16 + 17 + #include "util/u_misc.h" 18 + #include "util/u_hashmap.h" 19 + #include "util/u_logging.h" 20 + #include "util/u_space_overseer.h" 21 + 22 + #include <assert.h> 23 + #include <pthread.h> 24 + 25 + 26 + /* 27 + * 28 + * Structs and defines. 29 + * 30 + */ 31 + 32 + /*! 33 + * Keeps track of what kind of space it is. 34 + */ 35 + enum u_space_type 36 + { 37 + U_SPACE_TYPE_NULL, 38 + U_SPACE_TYPE_POSE, 39 + U_SPACE_TYPE_OFFSET, 40 + U_SPACE_TYPE_ROOT, 41 + }; 42 + 43 + /*! 44 + * Representing a single space, can be several ones. There should only be one 45 + * root space per overseer. 46 + */ 47 + struct u_space 48 + { 49 + struct xrt_space base; 50 + 51 + /*! 52 + * The space this space is in. 53 + */ 54 + struct u_space *next; 55 + 56 + /*! 57 + * The type of the space. 58 + */ 59 + enum u_space_type type; 60 + 61 + union { 62 + struct 63 + { 64 + struct xrt_device *xdev; 65 + enum xrt_input_name xname; 66 + } pose; 67 + 68 + struct 69 + { 70 + struct xrt_pose pose; 71 + } offset; 72 + }; 73 + }; 74 + 75 + /*! 76 + * Default implementation of the xrt_space_overseer object. 77 + */ 78 + struct u_space_overseer 79 + { 80 + struct xrt_space_overseer base; 81 + 82 + //! Main graph lock. 83 + pthread_rwlock_t lock; 84 + 85 + //! Map from xdev to space, each entry holds a reference. 86 + struct u_hashmap_int *xdev_map; 87 + }; 88 + 89 + 90 + /* 91 + * 92 + * Helper functions. 93 + * 94 + */ 95 + 96 + static inline struct u_space * 97 + u_space(struct xrt_space *xs) 98 + { 99 + return (struct u_space *)xs; 100 + } 101 + 102 + static inline struct u_space_overseer * 103 + u_space_overseer(struct xrt_space_overseer *xso) 104 + { 105 + return (struct u_space_overseer *)xso; 106 + } 107 + 108 + /*! 109 + * A lot of code here uses u_space directly and need to change reference count 110 + * so this helper is here to make that easier. 111 + */ 112 + static inline void 113 + u_space_reference(struct u_space **dst, struct u_space *src) 114 + { 115 + struct u_space *old_dst = *dst; 116 + 117 + if (old_dst == src) { 118 + return; 119 + } 120 + 121 + if (src) { 122 + xrt_reference_inc(&src->base.reference); 123 + } 124 + 125 + *dst = src; 126 + 127 + if (old_dst) { 128 + if (xrt_reference_dec(&old_dst->base.reference)) { 129 + old_dst->base.destroy(&old_dst->base); 130 + } 131 + } 132 + } 133 + 134 + /*! 135 + * Helper function when clearing a hashmap to also unreference a space. 136 + */ 137 + static void 138 + hashmap_unreference_space_items(void *item, void *priv) 139 + { 140 + struct u_space *us = (struct u_space *)item; 141 + u_space_reference(&us, NULL); 142 + } 143 + 144 + static struct u_space * 145 + find_xdev_space_read_locked(struct u_space_overseer *uso, struct xrt_device *xdev) 146 + { 147 + void *ptr = NULL; 148 + uint64_t key = (uint64_t)(intptr_t)xdev; 149 + u_hashmap_int_find(uso->xdev_map, key, &ptr); 150 + 151 + if (ptr == NULL) { 152 + U_LOG_E("Looking for space belonging to unknown xrt_device! '%s'", xdev->str); 153 + } 154 + assert(ptr != NULL); 155 + 156 + return (struct u_space *)ptr; 157 + } 158 + 159 + 160 + /* 161 + * 162 + * Graph traversing functions. 163 + * 164 + */ 165 + 166 + /*! 167 + * For each space, push the relation of that space and then traverse by calling 168 + * @p push_then_traverse again with the parent space. That means traverse goes 169 + * from a leaf space to a the root space, relations are pushed in the same 170 + * order. 171 + */ 172 + static void 173 + push_then_traverse(struct xrt_relation_chain *xrc, struct u_space *space, uint64_t at_timestamp_ns) 174 + { 175 + switch (space->type) { 176 + case U_SPACE_TYPE_NULL: break; // No-op 177 + case U_SPACE_TYPE_POSE: { 178 + assert(space->pose.xdev != NULL); 179 + assert(space->pose.xname != 0); 180 + 181 + struct xrt_space_relation xsr; 182 + xrt_device_get_tracked_pose(space->pose.xdev, space->pose.xname, at_timestamp_ns, &xsr); 183 + m_relation_chain_push_relation(xrc, &xsr); 184 + } break; 185 + case U_SPACE_TYPE_OFFSET: m_relation_chain_push_pose_if_not_identity(xrc, &space->offset.pose); break; 186 + case U_SPACE_TYPE_ROOT: return; // Stops the traversing. 187 + } 188 + 189 + // Please tail-call optimise this miss compiler. 190 + assert(space->next != NULL); 191 + push_then_traverse(xrc, space->next, at_timestamp_ns); 192 + } 193 + 194 + /*! 195 + * For each space, traverse by calling @p traverse_then_push_inverse again with 196 + * the parent space then push the inverse of the relation of that. That means 197 + * traverse goes from a leaf space to a the root space, relations are pushed in 198 + * the reversed order. 199 + */ 200 + static void 201 + traverse_then_push_inverse(struct xrt_relation_chain *xrc, struct u_space *space, uint64_t at_timestamp_ns) 202 + { 203 + // Done traversing. 204 + switch (space->type) { 205 + case U_SPACE_TYPE_NULL: break; 206 + case U_SPACE_TYPE_POSE: break; 207 + case U_SPACE_TYPE_OFFSET: break; 208 + case U_SPACE_TYPE_ROOT: return; // Stops the traversing. 209 + } 210 + 211 + // Can't tail-call optimise this one :( 212 + assert(space->next != NULL); 213 + traverse_then_push_inverse(xrc, space->next, at_timestamp_ns); 214 + 215 + switch (space->type) { 216 + case U_SPACE_TYPE_NULL: break; // No-op 217 + case U_SPACE_TYPE_POSE: { 218 + assert(space->pose.xdev != NULL); 219 + assert(space->pose.xname != 0); 220 + 221 + struct xrt_space_relation xsr; 222 + xrt_device_get_tracked_pose(space->pose.xdev, space->pose.xname, at_timestamp_ns, &xsr); 223 + m_relation_chain_push_inverted_relation(xrc, &xsr); 224 + } break; 225 + case U_SPACE_TYPE_OFFSET: m_relation_chain_push_inverted_pose_if_not_identity(xrc, &space->offset.pose); break; 226 + case U_SPACE_TYPE_ROOT: assert(false); // Should not get here. 227 + } 228 + } 229 + 230 + static void 231 + build_relation_chain_read_locked(struct u_space_overseer *uso, 232 + struct xrt_relation_chain *xrc, 233 + struct u_space *base, 234 + struct u_space *target, 235 + uint64_t at_timestamp_ns) 236 + { 237 + assert(xrc != NULL); 238 + assert(base != NULL); 239 + assert(target != NULL); 240 + 241 + push_then_traverse(xrc, target, at_timestamp_ns); 242 + traverse_then_push_inverse(xrc, base, at_timestamp_ns); 243 + } 244 + 245 + static void 246 + build_relation_chain(struct u_space_overseer *uso, 247 + struct xrt_relation_chain *xrc, 248 + struct u_space *base, 249 + struct u_space *target, 250 + uint64_t at_timestamp_ns) 251 + { 252 + pthread_rwlock_rdlock(&uso->lock); 253 + build_relation_chain_read_locked(uso, xrc, base, target, at_timestamp_ns); 254 + pthread_rwlock_unlock(&uso->lock); 255 + } 256 + 257 + static inline void 258 + special_resolve(struct xrt_relation_chain *xrc, struct xrt_space_relation *out_relation) 259 + { 260 + // A space chain with zero step is always valid. 261 + if (xrc->step_count == 0) { 262 + out_relation->pose = (struct xrt_pose)XRT_POSE_IDENTITY; 263 + out_relation->relation_flags = // 264 + XRT_SPACE_RELATION_ORIENTATION_VALID_BIT | // 265 + XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | // 266 + XRT_SPACE_RELATION_POSITION_VALID_BIT | // 267 + XRT_SPACE_RELATION_POSITION_TRACKED_BIT; 268 + } else { 269 + m_relation_chain_resolve(xrc, out_relation); 270 + } 271 + } 272 + 273 + 274 + /* 275 + * 276 + * Direct space functions. 277 + * 278 + */ 279 + 280 + static void 281 + space_destroy(struct xrt_space *xs) 282 + { 283 + struct u_space *us = u_space(xs); 284 + 285 + assert(us->next != NULL || us->type == U_SPACE_TYPE_ROOT); 286 + 287 + u_space_reference(&us->next, NULL); 288 + 289 + free(us); 290 + } 291 + 292 + /*! 293 + * Creates a space, returns with a reference of one. 294 + */ 295 + static struct u_space * 296 + create_space(enum u_space_type type, struct u_space *parent) 297 + { 298 + assert(parent != NULL || type == U_SPACE_TYPE_ROOT); 299 + 300 + struct u_space *us = U_TYPED_CALLOC(struct u_space); 301 + us->base.reference.count = 1; 302 + us->base.destroy = space_destroy; 303 + us->type = type; 304 + 305 + u_space_reference(&us->next, parent); 306 + 307 + return us; 308 + } 309 + 310 + static void 311 + create_and_set_root_space(struct u_space_overseer *uso) 312 + { 313 + assert(uso->base.semantic.root == NULL); 314 + 315 + struct u_space *us = create_space(U_SPACE_TYPE_ROOT, NULL); 316 + 317 + // Created with one reference. 318 + uso->base.semantic.root = &us->base; 319 + } 320 + 321 + 322 + /* 323 + * 324 + * Member functions. 325 + * 326 + */ 327 + 328 + static xrt_result_t 329 + create_offset_space(struct xrt_space_overseer *xso, 330 + struct xrt_space *parent, 331 + const struct xrt_pose *offset, 332 + struct xrt_space **out_space) 333 + { 334 + assert(out_space != NULL); 335 + assert(*out_space == NULL); 336 + 337 + struct u_space *uparent = u_space(parent); 338 + struct u_space *us = NULL; 339 + 340 + if (m_pose_is_identity(offset)) { // Small optimisation. 341 + us = create_space(U_SPACE_TYPE_NULL, uparent); 342 + } else { 343 + us = create_space(U_SPACE_TYPE_OFFSET, uparent); 344 + us->offset.pose = *offset; 345 + } 346 + 347 + // Created with one references. 348 + *out_space = &us->base; 349 + 350 + return XRT_SUCCESS; 351 + } 352 + 353 + static xrt_result_t 354 + create_pose_space(struct xrt_space_overseer *xso, 355 + struct xrt_device *xdev, 356 + enum xrt_input_name name, 357 + struct xrt_space **out_space) 358 + { 359 + assert(out_space != NULL); 360 + assert(*out_space == NULL); 361 + 362 + struct u_space_overseer *uso = u_space_overseer(xso); 363 + 364 + // Only need the read lock. 365 + pthread_rwlock_rdlock(&uso->lock); 366 + 367 + struct u_space *uparent = find_xdev_space_read_locked(uso, xdev); 368 + struct u_space *us = create_space(U_SPACE_TYPE_POSE, uparent); 369 + 370 + // Safe to unlock now. 371 + pthread_rwlock_unlock(&uso->lock); 372 + 373 + us->pose.xdev = xdev; 374 + us->pose.xname = name; 375 + 376 + // Created with one references. 377 + *out_space = &us->base; 378 + 379 + return XRT_SUCCESS; 380 + } 381 + 382 + static xrt_result_t 383 + locate_space(struct xrt_space_overseer *xso, 384 + struct xrt_space *base_space, 385 + const struct xrt_pose *base_offset, 386 + uint64_t at_timestamp_ns, 387 + struct xrt_space *space, 388 + const struct xrt_pose *offset, 389 + struct xrt_space_relation *out_relation) 390 + { 391 + struct u_space_overseer *uso = u_space_overseer(xso); 392 + 393 + struct u_space *ubase_space = u_space(base_space); 394 + struct u_space *uspace = u_space(space); 395 + 396 + struct xrt_relation_chain xrc = {0}; 397 + 398 + m_relation_chain_push_pose_if_not_identity(&xrc, offset); 399 + build_relation_chain(uso, &xrc, ubase_space, uspace, at_timestamp_ns); 400 + m_relation_chain_push_inverted_pose_if_not_identity(&xrc, base_offset); 401 + 402 + // For base_space =~= space (approx equals). 403 + special_resolve(&xrc, out_relation); 404 + 405 + return XRT_SUCCESS; 406 + } 407 + 408 + static xrt_result_t 409 + locate_device(struct xrt_space_overseer *xso, 410 + struct xrt_space *base_space, 411 + const struct xrt_pose *base_offset, 412 + uint64_t at_timestamp_ns, 413 + struct xrt_device *xdev, 414 + struct xrt_space_relation *out_relation) 415 + { 416 + struct u_space_overseer *uso = u_space_overseer(xso); 417 + 418 + struct u_space *ubase_space = u_space(base_space); 419 + 420 + struct xrt_relation_chain xrc = {0}; 421 + 422 + // Only need the read lock. 423 + pthread_rwlock_rdlock(&uso->lock); 424 + 425 + struct u_space *uspace = find_xdev_space_read_locked(uso, xdev); 426 + build_relation_chain_read_locked(uso, &xrc, ubase_space, uspace, at_timestamp_ns); 427 + 428 + // Safe to unlock now. 429 + pthread_rwlock_unlock(&uso->lock); 430 + 431 + // Do as much work outside of the lock. 432 + m_relation_chain_push_inverted_pose_if_not_identity(&xrc, base_offset); 433 + special_resolve(&xrc, out_relation); 434 + 435 + return XRT_SUCCESS; 436 + } 437 + 438 + static void 439 + destroy(struct xrt_space_overseer *xso) 440 + { 441 + struct u_space_overseer *uso = u_space_overseer(xso); 442 + 443 + xrt_space_reference(&uso->base.semantic.unbounded, NULL); 444 + xrt_space_reference(&uso->base.semantic.stage, NULL); 445 + xrt_space_reference(&uso->base.semantic.local, NULL); 446 + xrt_space_reference(&uso->base.semantic.view, NULL); 447 + xrt_space_reference(&uso->base.semantic.root, NULL); 448 + 449 + // Each device has a reference to its space, make sure to unreference before creating. 450 + u_hashmap_int_clear_and_call_for_each(uso->xdev_map, hashmap_unreference_space_items, uso); 451 + u_hashmap_int_destroy(&uso->xdev_map); 452 + 453 + pthread_rwlock_destroy(&uso->lock); 454 + 455 + free(uso); 456 + } 457 + 458 + 459 + /* 460 + * 461 + * 'Exported' functions. 462 + * 463 + */ 464 + 465 + struct u_space_overseer * 466 + u_space_overseer_create(void) 467 + { 468 + struct u_space_overseer *uso = U_TYPED_CALLOC(struct u_space_overseer); 469 + uso->base.create_offset_space = create_offset_space; 470 + uso->base.create_pose_space = create_pose_space; 471 + uso->base.locate_space = locate_space; 472 + uso->base.locate_device = locate_device; 473 + uso->base.destroy = destroy; 474 + 475 + XRT_MAYBE_UNUSED int ret = 0; 476 + 477 + ret = pthread_rwlock_init(&uso->lock, NULL); 478 + assert(ret == 0); 479 + 480 + ret = u_hashmap_int_create(&uso->xdev_map); 481 + assert(ret == 0); 482 + 483 + create_and_set_root_space(uso); 484 + 485 + return uso; 486 + } 487 + 488 + void 489 + u_space_overseer_legacy_setup(struct u_space_overseer *uso, 490 + struct xrt_device **xdevs, 491 + uint32_t xdev_count, 492 + struct xrt_device *head, 493 + const struct xrt_pose *local_offset) 494 + { 495 + struct xrt_space *root = uso->base.semantic.root; // Convenience 496 + 497 + struct u_hashmap_int *torig_map = NULL; 498 + u_hashmap_int_create(&torig_map); 499 + 500 + 501 + for (uint32_t i = 0; i < xdev_count; i++) { 502 + struct xrt_device *xdev = xdevs[i]; 503 + struct xrt_tracking_origin *torig = xdev->tracking_origin; 504 + uint64_t key = (uint64_t)(intptr_t)torig; 505 + struct xrt_space *xs = NULL; 506 + 507 + void *ptr = NULL; 508 + u_hashmap_int_find(torig_map, key, &ptr); 509 + 510 + if (ptr != NULL) { 511 + xs = (struct xrt_space *)ptr; 512 + } else { 513 + u_space_overseer_create_offset_space(uso, root, &torig->offset, &xs); 514 + u_hashmap_int_insert(torig_map, key, xs); 515 + } 516 + 517 + u_space_overseer_link_space_to_device(uso, xs, xdev); 518 + } 519 + 520 + // Each item has a exrta reference make sure to clear before destroying. 521 + u_hashmap_int_clear_and_call_for_each(torig_map, hashmap_unreference_space_items, uso); 522 + u_hashmap_int_destroy(&torig_map); 523 + 524 + // If these are set something is probably wrong, but just in case unset them. 525 + assert(uso->base.semantic.view == NULL); 526 + assert(uso->base.semantic.stage == NULL); 527 + assert(uso->base.semantic.local == NULL); 528 + xrt_space_reference(&uso->base.semantic.view, NULL); 529 + xrt_space_reference(&uso->base.semantic.stage, NULL); 530 + xrt_space_reference(&uso->base.semantic.local, NULL); 531 + 532 + xrt_space_reference(&uso->base.semantic.stage, uso->base.semantic.root); 533 + u_space_overseer_create_offset_space(uso, uso->base.semantic.root, local_offset, &uso->base.semantic.local); 534 + if (head != NULL) { 535 + u_space_overseer_create_pose_space(uso, head, XRT_INPUT_GENERIC_HEAD_POSE, &uso->base.semantic.view); 536 + } 537 + } 538 + 539 + void 540 + u_space_overseer_create_null_space(struct u_space_overseer *uso, struct xrt_space *parent, struct xrt_space **out_space) 541 + { 542 + assert(out_space != NULL); 543 + assert(*out_space == NULL); 544 + 545 + struct u_space *uparent = u_space(parent); 546 + struct u_space *us = create_space(U_SPACE_TYPE_NULL, uparent); 547 + 548 + // Created with one references. 549 + *out_space = &us->base; 550 + } 551 + 552 + void 553 + u_space_overseer_link_space_to_device(struct u_space_overseer *uso, struct xrt_space *xs, struct xrt_device *xdev) 554 + { 555 + pthread_rwlock_wrlock(&uso->lock); 556 + 557 + void *ptr = NULL; 558 + uint64_t key = (uint64_t)(intptr_t)xdev; 559 + u_hashmap_int_find(uso->xdev_map, key, &ptr); 560 + if (ptr != NULL) { 561 + U_LOG_W("Device '%s' already have a space attached!", xdev->str); 562 + } 563 + 564 + // Each xdev needs to add a reference to the space. 565 + struct xrt_space *new_space = NULL; 566 + xrt_space_reference(&new_space, xs); 567 + 568 + u_hashmap_int_insert(uso->xdev_map, (uint64_t)(intptr_t)xdev, new_space); 569 + 570 + pthread_rwlock_unlock(&uso->lock); 571 + 572 + // Dereferrence old space outside of lock. 573 + struct xrt_space *old_space = (struct xrt_space *)ptr; 574 + xrt_space_reference(&old_space, NULL); 575 + }
+117
src/xrt/auxiliary/util/u_space_overseer.h
··· 1 + // Copyright 2023, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief A implementation of the @ref xrt_space_overseer interface. 6 + * 7 + * @author Jakob Bornecrantz <jakob@collabora.com> 8 + * @ingroup aux_util 9 + */ 10 + 11 + #include "xrt/xrt_space.h" 12 + 13 + 14 + #ifdef __cplusplus 15 + extern "C" { 16 + #endif 17 + 18 + 19 + struct u_space_overseer; 20 + 21 + 22 + /* 23 + * 24 + * Main interface. 25 + * 26 + */ 27 + 28 + /*! 29 + * Create a default implementation of a space overseer. 30 + * 31 + * @ingroup aux_util 32 + */ 33 + struct u_space_overseer * 34 + u_space_overseer_create(void); 35 + 36 + /*! 37 + * Sets up the space overseer and all semantic spaces in a way that works with 38 + * the old @ref xrt_tracking_origin information. Will automatically create local 39 + * and stage spaces. If another setup is needed the builder should manually set 40 + * the space graph up using below functions. 41 + * 42 + * @ingroup aux_util 43 + */ 44 + void 45 + u_space_overseer_legacy_setup(struct u_space_overseer *uso, 46 + struct xrt_device **xdevs, 47 + uint32_t xdev_count, 48 + struct xrt_device *head, 49 + const struct xrt_pose *local_offset); 50 + 51 + /*! 52 + * Creates a space without any offset, this is just for optimisation over a 53 + * regular offset space. 54 + * 55 + * @ingroup aux_util 56 + */ 57 + void 58 + u_space_overseer_create_null_space(struct u_space_overseer *uso, 59 + struct xrt_space *parent, 60 + struct xrt_space **out_space); 61 + 62 + /*! 63 + * The space overseer internally keeps track the space that @ref xrt_device is 64 + * in, and then uses that mapping when creating pose spaces. This function 65 + * allows builders to create a much more bespoke setup. This function adds a 66 + * reference to the space. 67 + * 68 + * @ingroup aux_util 69 + */ 70 + void 71 + u_space_overseer_link_space_to_device(struct u_space_overseer *uso, struct xrt_space *xs, struct xrt_device *xdev); 72 + 73 + 74 + /* 75 + * 76 + * Builder helpers. 77 + * 78 + */ 79 + 80 + /*! 81 + * @copydoc xrt_space_overseer::create_offset_space 82 + * 83 + * Convenience helper for builder code using @ref u_space_overseer directly. 84 + * 85 + * @public @memberof u_space_overseer 86 + */ 87 + static inline xrt_result_t 88 + u_space_overseer_create_offset_space(struct u_space_overseer *uso, 89 + struct xrt_space *parent, 90 + const struct xrt_pose *offset, 91 + struct xrt_space **out_space) 92 + { 93 + struct xrt_space_overseer *xso = (struct xrt_space_overseer *)uso; 94 + return xrt_space_overseer_create_offset_space(xso, parent, offset, out_space); 95 + } 96 + 97 + /*! 98 + * @copydoc xrt_space_overseer::create_pose_space 99 + * 100 + * Convenience helper for builder code using @ref u_space_overseer directly. 101 + * 102 + * @public @memberof u_space_overseer 103 + */ 104 + static inline xrt_result_t 105 + u_space_overseer_create_pose_space(struct u_space_overseer *uso, 106 + struct xrt_device *xdev, 107 + enum xrt_input_name name, 108 + struct xrt_space **out_space) 109 + { 110 + struct xrt_space_overseer *xso = (struct xrt_space_overseer *)uso; 111 + return xrt_space_overseer_create_pose_space(xso, xdev, name, out_space); 112 + } 113 + 114 + 115 + #ifdef __cplusplus 116 + } 117 + #endif